Compare commits
198 Commits
feature/up
...
feature/新增
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35c8b9316e | ||
|
|
e46a13889e | ||
|
|
6b403f2c1e | ||
|
|
4f9ac16a07 | ||
|
|
8bc68e1edd | ||
|
|
8a10d18f37 | ||
|
|
181f490d47 | ||
|
|
97a5d91fce | ||
|
|
9eee8557c2 | ||
|
|
d39bbe4f99 | ||
|
|
b188c8adfe | ||
|
|
ea6fd42b2e | ||
|
|
625499f758 | ||
|
|
0c2d6dd582 | ||
|
|
9a3404f4b8 | ||
|
|
d000aa095f | ||
|
|
8945801db8 | ||
|
|
40f327ea17 | ||
|
|
091af92e8d | ||
|
|
3ed338ed64 | ||
|
|
65781570e7 | ||
|
|
e1f7aeaa96 | ||
|
|
a0a580588c | ||
|
|
1ddb446bc6 | ||
|
|
dcdc6851b6 | ||
|
|
ece4e56118 | ||
|
|
7cd104c047 | ||
|
|
ec2ef752b3 | ||
|
|
57d5c57048 | ||
|
|
6711f6f6e6 | ||
|
|
7ac7077f8c | ||
|
|
ed20249988 | ||
|
|
dba8d23348 | ||
|
|
ab9062983c | ||
|
|
37493a4688 | ||
|
|
689d149edc | ||
|
|
9b9106c5da | ||
|
|
9a43af98c3 | ||
|
|
4560bbc0ed | ||
|
|
2d5ef0aae4 | ||
|
|
096542c8ff | ||
|
|
d123ca5173 | ||
|
|
388929f207 | ||
|
|
cf283377cd | ||
|
|
5f218e69d4 | ||
|
|
00809d88f6 | ||
|
|
18b0cc6ea0 | ||
|
|
54d9902ae4 | ||
|
|
eff39a39e3 | ||
|
|
005a7bb68e | ||
|
|
afc1701150 | ||
|
|
979e8a47ab | ||
|
|
e119a364c1 | ||
|
|
c7b9bbb18d | ||
|
|
3cb3dce247 | ||
|
|
a7e1c89e66 | ||
|
|
ca1060ada5 | ||
|
|
69578ab054 | ||
|
|
511bed47ff | ||
|
|
26a0412649 | ||
|
|
4989ff773c | ||
|
|
04c0d68763 | ||
|
|
589035d42b | ||
|
|
6e9c326a52 | ||
|
|
fc1cdf4d0e | ||
|
|
45178ac38b | ||
|
|
09ee1c26b1 | ||
|
|
64180ca57f | ||
|
|
0c052a118c | ||
|
|
0ca6d46a7b | ||
|
|
3624ef4755 | ||
|
|
f07a003f2c | ||
|
|
0ef46407dd | ||
|
|
f7f3c005f8 | ||
|
|
cae5e14587 | ||
|
|
a2990b8f2e | ||
|
|
5121e455d2 | ||
|
|
4a14fa6bf6 | ||
|
|
f251878637 | ||
|
|
9a5aa52801 | ||
|
|
5b3193d3ff | ||
|
|
52befb6233 | ||
|
|
9ce1963d44 | ||
|
|
1b710b205f | ||
|
|
0d253c54c8 | ||
|
|
d2c5fdb509 | ||
|
|
40209958f2 | ||
|
|
778896a670 | ||
|
|
fcacf609e9 | ||
|
|
5cd134758a | ||
|
|
b8f8e90444 | ||
|
|
b50a852f77 | ||
|
|
ea94d74657 | ||
|
|
b5bea1f811 | ||
|
|
8416630593 | ||
|
|
6572ca25fe | ||
|
|
6a7e6ee5c5 | ||
|
|
aff35f2dd4 | ||
|
|
8ead5655cf | ||
|
|
d6b3a5a3f1 | ||
|
|
19d8f6ae73 | ||
|
|
ae8e304d7f | ||
|
|
e154f5b763 | ||
|
|
804bdb60e8 | ||
|
|
302636635c | ||
|
|
c465822774 | ||
|
|
f9063bf3d9 | ||
|
|
ea6fb5e570 | ||
|
|
0e672aef79 | ||
|
|
11ec04de5b | ||
|
|
efe98b5133 | ||
|
|
2d2abfce25 | ||
|
|
821a7a63be | ||
|
|
de221ba67e | ||
|
|
c5bb9a32d0 | ||
|
|
925fc2f2b3 | ||
|
|
41f379ef5f | ||
|
|
acc791978d | ||
|
|
f24c1ab4a5 | ||
|
|
179899e483 | ||
|
|
b8d21b7fa5 | ||
|
|
295f7ecae3 | ||
|
|
d2a76a03d2 | ||
|
|
1e172cb4e3 | ||
|
|
4424cb58f4 | ||
|
|
cd0ea08b21 | ||
|
|
10065e95d3 | ||
|
|
2c06c6f6a1 | ||
|
|
1eeaf2f85d | ||
|
|
aa96e63fff | ||
|
|
7ac09653c3 | ||
|
|
9c3ed0166b | ||
|
|
3c8ce870df | ||
|
|
d8e65a20d5 | ||
|
|
10a622b52d | ||
|
|
c1ffd3f870 | ||
|
|
a98b7456c0 | ||
|
|
7e3d40bd38 | ||
|
|
83d45b6d3f | ||
|
|
fe237dc742 | ||
|
|
35c13dc51c | ||
|
|
08477e4d94 | ||
|
|
069d1f50b0 | ||
|
|
dd60dee22d | ||
|
|
e49362e2e3 | ||
|
|
d5b5231873 | ||
|
|
2b080c1639 | ||
|
|
bd3bfc979e | ||
|
|
5a3233d539 | ||
|
|
3e8f045a40 | ||
|
|
d1fab6aab0 | ||
|
|
a3357e01aa | ||
|
|
c46a148856 | ||
|
|
5794f75f0b | ||
|
|
01d9d4a636 | ||
|
|
2931d6a92d | ||
|
|
5e6ae1ff55 | ||
|
|
d7a0dc578b | ||
|
|
44a11c839f | ||
|
|
9808f49b3d | ||
|
|
b40a87df88 | ||
|
|
72e9443048 | ||
|
|
c6f6927d57 | ||
|
|
ade7588a9c | ||
|
|
281f03670e | ||
|
|
7356e0afb7 | ||
|
|
fcd86e230a | ||
|
|
361f187026 | ||
|
|
b35f444e49 | ||
|
|
9b2a2d644d | ||
|
|
93ca2000a1 | ||
|
|
ca43aa836e | ||
|
|
26ad5b5b3f | ||
|
|
937ea42af3 | ||
|
|
9b957848c2 | ||
|
|
36579d22ac | ||
|
|
e662490cb5 | ||
|
|
038ce8e139 | ||
|
|
e1a4784092 | ||
|
|
6d4d393b9b | ||
|
|
c956b06f57 | ||
|
|
da02d68c12 | ||
|
|
eb9b43dc91 | ||
|
|
ab66e24c9d | ||
|
|
554b86e641 | ||
|
|
54317a529e | ||
|
|
78d00e9157 | ||
|
|
f1780181fa | ||
|
|
f5e36f601c | ||
|
|
6c0b84ec43 | ||
|
|
3e438a10ee | ||
|
|
9218633a5e | ||
|
|
40521e06a8 | ||
|
|
7fee98cdee | ||
|
|
6825364a2b | ||
|
|
0cac8fbb4f | ||
|
|
422edbcb02 | ||
|
|
d187bb2bba |
@@ -324,4 +324,4 @@ def unlink(self):
|
||||
|
||||
|
||||
BaseModel._create = _create
|
||||
BaseModel.unlink = unlink
|
||||
# BaseModel.unlink = unlink
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
@@ -12,7 +12,8 @@ class MrpProduction(models.Model):
|
||||
check_ids = fields.One2many('quality.check', 'production_id', string="Checks")
|
||||
|
||||
def _split_productions(self, amounts=False, cancel_remaining_qty=False, set_consumed_qty=False):
|
||||
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty, set_consumed_qty=set_consumed_qty)
|
||||
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty,
|
||||
set_consumed_qty=set_consumed_qty)
|
||||
backorders = productions[1:]
|
||||
if not backorders:
|
||||
return productions
|
||||
@@ -20,3 +21,4 @@ class MrpProduction(models.Model):
|
||||
if wo.current_quality_check_id.component_id:
|
||||
wo.current_quality_check_id._update_component_quantity()
|
||||
return productions
|
||||
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Manufacturing Order for Planing view -->
|
||||
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
|
||||
<field name="name">mrp.production.tree.inherit.planning</field>
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'" decoration-danger="date_planned_start<current_date and state not in ('done','cancel')" decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="date_planned_start"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty" sum="Total Qty" string="Quantity"/>
|
||||
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
|
||||
<field name="reservation_state" string="Availability"/>
|
||||
<field name="origin"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Manufacturing Order for Planing view -->
|
||||
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
|
||||
<field name="name">mrp.production.tree.inherit.planning</field>
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'"
|
||||
decoration-danger="date_planned_start<current_date and state not in ('done','cancel')"
|
||||
decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="date_planned_start"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty" sum="Total Qty" string="Quantity"/>
|
||||
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}"
|
||||
groups="uom.group_uom"/>
|
||||
<field name="reservation_state" string="Availability"/>
|
||||
<field name="origin"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
|
||||
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
|
||||
<!-- <field name="model">mrp.production</field>-->
|
||||
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
|
||||
<!-- <!– 这里放置替换后的内容 –>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//notebook" position="after">-->
|
||||
<!-- <div class="oe_chatter">-->
|
||||
<!-- <field name="message_follower_ids"/>-->
|
||||
<!-- <field name="activity_ids"/>-->
|
||||
<!-- <field name="message_ids"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
|
||||
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
|
||||
<!-- <field name="model">mrp.production</field>-->
|
||||
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
|
||||
<!-- <!– 这里放置替换后的内容 –>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//notebook" position="after">-->
|
||||
<!-- <div class="oe_chatter">-->
|
||||
<!-- <field name="message_follower_ids"/>-->
|
||||
<!-- <field name="activity_ids"/>-->
|
||||
<!-- <field name="message_ids"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
|
||||
<record id="mrp_production_view_search_inherit_planning" model="ir.ui.view">
|
||||
<field name="name">mrp.production.search.view.inherit.planning</field>
|
||||
@@ -43,7 +46,9 @@
|
||||
<field name="inherit_id" ref="mrp.view_mrp_production_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="filter_planned" position="attributes">
|
||||
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</attribute>
|
||||
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False),
|
||||
('date_planned_finished', '!=', False)]
|
||||
</attribute>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
@@ -51,30 +56,33 @@
|
||||
<record id="production_order_unplan_server_action" model="ir.actions.server">
|
||||
<field name="name">Unplan orders</field>
|
||||
<field name="model_id" ref="mrp.model_mrp_production"/>
|
||||
<field name="binding_model_id" ref="mrp.model_mrp_production" />
|
||||
<field name="binding_model_id" ref="mrp.model_mrp_production"/>
|
||||
<field name="binding_view_types">list</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">records.button_unplan()</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp.act_product_mrp_production_workcenter" model="ir.actions.act_window">
|
||||
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</field>
|
||||
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id),
|
||||
('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]
|
||||
</field>
|
||||
<field name="view_id" ref="mrp_production_tree_view_planning"/>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem id="mrp_workorder_menu_planning"
|
||||
name="Work Orders"
|
||||
sequence="2"
|
||||
parent="mrp.mrp_planning_menu_root"
|
||||
groups="mrp.group_mrp_routings"/>
|
||||
name="Work Orders"
|
||||
sequence="2"
|
||||
parent="mrp.mrp_planning_menu_root"
|
||||
groups="mrp.group_mrp_routings"/>
|
||||
<menuitem id="menu_mrp_workorder_production"
|
||||
name="Planning by Production"
|
||||
sequence="1"
|
||||
action="mrp.action_mrp_workorder_production"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
name="Planning by Production"
|
||||
sequence="1"
|
||||
action="mrp.action_mrp_workorder_production"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
<menuitem id="menu_mrp_workorder_workcenter"
|
||||
name="Planning by Workcenter"
|
||||
sequence="2"
|
||||
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
name="Planning by Workcenter"
|
||||
sequence="2"
|
||||
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
|
||||
parent="mrp_workorder_menu_planning"/>
|
||||
</odoo>
|
||||
|
||||
@@ -84,10 +84,12 @@ class MrsProductionProcessCategory(models.Model):
|
||||
class MrsProductionProcess(models.Model):
|
||||
_name = 'sf.production.process'
|
||||
_description = '表面工艺'
|
||||
order = 'sequence asc'
|
||||
|
||||
code = fields.Char("编码")
|
||||
name = fields.Char('名称')
|
||||
remark = fields.Text("备注")
|
||||
sequence = fields.Integer('排序')
|
||||
# processing_order_ids = fields.One2many('sf.processing.order', 'production_process_id', string='工序')
|
||||
partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂')
|
||||
active = fields.Boolean('有效', default=True)
|
||||
|
||||
@@ -19,7 +19,12 @@ class IrSequence(models.Model):
|
||||
# date mode
|
||||
dt = sequence_date or self._context.get('ir_sequence_date', fields.Date.today())
|
||||
seq_date = self.env['ir.sequence.date_range'].search(
|
||||
[('sequence_id', '=', self.id), ('date_from', '<=', dt), ('date_to', '>=', dt)], limit=1)
|
||||
[
|
||||
('sequence_id', '=', self.id),
|
||||
('date_from', '<=', dt),
|
||||
('date_to', '>=', dt),
|
||||
('date_range_period', '=', self.date_range_period)
|
||||
], limit=1)
|
||||
if not seq_date:
|
||||
if self.date_range_period:
|
||||
seq_date = self._create_date_range_seq_by_period(dt, self.date_range_period)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
diff a/sf_base/models/tool_base_new.py b/sf_base/models/tool_base_new.py (rejected hunks)
|
||||
@@ -108,6 +108,4 @@
|
||||
cutting_speed_ids = fields.One2many('sf.cutting.speed', 'standard_library_id', string='切削速度Vc')
|
||||
- feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz',
|
||||
- domain=[('cutting_speed', '!=', False)])
|
||||
- feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz',
|
||||
- domain=[('cutting_speed', '!=', False)])
|
||||
+ feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz')
|
||||
+ feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz')
|
||||
|
||||
61
sf_base/static/src/js/custom_barcode_handlers.js
Normal file
61
sf_base/static/src/js/custom_barcode_handlers.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { barcodeGenericHandlers } from '@barcodes/barcode_handlers';
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
// 定义新的 clickOnButton 函数
|
||||
function customClickOnButton(selector) {
|
||||
console.log("This is the custom clickOnButton function!");
|
||||
|
||||
const buttons = document.body.querySelectorAll(selector);
|
||||
|
||||
let length = buttons.length;
|
||||
if (length > 0) {
|
||||
buttons[length - 1].click();
|
||||
} else {
|
||||
console.warn(`Button with selector ${selector} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
patch(barcodeGenericHandlers, "start", {
|
||||
start(env, { ui, barcode, notification }) {
|
||||
// 使用新定义的 clickOnButton 函数
|
||||
const COMMANDS = {
|
||||
"O-CMD.EDIT": () => customClickOnButton(".o_form_button_edit"),
|
||||
"O-CMD.DISCARD": () => customClickOnButton(".o_form_button_cancel"),
|
||||
"O-CMD.SAVE": () => customClickOnButton(".o_form_button_save"),
|
||||
"O-CMD.PREV": () => customClickOnButton(".o_pager_previous"),
|
||||
"O-CMD.NEXT": () => customClickOnButton(".o_pager_next"),
|
||||
"O-CMD.PAGER-FIRST": () => updatePager("first"),
|
||||
"O-CMD.PAGER-LAST": () => updatePager("last"),
|
||||
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
|
||||
};
|
||||
|
||||
barcode.bus.addEventListener("barcode_scanned", (ev) => {
|
||||
const barcode = ev.detail.barcode;
|
||||
if (barcode.startsWith("O-BTN.")) {
|
||||
let targets = [];
|
||||
try {
|
||||
targets = getVisibleElements(ui.activeElement, `[barcode_trigger=${barcode.slice(6)}]`);
|
||||
} catch (_e) {
|
||||
console.warn(`Barcode '${barcode}' is not valid`);
|
||||
}
|
||||
for (let elem of targets) {
|
||||
elem.click();
|
||||
}
|
||||
}
|
||||
if (barcode.startsWith("O-CMD.")) {
|
||||
const fn = COMMANDS[barcode];
|
||||
if (fn) {
|
||||
fn();
|
||||
} else {
|
||||
notification.add(env._t("Barcode: ") + `'${barcode}'`, {
|
||||
title: env._t("Unknown barcode command"),
|
||||
type: "danger"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -5,9 +5,12 @@ import { registry } from '@web/core/registry';
|
||||
import { formView } from '@web/views/form/form_view';
|
||||
import { FormController } from '@web/views/form/form_controller';
|
||||
|
||||
import { listView } from '@web/views/list/list_view';
|
||||
import { ListController } from '@web/views/list/list_controller'
|
||||
|
||||
import { onRendered, onMounted } from "@odoo/owl";
|
||||
|
||||
export class RemoveFocusController extends FormController {
|
||||
export class RemoveFocusFormController extends FormController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
@@ -17,7 +20,23 @@ export class RemoveFocusController extends FormController {
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('views').add('remove_focus_view', {
|
||||
registry.category('views').add('remove_focus_form_view', {
|
||||
...formView,
|
||||
Controller: RemoveFocusController,
|
||||
Controller: RemoveFocusFormController,
|
||||
});
|
||||
|
||||
|
||||
export class RemoveFocusListController extends ListController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
onMounted(() => {
|
||||
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('views').add('remove_focus_list_view', {
|
||||
...listView,
|
||||
Controller: RemoveFocusListController,
|
||||
});
|
||||
@@ -12,7 +12,6 @@
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
|
||||
<field name="model">sf.production.process.parameter</field>
|
||||
<field name="arch" type="xml">
|
||||
@@ -26,19 +25,19 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="process_id" readonly="1"/>
|
||||
<field name="process_description" readonly="1"/>
|
||||
<field name="process_id" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="process_description" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="gain_way"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="processing_day" readonly="1"/>
|
||||
<field name="travel_day" readonly="1"/>
|
||||
<field name="processing_mm" readonly="1"/>
|
||||
<field name="processing_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="travel_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="processing_mm" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="适用材料">
|
||||
<field name="materials_model_ids" readonly="1"></field>
|
||||
<field name="materials_model_ids" attrs="{'readonly': [('code', '!=', False)]}"></field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
@@ -165,7 +164,7 @@
|
||||
<field name="model">sf.production.process</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="表面工艺" create="0" edit="0" delete="0">
|
||||
<field name="sequence" widget="handle" string="序号" readonly="1"/>
|
||||
<field name="sequence" string="加工顺序" readonly="1"/>
|
||||
<field name="code"/>
|
||||
<field name="name" string="名称"/>
|
||||
<field name="remark"/>
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
<field name="name">sf.cutter.function.tree</field>
|
||||
<field name="model">sf.functional.cutting.tool.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="功能刀具类型" create="0" delete="0" edit="1">
|
||||
<field name="name" string="名称"/>
|
||||
<field name="code"/>
|
||||
<field name="remark"/>
|
||||
<tree string="功能刀具类型" create="0" delete="0" edit="1" editable="bottom">
|
||||
<field name="name" string="名称" readonly="1"/>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="remark" readonly="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -17,6 +17,11 @@ class StatusChange(models.Model):
|
||||
logging.info('函数已经执行=============')
|
||||
server_product_none = []
|
||||
for order in self.order_line:
|
||||
gain_way_no = order.product_template_id.model_process_parameters_ids.filtered(lambda a: not a.gain_way)
|
||||
if gain_way_no:
|
||||
process_parameters = [item.name for item in gain_way_no]
|
||||
raise UserError(
|
||||
_("请先至【制造】-【配置】中【表面工艺可选参数】为【%s】填写获取方式", ", ".join(process_parameters)))
|
||||
for item in order.product_template_id.model_process_parameters_ids:
|
||||
if item.gain_way == '外协':
|
||||
server_product = self.env['product.template'].search(
|
||||
@@ -25,27 +30,34 @@ class StatusChange(models.Model):
|
||||
if not server_product:
|
||||
server_product_none.append(item.name)
|
||||
if server_product_none:
|
||||
raise UserError(_("请先至【产品】中创建【表面工艺参数】为%s的服务产品", ", ".join(server_product_none)))
|
||||
raise UserError(_("请先至【产品】中创建【表面工艺参数】为【%s】的服务产品", ", ".join(server_product_none)))
|
||||
|
||||
# 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法)
|
||||
res = super(StatusChange, self).action_confirm()
|
||||
|
||||
# 原有方法执行后,进行额外的操作(如调用外部API)
|
||||
process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
json1 = {
|
||||
'params': {
|
||||
'model_name': 'jikimo.process.order',
|
||||
'field_name': 'name',
|
||||
'default_code': self.default_code,
|
||||
'state': '加工中',
|
||||
'process_start_time': process_start_time,
|
||||
},
|
||||
}
|
||||
url1 = config['bfm_url_new'] + '/api/get/state/get_order'
|
||||
requests.post(url1, json=json1, data=None)
|
||||
logging.info('接口已经执行=============')
|
||||
|
||||
try:
|
||||
res = super(StatusChange, self).action_confirm()
|
||||
# 原有方法执行后,进行额外的操作(如调用外部API)
|
||||
process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
json1 = {
|
||||
'params': {
|
||||
'model_name': 'jikimo.process.order',
|
||||
'field_name': 'name',
|
||||
'default_code': self.default_code,
|
||||
'state': '加工中',
|
||||
'process_start_time': process_start_time,
|
||||
},
|
||||
}
|
||||
url1 = config['bfm_url_new'] + '/api/get/state/get_order'
|
||||
ret = requests.post(url1, json=json1, data=None)
|
||||
ret = ret.json()
|
||||
if not ret.get('error'):
|
||||
logging.info('接口已经执行=============')
|
||||
else:
|
||||
logging.error('工厂加工同步订单状态失败 {}'.format(ret))
|
||||
raise UserError('工厂加工同步订单状态失败')
|
||||
except UserError as e:
|
||||
logging.error('工厂加工同步订单状态失败 {}'.format(e))
|
||||
raise UserError('工厂加工同步订单状态失败')
|
||||
return res
|
||||
|
||||
def action_cancel(self):
|
||||
|
||||
@@ -163,6 +163,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
# 停机时间:关机时间 - 运行时间
|
||||
# 停机时长:关机时间 - 初次上线时间
|
||||
'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
|
||||
'equipment_type': machine_data.category_id.name,
|
||||
})
|
||||
|
||||
return json.dumps(res)
|
||||
@@ -172,49 +173,6 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
res['message'] = '前端请求机床数据失败,原因:%s' % e
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
# @http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
# cors="*")
|
||||
# def logs_list(self, **kw):
|
||||
# """
|
||||
# 拿到日志数据返回给大屏展示
|
||||
# :param kw:
|
||||
# :return:
|
||||
# """
|
||||
# res = {'status': 1, 'message': '成功', 'data': []}
|
||||
# logging.info('前端请求日志数据的参数为:%s' % kw)
|
||||
#
|
||||
# try:
|
||||
# # 获取请求的日志数据
|
||||
# logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo()
|
||||
# # 获取请求的机床数据
|
||||
# machine_list = ast.literal_eval(kw['machine_list'])
|
||||
# begin_time_str = kw['begin_time'].strip('"')
|
||||
# end_time_str = kw['end_time'].strip('"')
|
||||
#
|
||||
# begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
# end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
#
|
||||
# print('begin_time: %s' % begin_time)
|
||||
# for item in machine_list:
|
||||
# log_datas = logs_obj.search(
|
||||
# [('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)])
|
||||
# print('log_datas: %s' % log_datas)
|
||||
# for log_data in log_datas:
|
||||
# res['data'].append({
|
||||
# 'equipment_code': log_data.equipment_code,
|
||||
# 'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
# 'state': log_data.state
|
||||
#
|
||||
# })
|
||||
#
|
||||
# return json.JSONEncoder().encode(res)
|
||||
#
|
||||
# except Exception as e:
|
||||
# logging.info('前端请求日志数据失败,原因:%s' % e)
|
||||
# res['status'] = -1
|
||||
# res['message'] = '前端请求日志数据失败,原因:%s' % e
|
||||
# return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def logs_list(self, **kw):
|
||||
"""
|
||||
@@ -226,9 +184,9 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
logging.info('前端请求日志数据的参数为:%s' % kw)
|
||||
|
||||
try:
|
||||
# 获取请求的日志数据
|
||||
logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo()
|
||||
# 获取请求的机床数据
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
begin_time_str = kw['begin_time'].strip('"')
|
||||
end_time_str = kw['end_time'].strip('"')
|
||||
@@ -239,19 +197,25 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
print('begin_time: %s' % begin_time)
|
||||
|
||||
for item in machine_list:
|
||||
log_datas = logs_obj.search(
|
||||
[('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)])
|
||||
print('log_datas: %s' % log_datas)
|
||||
sql = '''
|
||||
SELECT time, device_state, program_name
|
||||
FROM device_data
|
||||
WHERE device_name = %s AND time >= %s AND time <= %s
|
||||
ORDER BY time DESC;
|
||||
'''
|
||||
# 执行SQL命令,使用参数绑定
|
||||
cur.execute(sql, (item, begin_time, end_time))
|
||||
results = cur.fetchall()
|
||||
|
||||
# 将数据按照 equipment_code 进行分组
|
||||
if item not in res['data']:
|
||||
res['data'][item] = []
|
||||
|
||||
for log_data in log_datas:
|
||||
for result in results:
|
||||
res['data'][item].append({
|
||||
'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'state': log_data.state,
|
||||
'production_name': log_data.production_name,
|
||||
'time': result[0].strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'state': result[1],
|
||||
'production_name': result[2],
|
||||
})
|
||||
|
||||
return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode()
|
||||
@@ -344,7 +308,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
plan_data_total_counts = plan_obj.search_count([('production_line_id.name', '=', line)])
|
||||
# 工单完成量
|
||||
plan_data_finish_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['draft'])])
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
# 工单计划量
|
||||
plan_data_plan_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
@@ -385,67 +349,66 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
return json.dumps(res)
|
||||
|
||||
# 日完成量统计
|
||||
class DailyFinishCount(http.Controller):
|
||||
@http.route('/api/DailyFinishCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def DailyFinishCount(self, **kw):
|
||||
"""
|
||||
获取日完成量统计
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_list'])
|
||||
begin_time_str = kw['begin_time'].strip('"')
|
||||
end_time_str = kw['end_time'].strip('"')
|
||||
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
print('line_list: %s' % line_list)
|
||||
@http.route('/api/DailyFinishCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def DailyFinishCount(self, **kw):
|
||||
"""
|
||||
获取日完成量统计
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_list'])
|
||||
begin_time_str = kw['begin_time'].strip('"')
|
||||
end_time_str = kw['end_time'].strip('"')
|
||||
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
print('line_list: %s' % line_list)
|
||||
|
||||
def get_date_list(start_date, end_date):
|
||||
date_list = []
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
date_list.append(current_date)
|
||||
current_date += timedelta(days=1)
|
||||
return date_list
|
||||
def get_date_list(start_date, end_date):
|
||||
date_list = []
|
||||
current_date = start_date
|
||||
while current_date <= end_date:
|
||||
date_list.append(current_date)
|
||||
current_date += timedelta(days=1)
|
||||
return date_list
|
||||
|
||||
for line in line_list:
|
||||
date_list = get_date_list(begin_time, end_time)
|
||||
order_counts = []
|
||||
for line in line_list:
|
||||
date_list = get_date_list(begin_time, end_time)
|
||||
order_counts = []
|
||||
|
||||
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
|
||||
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
|
||||
|
||||
for date in date_list:
|
||||
next_day = date + timedelta(days=1)
|
||||
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'not in', ['draft']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
for date in date_list:
|
||||
next_day = date + timedelta(days=1)
|
||||
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
|
||||
rework_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['rework']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
not_passed_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['scrap', 'cancel']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
order_counts.append({
|
||||
'date': date.strftime('%Y-%m-%d'),
|
||||
'order_count': len(orders),
|
||||
'rework_orders': len(rework_orders),
|
||||
'not_passed_orders': len(not_passed_orders)
|
||||
})
|
||||
# 外面包一层,没什么是包一层不能解决的,包一层就能区分了,类似于包一层div
|
||||
# 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理
|
||||
rework_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['rework']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
not_passed_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['scrap', 'cancel']),
|
||||
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
order_counts.append({
|
||||
'date': date.strftime('%Y-%m-%d'),
|
||||
'order_count': len(orders),
|
||||
'rework_orders': len(rework_orders),
|
||||
'not_passed_orders': len(not_passed_orders)
|
||||
})
|
||||
# 外面包一层,没什么是包一层不能解决的,包一层就能区分了,类似于包一层div
|
||||
# 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理
|
||||
|
||||
# date_list_dict = {line: order_counts}
|
||||
# date_list_dict = {line: order_counts}
|
||||
|
||||
res['data'][line] = order_counts
|
||||
return json.dumps(res)
|
||||
res['data'][line] = order_counts
|
||||
return json.dumps(res)
|
||||
|
||||
# 实时产量
|
||||
@http.route('/api/RealTimeProduct', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
@@ -623,7 +586,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
|
||||
# 查询pg库来获得待机次数
|
||||
@http.route('/api/IdleAlarmCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def idle_count(self, **kw):
|
||||
def idle_alarm_count(self, **kw):
|
||||
"""
|
||||
查询设备的待机次数
|
||||
"""
|
||||
@@ -636,69 +599,50 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
try:
|
||||
# 获取请求的机床数据
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
idle_times = []
|
||||
idle_dict = {}
|
||||
|
||||
total_alarm_time = 0
|
||||
alarm_count_num = 0
|
||||
for item in machine_list:
|
||||
sql = '''
|
||||
SELECT idle_start_time,alarm_time,alarm_repair_time FROM device_data WHERE device_name = %s;
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT DISTINCT ON (idle_start_time) idle_start_time
|
||||
FROM device_data
|
||||
WHERE device_name = %s AND idle_start_time IS NOT NULL
|
||||
ORDER BY idle_start_time, time
|
||||
) subquery;
|
||||
'''
|
||||
|
||||
sql2 = '''
|
||||
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_repair_time
|
||||
FROM device_data
|
||||
WHERE device_name = %s AND alarm_time IS NOT NULL
|
||||
ORDER BY alarm_time, time;
|
||||
|
||||
'''
|
||||
# 执行SQL命令
|
||||
cur.execute(sql, (item,))
|
||||
result = cur.fetchall()
|
||||
# # print('result', result)
|
||||
#
|
||||
# # 将查询结果添加到idle_times列表中
|
||||
# idle_times = [row[0] for row in result if row[0] is not None]
|
||||
#
|
||||
# # 对结果去重
|
||||
# unique_idle_times = set(idle_times)
|
||||
# # print('unique_idle_times', unique_idle_times)
|
||||
#
|
||||
# # 统计去重后的数量
|
||||
# idle_count = len(unique_idle_times)
|
||||
# # idle_dict[item] = idle_count
|
||||
#
|
||||
# res['data'][item] = idle_count
|
||||
|
||||
total_alarm_time = 0
|
||||
alarm_count = 0
|
||||
alarm_time_list = []
|
||||
idle_times = []
|
||||
alarm_times = []
|
||||
print('result========', result)
|
||||
|
||||
cur.execute(sql2, (item,))
|
||||
result2 = cur.fetchall()
|
||||
print('result2========', result2)
|
||||
#
|
||||
for row in result:
|
||||
idle_start_time = row[0]
|
||||
alarm_time = row[1]
|
||||
alarm_repair_time = row[2]
|
||||
|
||||
alarm_time_list.append(alarm_time) # 将时长累加,以秒为单位
|
||||
idle_times.append(idle_start_time)
|
||||
# if alarm_repair_time is not None:
|
||||
# alarm_times.append(alarm_repair_time)
|
||||
alarm_times.append(alarm_repair_time)
|
||||
|
||||
# 对结果去重
|
||||
unique_total_alarm_time = set(alarm_time_list)
|
||||
unique_idle_times = set(idle_times)
|
||||
unique_alarm_times = set(alarm_times)
|
||||
|
||||
# 统计去重后的数量
|
||||
idle_count = len(unique_idle_times)
|
||||
|
||||
for alarm_time in unique_total_alarm_time:
|
||||
if alarm_time is not None:
|
||||
total_alarm_time += abs(float(alarm_time))
|
||||
|
||||
alarm_count = len(unique_alarm_times) if unique_alarm_times else 0
|
||||
alarm_count = alarm_count if total_alarm_time else 0
|
||||
|
||||
# 存储待机次数和总待机时长(单位:秒)
|
||||
res['data'][item] = {
|
||||
'idle_count': idle_count,
|
||||
'total_alarm_time': total_alarm_time / 3600, # 以秒为单位
|
||||
'alarm_count': alarm_count
|
||||
}
|
||||
res['data'][item] = {'idle_count': row[0]}
|
||||
alarm_count = []
|
||||
for row in result2:
|
||||
alarm_count.append(row[0])
|
||||
total_alarm_time += abs(float(row[0]))
|
||||
if len(list(set(alarm_count))) == 1:
|
||||
if list(set(alarm_count))[0] is None:
|
||||
alarm_count_num = 0
|
||||
else:
|
||||
alarm_count_num = 1
|
||||
else:
|
||||
alarm_count_num = len(list(set(alarm_count)))
|
||||
res['data'][item]['total_alarm_time'] = total_alarm_time / 3600
|
||||
res['data'][item]['alarm_count_num'] = alarm_count_num
|
||||
|
||||
# 返回统计结果
|
||||
return json.dumps(res)
|
||||
@@ -711,7 +655,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
|
||||
# 查询pg库来获得异常情况
|
||||
@http.route('/api/alarm/logs', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def idle_count(self, **kw):
|
||||
def alarm_logs(self, **kw):
|
||||
"""
|
||||
查询设备的异常情况
|
||||
"""
|
||||
@@ -763,3 +707,126 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
finally:
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# 设备oee
|
||||
@http.route('/api/OEE', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def OEE(self, **kw):
|
||||
"""
|
||||
获取产线等oee
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
logging.info('前端请求oee数据的参数为:%s' % kw)
|
||||
|
||||
try:
|
||||
count_oee = 1
|
||||
workcenter_obj = request.env['mrp.workcenter'].sudo()
|
||||
workcenter_list = ast.literal_eval(kw['workcenter_list'])
|
||||
print('workcenter_list: %s' % workcenter_list)
|
||||
for line in workcenter_list:
|
||||
res['data'][line] = workcenter_obj.search([('name', '=', line)]).oee
|
||||
count_oee *= workcenter_obj.search([('name', '=', line)]).oee
|
||||
res['data']['综合oee'] = count_oee / 1000000
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
return json.dumps(res)
|
||||
|
||||
# # 查询某段时间的设备oee
|
||||
# @http.route('/api/OEEByTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
# def OEEByTime(self, **kw):
|
||||
# """
|
||||
# 获取某段时间的oee
|
||||
# """
|
||||
# res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
# logging.info('前端请求获取某段时间的oee的参数为:%s' % kw)
|
||||
# workcenter_list = ast.literal_eval(kw['workcenter_list'])
|
||||
# begin_time_str = kw['begin_time'].strip('"')
|
||||
# end_time_str = kw['end_time'].strip('"')
|
||||
# begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
# end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
# print('workcenter_list: %s' % workcenter_list)
|
||||
# # 连接数据库
|
||||
# conn = psycopg2.connect(**db_config)
|
||||
# cur = conn.cursor()
|
||||
# # 查询并计算OEE平均值
|
||||
# oee_data = {}
|
||||
# for workcenter in workcenter_list:
|
||||
# cur.execute("""
|
||||
# SELECT AVG(oee) as avg_oee
|
||||
# FROM oee_data
|
||||
# WHERE workcenter_name = %s
|
||||
# AND time BETWEEN %s AND %s
|
||||
# """, (workcenter, begin_time, end_time))
|
||||
#
|
||||
# result = cur.fetchone()
|
||||
# avg_oee = result[0] if result else 0.0
|
||||
# oee_data[workcenter] = avg_oee
|
||||
#
|
||||
# # 返回数据
|
||||
# res['data'] = oee_data
|
||||
# return json.dumps(res)
|
||||
|
||||
@http.route('/api/OEEByTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def OEEByTime(self, **kw):
|
||||
"""
|
||||
获取某段时间的OEE,根据用户指定的时间单位(day或hour)返回对应的平均值。
|
||||
如果不传time_unit,则默认按天返回,并补全没有数据的时间段,填充0值。
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
logging.info('前端请求获取某段时间的OEE的参数为:%s' % kw)
|
||||
|
||||
# 获取并解析参数
|
||||
workcenter_list = ast.literal_eval(kw['workcenter_list'])
|
||||
begin_time_str = kw['begin_time'].strip('"')
|
||||
end_time_str = kw['end_time'].strip('"')
|
||||
time_unit = kw.get('time_unit', 'day') # 默认单位为天
|
||||
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
cur = conn.cursor()
|
||||
|
||||
# 根据时间单位选择不同的时间格式
|
||||
if time_unit == 'hour':
|
||||
time_format = 'YYYY-MM-DD HH24:00:00'
|
||||
time_delta = timedelta(hours=1)
|
||||
else: # 默认为'day'
|
||||
time_format = 'YYYY-MM-DD'
|
||||
time_delta = timedelta(days=1)
|
||||
|
||||
# 查询并计算OEE平均值
|
||||
oee_data = {}
|
||||
for workcenter in workcenter_list:
|
||||
cur.execute(f"""
|
||||
SELECT to_char(time, '{time_format}') as time_unit, AVG(oee) as avg_oee
|
||||
FROM oee_data
|
||||
WHERE workcenter_name = %s
|
||||
AND time BETWEEN %s AND %s
|
||||
GROUP BY time_unit
|
||||
ORDER BY time_unit
|
||||
""", (workcenter, begin_time, end_time))
|
||||
|
||||
results = cur.fetchall()
|
||||
# 初始化当前产线的OEE数据字典
|
||||
workcenter_oee = {row[0]: row[1] for row in results}
|
||||
|
||||
# 补全缺失的时间段
|
||||
current_time = begin_time
|
||||
if time_unit != 'hour':
|
||||
while current_time <= end_time:
|
||||
time_key = current_time.strftime('%Y-%m-%d')
|
||||
if time_key not in workcenter_oee:
|
||||
workcenter_oee[time_key] = 0
|
||||
current_time += time_delta
|
||||
|
||||
# 按时间排序
|
||||
oee_data[workcenter] = dict(sorted(workcenter_oee.items()))
|
||||
|
||||
# 关闭数据库连接
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# 返回数据
|
||||
res['data'] = oee_data
|
||||
return json.dumps(res)
|
||||
|
||||
@@ -24,6 +24,7 @@ class SfEquipmentSaintenanceStandards(models.Model):
|
||||
remark = fields.Char('备注')
|
||||
maintenance_type = fields.Selection([('保养', '保养'), ("检修", "检修")], string='类型', default='保养')
|
||||
name = fields.Char(string='名称')
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
||||
@@ -6,15 +6,16 @@
|
||||
<field name="name">equipment.maintenance.standards.form</field>
|
||||
<field name="model">equipment.maintenance.standards</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="设备维保标准">
|
||||
<form string="设备维保标准" delete="false" duplicate="false">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="name" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_equipment_category_id" required="1" />
|
||||
<field name="eq_maintenance_ids" invisible='1'/>
|
||||
<field name="overhaul_ids" invisible='1'/>
|
||||
<group>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="name" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_equipment_category_id" required="1"/>
|
||||
<field name="eq_maintenance_ids" invisible='1'/>
|
||||
<field name="overhaul_ids" invisible='1'/>
|
||||
|
||||
|
||||
</group>
|
||||
@@ -50,7 +51,8 @@
|
||||
<field name="name">equipment.maintenance.standards.tree</field>
|
||||
<field name="model">equipment.maintenance.standards</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="设备维保标准">
|
||||
<tree string="设备维保标准" delete="false">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_type" required="1"/>
|
||||
<field name="name" required="1"/>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
'views/model_type_view.xml',
|
||||
'views/agv_setting_views.xml',
|
||||
'views/sf_maintenance_equipment.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
|
||||
@@ -44,6 +45,7 @@
|
||||
'sf_manufacturing/static/src/scss/kanban_change.scss',
|
||||
'sf_manufacturing/static/src/xml/button_show_on_tree.xml',
|
||||
'sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js',
|
||||
'sf_manufacturing/static/src/js/custom_barcode_handlers.js',
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
@@ -668,7 +668,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('AGVDownProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVStationState(self, **kw):
|
||||
"""
|
||||
|
||||
@@ -10,3 +10,4 @@ from . import res_user
|
||||
from . import production_line_base
|
||||
from . import agv_setting
|
||||
from . import agv_scheduling
|
||||
from . import res_config_setting
|
||||
|
||||
@@ -231,7 +231,9 @@ class AgvScheduling(models.Model):
|
||||
rec.site_state = '空闲'
|
||||
rec.end_site_id = agv_task_route.end_site_id.id
|
||||
rec.agv_route_id = agv_task_route.id
|
||||
# rec._delivery_avg()
|
||||
is_agv_task_dispatch = self.env['ir.config_parameter'].sudo().get_param('is_agv_task_dispatch')
|
||||
if is_agv_task_dispatch:
|
||||
rec._delivery_avg()
|
||||
# 更新接驳站状态
|
||||
rec.env['sf.agv.site'].update_site_state({rec.end_site_id.name: '占用'}, False)
|
||||
|
||||
@@ -263,3 +265,18 @@ class ResMrpWorkOrder(models.Model):
|
||||
'sf_agv_scheduling_mrp_workorder_ref',
|
||||
string='AGV调度',
|
||||
domain=[('state', '!=', '已取消')])
|
||||
|
||||
def get_down_product_agv_scheduling(self):
|
||||
"""
|
||||
获取关联的制造订单下产线的agv任务
|
||||
"""
|
||||
self.ensure_one()
|
||||
workorder_ids = self.production_id.workorder_ids
|
||||
cnc_workorder = workorder_ids.filtered(
|
||||
lambda w: w.routing_type == 'CNC加工' and w.state == 'done' and w.processing_panel == self.processing_panel
|
||||
)
|
||||
if cnc_workorder:
|
||||
agv_schedulingss = cnc_workorder.agv_scheduling_ids
|
||||
return agv_schedulingss.filtered(lambda a: a.state == '已配送' and a.agv_route_type == '下产线')
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import datetime
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
from itertools import groupby
|
||||
from datetime import datetime
|
||||
from collections import defaultdict, namedtuple
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
@@ -123,9 +123,39 @@ class MrpProduction(models.Model):
|
||||
# 上传零件图纸
|
||||
part_drawing = fields.Binary('零件图纸')
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
@api.depends('product_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
for item in self:
|
||||
item.manual_quotation = item.product_id.manual_quotation
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True)
|
||||
is_scrap = fields.Boolean('是否报废', default=False)
|
||||
is_remanufacture = fields.Boolean('是否重新制造', default=False)
|
||||
remanufacture_count = fields.Integer("重新制造订单数量", compute='_compute_remanufacture_production_ids')
|
||||
remanufacture_production_id = fields.Many2one('mrp.production', string='')
|
||||
|
||||
@api.depends('remanufacture_production_id')
|
||||
def _compute_remanufacture_production_ids(self):
|
||||
for production in self:
|
||||
if production.remanufacture_production_id:
|
||||
remanufacture_production = self.env['mrp.production'].search(
|
||||
[('id', '=', production.remanufacture_production_id.id)])
|
||||
if remanufacture_production:
|
||||
production.remanufacture_count = len(remanufacture_production)
|
||||
else:
|
||||
production.remanufacture_count = 0
|
||||
|
||||
def action_view_remanufacture_productions(self):
|
||||
self.ensure_one()
|
||||
mrp_production = self.env['mrp.production'].search(
|
||||
[('id', '=', self.remanufacture_production_id.id)])
|
||||
action = {
|
||||
'res_model': 'mrp.production',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_id': mrp_production.id,
|
||||
}
|
||||
return action
|
||||
|
||||
@api.depends(
|
||||
'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state',
|
||||
@@ -446,44 +476,45 @@ class MrpProduction(models.Model):
|
||||
# self.env['mrp.workorder'].json_workorder_str(k, production, route))
|
||||
# 表面工艺工序
|
||||
# 获取表面工艺id
|
||||
if production.product_id.model_process_parameters_ids:
|
||||
logging.info('model_process_parameters_ids:%s' % production.product_id.model_process_parameters_ids)
|
||||
surface_technics_arr = []
|
||||
# 工序id
|
||||
route_workcenter_arr = []
|
||||
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
|
||||
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
|
||||
route_workcenter_arr.append(item.route_workcenter_id.id)
|
||||
if surface_technics_arr:
|
||||
production_process_category = self.env['sf.production.process.category'].search(
|
||||
[('production_process_ids.id', 'in', surface_technics_arr)],
|
||||
order='sequence asc'
|
||||
)
|
||||
# 用filter刷选表面工艺id'是否存在工艺类别对象里
|
||||
if production_process_category:
|
||||
for p in production_process_category:
|
||||
logging.info('production_process_category:%s' % p.name)
|
||||
production_process = p.production_process_ids.filtered(
|
||||
lambda pp: pp.id in surface_technics_arr)
|
||||
if production_process:
|
||||
process_parameter = production.product_id.model_process_parameters_ids.filtered(
|
||||
lambda pm: pm.process_id.id == production_process.id)
|
||||
if process_parameter:
|
||||
# 产品为表面工艺服务的供应商
|
||||
product_production_process = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', process_parameter.id)])
|
||||
if product_production_process:
|
||||
route_production_process = self.env[
|
||||
'mrp.routing.workcenter'].search(
|
||||
[('surface_technics_id', '=', production_process.id),
|
||||
('id', 'in', route_workcenter_arr)])
|
||||
if route_production_process:
|
||||
workorders_values.append(
|
||||
self.env[
|
||||
'mrp.workorder']._json_workorder_surface_process_str(
|
||||
production, route_production_process,
|
||||
process_parameter,
|
||||
product_production_process.seller_ids[0].partner_id.id))
|
||||
# 工序id
|
||||
surface_technics_arr = []
|
||||
route_workcenter_arr = []
|
||||
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
|
||||
if item.route_workcenter_id.surface_technics_id.id:
|
||||
for process_param in production.product_id.model_process_parameters_ids:
|
||||
logging.info('process_param:%s%s' % (process_param.id, process_param.name))
|
||||
if item.route_workcenter_id.surface_technics_id == process_param.process_id:
|
||||
logging.info(
|
||||
'surface_technics_id:%s%s' % (item.route_workcenter_id.surface_technics_id.id,
|
||||
item.route_workcenter_id.surface_technics_id.name))
|
||||
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
|
||||
route_workcenter_arr.append(item.route_workcenter_id.id)
|
||||
if surface_technics_arr:
|
||||
production_process = self.env['sf.production.process'].search(
|
||||
[('id', 'in', surface_technics_arr)],
|
||||
order='sequence asc'
|
||||
)
|
||||
for p in production_process:
|
||||
logging.info('production_process:%s' % p.name)
|
||||
# if production_process:
|
||||
process_parameter = production.product_id.model_process_parameters_ids.filtered(
|
||||
lambda pm: pm.process_id.id == p.id)
|
||||
if process_parameter:
|
||||
# 产品为表面工艺服务的供应商
|
||||
product_production_process = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', process_parameter.id)])
|
||||
if product_production_process:
|
||||
route_production_process = self.env[
|
||||
'mrp.routing.workcenter'].search(
|
||||
[('surface_technics_id', '=', p.id),
|
||||
('id', 'in', route_workcenter_arr)])
|
||||
if route_production_process:
|
||||
workorders_values.append(
|
||||
self.env[
|
||||
'mrp.workorder']._json_workorder_surface_process_str(
|
||||
production, route_production_process,
|
||||
process_parameter,
|
||||
product_production_process.seller_ids[0].partner_id.id))
|
||||
elif production.product_id.categ_id.type == '坯料':
|
||||
embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search(
|
||||
[('embryo_model_type_id', '=', production.product_id.embryo_model_type_id.id)],
|
||||
@@ -633,7 +664,7 @@ class MrpProduction(models.Model):
|
||||
# 表面工艺工序
|
||||
# 模型类型的表面工艺工序模版
|
||||
surface_tmpl_ids = model_type_id.surface_technics_routing_tmpl_ids
|
||||
# 产品选择的表面工艺
|
||||
# 产品选择的表面工艺参数
|
||||
model_process_parameters_ids = rec.product_id.model_process_parameters_ids
|
||||
process_dict = {}
|
||||
if model_process_parameters_ids:
|
||||
@@ -642,7 +673,7 @@ class MrpProduction(models.Model):
|
||||
for surface_tmpl_id in surface_tmpl_ids:
|
||||
if process_id == surface_tmpl_id.route_workcenter_id.surface_technics_id:
|
||||
surface_tmpl_name = surface_tmpl_id.route_workcenter_id.name
|
||||
process_dict.update({int(process_id.category_id.code): '%s-%s' % (
|
||||
process_dict.update({int(process_id.sequence): '%s-%s' % (
|
||||
surface_tmpl_name, process_parameters_id.name)})
|
||||
process_list = sorted(process_dict.keys())
|
||||
for process_num in process_list:
|
||||
@@ -659,14 +690,16 @@ class MrpProduction(models.Model):
|
||||
raise ValidationError('该产品【加工面板】为空!')
|
||||
else:
|
||||
raise ValidationError('该产品没有选择【模版类型】!')
|
||||
|
||||
logging.info('sequence_list: %s' % sequence_list)
|
||||
for work in rec.workorder_ids:
|
||||
if sequence_list.get(work.name):
|
||||
work.sequence = sequence_list[work.name]
|
||||
work_name = work.name
|
||||
logging.info(work_name)
|
||||
if sequence_list.get(work_name):
|
||||
work.sequence = sequence_list[work_name]
|
||||
elif sequence_list.get(work.processing_panel):
|
||||
processing_panel = sequence_list.get(work.processing_panel)
|
||||
if processing_panel.get(work.name):
|
||||
work.sequence = processing_panel[work.name]
|
||||
if processing_panel.get(work_name):
|
||||
work.sequence = processing_panel[work_name]
|
||||
else:
|
||||
raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name)
|
||||
else:
|
||||
@@ -692,8 +725,9 @@ class MrpProduction(models.Model):
|
||||
sequence_max += 1
|
||||
panel_sequence_list.update({tmpl_id.route_workcenter_id.name: sequence_max})
|
||||
for work_id in work_ids:
|
||||
if panel_sequence_list.get(work_id.name):
|
||||
work_id.sequence = panel_sequence_list[work_id.name]
|
||||
work_name = work_id.name
|
||||
if panel_sequence_list.get(work_name):
|
||||
work_id.sequence = panel_sequence_list[work_name]
|
||||
|
||||
# 创建工单并进行排序
|
||||
def _create_workorder(self, item):
|
||||
@@ -701,6 +735,53 @@ class MrpProduction(models.Model):
|
||||
self._reset_work_order_sequence()
|
||||
return True
|
||||
|
||||
def process_range_time(self):
|
||||
for production in self:
|
||||
works = production.workorder_ids
|
||||
pro_plan = self.env['sf.production.plan'].search([('production_id', '=', production.id)], limit=1)
|
||||
if not pro_plan:
|
||||
continue
|
||||
type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False}
|
||||
# 最后一次加工结束时间
|
||||
last_time = pro_plan.date_planned_start
|
||||
# 预置时间
|
||||
for work in works:
|
||||
count = type_map.get(work.routing_type)
|
||||
date_planned_end = None
|
||||
date_planned_start = None
|
||||
duration_expected = datetime.timedelta(minutes=work.duration_expected)
|
||||
reserve_time = datetime.timedelta(minutes=work.reserved_duration)
|
||||
if not count:
|
||||
# 第一轮加工
|
||||
if work.routing_type == '装夹预调':
|
||||
date_planned_end = last_time - reserve_time
|
||||
date_planned_start = date_planned_end - duration_expected
|
||||
elif work.routing_type == 'CNC加工':
|
||||
date_planned_start = last_time
|
||||
date_planned_end = last_time + duration_expected
|
||||
last_time = date_planned_end
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
type_map.update({work.routing_type: True})
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
work.leave_id.write({
|
||||
'date_from': date_planned_start,
|
||||
'date_to': date_planned_end,
|
||||
})
|
||||
# work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end})
|
||||
work.date_planned_start = date_planned_start
|
||||
work.date_planned_finished = date_planned_end
|
||||
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', work.routing_type)])
|
||||
|
||||
work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,
|
||||
'duration_expected': routing_workcenter.time_cycle})
|
||||
|
||||
# 修改标记已完成方法
|
||||
def button_mark_done1(self):
|
||||
if not self.workorder_ids.filtered(lambda w: w.routing_type not in ['表面工艺']):
|
||||
@@ -808,8 +889,8 @@ class MrpProduction(models.Model):
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_production_id': self.id,
|
||||
'default_programming_state': '编程中' if cloud_programming[
|
||||
'programming_state'] != '已下发' else '已下发',
|
||||
'default_reprogramming_num': cloud_programming['reprogramming_num'],
|
||||
'default_programming_states': cloud_programming['programming_state'],
|
||||
'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False
|
||||
}
|
||||
}
|
||||
@@ -954,34 +1035,21 @@ class MrpProduction(models.Model):
|
||||
[('origin', '=', sale_order.name), ('name', 'ilike', 'WH/OUT/')])
|
||||
move = out_picking.move_ids.filtered(lambda pd: pd.product_id == self.product_id)
|
||||
move_values = {'product_description_variants': '',
|
||||
'date_planned': datetime.now(),
|
||||
'date_deadline': datetime.now(),
|
||||
# 'move_dest_ids': self.env['stock.move'].search([('id', '=', move.id)]),
|
||||
'date_planned': fields.Datetime.now(),
|
||||
'date_deadline': fields.Datetime.now(),
|
||||
'move_dest_ids': move,
|
||||
'group_id': False,
|
||||
'group_id': move.group_id,
|
||||
'route_ids': [],
|
||||
'warehouse_id': self.warehouse_id,
|
||||
'priority': 0,
|
||||
'orderpoint_id': False,
|
||||
'product_packaging_id': False}
|
||||
rule = self.env['stock.rule'].search(
|
||||
[('action', '=', 'pull'), ('procure_method', '=', 'mts_else_mto'), (
|
||||
'location_dest_id', '=', self.env['stock.location'].search([('parent_path', '=', '2/5/')]).id),
|
||||
('location_src_id', '=', self.env['stock.location'].search(
|
||||
[('barcode', '=', 'CP')]).id)])
|
||||
# origin = move._prepare_procurement_origin()
|
||||
procurement_requests.append(self.env['procurement.group'].Procurement(
|
||||
move.product_id, 1.0, move.product_uom,
|
||||
self.env['stock.location'].search([('barcode', '=', 'CP')]),
|
||||
rule and rule.name or "/",
|
||||
move.location_id, move.rule_id and move.rule_id.name or "/",
|
||||
sale_order.name, move.company_id, move_values))
|
||||
self.env['procurement.group'].run(procurement_requests,
|
||||
raise_user_error=not self.env.context.get('from_orderpoint'))
|
||||
# self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
|
||||
# productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
|
||||
# (
|
||||
# p.move_dest_ids.procure_method != 'make_to_order' and
|
||||
# not p.move_raw_ids and not p.workorder_ids)).action_confirm()
|
||||
productions = self.env['mrp.production'].sudo().search(
|
||||
[('origin', '=', self.origin)], order='id desc', limit=1)
|
||||
move = self.env['stock.move'].search([('origin', '=', productions.name)], order='id desc')
|
||||
@@ -1009,74 +1077,41 @@ class MrpProduction(models.Model):
|
||||
'picking_id': sfp_move.picking_id.id, 'picking_type_id': sfp_move.picking_type_id.id,
|
||||
'production_id': False})
|
||||
productions.write({'programming_no': self.programming_no, 'is_remanufacture': True})
|
||||
productions.procurement_group_id.mrp_production_ids.move_dest_ids.write(
|
||||
{'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])})
|
||||
# productions.procurement_group_id.mrp_production_ids.move_dest_ids.write(
|
||||
# {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])})
|
||||
stock_picking_remanufacture = self.env['stock.picking'].search([('origin', '=', productions.name)])
|
||||
for pick in stock_picking_remanufacture:
|
||||
if pick.name.startswith('WH/PC/') or pick.name.startswith('WH/INT/'):
|
||||
if pick.move_ids:
|
||||
product_type_id = pick.move_ids[0].product_id.categ_id
|
||||
if product_type_id.name == '坯料':
|
||||
location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
|
||||
if not location_id:
|
||||
logging.info(f'没有搜索到【坯料存货区】: {location_id}')
|
||||
break
|
||||
if pick.picking_type_id.name == '内部调拨':
|
||||
if pick.location_dest_id.product_type != product_type_id:
|
||||
pick.location_dest_id = location_id.id
|
||||
elif pick.picking_type_id.name == '生产发料':
|
||||
if pick.location_id.product_type != product_type_id:
|
||||
pick.location_id = location_id.id
|
||||
scarp_process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', self.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if scarp_process_parameter_workorder:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.programming_no)], order='name asc')
|
||||
[('programming_no', '=', self.programming_no), ('id', '!=', productions.id)], order='name asc')
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
|
||||
purchase_orders = self.env['purchase.order'].search([('origin', 'ilike', ','.join(production_list))])
|
||||
for purchase_item in purchase_orders.order_line:
|
||||
for process_item in scarp_process_parameter_workorder:
|
||||
if purchase_item.product_id.categ_type == '表面工艺':
|
||||
if purchase_item.product_id.server_product_process_parameters_id == process_item.surface_technics_parameters_id:
|
||||
print(purchase_orders.find(productions.name))
|
||||
if purchase_orders.find(productions.name) == -1:
|
||||
purchase_orders.origin += productions.name
|
||||
if purchase_orders.origin.find(productions.name) == -1:
|
||||
purchase_orders.origin += ',' + productions.name
|
||||
if item['is_reprogramming'] is False:
|
||||
productions._create_workorder(item)
|
||||
productions.programming_state = '已编程'
|
||||
for production_item in productions:
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_item.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if process_parameter_workorder:
|
||||
is_pick = False
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
|
||||
for i in range(len(sorted_workorders) - 1):
|
||||
if m == 0:
|
||||
is_pick = False
|
||||
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
|
||||
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
|
||||
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
|
||||
if sorted_workorders[i] not in consecutive_workorders:
|
||||
consecutive_workorders.append(sorted_workorders[i])
|
||||
consecutive_workorders.append(sorted_workorders[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
|
||||
if is_pick is False:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production_item)
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if is_pick is False and m == 0:
|
||||
if len(sorted_workorders) == 1:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production_item)
|
||||
else:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production_item)
|
||||
else:
|
||||
productions.programming_state = '编程中'
|
||||
return productions
|
||||
|
||||
@@ -21,7 +21,7 @@ class ResMrpRoutingWorkcenter(models.Model):
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
|
||||
bom_id = fields.Many2one('mrp.bom', required=False)
|
||||
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
|
||||
|
||||
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
||||
def get_no(self):
|
||||
international_standards = self.search(
|
||||
[('code', '!=', ''), ('active', 'in', [True, False])],
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import datetime
|
||||
from datetime import timedelta, time
|
||||
from collections import defaultdict
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
from odoo.addons.resource.models.resource import Intervals
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
import math
|
||||
|
||||
|
||||
class ResWorkcenter(models.Model):
|
||||
@@ -41,13 +44,16 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
oee_target = fields.Float(
|
||||
string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True)
|
||||
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month',
|
||||
store=True)
|
||||
|
||||
time_start = fields.Float('Setup Time', tracking=True)
|
||||
time_stop = fields.Float('Cleanup Time', tracking=True)
|
||||
costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True)
|
||||
|
||||
equipment_status = fields.Selection(
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||
("封存(报废)", "封存(报废)")],
|
||||
string="设备状态", related='equipment_id.state')
|
||||
|
||||
# @api.depends('equipment_id')
|
||||
@@ -126,6 +132,106 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
# AGV是否可配送
|
||||
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
|
||||
# 生产线优化
|
||||
available_machine_number = fields.Integer(string="可用机台数量")
|
||||
single_machine_capacity = fields.Float(string="单台小时产能")
|
||||
production_line_hour_capacity = fields.Float(string="生产线小时产能", readonly=True,
|
||||
compute='_compute_production_line_hour_capacity')
|
||||
effective_working_hours_day = fields.Float(string="日有效工作时长", default=0, readonly=True,
|
||||
compute='_compute_effective_working_hours_day')
|
||||
default_capacity = fields.Float(
|
||||
string='生产线日产能', compute='_compute_production_line_day_capacity', readonly=True)
|
||||
|
||||
# 计算生产线日产能
|
||||
@api.depends('production_line_hour_capacity', 'effective_working_hours_day')
|
||||
def _compute_production_line_day_capacity(self):
|
||||
for record in self:
|
||||
record.default_capacity = round(
|
||||
record.production_line_hour_capacity * record.effective_working_hours_day, 2)
|
||||
|
||||
# 计算日有效工作时长
|
||||
@api.depends('resource_calendar_id', 'resource_calendar_id.attendance_ids',
|
||||
'resource_calendar_id.attendance_ids.hour_to', 'resource_calendar_id.attendance_ids.hour_from')
|
||||
def _compute_effective_working_hours_day(self):
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == self.get_current_day_of_week(datetime.datetime.now())]
|
||||
if attendance_ids:
|
||||
for attendance_id in attendance_ids:
|
||||
if attendance_id.hour_from and attendance_id.hour_to:
|
||||
record.effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
|
||||
else:
|
||||
record.effective_working_hours_day = 0
|
||||
|
||||
# 获取传入时间是星期几
|
||||
def get_current_day_of_week(self, datetime):
|
||||
day_num = datetime.weekday()
|
||||
return str(day_num)
|
||||
|
||||
# 计算生产线小时产能
|
||||
@api.depends('single_machine_capacity', 'available_machine_number')
|
||||
def _compute_production_line_hour_capacity(self):
|
||||
for record in self:
|
||||
record.production_line_hour_capacity = round(
|
||||
record.single_machine_capacity * record.available_machine_number, 2)
|
||||
|
||||
# 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
def deal_with_workcenter_calendar(self, start_date):
|
||||
start_date = start_date + timedelta(hours=8) # 转换为北京时间
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == record.get_current_day_of_week(start_date) and self.is_between_times(
|
||||
p.hour_from, p.hour_to, start_date)]
|
||||
return False if not attendance_ids else True
|
||||
|
||||
# 判断传入时间是否在配置的工作中心的工作日历内
|
||||
def is_between_times(self, hour_from, hour_to, start_date):
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_from)
|
||||
start_time = time(integer_part, decimal_part)
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_to)
|
||||
end_time = time(integer_part, decimal_part)
|
||||
return start_time <= start_date.time() <= end_time
|
||||
|
||||
# 获取整数部分和小数部分
|
||||
def get_integer_and_decimal_parts(self, value):
|
||||
integer_part = int(value)
|
||||
decimal_part = value - integer_part
|
||||
if decimal_part > 0:
|
||||
decimal_part = round(decimal_part, 2) * 60
|
||||
return int(integer_part), math.ceil(decimal_part)
|
||||
|
||||
# 处理排程是否超过日产能
|
||||
def deal_available_default_capacity(self, date_planned):
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d')
|
||||
date_planned_end = date_planned + timedelta(days=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end),
|
||||
('state', 'not in', ['draft', 'cancel'])])
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.default_capacity:
|
||||
return False
|
||||
return True
|
||||
|
||||
# 处理排程是否超过小时产能
|
||||
def deal_available_single_machine_capacity(self, date_planned):
|
||||
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00')
|
||||
date_planned_end = date_planned + timedelta(hours=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d %H:00:00')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end),
|
||||
('state', 'not in', ['draft', 'cancel'])])
|
||||
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.production_line_hour_capacity:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class ResWorkcenterProductivity(models.Model):
|
||||
_inherit = 'mrp.workcenter.productivity'
|
||||
|
||||
@@ -58,9 +58,15 @@ class ResMrpWorkOrder(models.Model):
|
||||
('cancel', '取消')], string='Status',
|
||||
compute='_compute_state', store=True,
|
||||
default='pending', copy=False, readonly=True, recursive=True, index=True, tracking=True)
|
||||
|
||||
# state = fields.Selection(selection_add=[('to be detected', "待检测"), ('rework', '返工')], tracking=True)
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
@api.depends('production_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
for item in self:
|
||||
item.manual_quotation = item.production_id.manual_quotation
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True)
|
||||
|
||||
def _compute_working_users(self):
|
||||
super()._compute_working_users()
|
||||
@@ -178,17 +184,12 @@ class ResMrpWorkOrder(models.Model):
|
||||
if order.routing_type == '表面工艺':
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', order.production_id.programming_no)], order='name asc')
|
||||
production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False)
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
|
||||
for line in purchase.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id and line.product_qty == len(
|
||||
production_programming):
|
||||
# server_product = self.env['product.template'].search(
|
||||
# [('server_product_process_parameters_id', '=', pp.id),
|
||||
# ('detailed_type', '=', 'service')])
|
||||
# purchase_order_line = self.env['purchase.order.line'].search(
|
||||
# [('product_id', '=', server_product.id), ('product_qty', '=', len(production_programming))])
|
||||
# if purchase_order_line:
|
||||
production_no_remanufacture):
|
||||
order.surface_technics_purchase_count = len(purchase)
|
||||
else:
|
||||
order.surface_technics_purchase_count = 0
|
||||
@@ -237,6 +238,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='功能刀具状态', default='0',
|
||||
store=True, compute='_compute_tool_state')
|
||||
tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
|
||||
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
||||
|
||||
@api.depends('cnc_ids.tool_state')
|
||||
def _compute_tool_state_remark(self):
|
||||
@@ -646,29 +648,36 @@ class ResMrpWorkOrder(models.Model):
|
||||
# 拼接工单对象属性值
|
||||
def json_workorder_str(self, k, production, route, item):
|
||||
# 计算预计时长duration_expected
|
||||
if route.routing_type == '切割':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', '切割')]).time_cycle
|
||||
# elif route.routing_type == '获取CNC加工程序':
|
||||
routing_types = ['切割', '装夹预调', 'CNC加工', '解除装夹']
|
||||
if route.routing_type in routing_types:
|
||||
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', route.routing_type)])
|
||||
duration_expected = routing_workcenter.time_cycle
|
||||
reserved_duration = routing_workcenter.reserved_duration
|
||||
# if route.routing_type == '切割':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '获取CNC加工程序')]).time_cycle
|
||||
elif route.routing_type == '装夹预调':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', '装夹预调')]).time_cycle
|
||||
# elif route.routing_type == '前置三元定位检测':
|
||||
# [('name', '=', '切割')]).time_cycle
|
||||
# # elif route.routing_type == '获取CNC加工程序':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '获取CNC加工程序')]).time_cycle
|
||||
# elif route.routing_type == '装夹预调':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '前置三元定位检测')]).time_cycle
|
||||
elif route.routing_type == 'CNC加工':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', 'CNC加工')]).time_cycle
|
||||
# elif route.routing_type == '后置三元质量检测':
|
||||
# [('name', '=', '装夹预调')]).time_cycle
|
||||
# # elif route.routing_type == '前置三元定位检测':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '前置三元定位检测')]).time_cycle
|
||||
# elif route.routing_type == 'CNC加工':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '后置三元质量检测')]).time_cycle
|
||||
elif route.routing_type == '解除装夹':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', '解除装夹')]).time_cycle
|
||||
# [('name', '=', 'CNC加工')]).time_cycle
|
||||
# # elif route.routing_type == '后置三元质量检测':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '后置三元质量检测')]).time_cycle
|
||||
# elif route.routing_type == '解除装夹':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '解除装夹')]).time_cycle
|
||||
else:
|
||||
duration_expected = 60
|
||||
reserved_duration = 30
|
||||
workorders_values_str = [0, '', {
|
||||
'product_uom_id': production.product_uom_id.id,
|
||||
'qty_producing': 0,
|
||||
@@ -692,6 +701,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
item),
|
||||
# 'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
|
||||
# production)
|
||||
'reserved_duration': reserved_duration,
|
||||
}]
|
||||
return workorders_values_str
|
||||
|
||||
@@ -956,12 +966,14 @@ class ResMrpWorkOrder(models.Model):
|
||||
else:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.production_id.programming_no)], order='name asc')
|
||||
production_no_remanufacture = production_programming.filtered(
|
||||
lambda a: a.is_remanufacture is False)
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search(
|
||||
[('origin', '=', ','.join(production_list))])
|
||||
[('origin', 'ilike', ','.join(production_list))])
|
||||
for line in purchase_orders.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id and line.product_qty == len(
|
||||
production_programming):
|
||||
production_no_remanufacture):
|
||||
if purchase_orders.state == 'purchase':
|
||||
workorder.state = 'ready'
|
||||
else:
|
||||
@@ -1088,7 +1100,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
[('barcode', 'ilike', 'VL-SPOC')]).id),
|
||||
('origin', '=', self.production_id.name)])
|
||||
if move_out.state != 'done':
|
||||
move_out.write({'state': 'assigned'})
|
||||
move_out.write({'state': 'assigned', 'production_id': False})
|
||||
self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self))
|
||||
|
||||
# move_out._action_assign()
|
||||
@@ -1154,10 +1166,11 @@ class ResMrpWorkOrder(models.Model):
|
||||
def button_finish(self):
|
||||
for record in self:
|
||||
if record.routing_type == '装夹预调':
|
||||
if not record.material_center_point and record.X_deviation_angle > 0:
|
||||
raise UserError("请对前置三元检测定位参数进行计算定位")
|
||||
if not record.rfid_code and record.is_rework is False:
|
||||
raise UserError("请扫RFID码进行绑定")
|
||||
if record.is_rework is False:
|
||||
if not record.material_center_point and record.X_deviation_angle > 0:
|
||||
raise UserError("坯料中心点为空或X偏差角度小于等于0")
|
||||
record.process_state = '待加工'
|
||||
# record.write({'process_state': '待加工'})
|
||||
record.production_id.process_state = '待加工'
|
||||
@@ -1257,15 +1270,14 @@ class ResMrpWorkOrder(models.Model):
|
||||
|
||||
# 解绑托盘
|
||||
def unbind_tray(self):
|
||||
self.write({
|
||||
self.production_id.workorder_ids.write({
|
||||
'rfid_code': False,
|
||||
'tray_serial_number': False,
|
||||
'tray_product_id': False,
|
||||
'tray_brand_id': False,
|
||||
'tray_type_id': False,
|
||||
'tray_model_id': False,
|
||||
'is_trayed': False
|
||||
})
|
||||
'is_trayed': False})
|
||||
|
||||
# 将FTP的检测报告文件下载到临时目录
|
||||
def download_reportfile_tmp(self, workorder, reportpath):
|
||||
@@ -1328,7 +1340,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
'name': 'button_delivery',
|
||||
'type': 'object',
|
||||
'string': '解除装夹',
|
||||
'class': 'btn-primary',
|
||||
'class': 'btn-primary jikimo_button_confirm',
|
||||
# 'className': 'btn-primary',
|
||||
'modifiers': '{"force_show": 1}'
|
||||
})
|
||||
@@ -1339,18 +1351,23 @@ class ResMrpWorkOrder(models.Model):
|
||||
return res
|
||||
|
||||
def button_delivery(self):
|
||||
production_ids = []
|
||||
workorder_ids = []
|
||||
# production_ids = []
|
||||
# workorder_ids = []
|
||||
delivery_type = '运送空料架'
|
||||
max_num = 4 # 最大配送数量
|
||||
if len(self) > max_num:
|
||||
raise UserError('仅限于拆卸1-4个制造订单,请重新选择')
|
||||
for item in self:
|
||||
if item.state != 'ready':
|
||||
raise UserError('请选择状态为【就绪】的工单进行解除装夹')
|
||||
|
||||
production_ids.append(item.production_id.id)
|
||||
workorder_ids.append(item.id)
|
||||
# max_num = 4 # 最大配送数量
|
||||
# feeder_station_start_id = False
|
||||
# if len(self) > max_num:
|
||||
# raise UserError('仅限于拆卸1-4个制造订单,请重新选择')
|
||||
# for item in self:
|
||||
# if item.state != 'ready':
|
||||
# raise UserError('请选择状态为【就绪】的工单进行解除装夹')
|
||||
#
|
||||
# production_ids.append(item.production_id.id)
|
||||
# workorder_ids.append(item.id)
|
||||
# if not feeder_station_start_id:
|
||||
# down_product_agv_scheduling = item.get_down_product_agv_scheduling()
|
||||
# if down_product_agv_scheduling:
|
||||
# feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
|
||||
return {
|
||||
'name': _('确认'),
|
||||
'type': 'ir.actions.act_window',
|
||||
@@ -1359,11 +1376,12 @@ class ResMrpWorkOrder(models.Model):
|
||||
'target': 'new',
|
||||
'context': {
|
||||
# 'default_delivery_ids': [(6, 0, delivery_ids)],
|
||||
'default_production_ids': [(6, 0, production_ids)],
|
||||
# 'default_production_ids': [(6, 0, production_ids)],
|
||||
'default_delivery_type': delivery_type,
|
||||
'default_workorder_ids': [(6, 0, workorder_ids)],
|
||||
# 'default_workorder_ids': [(6, 0, workorder_ids)],
|
||||
'default_workcenter_id': self.env.context.get('default_workcenter_id'),
|
||||
'default_confirm_button': '确认解除'
|
||||
'default_confirm_button': '确认解除',
|
||||
# 'default_feeder_station_start_id': feeder_station_start_id,
|
||||
}}
|
||||
|
||||
|
||||
|
||||
22
sf_manufacturing/models/res_config_setting.py
Normal file
22
sf_manufacturing/models/res_config_setting.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
is_agv_task_dispatch = fields.Boolean('是否下发AGV任务', default=False)
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
values = super(ResConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
is_agv_task_dispatch = config.get_param('is_agv_task_dispatch')
|
||||
values.update(
|
||||
is_agv_task_dispatch=is_agv_task_dispatch,
|
||||
)
|
||||
return values
|
||||
|
||||
def set_values(self):
|
||||
super(ResConfigSettings, self).set_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
config.set_param("is_agv_task_dispatch", self.is_agv_task_dispatch or False)
|
||||
@@ -169,7 +169,6 @@ class StockRule(models.Model):
|
||||
else:
|
||||
forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
|
||||
procure_method = 'make_to_stock'
|
||||
|
||||
move_values = rule._get_stock_move_values(*procurement)
|
||||
move_values['procure_method'] = procure_method
|
||||
moves_values_by_company[procurement.company_id.id].append(move_values)
|
||||
@@ -177,13 +176,10 @@ class StockRule(models.Model):
|
||||
for company_id, moves_values in moves_values_by_company.items():
|
||||
# create the move as SUPERUSER because the current user may not have the rights to do it (mto product
|
||||
# launched by a sale for example)
|
||||
logging.info(moves_values)
|
||||
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
|
||||
moves_values)
|
||||
logging.info(moves)
|
||||
# Since action_confirm launch following procurement_group we should activate it.
|
||||
moves._action_confirm()
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
@@ -292,28 +288,46 @@ class StockRule(models.Model):
|
||||
# 为同一个product_id创建一个生产订单名称列表
|
||||
product_id_to_production_names[product_id] = [production.name for production in all_production]
|
||||
for production_item in productions:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('product_id.id', '=', production_item.product_id.id),
|
||||
('origin', '=', production_item.origin)],
|
||||
limit=1, order='id asc')
|
||||
if production_item.product_id.id in product_id_to_production_names:
|
||||
if production_item.product_id.model_process_parameters_ids:
|
||||
is_purchase = False
|
||||
sorted_process_parameters = sorted(production_item.product_id.model_process_parameters_ids,
|
||||
key=lambda w: w.id)
|
||||
if not production_programming.programming_no:
|
||||
if production_item.product_id.model_process_parameters_ids:
|
||||
is_purchase = False
|
||||
sorted_process_parameters = sorted(production_item.product_id.model_process_parameters_ids,
|
||||
key=lambda w: w.id)
|
||||
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
for i in range(len(sorted_process_parameters) - 1):
|
||||
if m == 0:
|
||||
is_purchase = False
|
||||
if self.env['product.template']._get_process_parameters_product(
|
||||
sorted_process_parameters[i]).partner_id == self.env[
|
||||
'product.template']._get_process_parameters_product(sorted_process_parameters[
|
||||
i + 1]).partner_id and \
|
||||
sorted_process_parameters[i].gain_way == '外协':
|
||||
if sorted_process_parameters[i] not in consecutive_process_parameters:
|
||||
consecutive_process_parameters.append(sorted_process_parameters[i])
|
||||
consecutive_process_parameters.append(sorted_process_parameters[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
for i in range(len(sorted_process_parameters) - 1):
|
||||
if m == 0:
|
||||
is_purchase = False
|
||||
if self.env['product.template']._get_process_parameters_product(
|
||||
sorted_process_parameters[i]).partner_id == self.env[
|
||||
'product.template']._get_process_parameters_product(sorted_process_parameters[
|
||||
i + 1]).partner_id and \
|
||||
sorted_process_parameters[i].gain_way == '外协':
|
||||
if sorted_process_parameters[i] not in consecutive_process_parameters:
|
||||
consecutive_process_parameters.append(sorted_process_parameters[i])
|
||||
consecutive_process_parameters.append(sorted_process_parameters[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if sorted_process_parameters[i] in consecutive_process_parameters:
|
||||
is_purchase = True
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
# 当前面的连续外协采购单生成再生成当前外协采购单
|
||||
if is_purchase is False:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
@@ -322,39 +336,22 @@ class StockRule(models.Model):
|
||||
is_purchase = True
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
# 当前面的连续外协采购单生成再生成当前外协采购单
|
||||
if is_purchase is False:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if sorted_process_parameters[i] in consecutive_process_parameters:
|
||||
is_purchase = True
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if is_purchase is False and m == 0:
|
||||
if len(sorted_process_parameters) == 1:
|
||||
self.env['purchase.order'].get_purchase_order(sorted_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
else:
|
||||
self.env['purchase.order'].get_purchase_order(sorted_process_parameters[i],
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if is_purchase is False and m == 0:
|
||||
if len(sorted_process_parameters) == 1:
|
||||
self.env['purchase.order'].get_purchase_order(sorted_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
else:
|
||||
self.env['purchase.order'].get_purchase_order(sorted_process_parameters[i],
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
# # 同一个产品多个制造订单对应一个编程单和模型库
|
||||
# # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递
|
||||
if not production_item.programming_no:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('product_id.id', '=', production_item.product_id.id),
|
||||
('origin', '=', production_item.origin)],
|
||||
limit=1, order='id asc')
|
||||
if not production_programming.programming_no:
|
||||
production_item.fetchCNC(
|
||||
', '.join(product_id_to_production_names[production_item.product_id.id]))
|
||||
@@ -444,7 +441,7 @@ class ProductionLot(models.Model):
|
||||
if product.tracking == "serial":
|
||||
last_serial = self.env['stock.lot'].search(
|
||||
[('company_id', '=', company.id), ('product_id', '=', product.id)],
|
||||
limit=1, order='id DESC')
|
||||
limit=1, order='name desc')
|
||||
if last_serial:
|
||||
if product.categ_id.name == '刀具':
|
||||
return self.env['stock.lot'].get_tool_generate_lot_names1(company, product)
|
||||
@@ -548,7 +545,7 @@ class StockPicking(models.Model):
|
||||
|
||||
# 设置外协出入单的名称
|
||||
def _get_name_Res(self, rescode):
|
||||
last_picking = self.sudo().search([('name', 'like', rescode)], order='create_date desc,id desc', limit=1)
|
||||
last_picking = self.sudo().search([('name', 'ilike', rescode)], order='create_date desc,id desc', limit=1)
|
||||
if not last_picking:
|
||||
num = "%04d" % 1
|
||||
else:
|
||||
@@ -577,6 +574,7 @@ class StockPicking(models.Model):
|
||||
('origin', '=', self.origin), ('picking_id', '=', self.id)])
|
||||
if self.location_id == move_in.location_id and self.location_dest_id == move_in.location_dest_id:
|
||||
if move_out.origin == move_in.origin:
|
||||
move_in.write({'production_id': False})
|
||||
if move_out.picking_id.state != 'done':
|
||||
raise UserError(
|
||||
_('该入库单对应的单号为%s的出库单还未完成,不能进行验证操作!' % move_out.picking_id.name))
|
||||
@@ -662,6 +660,11 @@ class ReStockMove(models.Model):
|
||||
return move_values
|
||||
|
||||
def _get_new_picking_values_Res(self, item, sorted_workorders, rescode):
|
||||
picking_type_id = self.mapped('picking_type_id').id
|
||||
if rescode == 'WH/OCOUT/':
|
||||
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_out').id
|
||||
elif rescode == 'WH/OCIN/':
|
||||
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_in').id
|
||||
return {
|
||||
'name': self.env['stock.picking']._get_name_Res(rescode),
|
||||
'origin': item.name,
|
||||
@@ -670,7 +673,7 @@ class ReStockMove(models.Model):
|
||||
'user_id': False,
|
||||
'move_type': self.mapped('group_id').move_type or 'direct',
|
||||
'partner_id': sorted_workorders.supplier_id.id,
|
||||
'picking_type_id': self.mapped('picking_type_id').id,
|
||||
'picking_type_id': picking_type_id,
|
||||
'location_id': self.mapped('location_id').id,
|
||||
'location_dest_id': self.mapped('location_dest_id').id,
|
||||
'state': 'confirmed',
|
||||
|
||||
@@ -3,13 +3,34 @@ $(document).off('keydown')
|
||||
$(document).on('keydown', 'body.o_web_client', function (e) {
|
||||
setTimeout(() => {
|
||||
RFID = ''
|
||||
}, 200)
|
||||
|
||||
}, 200)
|
||||
if(e.key == 'Enter' && e.keyCode == 13 || e.key == 'Tab' && e.keyCode == 9){
|
||||
|
||||
let fieldValue1 = $('[name="routing_type"]');
|
||||
console.log('字段值:', fieldValue1.text());
|
||||
console.log(RFID)
|
||||
if(!RFID || RFID.length <= 3) return;
|
||||
$('[name="button_start"]').trigger('click')
|
||||
RFID = ''
|
||||
let fieldValue2 = $('[name="rfid_code"]');
|
||||
console.log('字段值2:', fieldValue2.text());
|
||||
// if(!RFID || RFID.length <= 3) return;
|
||||
// $('[name="button_start"]').trigger('click')
|
||||
// setTimeout(() => {
|
||||
// $('.o_dialog .modal-footer .btn-primary').trigger('click')
|
||||
// }, 50)
|
||||
// RFID = ''
|
||||
// return;
|
||||
|
||||
// fieldValue2.val() === '')
|
||||
// 检查字段值是否等于“装夹预调”
|
||||
if (fieldValue1.text() === '装夹预调') {
|
||||
if (!RFID || RFID.length <= 3) return;
|
||||
$('[name="button_start"]').trigger('click');
|
||||
setTimeout(() => {
|
||||
$('.o_dialog .modal-footer .btn-primary').trigger('click');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
RFID = '';
|
||||
return;
|
||||
}
|
||||
RFID += e.key
|
||||
|
||||
@@ -10,7 +10,7 @@ odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) {
|
||||
title: "确认",
|
||||
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
|
||||
buttons: [
|
||||
{ text: "确认", classes: 'btn-primary', close: true, click: () => dispatchConfirmed(parent, params) },
|
||||
{ text: "确认", classes: 'btn-primary jikimo_button_confirm', close: true, click: () => dispatchConfirmed(parent, params) },
|
||||
{ text: "取消", close: true },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
confirm="是否确认更新程序"
|
||||
attrs="{'invisible': ['|',('state', '!=', 'rework'),('programming_state', '!=', '已编程未下发')]}"/>
|
||||
<button name="button_rework" string="返工" type="object" groups="sf_base.group_sf_mrp_user"
|
||||
attrs="{'invisible': ['|',('state', '!=', 'rework') ,('programming_state', '!=', '已编程')]}"/>
|
||||
attrs="{'invisible': ['|','|',('state', '!=', 'rework') ,('programming_state', '!=', '已编程'),('is_rework', '=', True)]}"/>
|
||||
<button name="button_scrap_new" string="报废" type="object"
|
||||
groups="sf_base.group_sf_mrp_user"
|
||||
attrs="{'invisible': ['|',('is_scrap', '=', False),('state','=','cancel')]}"/>
|
||||
@@ -201,6 +201,19 @@
|
||||
data-hotkey="l"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[@name='action_view_mo_delivery']" position="before">
|
||||
<button class="oe_stat_button" name="action_view_remanufacture_productions" type="object"
|
||||
icon="fa-wrench" attrs="{'invisible': [('remanufacture_count', '=', 0)]}"
|
||||
groups="mrp.group_mrp_user">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="remanufacture_count"/>
|
||||
</span>
|
||||
<span class="o_stat_text">新的制造</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//header//button[@name='action_toggle_is_locked']" position="replace">
|
||||
<button name="action_toggle_is_locked"
|
||||
attrs="{'invisible': ['|', ('show_lock', '=', False), ('is_locked', '=', True)]}"
|
||||
@@ -424,6 +437,12 @@
|
||||
<xpath expr="//header//button[@name='action_cancel']" position="replace">
|
||||
<button name="action_cancel" type="object" string="取消" groups="sf_base.group_sf_mrp_user"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='state']" position="replace">
|
||||
<field name="state" decoration-success="state in ('done', 'to_close')"
|
||||
decoration-warning="state == 'progress'" decoration-info="state == 'confirmed'"
|
||||
decoration-danger="state in ('cancel','rework','scrap')" decoration-muted="state == 'draft'"
|
||||
optional="show" widget="badge" class="text-dark"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='state']" position="after">
|
||||
<field name="tool_state" invisible="1"/>
|
||||
</xpath>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<field name="bom_product_template_attribute_value_ids" position="after">
|
||||
<field name="routing_type" required="1"/>
|
||||
<field name="is_repeat"/>
|
||||
<field name="reserved_duration"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<div name="button_box" position="inside">
|
||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||
@@ -25,38 +25,81 @@
|
||||
</record>
|
||||
|
||||
<record id="custom_model_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">custom.model.form.view.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//form//sheet' position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<field name="name">custom.model.form.view.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//form//sheet' position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="after">
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Desktop view -->
|
||||
<xpath expr='(//field[@name="name"])[1]' position="after">
|
||||
<field name="equipment_status" />
|
||||
<field name="equipment_image" />
|
||||
</xpath>
|
||||
<xpath expr='(//field[@name="name"])[2]' position="after">
|
||||
<field name="equipment_status" />
|
||||
<field name="equipment_image" widget="image" />
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<label for="default_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="default_capacity" string="产线日产能"/>
|
||||
台
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="before">
|
||||
<label for="available_machine_number"/>
|
||||
<div class="o_row">
|
||||
<field name="available_machine_number"/>
|
||||
台
|
||||
</div>
|
||||
<label for="single_machine_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="single_machine_capacity"/>
|
||||
件/小时
|
||||
</div>
|
||||
<label for="production_line_hour_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="production_line_hour_capacity"/>
|
||||
件/小时
|
||||
</div>
|
||||
<label for="effective_working_hours_day"/>
|
||||
<div class="o_row">
|
||||
<field name="effective_working_hours_day"/>
|
||||
小时
|
||||
</div>
|
||||
</xpath>
|
||||
<!-- <xpath expr='//group[@name="capacity"]//field[@name="default_capacity"])' position="after">-->
|
||||
<!-- <group>-->
|
||||
<!-- <field name="available_machine_number"/>-->
|
||||
<!-- <field name="single_machine_capacity"/>-->
|
||||
<!-- <field name="production_line_hour_capacity"/>-->
|
||||
<!-- <field name="effective_working_hours_day"/>-->
|
||||
|
||||
<!-- </group>-->
|
||||
<!-- </xpath>-->
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Desktop view -->
|
||||
<xpath expr='(//field[@name="name"])[1]' position="after">
|
||||
<field name="equipment_status"/>
|
||||
<field name="equipment_image"/>
|
||||
</xpath>
|
||||
<xpath expr='(//field[@name="name"])[2]' position="after">
|
||||
<field name="equipment_status"/>
|
||||
<field name="equipment_image" widget="image"/>
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
@@ -73,11 +116,11 @@
|
||||
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
||||
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<!-------------------->
|
||||
<!-------------------->
|
||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
@@ -99,7 +142,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 继承原有的看板视图 -->
|
||||
<!-- 继承原有的看板视图 -->
|
||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
@@ -391,9 +434,9 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<div name="button_box" position="inside">
|
||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<field name="name" decoration-success="is_subcontract" decoration-bf="is_subcontract"/>
|
||||
</field>
|
||||
<field name="name" position="before">
|
||||
<field name="sequence"/>
|
||||
<field name="sequence" string="序号"/>
|
||||
<field name='user_permissions' invisible="1"/>
|
||||
</field>
|
||||
<field name="name" position="after">
|
||||
@@ -36,6 +36,9 @@
|
||||
<xpath expr="//field[@name='date_planned_start']" position="replace">
|
||||
<field name="date_planned_start" string="计划开始日期" optional="show"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_planned_start']" position="before">
|
||||
<field name="reserved_duration" string="计划预留时间" optional="hide"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_planned_finished']" position="replace">
|
||||
<field name="date_planned_finished" string="计划结束日期" optional="hide"/>
|
||||
</xpath>
|
||||
@@ -195,7 +198,7 @@
|
||||
attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('state','!=','progress'),('is_rework','=',True)]}"/>
|
||||
<button name="unbind_tray" type="object" string="解绑托盘"
|
||||
class="btn-primary"
|
||||
attrs="{'invisible': ['|', '|', ('routing_type','!=','装夹预调'),('state','!=','progress'), ('is_trayed', '=', False)]}"/>
|
||||
attrs="{'invisible': ['|', '|', '|', ('routing_type','!=','装夹预调'),('state','!=','progress'), ('is_trayed', '=', False), ('state', 'in', ('done'))]}"/>
|
||||
<button name="print_method" type="object" string="打印二维码" class="btn-primary"
|
||||
attrs="{'invisible': ['|',('routing_type','!=','解除装夹'),('state','!=','done')]}"/>
|
||||
</xpath>
|
||||
@@ -656,9 +659,9 @@
|
||||
<field name="name">工件配送</field>
|
||||
<field name="model">sf.workpiece.delivery</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="工件配送" class="center" create="0" delete="0">
|
||||
<tree string="工件配送" class="center" create="0" delete="0" js_class="remove_focus_list_view">
|
||||
<header>
|
||||
<button name="button_delivery" type="object" string="工件配送" class="btn-primary" attrs="{'force_show':1}"/>
|
||||
<button name="button_delivery" type="object" string="工件配送" class="btn-primary jikimo_button_confirm" attrs="{'force_show':1}"/>
|
||||
</header>
|
||||
<field name="status" widget="badge"
|
||||
decoration-success="status == '已配送'"
|
||||
|
||||
24
sf_manufacturing/views/res_config_settings_views.xml
Normal file
24
sf_manufacturing/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_view_form_sf_sync" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.sf_sync</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='agv_config']/div" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="is_agv_task_dispatch"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="is_agv_task_dispatch"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -13,9 +13,10 @@ class ProductionWizard(models.TransientModel):
|
||||
_description = '制造订单向导'
|
||||
|
||||
production_id = fields.Many2one('mrp.production', string='制造订单号')
|
||||
reprogramming_num = fields.Integer('重新编程次数', default=0)
|
||||
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
|
||||
is_remanufacture = fields.Boolean(string='重新生成制造订单', default=True)
|
||||
programming_state = fields.Selection(
|
||||
programming_states = fields.Selection(
|
||||
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
|
||||
('已下发', '已下发')],
|
||||
string='编程状态')
|
||||
@@ -26,18 +27,21 @@ class ProductionWizard(models.TransientModel):
|
||||
self.is_reprogramming = False
|
||||
|
||||
def confirm(self):
|
||||
self.production_id.action_cancel()
|
||||
self.production_id.detection_result_ids.write({'handle_result': '已处理'})
|
||||
self.production_id.write({'state': 'cancel', 'scrap_ids': [(0, 0, {
|
||||
'name': self.env['ir.sequence'].next_by_code('stock.scrap') or _('New'),
|
||||
'product_id': self.production_id.product_id.id,
|
||||
'scrap_qty': 1,
|
||||
'lot_id': self.production_id.move_line_raw_ids.lot_id.id,
|
||||
'origin': self.production_id.origin,
|
||||
'date_done': fields.datetime.now(),
|
||||
'lot_id': self.env['stock.move.line'].search(
|
||||
[('move_id', '=', self.production_id.move_raw_ids[0].id)]).lot_id.id,
|
||||
'location_id': self.production_id.move_raw_ids.filtered(lambda x: x.state not in (
|
||||
'done',
|
||||
'cancel')) and self.production_id.location_src_id.id or self.production_id.location_dest_id.id,
|
||||
'scrap_location_id': self.env['stock.scrap']._get_default_scrap_location_id(),
|
||||
'state': 'done'})]})
|
||||
self.production_id.action_cancel()
|
||||
if self.is_remanufacture is True:
|
||||
ret = {'programming_list': [], 'is_reprogramming': self.is_reprogramming}
|
||||
if self.is_reprogramming is True:
|
||||
@@ -78,6 +82,7 @@ class ProductionWizard(models.TransientModel):
|
||||
ret['programming_list'].append(vals)
|
||||
|
||||
new_production = self.production_id.recreateManufacturing(ret)
|
||||
self.production_id.write({'remanufacture_production_id': new_production.id})
|
||||
if self.is_reprogramming is False:
|
||||
for panel in new_production.product_id.model_processing_panel.split(','):
|
||||
scrap_cnc_workorder = max(
|
||||
|
||||
@@ -7,15 +7,26 @@
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="programming_state" invisible="1"/>
|
||||
<field name="programming_states" invisible="1"/>
|
||||
<div>
|
||||
重新生成制造订单
|
||||
<field name="is_remanufacture" force_save="1"/>
|
||||
</div>
|
||||
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
|
||||
注意: 该制造订单产品已申请重新编程次数为<field
|
||||
name="reprogramming_num" string=""
|
||||
readonly="1"
|
||||
style='color:red;'/>,且当前编程状态为
|
||||
<field name="programming_states" string=""
|
||||
decoration-info="programming_states == '待编程'"
|
||||
decoration-success="programming_states == '已下发'"
|
||||
decoration-warning="programming_states =='编程中'"
|
||||
decoration-danger="programming_states =='已编程'" readonly="1"/>
|
||||
</div>
|
||||
<div attrs='{"invisible": [("is_remanufacture","=",False)]}'>
|
||||
<span style='font-weight:bold;'>申请重新编程
|
||||
<field name="is_reprogramming" force_save="1"
|
||||
attrs='{"readonly": [("programming_state","not in",["已下发"])]}'/>
|
||||
attrs='{"readonly": [("programming_states","not in",["已下发"])]}'/>
|
||||
</span>
|
||||
</div>
|
||||
<footer>
|
||||
|
||||
@@ -25,6 +25,7 @@ class ReworkWizard(models.TransientModel):
|
||||
processing_panel_id = fields.Many2many('sf.processing.panel', string="加工面")
|
||||
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
|
||||
is_reprogramming_readonly = fields.Boolean(string='申请重新编程(只读)', default=False)
|
||||
is_clamp_measure = fields.Boolean(string='保留装夹测量数据', default=True)
|
||||
reprogramming_num = fields.Integer('重新编程次数', default=0)
|
||||
programming_state = fields.Selection(
|
||||
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
|
||||
@@ -35,6 +36,7 @@ class ReworkWizard(models.TransientModel):
|
||||
|
||||
def confirm(self):
|
||||
if self.routing_type in ['装夹预调', 'CNC加工']:
|
||||
self.is_clamp_measure = False
|
||||
self.workorder_id.is_rework = True
|
||||
self.production_id.write({'detection_result_ids': [(0, 0, {
|
||||
'rework_reason': self.rework_reason,
|
||||
@@ -58,19 +60,15 @@ class ReworkWizard(models.TransientModel):
|
||||
if processing_panels_missing:
|
||||
processing_panels_str = ','.join(processing_panels_missing)
|
||||
raise UserError('您还有待处理的检测结果中为%s的加工面未选择' % processing_panels_str)
|
||||
# processing_panels = set()
|
||||
# for handle_item in handle_result:
|
||||
# for dr_panel in self.processing_panel_id:
|
||||
# if dr_panel.name == handle_item.processing_panel:
|
||||
# processing_panels.add(dr_panel.name)
|
||||
# if len(processing_panels) != len(handle_result):
|
||||
# processing_panels_str = ','.join(processing_panels)
|
||||
# return UserError(f'您还有待处理的检测结果中为{processing_panels_str}的加工面未选择')
|
||||
for panel in self.processing_panel_id:
|
||||
panel_workorder = self.production_id.workorder_ids.filtered(
|
||||
lambda ap: ap.processing_panel == panel.name and ap.state != 'rework')
|
||||
if panel_workorder:
|
||||
panel_workorder.write({'state': 'rework'})
|
||||
rework_clamp_workorder = max(panel_workorder.filtered(
|
||||
lambda
|
||||
rp: rp.processing_panel == panel.name and rp.routing_type == '装夹预调' and rp.state in [
|
||||
'done', 'rework']))
|
||||
# panel_workorder.filtered(
|
||||
# lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
|
||||
# lambda wd: wd.status == '待下发').write({'status': '已取消'})
|
||||
@@ -93,6 +91,43 @@ class ReworkWizard(models.TransientModel):
|
||||
self.production_id.detection_result_ids.filtered(
|
||||
lambda ap1: ap1.processing_panel == panel.name and ap1.handle_result == '待处理').write(
|
||||
{'handle_result': '已处理'})
|
||||
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
|
||||
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
|
||||
'rework', 'done'))
|
||||
if new_pre_workorder and rework_clamp_workorder and self.is_clamp_measure is True:
|
||||
new_pre_workorder.write(
|
||||
{'X1_axis': rework_clamp_workorder.X1_axis, 'Y1_axis': rework_clamp_workorder.Y1_axis
|
||||
, 'Z1_axis': rework_clamp_workorder.Z1_axis,
|
||||
'X2_axis': rework_clamp_workorder.X2_axis
|
||||
, 'Y2_axis': rework_clamp_workorder.Y2_axis,
|
||||
'Z2_axis': rework_clamp_workorder.Z2_axis
|
||||
, 'X3_axis': rework_clamp_workorder.X3_axis,
|
||||
'Y3_axis': rework_clamp_workorder.Y3_axis
|
||||
, 'Z3_axis': rework_clamp_workorder.Z3_axis,
|
||||
'X4_axis': rework_clamp_workorder.X4_axis
|
||||
, 'Y4_axis': rework_clamp_workorder.Y4_axis,
|
||||
'Z4_axis': rework_clamp_workorder.Z4_axis
|
||||
, 'X5_axis': rework_clamp_workorder.X5_axis,
|
||||
'Y5_axis': rework_clamp_workorder.Y5_axis
|
||||
, 'Z5_axis': rework_clamp_workorder.Z5_axis,
|
||||
'X6_axis': rework_clamp_workorder.X6_axis
|
||||
, 'Y6_axis': rework_clamp_workorder.Y6_axis,
|
||||
'Z6_axis': rework_clamp_workorder.Z6_axis
|
||||
, 'X7_axis': rework_clamp_workorder.X7_axis,
|
||||
'Y7_axis': rework_clamp_workorder.Y7_axis
|
||||
, 'Z7_axis': rework_clamp_workorder.Z7_axis,
|
||||
'X8_axis': rework_clamp_workorder.X8_axis
|
||||
, 'Y8_axis': rework_clamp_workorder.Y8_axis,
|
||||
'Z8_axis': rework_clamp_workorder.Z8_axis
|
||||
, 'X9_axis': rework_clamp_workorder.X9_axis,
|
||||
'Y9_axis': rework_clamp_workorder.Y9_axis
|
||||
, 'Z9_axis': rework_clamp_workorder.Z9_axis,
|
||||
'X10_axis': rework_clamp_workorder.X10_axis
|
||||
, 'Y10_axis': rework_clamp_workorder.Y10_axis,
|
||||
'Z10_axis': rework_clamp_workorder.Z10_axis
|
||||
, 'X_deviation_angle': rework_clamp_workorder.X_deviation_angle,
|
||||
'material_center_point': rework_clamp_workorder.material_center_point
|
||||
})
|
||||
if self.is_reprogramming is False:
|
||||
if self.programming_state in ['已编程', '已下发']:
|
||||
if self.reprogramming_num >= 1 and self.programming_state == '已编程':
|
||||
@@ -149,9 +184,7 @@ class ReworkWizard(models.TransientModel):
|
||||
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel.name,
|
||||
ret),
|
||||
'cnc_worksheet': cnc_rework.cnc_worksheet})
|
||||
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
|
||||
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
|
||||
'rework', 'done'))
|
||||
|
||||
if new_pre_workorder:
|
||||
pre_rework = max(self.production_id.workorder_ids.filtered(
|
||||
lambda pr: pr.processing_panel == panel.name and pr.state in (
|
||||
|
||||
@@ -14,17 +14,25 @@
|
||||
<group>
|
||||
<field name="processing_panel_id" options="{'no_create': True}"
|
||||
attrs='{"invisible": [("routing_type","=","装夹预调")]}' widget="many2many_tags"/>
|
||||
|
||||
</group>
|
||||
<div attrs='{"invisible": [("routing_type","=","装夹预调")]}'>
|
||||
<span style='font-weight:bold;'>保留装夹测量数据
|
||||
<field name="is_clamp_measure" force_save="1"/>
|
||||
</span>
|
||||
</div>
|
||||
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
|
||||
注意: 该制造订单产品已申请重新编程次数为<field
|
||||
name="reprogramming_num" string=""
|
||||
readonly="1"
|
||||
style='color:red;'/>,且当前编程状态为
|
||||
<field name="programming_state" string=""
|
||||
decoration-info="programming_state == '待编程'"
|
||||
decoration-success="programming_state == '已下发'"
|
||||
decoration-warning="programming_state =='编程中'"
|
||||
decoration-danger="programming_state =='已编程'" readonly="1"/>
|
||||
<span style='font-weight:bold;'>
|
||||
注意: 该制造订单产品已申请重新编程次数为<field
|
||||
name="reprogramming_num" string=""
|
||||
readonly="1"
|
||||
style='color:red;'/>,且当前编程状态为
|
||||
<field name="programming_state" string=""
|
||||
decoration-info="programming_state == '待编程'"
|
||||
decoration-success="programming_state == '已下发'"
|
||||
decoration-warning="programming_state =='编程中'"
|
||||
decoration-danger="programming_state =='已编程'" readonly="1"/>
|
||||
</span>
|
||||
</div>
|
||||
<div attrs='{"invisible": ["|",("routing_type","in",["装夹预调","CNC加工"]),("programming_state","not in",["已下发"])],"readonly": [("tool_state", "=", "2")]}'>
|
||||
<span style='font-weight:bold;'>申请重新编程
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
|
||||
<field name="model">sf.workpiece.delivery.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form js_class="remove_focus_view">
|
||||
<form js_class="remove_focus_form_view">
|
||||
<sheet>
|
||||
<field name="delivery_ids" invisible="True"/>
|
||||
<field name="workorder_ids" invisible="True"/>
|
||||
@@ -18,8 +18,8 @@
|
||||
<field name="workcenter_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="确认配送" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
|
||||
<button string="确认解除" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
|
||||
<button string="确认配送" name="confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
|
||||
<button string="确认解除" name="confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
|
||||
<button string="取消" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</sheet>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
|
||||
import json
|
||||
import logging
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from datetime import datetime, date
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import models, api, fields
|
||||
|
||||
|
||||
@@ -79,29 +78,18 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
||||
'tag': 'display_notification',
|
||||
'target': 'new',
|
||||
'params': {
|
||||
'message': '任务下发成功!AGV任务调度编号为【%s】' % scheduling['name'],
|
||||
'message': f'任务下发成功!AGV任务调度编号为【{scheduling["name"]}】',
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
|
||||
def confirm(self):
|
||||
try:
|
||||
# if self.workorder_id:
|
||||
# self.workorder_id.workpiece_delivery_ids[0].agv_scheduling_id()
|
||||
# else:
|
||||
# is_not_production_line = 0
|
||||
# same_production_line_id = None
|
||||
# notsame_production_line_arr = []
|
||||
# for item in self.production_ids:
|
||||
# if same_production_line_id is None:
|
||||
# same_production_line_id = item.production_line_id.id
|
||||
# if item.production_line_id.id != same_production_line_id:
|
||||
# notsame_production_line_arr.append(item.name)
|
||||
# notsame_production_line_str = ','.join(map(str, notsame_production_line_arr))
|
||||
# if is_not_production_line >= 1:
|
||||
# raise UserError('制造订单号为%s的目的生产线不一致' % notsame_production_line_str)
|
||||
# else:
|
||||
if not self.workorder_ids:
|
||||
raise UserError('制造订单不能为空')
|
||||
|
||||
scheduling = self.env['sf.agv.scheduling'].add_scheduling(
|
||||
agv_start_site_name=self.feeder_station_start_id.name,
|
||||
agv_route_type=self.delivery_type,
|
||||
@@ -109,11 +97,19 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
||||
)
|
||||
# 如果关联了工件配送单,则修改状态为已下发
|
||||
if self.delivery_ids:
|
||||
self.delivery_ids.write({
|
||||
val = {
|
||||
'status': '已下发',
|
||||
'agv_scheduling_id': scheduling.id,
|
||||
'feeder_station_start_id': scheduling.start_site_id.id,
|
||||
})
|
||||
}
|
||||
# 如果agv任务已经下发,则修改工件配送单信息
|
||||
if scheduling.state == '配送中':
|
||||
val.update({
|
||||
'feeder_station_destination_id': scheduling.end_site_id.id,
|
||||
'route_id': scheduling.agv_route_id.id,
|
||||
'task_delivery_time': fields.Datetime.now()
|
||||
})
|
||||
self.delivery_ids.write(val)
|
||||
|
||||
# 如果是解除装夹工单,则需要处理工单逻辑
|
||||
for item in self.workorder_ids:
|
||||
@@ -121,10 +117,21 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
||||
item.button_start()
|
||||
item.button_finish()
|
||||
|
||||
return scheduling.read()[0]
|
||||
# return scheduling.read()[0]
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'target': 'new',
|
||||
'params': {
|
||||
'message': f'任务下发成功!AGV任务调度编号为【{scheduling.name}】',
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logging.info('%s任务下发失败:%s' % (self.delivery_type, e))
|
||||
raise UserError('%s任务下发失败:%s' % (self.delivery_type, e))
|
||||
logging.info('%s任务下发失败:%s', self.delivery_type, e)
|
||||
raise UserError(f'{self.delivery_type}任务下发失败:{e}') from e
|
||||
|
||||
# def recognize_production(self):
|
||||
# # production_ids = []
|
||||
@@ -172,6 +179,17 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
||||
self.feeder_station_destination_id = self.route_id.end_site_id.id
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
# 判断barcode是否是数字
|
||||
if not barcode.isdigit():
|
||||
# 判断是否是AGV接驳站名称
|
||||
agv_site = self.env['sf.agv.site'].search([('name', '=', barcode)])
|
||||
if agv_site:
|
||||
if not self.feeder_station_start_id:
|
||||
self.feeder_station_start_id = agv_site.id
|
||||
else:
|
||||
if self.feeder_station_start_id.id != agv_site.id:
|
||||
raise UserError('起点接驳站不匹配!')
|
||||
return
|
||||
delivery_type = self.env.context.get('default_delivery_type')
|
||||
if delivery_type == '上产线':
|
||||
workorder = self.env['mrp.workorder'].search(
|
||||
@@ -190,11 +208,22 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
||||
if workorder:
|
||||
if (len(self.production_ids) > 0 and
|
||||
workorder.production_line_id.id != self.production_ids[0].production_line_id.id):
|
||||
raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % workorder.production_id.name)
|
||||
raise UserError(f'该rfid对应的制造订单号为{workorder.production_id.name}的目的生产线不一致')
|
||||
|
||||
# 将对象添加到对应的同模型且是多对多类型里
|
||||
self.production_ids |= workorder.production_id
|
||||
self.workorder_ids |= workorder
|
||||
|
||||
down_product_agv_scheduling = self.get_down_product_agv_scheduling()
|
||||
if down_product_agv_scheduling:
|
||||
if not self.feeder_station_start_id:
|
||||
self.feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
|
||||
else:
|
||||
if self.feeder_station_start_id.id != down_product_agv_scheduling.end_site_id.id:
|
||||
raise UserError(f'该rfid不在{self.feeder_station_start_id.name}接驳站内')
|
||||
|
||||
else:
|
||||
raise UserError('该rfid码对应的工单不存在')
|
||||
return
|
||||
|
||||
|
||||
|
||||
@@ -24,11 +24,13 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
ret = json.loads(datas)
|
||||
ret = json.loads(ret['result'])
|
||||
logging.info('下发编程单:%s' % ret)
|
||||
domain = [('programming_no', '=', ret['programming_no'])]
|
||||
if ret['manufacturing_type'] == 'scrap':
|
||||
domain += [('state', 'not in', ['done', 'scrap', 'cancel'])]
|
||||
productions = request.env['mrp.production'].with_user(
|
||||
request.env.ref("base.user_admin")).search(
|
||||
[('programming_no', '=', ret['programming_no'])])
|
||||
request.env.ref("base.user_admin")).search(domain)
|
||||
if productions:
|
||||
# # 拉取所有加工面的程序文件
|
||||
# 拉取所有加工面的程序文件
|
||||
for r in ret['processing_panel'].split(','):
|
||||
program_path_tmp_r = os.path.join('/tmp', ret['folder_name'], 'return', r)
|
||||
if os.path.exists(program_path_tmp_r):
|
||||
@@ -48,48 +50,31 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
if not production.workorder_ids:
|
||||
production.product_id.model_processing_panel = ret['processing_panel']
|
||||
production._create_workorder(ret)
|
||||
# else:
|
||||
# for panel in ret['processing_panel'].split(','):
|
||||
# # 查询状态为进行中且工序类型为CNC加工的工单
|
||||
# cnc_workorder = production.workorder_ids.filtered(
|
||||
# lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done',
|
||||
# 'cancel'] and ac.processing_panel == panel)
|
||||
# if cnc_workorder:
|
||||
# if cnc_workorder.cnc_ids:
|
||||
# cnc_workorder.cmm_ids.sudo().unlink()
|
||||
# cnc_workorder.cnc_ids.sudo().unlink()
|
||||
# request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
# production)
|
||||
# # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
|
||||
# # panel)
|
||||
# program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
|
||||
# logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel)
|
||||
# files_panel = os.listdir(program_path_tmp_panel)
|
||||
# if files_panel:
|
||||
# for file in files_panel:
|
||||
# file_extension = os.path.splitext(file)[1]
|
||||
# logging.info('file_extension:%s' % file_extension)
|
||||
# if file_extension.lower() == '.pdf':
|
||||
# panel_file_path = os.path.join(program_path_tmp_panel, file)
|
||||
# logging.info('panel_file_path:%s' % panel_file_path)
|
||||
# cnc_workorder.write(
|
||||
# {'cnc_ids': cnc_workorder.cnc_ids.sudo()._json_cnc_processing(panel, ret),
|
||||
# 'cmm_ids': cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel, ret),
|
||||
# 'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
# pre_workorder = production.workorder_ids.filtered(
|
||||
# lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done',
|
||||
# 'cancel'] and ap.processing_panel == panel)
|
||||
# if pre_workorder:
|
||||
# pre_workorder.write(
|
||||
# {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
productions.process_range_time()
|
||||
else:
|
||||
for panel in ret['processing_panel'].split(','):
|
||||
# 查询状态为进行中且工序类型为CNC加工的工单
|
||||
cnc_workorder_has = production.workorder_ids.filtered(
|
||||
lambda ach: ach.routing_type == 'CNC加工' and ach.state not in ['progress', 'done',
|
||||
'rework',
|
||||
'cancel'] and ach.processing_panel == panel)
|
||||
if cnc_workorder_has:
|
||||
if cnc_workorder_has.cnc_ids:
|
||||
cnc_workorder_has.cmm_ids.sudo().unlink()
|
||||
cnc_workorder_has.cnc_ids.sudo().unlink()
|
||||
request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
production)
|
||||
cnc_workorder_has.write(
|
||||
{'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret),
|
||||
'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)})
|
||||
for panel in ret['processing_panel'].split(','):
|
||||
# 查询状态为进行中且工序类型为CNC加工的工单
|
||||
cnc_workorder = productions.workorder_ids.filtered(
|
||||
lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done',
|
||||
'cancel'] and ac.processing_panel == panel)
|
||||
lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done', 'rework'
|
||||
'cancel'] and ac.processing_panel == panel)
|
||||
if cnc_workorder:
|
||||
program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
|
||||
panel)
|
||||
# program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
|
||||
# panel)
|
||||
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
|
||||
logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel)
|
||||
files_panel = os.listdir(program_path_tmp_panel)
|
||||
@@ -102,18 +87,12 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
logging.info('panel_file_path:%s' % panel_file_path)
|
||||
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
pre_workorder = productions.workorder_ids.filtered(
|
||||
lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done',
|
||||
'cancel'] and ap.processing_panel == panel)
|
||||
lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', 'rework'
|
||||
'cancel'] and ap.processing_panel == panel)
|
||||
if pre_workorder:
|
||||
pre_workorder.write(
|
||||
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
productions.write({'programming_state': '已编程', 'work_state': '已编程'})
|
||||
cnc_program_ids = [item.id for item in productions]
|
||||
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
[('production_id', 'in', cnc_program_ids)])
|
||||
if workpiece_delivery:
|
||||
workpiece_delivery.write(
|
||||
{'is_cnc_program_down': True, 'production_line_id': productions.production_line_id.id})
|
||||
return json.JSONEncoder().encode(res)
|
||||
else:
|
||||
res = {'status': 0, 'message': '该制造订单暂未开始'}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -86,7 +89,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
_logger.info("同步刀具物料每齿走刀量完成")
|
||||
|
||||
except Exception as e:
|
||||
_logger.info("捕获错误信息:%s" % e)
|
||||
_logger.info("sf_all_sync error: %s" % e)
|
||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||
|
||||
@api.model
|
||||
@@ -144,3 +147,68 @@ class ResConfigSettings(models.TransientModel):
|
||||
ir_config.set_param("ftp_user", self.ftp_user or "")
|
||||
ir_config.set_param("ftp_password", self.ftp_password or "")
|
||||
ir_config.set_param("enable_tool_presetter", self.enable_tool_presetter or False)
|
||||
|
||||
def sync_sale_price(self):
|
||||
self.get_page_all_records(self.sale_order_price_process)
|
||||
|
||||
def sale_order_price_process(self, datas):
|
||||
if not datas:
|
||||
return
|
||||
try:
|
||||
convert_sale_data = map(lambda data:
|
||||
{'name': data.name, 'order_lines': {
|
||||
str(i): str(value.price_unit) for i, value in enumerate(data.order_line)
|
||||
}
|
||||
},
|
||||
datas)
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
url = config['bfm_url_new'] + '/api/sync/order/price'
|
||||
json_data = {
|
||||
'params': {
|
||||
'data': list(convert_sale_data),
|
||||
},
|
||||
}
|
||||
response = requests.post(url, json=json_data, data=None)
|
||||
response = response.json()
|
||||
if not response.get('error'):
|
||||
result = response.get('result')
|
||||
for need_change_sale_data in result:
|
||||
res_order_lines_map = need_change_sale_data.get('order_lines')
|
||||
if not res_order_lines_map:
|
||||
continue
|
||||
need_change_sale_order = self.env['sale.order'].sudo().search([('name', '=', need_change_sale_data.get('name'))])
|
||||
for index,need_change_sale_order_line in enumerate(need_change_sale_order.order_line):
|
||||
if not res_order_lines_map.get(str(index)):
|
||||
continue
|
||||
order_line = self.env['sale.order.line'].browse(need_change_sale_order_line.id)
|
||||
new_price = res_order_lines_map.get(str(index))
|
||||
if order_line:
|
||||
# 修改单价
|
||||
order_line.write({'remark': new_price})
|
||||
else:
|
||||
logging.error('同步销售订单价格失败 {}'.format(response.text))
|
||||
raise UserError('同步销售订单价格失败')
|
||||
except Exception as e:
|
||||
raise UserError(e)
|
||||
|
||||
def get_page_all_records(self, func, page_size=100):
|
||||
# 获取模型对象
|
||||
model = self.env['sale.order'].sudo()
|
||||
|
||||
# 初始化分页参数
|
||||
page_number = 1
|
||||
while True:
|
||||
# 计算偏移量
|
||||
offset = (page_number - 1) * page_size
|
||||
|
||||
# 获取当前页的数据
|
||||
records = model.search([], limit=page_size, offset=offset)
|
||||
|
||||
# 如果没有更多记录,退出循环
|
||||
if not records:
|
||||
break
|
||||
|
||||
# 将当前页的数据添加到结果列表
|
||||
func(records)
|
||||
# 增加页码
|
||||
page_number += 1
|
||||
@@ -2,6 +2,8 @@
|
||||
import logging
|
||||
import json
|
||||
import base64
|
||||
import traceback
|
||||
|
||||
import requests
|
||||
from odoo import models
|
||||
from odoo.exceptions import ValidationError
|
||||
@@ -73,7 +75,8 @@ class MrStaticResourceDataSync(models.Model):
|
||||
self.env['sf.feed.per.tooth'].sync_feed_per_tooth_yesterday()
|
||||
_logger.info("同步刀具物料每齿走刀量完成")
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error("同步静态资源库失败:%s" % traceback_error)
|
||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||
|
||||
|
||||
@@ -2759,8 +2762,9 @@ class CuttingToolBasicParameters(models.Model):
|
||||
if result['status'] == 1:
|
||||
if 'basic_parameters_integral_tool' in result['cutting_tool_basic_parameters_yesterday_list']:
|
||||
if result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool']:
|
||||
basic_parameters_integral_tool_list = json.loads(
|
||||
result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool'])
|
||||
cutting_tool_basic_parameters_yesterday_list= result['cutting_tool_basic_parameters_yesterday_list']
|
||||
basic_parameters_integral_tool_list = cutting_tool_basic_parameters_yesterday_list['basic_parameters_integral_tool']
|
||||
|
||||
if basic_parameters_integral_tool_list:
|
||||
for integral_tool_item in basic_parameters_integral_tool_list:
|
||||
integral_tool = self.search(
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<h2>AGV参数配置</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="row mt16 o_settings_container" id="agv_config">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
@@ -129,6 +129,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>销售订单价格同步</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<button type="object" class="oe_highlight" name="sync_sale_price" confirm="确认同步"
|
||||
string="同步销售订单价格"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -191,7 +191,7 @@ class sf_production_plan(models.Model):
|
||||
|
||||
return num
|
||||
|
||||
def do_production_schedule(self):
|
||||
def do_production_schedule(self, date_planned_start):
|
||||
"""
|
||||
排程方法
|
||||
"""
|
||||
@@ -199,6 +199,10 @@ class sf_production_plan(models.Model):
|
||||
if not record.production_line_id:
|
||||
raise ValidationError("未选择生产线")
|
||||
else:
|
||||
|
||||
is_schedule = self.deal_processing_schedule(date_planned_start)
|
||||
if not is_schedule:
|
||||
raise ValidationError("排程失败")
|
||||
workorder_id_list = record.production_id.workorder_ids.ids
|
||||
if record.production_id:
|
||||
if record.production_id.workorder_ids:
|
||||
@@ -249,6 +253,26 @@ class sf_production_plan(models.Model):
|
||||
'target': 'current', # 跳转的目标窗口,可以是'current'或'new'
|
||||
}
|
||||
|
||||
# 处理是否可排程
|
||||
def deal_processing_schedule(self, date_planned_start):
|
||||
for record in self:
|
||||
workcenter_ids = record.production_line_id.mrp_workcenter_ids
|
||||
if not workcenter_ids:
|
||||
raise UserError('生产线没有配置工作中心')
|
||||
production_lines = workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
|
||||
if not production_lines: # 判断是否配置了自动生产线
|
||||
raise UserError('生产线没有配置自动生产线')
|
||||
if date_planned_start < datetime.now(): # 判断计划开始时间是否小于当前时间
|
||||
raise UserError('计划开始时间不能小于当前时间')
|
||||
if all(not production_line.deal_with_workcenter_calendar(date_planned_start) for production_line in
|
||||
production_lines): # 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程')
|
||||
if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源')
|
||||
if not production_lines.deal_available_single_machine_capacity(date_planned_start): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源')
|
||||
return True
|
||||
|
||||
def calculate_plan_time_before(self, item, workorder_id_list):
|
||||
"""
|
||||
根据CNC工单的时间去计算之前的其他工单的开始结束时间
|
||||
|
||||
@@ -291,6 +291,7 @@
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">mrp.production</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('picking_type_id.active', '=', True)]</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="sale_custom_action">
|
||||
<!-- 自定义额外的动作 -->
|
||||
|
||||
@@ -16,6 +16,8 @@ class Action_Plan_All_Wizard(models.TransientModel):
|
||||
|
||||
# 选择生产线
|
||||
production_line_id = fields.Many2one('sf.production.line', string=u'生产线', required=True)
|
||||
date_planned_start = fields.Datetime(string='计划开始时间', index=True, copy=False,
|
||||
default=fields.Datetime.now)
|
||||
|
||||
# 接收传递过来的计划ID
|
||||
plan_ids = fields.Many2many('sf.production.plan', string=u'计划ID')
|
||||
@@ -33,7 +35,8 @@ class Action_Plan_All_Wizard(models.TransientModel):
|
||||
# 拿到计划对象
|
||||
plan_obj = self.env['sf.production.plan'].browse(plan.id)
|
||||
plan_obj.production_line_id = self.production_line_id.id
|
||||
plan_obj.do_production_schedule()
|
||||
plan.date_planned_start = self.date_planned_start
|
||||
plan_obj.do_production_schedule(self.date_planned_start)
|
||||
# plan_obj.state = 'done'
|
||||
print('处理计划:', plan.id, '完成')
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<form>
|
||||
<group>
|
||||
<field name="production_line_id" domain="[('name', 'ilike', 'CNC')]"/>
|
||||
<field name="date_planned_start"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="确认排程" name="action_plan_all" type="object" class="btn-primary"/>
|
||||
|
||||
@@ -48565,3 +48565,16 @@ msgstr ""
|
||||
#: model:ir.model.fields.selection,name:sf_maintenance.selection__maintenance_equipment__heightened_way__chilunjia
|
||||
msgid "齿轮架驱动"
|
||||
msgstr ""
|
||||
|
||||
#. module: sf_manufacturing
|
||||
#. odoo-python
|
||||
#: code:addons/sf_manufacturing/models/mrp_production.py:0
|
||||
#: model:ir.actions.act_window,name:sf_manufacturing.action_sf_production_wizard
|
||||
#: model:ir.model.fields.selection,name:sf_manufacturing.selection__mrp_production__state__scrap
|
||||
#: model:ir.model.fields.selection,name:sf_manufacturing.selection__mrp_workorder__test_results__报废
|
||||
#: model:ir.model.fields.selection,name:sf_manufacturing.selection__sf_detection_result__test_results__报废
|
||||
#: model_terms:ir.ui.view,arch_db:sf_manufacturing.custom_mrp_production_form_view
|
||||
#: model_terms:ir.ui.view,arch_db:sf_manufacturing.custom_view_mrp_production_filter
|
||||
#, python-format
|
||||
msgid "报废"
|
||||
msgstr "报废"
|
||||
@@ -56,6 +56,18 @@ class QuickEasyOrder(models.Model):
|
||||
processing_time = fields.Integer('加工时长(min)')
|
||||
sale_order_id = fields.Many2one('sale.order', '销售订单号')
|
||||
|
||||
part_drawing_number = fields.Char('零件图号')
|
||||
machining_drawings = fields.Binary('2D加工图纸')
|
||||
|
||||
@api.onchange('parameter_ids')
|
||||
def _compute_parameter_ids(self):
|
||||
my_parameter_ids = {}
|
||||
for item in self:
|
||||
for item1 in item.parameter_ids:
|
||||
my_parameter_ids[item1.process_id.id] = item1.ids[0]
|
||||
my_parameter_ids = list(my_parameter_ids.values())
|
||||
item.write({'parameter_ids': [(6, 0, my_parameter_ids)]})
|
||||
|
||||
@api.depends('unit_price', 'quantity')
|
||||
def _compute_total_amount(self):
|
||||
for item in self:
|
||||
|
||||
@@ -60,7 +60,7 @@ class ReSaleOrder(models.Model):
|
||||
deadline_of_delivery, payments_way, pay_way):
|
||||
now_time = datetime.datetime.now()
|
||||
partner = self.get_customer()
|
||||
data ={
|
||||
data = {
|
||||
'company_id': company_id.id,
|
||||
'date_order': now_time,
|
||||
'name': self.env['ir.sequence'].next_by_code('sale.order', sequence_date=now_time),
|
||||
@@ -79,7 +79,7 @@ class ReSaleOrder(models.Model):
|
||||
if not isinstance(deadline_of_delivery, str):
|
||||
data.update({'deadline_of_delivery': deadline_of_delivery})
|
||||
else:
|
||||
if deadline_of_delivery!="False":
|
||||
if deadline_of_delivery != "False":
|
||||
data.update({'deadline_of_delivery': deadline_of_delivery})
|
||||
|
||||
order_id = self.env['sale.order'].sudo().create(data)
|
||||
@@ -225,45 +225,42 @@ class RePurchaseOrder(models.Model):
|
||||
raise UserError('请对【产品】中的【税】进行选择')
|
||||
|
||||
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
|
||||
is_exist = True
|
||||
server_product_process = []
|
||||
production_process = product_id_to_production_names.get(
|
||||
production.product_id.id)
|
||||
for pp in consecutive_process_parameters:
|
||||
if pp.gain_way == '外协':
|
||||
server_product = self.env['product.template'].search(
|
||||
server_template = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', pp.id),
|
||||
('detailed_type', '=', 'service')])
|
||||
purchase_order_line = self.env['purchase.order.line'].search(
|
||||
[('product_id', '=', server_product.id), ('product_qty', '=', len(production_process))])
|
||||
[('product_id', '=', server_template.product_variant_id.id),
|
||||
('product_qty', '=', len(production_process))], limit=1, order='id desc')
|
||||
if not purchase_order_line:
|
||||
is_exist = False
|
||||
server_product_process.append((0, 0, {
|
||||
'product_id': server_product.product_variant_id.id,
|
||||
'product_id': server_template.product_variant_id.id,
|
||||
'product_qty': len(production_process),
|
||||
'product_uom': server_product.uom_id.id
|
||||
'product_uom': server_template.uom_id.id
|
||||
}))
|
||||
else:
|
||||
for item in purchase_order_line:
|
||||
purchase_order = self.env['purchase.order'].search(
|
||||
[('state', '=', 'draft'), ('origin', 'ilike', production.name),
|
||||
('id', '=', item.order_id.id)])
|
||||
if not purchase_order:
|
||||
is_exist = False
|
||||
server_product_process.append((0, 0, {
|
||||
'product_id': server_product.product_variant_id.id,
|
||||
'product_qty': len(production_process),
|
||||
'product_uom': server_product.uom_id.id
|
||||
}))
|
||||
if is_exist is False:
|
||||
purchase_order = self.env['purchase.order'].search(
|
||||
[('state', '=', 'draft'), ('origin', '=', ','.join(production_process))])
|
||||
if not purchase_order:
|
||||
self.env['purchase.order'].sudo().create({
|
||||
'partner_id': server_product.seller_ids.partner_id.id,
|
||||
'origin': ','.join(production_process),
|
||||
'state': 'draft',
|
||||
'order_line': server_product_process})
|
||||
if production.name in production_process:
|
||||
purchase_order = self.env['purchase.order'].search(
|
||||
[('state', '=', 'draft'), ('origin', '=', ','.join(production_process)),
|
||||
('id', '=', item.order_id.id)])
|
||||
if not purchase_order:
|
||||
server_product_process.append((0, 0, {
|
||||
'product_id': server_template.product_variant_id.id,
|
||||
'product_qty': len(production_process),
|
||||
'product_uom': server_template.uom_id.id
|
||||
}))
|
||||
if server_product_process:
|
||||
self.env['purchase.order'].sudo().create({
|
||||
'partner_id': server_template.seller_ids.partner_id.id,
|
||||
'origin': ','.join(production_process),
|
||||
'state': 'draft',
|
||||
'order_line': server_product_process})
|
||||
# self.env.cr.commit()
|
||||
|
||||
@api.onchange('order_line')
|
||||
def _onchange_order_line(self):
|
||||
|
||||
@@ -73,12 +73,14 @@
|
||||
<field name="material_model_id" options="{'no_create': True}" required="1"/>
|
||||
<!-- <field name="process_id"/>-->
|
||||
<field name="parameter_ids" widget="many2many_tags" string="表面工艺参数"
|
||||
options="{'no_create': True}" required="1"/>
|
||||
options="{'no_create': True}"/>
|
||||
<field name="machining_precision" required="1"/>
|
||||
<field name="processing_time"/>
|
||||
<field name="quantity" options="{'format': false}"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="price" options="{'format': false}"/>
|
||||
<field name="part_drawing_number"/>
|
||||
<field name="machining_drawings" widget="image"/>
|
||||
<field name="sale_order_id"
|
||||
attrs='{"invisible": [("sale_order_id","=",False)],"readonly": [("sale_order_id","!=",False)]}'/>
|
||||
</group>
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&','&',('check_status',
|
||||
'=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}
|
||||
</attribute>
|
||||
<attribute name="string">拒绝接单</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//form/header/button[@name='action_draft']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
|
||||
4
sf_stock/__init__.py
Normal file
4
sf_stock/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
35
sf_stock/__manifest__.py
Normal file
35
sf_stock/__manifest__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "sf_stock",
|
||||
|
||||
'summary': """
|
||||
处理代发货业务""",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['sf_sale', 'stock'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
'views/stock_picking.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
}
|
||||
3
sf_stock/controllers/__init__.py
Normal file
3
sf_stock/controllers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
21
sf_stock/controllers/controllers.py
Normal file
21
sf_stock/controllers/controllers.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# from odoo import http
|
||||
|
||||
|
||||
# class SfStock(http.Controller):
|
||||
# @http.route('/sf_stock/sf_stock', auth='public')
|
||||
# def index(self, **kw):
|
||||
# return "Hello, world"
|
||||
|
||||
# @http.route('/sf_stock/sf_stock/objects', auth='public')
|
||||
# def list(self, **kw):
|
||||
# return http.request.render('sf_stock.listing', {
|
||||
# 'root': '/sf_stock/sf_stock',
|
||||
# 'objects': http.request.env['sf_stock.sf_stock'].search([]),
|
||||
# })
|
||||
|
||||
# @http.route('/sf_stock/sf_stock/objects/<model("sf_stock.sf_stock"):obj>', auth='public')
|
||||
# def object(self, obj, **kw):
|
||||
# return http.request.render('sf_stock.object', {
|
||||
# 'object': obj
|
||||
# })
|
||||
30
sf_stock/demo/demo.xml
Normal file
30
sf_stock/demo/demo.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="sf_stock.sf_stock">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="sf_stock.sf_stock">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="sf_stock.sf_stock">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="sf_stock.sf_stock">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="sf_stock.sf_stock">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
3
sf_stock/models/__init__.py
Normal file
3
sf_stock/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import stock_picking
|
||||
110
sf_stock/models/stock_picking.py
Normal file
110
sf_stock/models/stock_picking.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import requests
|
||||
from odoo import models, fields, api
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
import logging
|
||||
from odoo.tools import date_utils
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
cancel_backorder_ids = fields.Boolean(default=False, string='是否取消后置单据')
|
||||
|
||||
# 重写验证,下发发货到bfm
|
||||
def button_validate(self):
|
||||
info = super(StockPicking, self).button_validate()
|
||||
if self.picking_type_code == 'outgoing':
|
||||
self.send_to_bfm()
|
||||
return info
|
||||
|
||||
def deal_move_ids(self, send_move_ids, send_move_line_ids):
|
||||
move_ids = [] # 本次发货单
|
||||
move_line_ids = [] # 本次发货单行
|
||||
if send_move_ids:
|
||||
for item in send_move_ids:
|
||||
val = {
|
||||
'name': item.product_id.upload_model_file.display_name,
|
||||
'quantity_done': item.quantity_done,
|
||||
'date': date_utils.json_default(item.date) if item.date else None,
|
||||
'description_picking': item.description_picking,
|
||||
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
|
||||
'product_uom_qty': item.product_uom_qty,
|
||||
'sequence': item.sequence,
|
||||
'price_unit': item.price_unit,
|
||||
'priority': item.priority,
|
||||
'state': item.state,
|
||||
}
|
||||
move_ids.append(val)
|
||||
for item in send_move_line_ids:
|
||||
val = {
|
||||
'qty_done': item.qty_done,
|
||||
'reserved_qty': item.reserved_qty,
|
||||
'reserved_uom_qty': item.reserved_uom_qty,
|
||||
'date': date_utils.json_default(item.date) if item.date else None,
|
||||
'description_picking': item.description_picking,
|
||||
'state': item.state,
|
||||
}
|
||||
move_line_ids.append(val)
|
||||
return move_ids, move_line_ids
|
||||
|
||||
def deal_send_backorder_id(self, backorder_ids1):
|
||||
backorder_ids = []
|
||||
|
||||
if backorder_ids1:
|
||||
for item in backorder_ids1:
|
||||
move_ids, move_line_ids = self.deal_move_ids(item.move_ids, item.move_line_ids)
|
||||
val = {
|
||||
'receiverName': item.receiverName,
|
||||
'name': item.sale_id.default_code,
|
||||
'send_no': item.name,
|
||||
'scheduled_date': date_utils.json_default(item.scheduled_date) if item.scheduled_date else None,
|
||||
'date': date_utils.json_default(item.date) if item.date else None,
|
||||
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
|
||||
'date_done': date_utils.json_default(item.date_done) if item.date_done else None,
|
||||
'move_ids': move_ids,
|
||||
'move_line_ids': move_line_ids,
|
||||
'state': item.state,
|
||||
'move_type': item.move_type,
|
||||
}
|
||||
backorder_ids.append(val)
|
||||
return backorder_ids
|
||||
|
||||
def send_to_bfm(self):
|
||||
skip_backorder = self.env.context.get('skip_backorder')
|
||||
# 下发发货到bfm
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
move_ids, move_line_ids = self.deal_move_ids(self.move_ids, self.move_line_ids)
|
||||
data = {
|
||||
'params': {
|
||||
'receiverName': self.receiverName,
|
||||
'priority': self.priority,
|
||||
'name': self.sale_id.default_code,
|
||||
'send_no': self.name,
|
||||
'scheduled_date': date_utils.json_default(self.scheduled_date) if self.scheduled_date else None,
|
||||
'date': date_utils.json_default(self.date) if self.date else None,
|
||||
'date_deadline': date_utils.json_default(self.date_deadline) if self.date_deadline else None,
|
||||
'date_done': date_utils.json_default(self.date_done) if self.date_done else None,
|
||||
'move_ids': move_ids,
|
||||
'move_line_ids': move_line_ids,
|
||||
'state': self.state,
|
||||
'backorder_id': self.deal_send_backorder_id(self.backorder_id),
|
||||
'backorder_ids': self.deal_send_backorder_id(self.backorder_ids),
|
||||
'cancel_backorder_ids': skip_backorder,
|
||||
'move_type': self.move_type,
|
||||
},
|
||||
}
|
||||
url1 = config['bfm_url_new'] + '/api/stock/deliver_goods'
|
||||
json_str = json.dumps(data)
|
||||
print('json_str', json_str)
|
||||
r = requests.post(url1, json=data, data=None)
|
||||
if r.status_code == 200:
|
||||
result = json.loads(r.json()['result'])
|
||||
if result['code'] != 200:
|
||||
raise UserError(result['message'] or '工厂发货下发bfm失败')
|
||||
else:
|
||||
raise UserError('工厂发货下发bfm失败')
|
||||
2
sf_stock/security/ir.model.access.csv
Normal file
2
sf_stock/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sf_stock_sf_stock,sf_stock.sf_stock,model_sf_stock_sf_stock,base.group_user,1,1,1,1
|
||||
|
5
sf_stock/views/stock_picking.xml
Normal file
5
sf_stock/views/stock_picking.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -10,7 +10,7 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['sf_manufacturing'],
|
||||
'depends': ['sf_manufacturing', 'sf_base'],
|
||||
'data': [
|
||||
'security/group_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
@@ -24,6 +24,11 @@
|
||||
'views/menu_view.xml',
|
||||
'views/stock.xml',
|
||||
'data/tool_data.xml',
|
||||
'wizard/jikimo_bom_wizard.xml',
|
||||
'views/tool_inventory.xml',
|
||||
'views/jikimo_bom.xml',
|
||||
'views/tool_views.xml',
|
||||
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
|
||||
@@ -8,4 +8,7 @@ from . import fixture_material_search
|
||||
from . import fixture_enroll
|
||||
from . import temporary_data_processing_methods
|
||||
from . import stock
|
||||
|
||||
from . import jikimo_bom
|
||||
from . import tool_inventory
|
||||
from . import functional_cutting_tool_model
|
||||
# from . import product_product
|
||||
@@ -0,0 +1,6 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class SyncFunctionalCuttingToolModel(models.Model):
|
||||
_inherit = 'sf.functional.cutting.tool.model'
|
||||
cutting_tool_type_ids = fields.Many2many('sf.cutting.tool.type', string='适用刀具物料类型', required=True)
|
||||
@@ -50,7 +50,7 @@ class ToolDatasync(models.Model):
|
||||
# self.env['sf.real.time.distribution.of.functional.tools'].sudo().sync_enroll_functional_tool_real_time_distribution_all()
|
||||
# logging.info("功能刀具安全库存每日同步成功")
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("刀具物料、刀具信息同步失败:%s" % e)
|
||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ class FunctionalToolWarning(models.Model):
|
||||
else:
|
||||
logging.info('没有注册功能刀具预警信息')
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("功能刀具预警同步失败:%s" % e)
|
||||
|
||||
|
||||
class StockMoveLine(models.Model):
|
||||
@@ -373,7 +373,7 @@ class StockMoveLine(models.Model):
|
||||
else:
|
||||
logging.info('没有注册功能刀具出入库记录信息')
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("出入库记录信息同步失败:%s" % e)
|
||||
|
||||
|
||||
class RealTimeDistributionFunctionalTools(models.Model):
|
||||
@@ -446,4 +446,4 @@ class RealTimeDistributionFunctionalTools(models.Model):
|
||||
else:
|
||||
logging.info('没有注册功能刀具出入库记录信息')
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("实时功能刀具同步失败:%s" % e)
|
||||
|
||||
134
sf_tool_management/models/jikimo_bom.py
Normal file
134
sf_tool_management/models/jikimo_bom.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from collections import Counter
|
||||
from xml import etree
|
||||
|
||||
from odoo import models, fields, api, Command
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class jikimo_bom(models.Model):
|
||||
_name = 'jikimo.bom'
|
||||
_description = '功能刀具物料清单'
|
||||
tool_inventory_id = fields.Many2one('sf.tool.inventory', '功能刀具清单')
|
||||
tool_name = fields.Char(related="tool_inventory_id.name", string='功能刀具名称')
|
||||
functional_cutting_tool_model_id = fields.Many2one(related='tool_inventory_id.functional_cutting_tool_model_id',
|
||||
string='功能刀具类型')
|
||||
tool_groups_id = fields.Many2one(related='tool_inventory_id.tool_groups_id', string='刀具组')
|
||||
tool_length = fields.Float(related='tool_inventory_id.tool_length', string='刀具总长(mm)')
|
||||
diameter = fields.Float(related='tool_inventory_id.diameter', string='直径(mm)')
|
||||
angle = fields.Float(related='tool_inventory_id.angle', string='R角(mm)')
|
||||
extension = fields.Float(related='tool_inventory_id.extension', string='伸出长度(mm)')
|
||||
product_ids = fields.Many2many('product.product', string='产品')
|
||||
knife_handle_model = fields.Selection(related='tool_inventory_id.knife_handle_model', string='使用刀柄型号')
|
||||
options = fields.Char('产品清单')
|
||||
|
||||
def name_get(self):
|
||||
result = []
|
||||
for bom in self:
|
||||
result.append((bom.id, '功能刀具物料清单'))
|
||||
return result
|
||||
|
||||
def check_types_in_list(self):
|
||||
# 统计每个元素的类型
|
||||
type_counts = Counter(item.cutting_tool_material_id.name for item in self.product_ids)
|
||||
return all(count > 0 for count in type_counts.values()) and len(type_counts) == self.options.split('+')
|
||||
|
||||
def write(self, vals):
|
||||
# 在更新模型时记录旧的 Many2many ID 列表
|
||||
if 'product_ids' in vals:
|
||||
old_product_counter = Counter(self.product_ids.ids)
|
||||
super(jikimo_bom, self).write(vals)
|
||||
new_product_counter = Counter(self.product_ids.ids)
|
||||
delete_product_counter = old_product_counter - new_product_counter
|
||||
if delete_product_counter:
|
||||
# 删除操作
|
||||
if self.check_types_in_list():
|
||||
return True
|
||||
else:
|
||||
raise UserError('每种物料最少要有一个')
|
||||
return super(jikimo_bom, self).write(vals)
|
||||
|
||||
def bom_product_domains(self, assembly_options):
|
||||
self.options = assembly_options
|
||||
cutting_tool_materials = self.env['sf.cutting.tool.material'].search(
|
||||
[('name', 'in', assembly_options.split('+'))])
|
||||
domains = []
|
||||
for index, option in enumerate(cutting_tool_materials):
|
||||
domain = ['&', ('cutting_tool_material_id', '=', option.id),
|
||||
("cutting_tool_type_id", "in",
|
||||
self.tool_inventory_id.functional_cutting_tool_model_id.cutting_tool_type_ids.ids)]
|
||||
if option.name == '刀柄':
|
||||
domain = ['&'] + domain + [
|
||||
("cutting_tool_taper_shank_model", "=", self.tool_inventory_id.knife_handle_model)]
|
||||
|
||||
if option.name == '整体式刀具':
|
||||
domain = ['&'] + domain + [
|
||||
'|',
|
||||
# 刀具直径
|
||||
('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
|
||||
|
||||
# r角
|
||||
('cutting_tool_blade_tip_working_size', '=', self.tool_inventory_id.angle)]
|
||||
if option.name == '刀杆':
|
||||
domain = ['&'] + domain + [
|
||||
("cutting_tool_cutter_arbor_diameter", "=", self.tool_inventory_id.diameter)]
|
||||
if option.name == '刀片':
|
||||
domain = ['&'] + domain + [
|
||||
("cutting_tool_blade_tip_circular_arc_radius", "=", self.tool_inventory_id.angle)]
|
||||
if option.name == '刀盘':
|
||||
domain = ['&'] + domain + [
|
||||
("cutting_tool_cutter_head_diameter", "=", self.tool_inventory_id.diameter)]
|
||||
domains = domains + domain
|
||||
if index != 0:
|
||||
domains = ['|'] + domains
|
||||
# wqwqwe = self.env['product.product'].search(ddd)
|
||||
# product = self.env['product.product'].search(domain)
|
||||
# if product:
|
||||
# products = products + product
|
||||
domains = domains + [('stock_move_count', '>', 0)]
|
||||
return domains
|
||||
|
||||
def generate_bill_materials(self, assembly_options):
|
||||
domains = self.bom_product_domains(assembly_options)
|
||||
products = self.env['product.product'].search(domains)
|
||||
if products:
|
||||
self.product_ids = [Command.set(products.ids)]
|
||||
# if option.name == '刀盘':
|
||||
# hilt = self.env['product.product'].search(
|
||||
# [('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
|
||||
# ('cutting_tool_material_id', '=', option.id)])
|
||||
# self.product_ids = [Command.set(hilt.ids)]k
|
||||
|
||||
|
||||
class jikimo_bom_line(models.Model):
|
||||
_name = 'jikimo.bom.line'
|
||||
_description = 'jikimo.bom.line'
|
||||
|
||||
name = fields.Char()
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
_order = 'cutting_tool_material_id, cutting_tool_type_id'
|
||||
stock_move_count = fields.Integer(string='stock_move count', compute='_compute_stock_move_count', store=True)
|
||||
|
||||
@api.depends('stock_move_ids')
|
||||
def _compute_stock_move_count(self):
|
||||
for record in self:
|
||||
if record.stock_move_ids:
|
||||
record.stock_move_count = len(record.stock_move_ids)
|
||||
else:
|
||||
record.stock_move_count = 0
|
||||
|
||||
def search(self, args, offset=0, limit=None, order=None, count=False):
|
||||
# 你可以在这里修改 `args` 以调整搜索条件
|
||||
# 例如,添加额外的搜索条件
|
||||
if self.env.context.get('jikimo_bom_product'):
|
||||
bom_id = self.env['jikimo.bom'].browse(request.session.get('jikimo_bom_product').get('bom_id'))
|
||||
|
||||
if not bom_id.options:
|
||||
raise UserError('请先选择组装方式')
|
||||
domains = bom_id.bom_product_domains(bom_id.options)
|
||||
args = args + domains
|
||||
return super(ProductProduct, self).search(args, offset=offset, limit=limit, order=order, count=count)
|
||||
34
sf_tool_management/models/tool_inventory.py
Normal file
34
sf_tool_management/models/tool_inventory.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from odoo import models, fields
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class ToolInventory(models.Model):
|
||||
_inherit = 'sf.tool.inventory'
|
||||
_description = '功能刀具清单'
|
||||
knife_handle_model = fields.Selection([('BT30', 'BT30'), ('BT40', 'BT40'), ('BT50', 'BT50'), ('GSK30', 'GSK30'), ('GSK40', 'GSK40'), ('GSK50', 'GSK50')], string='使用刀柄型号', required=True)
|
||||
jikimo_bom_ids = fields.One2many('jikimo.bom','tool_inventory_id', 'bom单')
|
||||
blade_length = fields.Float('刃长(mm)')
|
||||
def bom_mainfest(self):
|
||||
jikimo_bom_ids = self.mapped('jikimo_bom_ids')
|
||||
if not jikimo_bom_ids:
|
||||
self._bom_mainfest()
|
||||
return self.bom_mainfest()
|
||||
request.session['jikimo_bom_product'] = {'bom_id': int(self.jikimo_bom_ids)}
|
||||
# context = dict(self.env.context)
|
||||
# context.update({'jikimo_bom_product': self.jikimo_bom_ids.options})
|
||||
# if self.functional_cutting_tool_model_id.cutting_tool_type_ids:
|
||||
# context.update({'jikimo_bom_product_cutting_tool_type': self.functional_cutting_tool_model_id.cutting_tool_type_ids.ids})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': '刀具组装清单',
|
||||
'res_model': 'jikimo.bom',
|
||||
'view_mode': 'form',
|
||||
'view_id': self.env.ref('sf_tool_management.view_jikimo_bom_form').id,
|
||||
'res_id': int(self.jikimo_bom_ids),
|
||||
'target': 'current', # Use 'new' to open in a new window/tab
|
||||
# {'jikimo_bom_product': self.jikimo_bom_ids.options}
|
||||
}
|
||||
|
||||
# 创建bom单
|
||||
def _bom_mainfest(self):
|
||||
self.env['jikimo.bom'].create({'tool_inventory_id':self.id})
|
||||
@@ -38,3 +38,6 @@ access_sf_fixture_material_search_group_plan_dispatch,sf.fixture.material.search
|
||||
access_sf_functional_tool_dismantle,sf.functional.tool.dismantle,model_sf_functional_tool_dismantle,base.group_user,1,1,1,0
|
||||
access_sf_functional_tool_dismantle_group_sf_tool_user,sf.functional.tool.dismantle_group_sf_tool_user,model_sf_functional_tool_dismantle,sf_base.group_sf_tool_user,1,1,1,0
|
||||
access_sf_functional_tool_dismantle_group_plan_dispatch,sf.functional.tool.dismantle_group_plan_dispatch,model_sf_functional_tool_dismantle,sf_base.group_plan_dispatch,1,0,0,0
|
||||
|
||||
access_jikimo_bom,jikimo.bom,model_jikimo_bom,base.group_user,1,1,1,1
|
||||
access_jikimo_bom_wizard,jikimo.bom.wizard,model_jikimo_bom_wizard,base.group_user,1,1,1,1
|
||||
|
53
sf_tool_management/views/jikimo_bom.xml
Normal file
53
sf_tool_management/views/jikimo_bom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="ir.actions.act_window" id="view_jikimo_bom_form_act">
|
||||
<field name="name">bom物料清单</field>
|
||||
<field name="res_model">jikimo.bom</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<record id="view_jikimo_bom_form" model="ir.ui.view">
|
||||
<field name="name">jikimo.bom.form</field>
|
||||
<field name="model">jikimo.bom</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<header>
|
||||
<button type="action" name="%(action_jikimo_bom_wizard)d"
|
||||
class="btn btn-info" string="组装方式.." context="{'default_bom_id':id}"
|
||||
/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="tool_name"/>
|
||||
<field name="functional_cutting_tool_model_id"/>
|
||||
<field name="tool_groups_id"/>
|
||||
<field name="diameter"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="angle"/>
|
||||
<field name="tool_length"/>
|
||||
<field name="extension"/>
|
||||
<field name="knife_handle_model"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="物料清单">
|
||||
<field name="product_ids" context="{'jikimo_bom_product': True}">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<!-- <field name="categ_id"/>-->
|
||||
<field name="cutting_tool_material_id"/>
|
||||
<field name="cutting_tool_type_id"/>
|
||||
|
||||
<field name="cutting_tool_model_id"/>
|
||||
<field name="specification_id"/>
|
||||
<field name="brand_id"/>
|
||||
<field name="qty_available"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -842,21 +842,21 @@
|
||||
<group string="刀柄" attrs="{'invisible': [('handle_product_id', '=', False)]}"
|
||||
col="1">
|
||||
<group attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])]}">
|
||||
<group col="3">
|
||||
<group>
|
||||
<field name="scrap_boolean" string="是否报废"/>
|
||||
</group>
|
||||
<group></group>
|
||||
<group>
|
||||
<button string="报废" name="tool_scrap" type="object"
|
||||
class="btn-primary" confirm="是否确认报废刀柄"
|
||||
attrs="{'invisible': [('scrap_boolean', '=', True)]}"/>
|
||||
<button string="取消" name="tool_no_scrap" type="object"
|
||||
class="btn-primary" confirm="是否取消报废刀柄"
|
||||
attrs="{'invisible': [('scrap_boolean', '=', False)]}"/>
|
||||
<group></group>
|
||||
</group>
|
||||
<!-- <group col="3">-->
|
||||
<group>
|
||||
<field name="scrap_boolean" string="是否报废" readonly="0"/>
|
||||
</group>
|
||||
<!-- <group></group>-->
|
||||
<!-- <group>-->
|
||||
<!-- <button string="报废" name="tool_scrap" type="object"-->
|
||||
<!-- class="btn-primary" confirm="是否确认报废刀柄"-->
|
||||
<!-- attrs="{'invisible': [('scrap_boolean', '=', True)]}"/>-->
|
||||
<!-- <button string="取消" name="tool_no_scrap" type="object"-->
|
||||
<!-- class="btn-primary" confirm="是否取消报废刀柄"-->
|
||||
<!-- attrs="{'invisible': [('scrap_boolean', '=', False)]}"/>-->
|
||||
<!-- <group></group>-->
|
||||
<!-- </group>-->
|
||||
<!-- </group>-->
|
||||
</group>
|
||||
<group>
|
||||
<field name="handle_rfid" string="Rfid"/>
|
||||
|
||||
15
sf_tool_management/views/tool_inventory.xml
Normal file
15
sf_tool_management/views/tool_inventory.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_tool_inventory_inherit_tree" model="ir.ui.view">
|
||||
<field name="name">sf.tool.inventory.inherit.tree</field>
|
||||
<field name="model">sf.tool.inventory</field>
|
||||
<field name="inherit_id" ref="sf_base.view_tool_inventory_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='extension']" position="before">
|
||||
<field name="knife_handle_model" />
|
||||
<button name="bom_mainfest" string="bom清单" type="object" class="btn-link"
|
||||
icon="fa-refresh" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
18
sf_tool_management/views/tool_views.xml
Normal file
18
sf_tool_management/views/tool_views.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ================================================功能刀具类型================================================ -->
|
||||
<record id="view_cutter_function_inherit_tree" model="ir.ui.view">
|
||||
<field name="name">sf.cutter.function.inherit.tree</field>
|
||||
<field name="model">sf.functional.cutting.tool.model</field>
|
||||
<field name="inherit_id" ref="sf_base.view_cutter_function_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="cutting_tool_type_ids" widget="many2many_tags"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 功能刀具类型搜索 -->
|
||||
|
||||
</odoo>
|
||||
@@ -1 +1,2 @@
|
||||
from . import wizard
|
||||
from . import jikimo_bom_wizard
|
||||
28
sf_tool_management/wizard/jikimo_bom_wizard.py
Normal file
28
sf_tool_management/wizard/jikimo_bom_wizard.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
|
||||
from datetime import timedelta, datetime, date
|
||||
|
||||
from odoo import fields, models, api
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
|
||||
class JikimoBomWizard(models.TransientModel):
|
||||
_name = 'jikimo.bom.wizard'
|
||||
_description = '组装方式选择'
|
||||
bom_id = fields.Many2one('jikimo.bom', '刀具组装清单')
|
||||
assembly_options = fields.Selection([
|
||||
('刀柄+整体式刀具', '刀柄+整体式刀具'),
|
||||
('刀柄+刀杆+刀片', '刀柄+刀杆+刀片'),
|
||||
('刀柄+刀盘+刀片', '刀柄+刀盘+刀片')
|
||||
], string='组装方式', required=True)
|
||||
# assembly_options_ids = fields.Many2many('sf.cutting.tool.material', string="组装方式")
|
||||
is_ok = fields.Boolean('确认上述信息正确无误。')
|
||||
|
||||
def submit(self):
|
||||
if not self.bom_id:
|
||||
raise UserError('缺少bom信息')
|
||||
if self.bom_id.tool_inventory_id.functional_cutting_tool_model_id.name == '飞刀' and self.assembly_options == '刀柄+整体式刀具':
|
||||
raise UserError('飞刀只可选 刀柄+刀杆+刀片 或 刀柄+刀盘+刀片')
|
||||
if self.bom_id.tool_inventory_id.functional_cutting_tool_model_id.name in['中心钻','合金钻','合金刀','整体刀','倒角刀','丝锥'] and self.assembly_options != '刀柄+整体式刀具':
|
||||
raise UserError('此功能刀具只可选 刀柄+整体式刀具')
|
||||
self.bom_id.generate_bill_materials(self.assembly_options)
|
||||
33
sf_tool_management/wizard/jikimo_bom_wizard.xml
Normal file
33
sf_tool_management/wizard/jikimo_bom_wizard.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="action_jikimo_bom_wizard" model="ir.actions.act_window">
|
||||
<field name="name">组装方式..</field>
|
||||
<field name="res_model">jikimo.bom.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="jikimo_bom_wizard_form_view">
|
||||
<field name="name">jikimo.bom.wizard.form.view</field>
|
||||
<field name="model">jikimo.bom.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="组装方式..">
|
||||
<group>
|
||||
<field name="assembly_options"/>
|
||||
<!-- <field name="factory_no" required="1"/>-->
|
||||
</group>
|
||||
<div>
|
||||
<field name="is_ok"/>
|
||||
确认上述信息正确无误.
|
||||
</div>
|
||||
<footer>
|
||||
<button string="确认组装方式" name="submit" type="object" class="oe_highlight"
|
||||
attrs="{'invisible':[('is_ok','=',False)]}"/>
|
||||
<button string="取消" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -387,9 +387,9 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
|
||||
lot_ids = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)])
|
||||
if lot_ids:
|
||||
for lot_id in lot_ids:
|
||||
if lot_id.quant_ids[-1].location_id.name in '刀具房':
|
||||
if lot_id.tool_material_status == '可用':
|
||||
record.handle_code_id = lot_id.id
|
||||
elif lot_id.quant_ids[-1].location_id.name == '刀具组装位置':
|
||||
elif lot_id.quant_ids[-1].location_id.name in ['刀具组装位置']:
|
||||
raise ValidationError('该刀柄已使用,请重新扫描!!!')
|
||||
else:
|
||||
raise ValidationError('该刀柄未入库,请重新扫描!!!')
|
||||
|
||||
@@ -139,5 +139,5 @@ class MrsShelfLocationDataSync(models.Model):
|
||||
location_id.product_sn_id = False
|
||||
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("库区信息同步失败:%s" % e)
|
||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||
|
||||
Reference in New Issue
Block a user