Compare commits
130 Commits
master_sf_
...
feature/or
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56f1ba0f25 | ||
|
|
d067c5b8c4 | ||
|
|
0cee6ebd77 | ||
|
|
98e13cba99 | ||
|
|
946abc5cf7 | ||
|
|
57ea9ed8f4 | ||
|
|
722f6257ae | ||
|
|
0f6ca0876c | ||
|
|
bb60fd0339 | ||
|
|
eefbd5939b | ||
|
|
7c01196f17 | ||
|
|
4c58f4d7f3 | ||
|
|
c741587341 | ||
|
|
6ad2fa80f1 | ||
|
|
8423b12119 | ||
|
|
302a5f0cf1 | ||
|
|
b4fef421dd | ||
|
|
5f5e991d33 | ||
|
|
43a49242c5 | ||
|
|
2e5694cd1a | ||
|
|
3c123a07ea | ||
|
|
22864b9669 | ||
|
|
528fa483a2 | ||
|
|
b5a3815f1f | ||
|
|
1719e0394e | ||
|
|
eae902ee92 | ||
|
|
06c5f65df2 | ||
|
|
e0bad6ed40 | ||
|
|
35c8b9316e | ||
|
|
988c4a460d | ||
|
|
c050e2f11a | ||
|
|
95e49c7b0f | ||
|
|
e46a13889e | ||
|
|
335151f6ca | ||
|
|
908e4a3df6 | ||
|
|
6b403f2c1e | ||
|
|
da5e322229 | ||
|
|
4f9ac16a07 | ||
|
|
4342844845 | ||
|
|
8bc68e1edd | ||
|
|
763d64a1ca | ||
|
|
8a10d18f37 | ||
|
|
181f490d47 | ||
|
|
322718d19a | ||
|
|
96eeb1cab7 | ||
|
|
97a5d91fce | ||
|
|
9eee8557c2 | ||
|
|
4394dd023e | ||
|
|
d39bbe4f99 | ||
|
|
b188c8adfe | ||
|
|
ea6fd42b2e | ||
|
|
625499f758 | ||
|
|
0c2d6dd582 | ||
|
|
f6a9c97e7f | ||
|
|
e2d741937e | ||
|
|
9a3404f4b8 | ||
|
|
d000aa095f | ||
|
|
8945801db8 | ||
|
|
40f327ea17 | ||
|
|
44a2604dbd | ||
|
|
0ae7424f04 | ||
|
|
091af92e8d | ||
|
|
3ed338ed64 | ||
|
|
65781570e7 | ||
|
|
e1f7aeaa96 | ||
|
|
a0a580588c | ||
|
|
a2f2a21a4c | ||
|
|
1ddb446bc6 | ||
|
|
dcdc6851b6 | ||
|
|
ece4e56118 | ||
|
|
7cd104c047 | ||
|
|
80a7dd75c8 | ||
|
|
ec2ef752b3 | ||
|
|
57d5c57048 | ||
|
|
9b7c22c35e | ||
|
|
6711f6f6e6 | ||
|
|
7ac7077f8c | ||
|
|
ed20249988 | ||
|
|
dba8d23348 | ||
|
|
ab9062983c | ||
|
|
37493a4688 | ||
|
|
689d149edc | ||
|
|
9b9106c5da | ||
|
|
9a43af98c3 | ||
|
|
1eae92f2b2 | ||
|
|
4560bbc0ed | ||
|
|
2d5ef0aae4 | ||
|
|
096542c8ff | ||
|
|
d123ca5173 | ||
|
|
388929f207 | ||
|
|
29b9223a14 | ||
|
|
cf283377cd | ||
|
|
5f218e69d4 | ||
|
|
7f5fb165de | ||
|
|
eec79d7d3e | ||
|
|
00809d88f6 | ||
|
|
18b0cc6ea0 | ||
|
|
54d9902ae4 | ||
|
|
eff39a39e3 | ||
|
|
005a7bb68e | ||
|
|
afc1701150 | ||
|
|
72eada0639 | ||
|
|
979e8a47ab | ||
|
|
e119a364c1 | ||
|
|
c7b9bbb18d | ||
|
|
3cb3dce247 | ||
|
|
a7e1c89e66 | ||
|
|
ca1060ada5 | ||
|
|
69578ab054 | ||
|
|
511bed47ff | ||
|
|
26a0412649 | ||
|
|
4989ff773c | ||
|
|
04c0d68763 | ||
|
|
589035d42b | ||
|
|
5726d50106 | ||
|
|
6e9c326a52 | ||
|
|
fc1cdf4d0e | ||
|
|
45178ac38b | ||
|
|
09ee1c26b1 | ||
|
|
64180ca57f | ||
|
|
0c052a118c | ||
|
|
cb9fd43f11 | ||
|
|
1dc55c2770 | ||
|
|
0ca6d46a7b | ||
|
|
3624ef4755 | ||
|
|
f07a003f2c | ||
|
|
0ef46407dd | ||
|
|
f7f3c005f8 | ||
|
|
9a5aa52801 | ||
|
|
1e172cb4e3 |
BIN
jikimo_system_order/static/description/系统工单.png
Normal file
BIN
jikimo_system_order/static/description/系统工单.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 533 B |
@@ -234,7 +234,7 @@
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem name="系统工单" id="work_order_1_list" web_icon="jikimo_system_order,static/description/icon.png"/>
|
||||
<menuitem name="系统工单" id="work_order_1_list" web_icon="jikimo_system_order,static/description/系统工单.png"/>
|
||||
<menuitem name="工单" id="work_order" parent="work_order_1_list" action="system_order"/>
|
||||
<menuitem name="工单模板" id="work_order_template" parent="work_order_1_list" action="work_template" groups="jikimo_system_order.group_operations_permissions_rwc"/>
|
||||
<menuitem name="工单分类" id="work_order_type" parent="work_order_1_list" action="classify" groups="jikimo_system_order.group_operations_permissions_rwc"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
BIN
quality_control/static/description/质量.png
Normal file
BIN
quality_control/static/description/质量.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 702 B |
@@ -1024,7 +1024,7 @@
|
||||
<menuitem
|
||||
id="menu_quality_root"
|
||||
name="Quality"
|
||||
web_icon="quality_control,static/description/icon.svg"
|
||||
web_icon="quality_control,static/description/质量.png"
|
||||
sequence="150"
|
||||
groups="quality.group_quality_user"/>
|
||||
|
||||
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -53,7 +53,7 @@ class StatusChange(models.Model):
|
||||
if not ret.get('error'):
|
||||
logging.info('接口已经执行=============')
|
||||
else:
|
||||
logging.error('工厂加工同步订单状态失败 {}'.format(ret.text))
|
||||
logging.error('工厂加工同步订单状态失败 {}'.format(ret))
|
||||
raise UserError('工厂加工同步订单状态失败')
|
||||
except UserError as e:
|
||||
logging.error('工厂加工同步订单状态失败 {}'.format(e))
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="mrp.product_template_action" model="ir.actions.act_window">
|
||||
<field name="context">
|
||||
{"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'}
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="view_sale_product_template_form_inherit_sf">
|
||||
<field name="name">product.template.form.inherit.sf</field>
|
||||
<field name="model">product.template</field>
|
||||
@@ -88,7 +93,8 @@
|
||||
</div>
|
||||
<field name="model_volume" string="体积[mm³]"/>
|
||||
<field name="product_model_type_id" string="模型类型"/>
|
||||
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板" readonly="1"/>
|
||||
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板"
|
||||
readonly="1"/>
|
||||
<field name="model_machining_precision"/>
|
||||
<field name="model_process_parameters_ids" string="表面工艺参数"
|
||||
widget="many2many_tags"
|
||||
@@ -103,11 +109,11 @@
|
||||
'刀具')], 'required': True}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
<!-- </attribute>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
<!-- </attribute>-->
|
||||
<!-- </xpath>-->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
'sequence': 1,
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['hr'],
|
||||
'depends': ['base', 'hr'],
|
||||
'data': [
|
||||
'views/hr_employee.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/res_users_view.xml',
|
||||
'data/cron_data.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
|
||||
15
sf_hr/data/cron_data.xml
Normal file
15
sf_hr/data/cron_data.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.cron" id="ir_cron_employee_info_sync">
|
||||
<field name="name">员工企微id同步</field>
|
||||
<field name="model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._employee_info_sync()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,2 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import hr_employee
|
||||
from . import res_config_setting
|
||||
from . import res_users
|
||||
|
||||
28
sf_hr/models/hr_employee.py
Normal file
28
sf_hr/models/hr_employee.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import logging
|
||||
import requests
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JkmPracticeEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
_description = '员工信息'
|
||||
|
||||
we_id = fields.Char(string='企微ID', index=True)
|
||||
|
||||
def _employee_info_sync(self):
|
||||
url = '/api/get/organization'
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
ret = requests.post((config['ims_url'] + url), json={}, data={})
|
||||
result = ret.json()['result']
|
||||
if result['code'] == 200:
|
||||
if result['employee_list']:
|
||||
for employee_info in result['employee_list']:
|
||||
if employee_info['work_email']:
|
||||
hr_employee = self.sudo().search([('work_email', '=', employee_info['work_email'])])
|
||||
hr_employee.write({'we_id': employee_info['we_id']})
|
||||
if hr_employee.user_id:
|
||||
hr_employee.user_id.write({'we_employee_id': employee_info['we_id']})
|
||||
else:
|
||||
logging.info('_employee_info_sync error:%s' % result['message'])
|
||||
30
sf_hr/models/res_config_setting.py
Normal file
30
sf_hr/models/res_config_setting.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResIMSConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
ims_url = fields.Char('综合管理系统访问地址')
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
"""
|
||||
重载获取参数的方法,参数都存在系统参数中
|
||||
:return:
|
||||
"""
|
||||
values = super(ResIMSConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
ims_url = config.get_param('ims_url', default='')
|
||||
values.update(
|
||||
ims_url=ims_url,
|
||||
)
|
||||
return values
|
||||
|
||||
def set_values(self):
|
||||
super(ResIMSConfigSettings, self).set_values()
|
||||
ir_config = self.env['ir.config_parameter'].sudo()
|
||||
ir_config.set_param("ims_url", self.ims_url or "")
|
||||
12
sf_hr/models/res_users.py
Normal file
12
sf_hr/models/res_users.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
we_employee_id = fields.Char(string=u'企业微信账号', default="")
|
||||
@@ -7,7 +7,16 @@
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group//field[@name='work_email']" position="attributes">
|
||||
<attribute name="required">1</attribute>
|
||||
<attribute name="required">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='public']" position='after'>
|
||||
<page string="企业微信">
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="we_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
28
sf_hr/views/res_config_settings_views.xml
Normal file
28
sf_hr/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_finance_view_form_extend" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.finance.view.form.extend</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
|
||||
<div>
|
||||
<h2>综合管理系统接口配置</h2>
|
||||
<div class="row mt16 o_settings_container" id="jd_api">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="ims_url"/>
|
||||
<field name="ims_url"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
20
sf_hr/views/res_users_view.xml
Normal file
20
sf_hr/views/res_users_view.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_users_account_form" model="ir.ui.view">
|
||||
<field name="name">res.users.account.form</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<page name="preferences" position="after">
|
||||
<page name="account" string="企业微信">
|
||||
<group>
|
||||
<field name="we_employee_id"/>
|
||||
</group>
|
||||
</page>
|
||||
</page>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -6,7 +6,7 @@ import base64
|
||||
import logging
|
||||
import psycopg2
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import http
|
||||
from odoo import http, fields
|
||||
from odoo.http import request
|
||||
|
||||
# 数据库连接配置
|
||||
@@ -15,14 +15,18 @@ db_config = {
|
||||
"user": "postgres",
|
||||
"password": "postgres",
|
||||
"port": "5432",
|
||||
"host": "172.16.10.98"
|
||||
"host": "172.16.10.113"
|
||||
}
|
||||
|
||||
|
||||
def convert_to_seconds(time_str):
|
||||
# 修改正则表达式,使 H、M、S 部分可选
|
||||
|
||||
if time_str is None:
|
||||
return 0
|
||||
|
||||
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
|
||||
|
||||
match = re.match(pattern, time_str)
|
||||
|
||||
if match:
|
||||
@@ -66,7 +70,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
|
||||
# 获取当前时间的时间戳
|
||||
current_timestamp = datetime.now().timestamp()
|
||||
print(current_timestamp)
|
||||
# print(current_timestamp)
|
||||
|
||||
# tem_list = [
|
||||
# "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3",
|
||||
@@ -162,7 +166,8 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
'first_online_duration': first_online_duration,
|
||||
# 停机时间:关机时间 - 运行时间
|
||||
# 停机时长:关机时间 - 初次上线时间
|
||||
'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
|
||||
# 'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
|
||||
'img': f'https://xt.sf.jikimo.com/equipment/get_image/{machine_data.id}',
|
||||
'equipment_type': machine_data.category_id.name,
|
||||
})
|
||||
|
||||
@@ -194,7 +199,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
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)
|
||||
# print('begin_time: %s' % begin_time)
|
||||
|
||||
for item in machine_list:
|
||||
sql = '''
|
||||
@@ -275,7 +280,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
res = {'Succeed': True}
|
||||
line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')])
|
||||
line_list = list(map(lambda x: x.name, line_list_obj))
|
||||
print('line_list: %s' % line_list)
|
||||
# print('line_list: %s' % line_list)
|
||||
res['LineList'] = line_list
|
||||
|
||||
except Exception as e:
|
||||
@@ -300,21 +305,40 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
|
||||
try:
|
||||
plan_obj = request.env['sf.production.plan'].sudo()
|
||||
production_obj = request.env['mrp.production'].sudo()
|
||||
line_list = ast.literal_eval(kw['line_list'])
|
||||
print('line_list: %s' % line_list)
|
||||
# print('line_list: %s' % line_list)
|
||||
for line in line_list:
|
||||
plan_data = plan_obj.search([('production_line_id.name', '=', line)])
|
||||
# 工单总量
|
||||
plan_data_total_counts = plan_obj.search_count([('production_line_id.name', '=', line)])
|
||||
|
||||
# 工单计划量
|
||||
plan_data_total_counts = production_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['cancel']),
|
||||
('active', '=', True)])
|
||||
# 工单完成量
|
||||
plan_data_finish_counts = plan_obj.search_count(
|
||||
[('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'])])
|
||||
# 工单不良累计
|
||||
plan_data_fault_counts = plan_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['scrap', 'cancel'])])
|
||||
# # 工单计划量
|
||||
# plan_data_plan_counts = plan_obj.search_count(
|
||||
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
|
||||
# 查找符合条件的生产计划记录
|
||||
plan_data = plan_obj.search([
|
||||
('production_line_id.name', '=', line),
|
||||
])
|
||||
|
||||
# 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录
|
||||
faulty_plans = plan_data.filtered(lambda p: any(
|
||||
result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids
|
||||
))
|
||||
|
||||
# 查找制造订单取消与归档的数量
|
||||
cancel_order_count = production_obj.search_count(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['cancel']),
|
||||
('active', '=', False)])
|
||||
|
||||
# 计算符合条件的记录数量
|
||||
# plan_data_fault_counts = len(faulty_plans) + cancel_order_count
|
||||
plan_data_fault_counts = len(faulty_plans)
|
||||
|
||||
# 工单返工数量
|
||||
|
||||
@@ -326,17 +350,76 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
(plan_data_finish_counts / plan_data_total_counts if plan_data_total_counts > 0 else 0), 3)
|
||||
|
||||
# 工单进度偏差
|
||||
plan_data_progress_deviation = plan_data_finish_counts - plan_data_plan_counts
|
||||
plan_data_progress_deviation = plan_data_total_counts - plan_data_finish_counts - plan_data_fault_counts
|
||||
|
||||
# 完成记录
|
||||
plan_data_finish_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
|
||||
# # 检测量
|
||||
# detection_nums = 0
|
||||
# for i in plan_data_finish_orders:
|
||||
# print('i: %s' % i)
|
||||
# if i.production_id.detection_result_ids:
|
||||
# detection_nums += 1
|
||||
#
|
||||
# print('detection_nums: %s' % detection_nums)
|
||||
|
||||
# 检测量
|
||||
detection_data = len(
|
||||
plan_data_finish_orders.mapped('production_id.detection_result_ids').filtered(lambda r: r))
|
||||
|
||||
# 检测合格量
|
||||
pass_nums = plan_data_finish_orders.filtered(lambda p: any(
|
||||
result.test_results in ['合格'] for result in p.production_id.detection_result_ids
|
||||
))
|
||||
|
||||
# 质量合格率
|
||||
pass_rate = 1
|
||||
if pass_nums:
|
||||
pass_rate = round(
|
||||
(len(pass_nums) / detection_data if len(plan_data_finish_orders) > 0 else 0), 3)
|
||||
|
||||
# 返工率
|
||||
rework_rate = round(
|
||||
(plan_data_rework_counts / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3)
|
||||
|
||||
# 交付准时率
|
||||
delay_num = 0
|
||||
for plan in plan_data_finish_orders:
|
||||
sale_obj = request.env['sale.order'].sudo().search([('name', '=', plan.origin)])
|
||||
if sale_obj:
|
||||
if sale_obj.deadline_of_delivery and plan.actual_end_time:
|
||||
# 将 deadline_of_delivery 转换为字符串
|
||||
date_as_string = sale_obj.deadline_of_delivery.strftime('%Y-%m-%d')
|
||||
# 然后使用 strptime 将字符串转换为 datetime 对象
|
||||
date_as_datetime = datetime.strptime(date_as_string, '%Y-%m-%d')
|
||||
|
||||
# 将 actual_end_time 转换为 datetime 对象
|
||||
datetime_value = fields.Datetime.from_string(plan.actual_end_time)
|
||||
|
||||
# 给 datetime_value 加1天
|
||||
new_datetime_value = datetime_value + timedelta(days=1)
|
||||
|
||||
# 比较 new_datetime_value 和 date_as_datetime 的大小
|
||||
if new_datetime_value.date() > date_as_datetime.date():
|
||||
delay_num += 1
|
||||
delay_rate = round((delay_num / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3)
|
||||
on_time_rate = 1 - delay_rate
|
||||
|
||||
if plan_data:
|
||||
data = {
|
||||
'plan_data_total_counts': plan_data_total_counts,
|
||||
'plan_data_finish_counts': plan_data_finish_counts,
|
||||
'plan_data_plan_counts': plan_data_plan_counts,
|
||||
'plan_data_plan_counts': plan_data_total_counts,
|
||||
'plan_data_fault_counts': plan_data_fault_counts,
|
||||
'nopass_orders_counts': detection_data - len(pass_nums),
|
||||
'finishe_rate': finishe_rate,
|
||||
'plan_data_progress_deviation': plan_data_progress_deviation,
|
||||
'plan_data_rework_counts': plan_data_rework_counts
|
||||
'plan_data_rework_counts': plan_data_rework_counts,
|
||||
'on_time_rate': on_time_rate,
|
||||
'detection_data': detection_data,
|
||||
'pass_rate': pass_rate
|
||||
}
|
||||
res['data'][line] = data
|
||||
|
||||
@@ -363,7 +446,28 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
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)
|
||||
# print('line_list: %s' % line_list)
|
||||
print('kw', kw)
|
||||
time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天
|
||||
print('time_unit: %s' % time_unit)
|
||||
|
||||
# 日期或小时循环生成器,根据time_unit决定是按天还是按小时
|
||||
def get_time_intervals(start_time, end_time, time_unit):
|
||||
intervals = []
|
||||
current_time = start_time
|
||||
if time_unit == 'hour':
|
||||
delta = timedelta(hours=1)
|
||||
else:
|
||||
delta = timedelta(days=1)
|
||||
|
||||
while current_time < end_time:
|
||||
next_time = current_time + delta
|
||||
# 确保最后一个时间段不会超出end_time
|
||||
if next_time > end_time:
|
||||
next_time = end_time
|
||||
intervals.append((current_time, next_time))
|
||||
current_time = next_time
|
||||
return intervals
|
||||
|
||||
def get_date_list(start_date, end_date):
|
||||
date_list = []
|
||||
@@ -374,10 +478,33 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
return date_list
|
||||
|
||||
for line in line_list:
|
||||
date_list = get_date_list(begin_time, end_time)
|
||||
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
|
||||
order_counts = []
|
||||
|
||||
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
|
||||
if time_unit == 'hour':
|
||||
time_intervals = get_time_intervals(begin_time, end_time, time_unit)
|
||||
print('============================= %s' % time_intervals)
|
||||
|
||||
time_count_dict = {}
|
||||
|
||||
for time_interval in time_intervals:
|
||||
start_time, end_time = time_interval
|
||||
# print(start_time, end_time)
|
||||
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished']),
|
||||
(date_field_name, '>=', start_time.strftime('%Y-%m-%d 00:00:00')),
|
||||
(date_field_name, '<', end_time.strftime('%Y-%m-%d 00:00:00'))
|
||||
])
|
||||
# 使用小时和分钟作为键,确保每个小时的数据有独立的键
|
||||
key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
|
||||
time_count_dict[key] = len(orders)
|
||||
# order_counts.append()
|
||||
res['data'][line] = {
|
||||
'finish_order_nums': time_count_dict,
|
||||
'plan_order_nums': 28
|
||||
}
|
||||
return json.dumps(res)
|
||||
|
||||
date_list = get_date_list(begin_time, end_time)
|
||||
|
||||
for date in date_list:
|
||||
next_day = date + timedelta(days=1)
|
||||
@@ -487,7 +614,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
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)
|
||||
# print('line_list: %s' % line_list)
|
||||
not_done_data = []
|
||||
done_data = []
|
||||
final_data = {}
|
||||
@@ -495,11 +622,16 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
for line in line_list:
|
||||
# 未完成订单
|
||||
not_done_orders = plan_obj.search(
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
|
||||
print(not_done_orders)
|
||||
[('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
|
||||
('production_id.state', 'not in', ['cancel']), ('active', '=', True)
|
||||
])
|
||||
# print(not_done_orders)
|
||||
# 完成订单
|
||||
finish_orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
|
||||
print(finish_orders)
|
||||
finish_orders = plan_obj.search([
|
||||
('production_line_id.name', '=', line), ('state', 'in', ['finished']),
|
||||
('production_id.state', 'not in', ['cancel']), ('active', '=', True)
|
||||
])
|
||||
# print(finish_orders)
|
||||
|
||||
# 获取所有未完成订单的ID列表
|
||||
order_ids = [order.id for order in not_done_orders]
|
||||
@@ -622,11 +754,11 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
# 执行SQL命令
|
||||
cur.execute(sql, (item,))
|
||||
result = cur.fetchall()
|
||||
print('result========', result)
|
||||
# print('result========', result)
|
||||
|
||||
cur.execute(sql2, (item,))
|
||||
result2 = cur.fetchall()
|
||||
print('result2========', result2)
|
||||
# print('result2========', result2)
|
||||
#
|
||||
for row in result:
|
||||
res['data'][item] = {'idle_count': row[0]}
|
||||
@@ -682,7 +814,7 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
# 执行SQL命令
|
||||
cur.execute(sql)
|
||||
result = cur.fetchall()
|
||||
print('result', result)
|
||||
# print('result', result)
|
||||
|
||||
# 将查询结果转换为字典列表
|
||||
data = []
|
||||
@@ -830,3 +962,222 @@ class Sf_Dashboard_Connect(http.Controller):
|
||||
# 返回数据
|
||||
res['data'] = oee_data
|
||||
return json.dumps(res)
|
||||
|
||||
@http.route(['/equipment/get_image/<int:record_id>'], type='http', auth="public", website=True)
|
||||
def get_image(self, record_id, **kwargs):
|
||||
# 获取模型中的记录
|
||||
record = request.env['maintenance.equipment'].sudo().browse(record_id)
|
||||
|
||||
# 获取图片字段的数据
|
||||
image_data_base64 = record.machine_tool_picture
|
||||
|
||||
if image_data_base64:
|
||||
# 将Base64解码为二进制数据
|
||||
image_data_binary = base64.b64decode(image_data_base64)
|
||||
|
||||
# 返回图片数据,并设置正确的Content-Type
|
||||
return request.make_response(image_data_binary, headers=[('Content-Type', 'image/png')])
|
||||
else:
|
||||
# 如果没有图片数据,返回404
|
||||
return request.not_found()
|
||||
|
||||
# 设备运行率
|
||||
@http.route('/api/RunningTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def RunningTime(self, **kw):
|
||||
"""
|
||||
获取设备运行时长
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
# 获取请求的机床数据
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
|
||||
def fetch_result_as_dict(cursor):
|
||||
"""辅助函数:将查询结果转为字典"""
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
return dict(zip(columns, cursor.fetchone())) if cursor.rowcount != 0 else None
|
||||
|
||||
# 初始化当天、当月和有记录以来的总时长
|
||||
day_total_running_time = 0
|
||||
day_total_process_time = 0
|
||||
day_work_rate = 0
|
||||
month_total_running_time = 0
|
||||
month_total_process_time = 0
|
||||
month_work_rate = 0
|
||||
all_time_total_running_time = 0
|
||||
all_time_total_process_time = 0
|
||||
all_time_work_rate = 0
|
||||
|
||||
for item in machine_list:
|
||||
# 获取当天第一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND time::date = CURRENT_DATE
|
||||
AND device_state != '离线'
|
||||
ORDER BY time ASC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
first_today = fetch_result_as_dict(cur)
|
||||
# print("当天第一条记录(非离线):", first_today)
|
||||
|
||||
# 获取当天最新一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND time::date = CURRENT_DATE
|
||||
AND device_state != '离线'
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
last_today = fetch_result_as_dict(cur)
|
||||
# print("当天最新一条记录(非离线):", last_today)
|
||||
|
||||
# 计算当天运行时长
|
||||
if first_today and last_today:
|
||||
running_time = convert_to_seconds(last_today['run_time']) - convert_to_seconds(first_today['run_time'])
|
||||
process_time = convert_to_seconds(last_today['process_time']) - convert_to_seconds(
|
||||
first_today['process_time'])
|
||||
day_total_running_time += abs(running_time)
|
||||
day_total_process_time += abs(process_time)
|
||||
|
||||
# 获取当月第一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE)
|
||||
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE)
|
||||
AND device_state != '离线'
|
||||
ORDER BY time ASC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
first_month = fetch_result_as_dict(cur)
|
||||
# print("当月第一条记录(非离线):", first_month)
|
||||
|
||||
# 获取当月最新一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE)
|
||||
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE)
|
||||
AND device_state != '离线'
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
last_month = fetch_result_as_dict(cur)
|
||||
# print("当月最新一条记录(非离线):", last_month)
|
||||
|
||||
# 计算当月运行时长
|
||||
if first_month and last_month:
|
||||
month_running_time = convert_to_seconds(last_month['run_time']) - convert_to_seconds(
|
||||
first_month['run_time'])
|
||||
month_process_time = convert_to_seconds(last_month['process_time']) - convert_to_seconds(
|
||||
first_month['process_time'])
|
||||
month_total_running_time += abs(month_running_time)
|
||||
month_total_process_time += abs(month_process_time)
|
||||
|
||||
# 获取有记录以来的第一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND device_state != '离线'
|
||||
ORDER BY time ASC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
first_all_time = fetch_result_as_dict(cur)
|
||||
# print("有记录以来的第一条记录(非离线):", first_all_time)
|
||||
|
||||
# 获取有记录以来的最新一条记录(排除device_state等于‘离线’的记录)
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND device_state != '离线'
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
last_all_time = fetch_result_as_dict(cur)
|
||||
# print("有记录以来的最新一条记录(非离线):", last_all_time)
|
||||
|
||||
# 计算有记录以来的运行时长
|
||||
if first_all_time and last_all_time:
|
||||
all_time_running_time = convert_to_seconds(last_all_time['run_time']) - convert_to_seconds(
|
||||
first_all_time['run_time'])
|
||||
all_time_process_time = convert_to_seconds(last_all_time['process_time']) - convert_to_seconds(
|
||||
first_all_time['process_time'])
|
||||
all_time_total_running_time += abs(all_time_running_time)
|
||||
all_time_total_process_time += abs(all_time_process_time)
|
||||
|
||||
# 计算当天工作效率
|
||||
if day_total_running_time > day_total_process_time:
|
||||
day_work_rate = day_total_process_time / day_total_running_time if day_total_running_time != 0 else 0
|
||||
else:
|
||||
day_work_rate = day_total_running_time / day_total_process_time if day_total_process_time != 0 else 0
|
||||
print("当天工作效率: %s" % day_work_rate)
|
||||
|
||||
# 计算当月工作效率
|
||||
if month_total_running_time > month_total_process_time:
|
||||
month_work_rate = month_total_process_time / month_total_running_time if month_total_running_time != 0 else 0
|
||||
else:
|
||||
month_work_rate = month_total_running_time / month_total_process_time if month_total_process_time != 0 else 0
|
||||
print("当月工作效率: %s" % month_work_rate)
|
||||
|
||||
# 计算有记录以来的工作效率
|
||||
if all_time_total_running_time > all_time_total_process_time:
|
||||
all_time_work_rate = all_time_total_process_time / all_time_total_running_time if all_time_total_running_time != 0 else 0
|
||||
else:
|
||||
all_time_work_rate = all_time_total_running_time / all_time_total_process_time if all_time_total_process_time != 0 else 0
|
||||
print("有记录以来的工作效率: %s" % all_time_work_rate)
|
||||
|
||||
conn.close()
|
||||
|
||||
# 返回数据
|
||||
res['data']['day_work_rate'] = day_work_rate
|
||||
res['data']['month_work_rate'] = month_work_rate
|
||||
res['data']['all_time_work_rate'] = all_time_work_rate
|
||||
|
||||
return json.dumps(res)
|
||||
|
||||
# 设备运行时长
|
||||
@http.route('/api/RunningTimeDetail', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
|
||||
def RunningTimeDetail(self, **kw):
|
||||
"""
|
||||
获取
|
||||
"""
|
||||
res = {'status': 1, 'message': '成功', 'data': {}}
|
||||
# 连接数据库
|
||||
conn = psycopg2.connect(**db_config)
|
||||
# 获取请求的机床数据
|
||||
machine_list = ast.literal_eval(kw['machine_list'])
|
||||
|
||||
def fetch_result_as_dict(cursor):
|
||||
"""辅助函数:将查询结果转为字典"""
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
return dict(zip(columns, cursor.fetchone())) if cursor.rowcount != 0 else None
|
||||
|
||||
for item in machine_list:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM device_data
|
||||
WHERE device_name = %s
|
||||
AND device_state != '离线'
|
||||
ORDER BY time DESC
|
||||
LIMIT 1;
|
||||
""", (item,))
|
||||
last_all_time = fetch_result_as_dict(cur)
|
||||
# 返回数据
|
||||
res['data'][item] = {
|
||||
'wait_time': last_all_time['run_time'] if last_all_time['run_time'] is not None else 0,
|
||||
'cut_time': last_all_time['process_time'] if last_all_time['process_time'] is not None else 0,
|
||||
'power_on_time': last_all_time['power_on_time'] if last_all_time['power_on_time'] is not None else 0
|
||||
}
|
||||
|
||||
conn.close()
|
||||
|
||||
return json.dumps(res)
|
||||
|
||||
@@ -16,6 +16,7 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
# ("https://bfm.jikimo.com", "正式环境(https://bfm.jikimo.com)")], string='bfm环境', store=True)
|
||||
|
||||
bfm_url_new = fields.Char('业务平台环境路径', placeholder='请输入当前对应的业务平台环境路径')
|
||||
get_check_file_path = fields.Char('获取检查文件路径', default='')
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
@@ -26,9 +27,11 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
values = super(ResBFMConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
bfm_url_new = config.get_param('bfm_url_new', default='')
|
||||
get_check_file_path = config.get_param('get_check_file_path', default='')
|
||||
|
||||
values.update(
|
||||
bfm_url_new=bfm_url_new,
|
||||
get_check_file_path=get_check_file_path
|
||||
)
|
||||
return values
|
||||
|
||||
@@ -36,3 +39,4 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
super(ResBFMConfigSettings, self).set_values()
|
||||
ir_config = self.env['ir.config_parameter'].sudo()
|
||||
ir_config.set_param("bfm_url_new", self.bfm_url_new or "")
|
||||
ir_config.set_param("get_check_file_path", self.get_check_file_path or "")
|
||||
|
||||
@@ -18,6 +18,21 @@
|
||||
<field name="bfm_url_new" string="业务平台访问地址"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>获取检测报告服务配置</h2>
|
||||
<div class="row mt16 o_settings_container" id="jd_api">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="get_check_file_path" />
|
||||
<field name="get_check_file_path" string="检测报告服务地址"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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):
|
||||
|
||||
BIN
sf_maintenance/static/description/维护.png
Normal file
BIN
sf_maintenance/static/description/维护.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 858 B |
@@ -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"/>
|
||||
@@ -77,6 +79,7 @@
|
||||
<field name="name" string="日常机床保养"/>
|
||||
<field name="created_user_id" string="创建人"/>
|
||||
<field name="maintenance_equipment_category_id" string="设备类别"/>
|
||||
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1230,5 +1230,9 @@
|
||||
action="hr_equipment_action1"
|
||||
sequence="0"/>
|
||||
|
||||
<menuitem
|
||||
id="maintenance.menu_maintenance_title"
|
||||
web_icon="sf_maintenance,static/description/维护.png"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
@@ -45,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',
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
@@ -477,7 +477,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('LocationChange error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVToProduct(self, **kw):
|
||||
"""
|
||||
@@ -549,7 +549,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('AGVToProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVDownProduct(self, **kw):
|
||||
"""
|
||||
@@ -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='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVStationState(self, **kw):
|
||||
"""
|
||||
|
||||
@@ -8,7 +8,7 @@ from odoo.http import request
|
||||
|
||||
class Workpiece(http.Controller):
|
||||
|
||||
@http.route('/agvApi/backfeed', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/agvApi/backfeed', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def backfeed(self, **kw):
|
||||
"""
|
||||
|
||||
@@ -265,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
|
||||
|
||||
@@ -779,7 +779,8 @@ class MrpProduction(models.Model):
|
||||
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})
|
||||
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):
|
||||
@@ -1078,27 +1079,22 @@ class MrpProduction(models.Model):
|
||||
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)])})
|
||||
stock_picking = None
|
||||
pc_picking = self.env['stock.picking'].search(
|
||||
[('origin', '=', productions.name), ('name', 'ilike', 'WH/PC/')])
|
||||
stock_picking = pc_picking
|
||||
int_picking = self.env['stock.picking'].search(
|
||||
[('origin', '=', productions.name), ('name', 'ilike', 'WH/INT/')])
|
||||
stock_picking |= int_picking
|
||||
for pick in stock_picking:
|
||||
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
|
||||
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)])
|
||||
@@ -1111,7 +1107,6 @@ class MrpProduction(models.Model):
|
||||
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.origin.find(productions.name))
|
||||
if purchase_orders.origin.find(productions.name) == -1:
|
||||
purchase_orders.origin += ',' + productions.name
|
||||
if item['is_reprogramming'] is 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,14 +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)
|
||||
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')
|
||||
@@ -127,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'
|
||||
|
||||
@@ -222,7 +222,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
material_width = fields.Float(string='宽')
|
||||
material_height = fields.Float(string='高')
|
||||
# 零件图号
|
||||
part_number = fields.Char(string='零件图号')
|
||||
part_number = fields.Char(related='production_id.part_number', string='零件图号')
|
||||
# 工序状态
|
||||
process_state = fields.Selection([
|
||||
('待装夹', '待装夹'),
|
||||
@@ -414,12 +414,10 @@ class ResMrpWorkOrder(models.Model):
|
||||
|
||||
# 获取三次元检测点数据
|
||||
def get_three_check_datas(self):
|
||||
factory_nick_name = 'XT'
|
||||
ftp_resconfig = self.env['res.config.settings'].get_values()
|
||||
ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']),
|
||||
ftp_resconfig['ftp_user'],
|
||||
ftp_resconfig['ftp_password'])
|
||||
# ftp.connect()
|
||||
|
||||
local_dir_path = '/ftp/before'
|
||||
os.makedirs(local_dir_path, exist_ok=True)
|
||||
@@ -428,6 +426,17 @@ class ResMrpWorkOrder(models.Model):
|
||||
logging.info('local_file_path:%s' % local_file_path)
|
||||
remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename
|
||||
logging.info('remote_path:%s' % remote_path)
|
||||
# if not ftp.file_exists(remote_path):
|
||||
paload_data = {
|
||||
"filename": local_filename
|
||||
}
|
||||
if not ftp_resconfig['get_check_file_path']:
|
||||
raise UserError('请先配置获取检测报告地址')
|
||||
url = ftp_resconfig['get_check_file_path'] + '/get/check/report'
|
||||
response = requests.post(url, json=paload_data)
|
||||
logging.info('response:%s' % response.json())
|
||||
if response.json().get('detail'):
|
||||
raise UserError(response.json().get('detail'))
|
||||
|
||||
if not ftp.file_exists(remote_path):
|
||||
raise UserError(f"文件不存在: {remote_path}")
|
||||
@@ -540,6 +549,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
raise UserError('PT10点未测或数据错误')
|
||||
|
||||
self.data_state = True
|
||||
self.getcenter()
|
||||
|
||||
return True
|
||||
|
||||
@@ -1100,7 +1110,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()
|
||||
@@ -1169,7 +1179,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
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:
|
||||
if not record.material_center_point or record.X_deviation_angle <= 0:
|
||||
raise UserError("坯料中心点为空或X偏差角度小于等于0")
|
||||
record.process_state = '待加工'
|
||||
# record.write({'process_state': '待加工'})
|
||||
@@ -1340,7 +1350,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}'
|
||||
})
|
||||
@@ -1351,18 +1361,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',
|
||||
@@ -1371,11 +1386,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,
|
||||
}}
|
||||
|
||||
|
||||
|
||||
@@ -267,6 +267,10 @@ class StockRule(models.Model):
|
||||
workorder_duration += workorder.duration_expected
|
||||
|
||||
sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)])
|
||||
# 根据销售订单号查询快速订单
|
||||
quick_easy_order = self.env['quick.easy.order'].sudo().search([('sale_order_id', '=', sale_order.id)])
|
||||
production.write({'part_number': quick_easy_order.part_drawing_number,
|
||||
'part_drawing': quick_easy_order.machining_drawings})
|
||||
if sale_order:
|
||||
# sale_order.write({'schedule_status': 'to schedule'})
|
||||
self.env['sf.production.plan'].sudo().with_company(company_id).create({
|
||||
@@ -288,6 +292,7 @@ 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)],
|
||||
@@ -574,6 +579,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))
|
||||
@@ -659,6 +665,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,
|
||||
@@ -667,7 +678,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',
|
||||
|
||||
@@ -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 },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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)]}"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<field name="date_planned_start" string="计划开始日期" optional="show"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_planned_start']" position="before">
|
||||
<field name="reserved_duration" string="计划预留时间" optional="show"/>
|
||||
<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"/>
|
||||
@@ -513,6 +513,11 @@
|
||||
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//form//header" position="inside">
|
||||
<button type="object" class="oe_highlight" name="get_three_check_datas" string="获取数据"
|
||||
attrs='{"invisible": [("state","!=","progress")]}'/>
|
||||
</xpath>
|
||||
|
||||
|
||||
<xpath expr="//page[1]" position="before">
|
||||
<field name="results" invisible="1"/>
|
||||
@@ -659,9 +664,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 == '已配送'"
|
||||
|
||||
@@ -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,
|
||||
@@ -129,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 = []
|
||||
@@ -180,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(
|
||||
@@ -198,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
|
||||
|
||||
|
||||
|
||||
1
sf_message/__init__.py
Normal file
1
sf_message/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
25
sf_message/__manifest__.py
Normal file
25
sf_message/__manifest__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': '机企猫智能工厂 消息提醒',
|
||||
'version': '1.0',
|
||||
'summary': '智能工厂消息提醒模块',
|
||||
'sequence': 1,
|
||||
'description': """
|
||||
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['base', 'sf_base'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/sf_message_template_view.xml',
|
||||
],
|
||||
'test': [
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': False,
|
||||
}
|
||||
1
sf_message/models/__init__.py
Normal file
1
sf_message/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import sf_message_template
|
||||
48
sf_message/models/sf_message_template.py
Normal file
48
sf_message/models/sf_message_template.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class SfMessageTemplate(models.Model):
|
||||
_name = "sf.message.template"
|
||||
_description = u'消息模板'
|
||||
|
||||
name = fields.Char(string=u"名称", required=True)
|
||||
type = fields.Selection([
|
||||
('待接单', '待接单'),
|
||||
('待排程', '待排程'),
|
||||
('坯料采购', '坯料采购'),
|
||||
('坯料发料', '坯料发料'),
|
||||
('待编程', '待编程'),
|
||||
('调拨入库', '调拨入库'),
|
||||
('功能刀具组装', '功能刀具组装'),
|
||||
('功能刀具寿命到期', '功能刀具寿命到期'),
|
||||
('程序用刀计划异常', '程序用刀计划异常'),
|
||||
('工单无CNC程序', '工单无CNC程序'),
|
||||
('生产线无功能刀具', '生产线无功能刀具'),
|
||||
('工单无定位数据', '工单无定位数据'),
|
||||
('工单FTP无文件', '工单FTP无文件'),
|
||||
('工单加工失败', '工单加工失败'),
|
||||
('设备故障及异常', '设备故障及异常'),
|
||||
('工单逾期预警', '工单逾期预警'),
|
||||
('工单已逾期', '工单已逾期'),
|
||||
('销售订单逾期', '销售订单逾期'),
|
||||
('销售订单已逾期', '销售订单已逾期'),
|
||||
('待质量判定', '待质量判定'),
|
||||
('生产完工待入库', '生产完工待入库'),
|
||||
('订单发货', '订单发货')
|
||||
], string='类型', required=True)
|
||||
description = fields.Char(string=u"描述")
|
||||
content = fields.Html(string=u"内容", render_engine='qweb', translate=True, prefetch=True, sanitize=False)
|
||||
msgtype = fields.Selection(
|
||||
[('text', u'文字'), ('markdown', u'Markdown')], u'消息类型',
|
||||
required=True, default='markdown')
|
||||
notification_department_id = fields.Many2one('hr.department', u'通知部门', required=True)
|
||||
notification_employee_ids = fields.Many2many('hr.employee', string=u'员工',
|
||||
domain="[('department_id', '=',notification_department_id)]",
|
||||
required=True)
|
||||
active = fields.Boolean(string=u"是否有效", default=True)
|
||||
|
||||
@api.onchange('notification_department_id')
|
||||
def _clear_employee_ids(self):
|
||||
if self.notification_department_id:
|
||||
self.notification_employee_ids = False
|
||||
5
sf_message/security/group_security.xml
Normal file
5
sf_message/security/group_security.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
15
sf_message/security/ir.model.access.csv
Normal file
15
sf_message/security/ir.model.access.csv
Normal file
@@ -0,0 +1,15 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sf_message_template_group_sale_salemanager,sf_message_template,model_sf_message_template,sf_base.group_sale_salemanager,1,1,1,0
|
||||
access_sf_message_template_group_purchase,sf_message_template,model_sf_message_template,sf_base.group_purchase,1,1,1,0
|
||||
access_sf_message_template_group_sf_stock_user,sf_message_template,model_sf_message_template,sf_base.group_sf_stock_user,1,1,1,0
|
||||
access_sf_message_template_group_sf_order_user,sf_message_template,model_sf_message_template,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_sf_message_template_group_sf_tool_user,sf_message_template,model_sf_message_template,sf_base.group_sf_tool_user,1,1,1,0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
77
sf_message/views/sf_message_template_view.xml
Normal file
77
sf_message/views/sf_message_template_view.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © <2016> <top hy>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="sf_message_template_view_form" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.view.form</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="消息模板">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1>
|
||||
<field name="name" class="w-100" required="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="type"/>
|
||||
<field name="content" widget="html" class="oe-bordered-editor"
|
||||
options="{'style-inline': true, 'codeview': true, 'dynamic_placeholder': true}"/>
|
||||
<field name="description"/>
|
||||
<field name="msgtype"/>
|
||||
<field name="type"/>
|
||||
<field name="notification_department_id"/>
|
||||
<field name="notification_employee_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_message_template_view_tree" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.view.tree</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="消息模板">
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="content"/>
|
||||
<field name="msgtype"/>
|
||||
<field name="type"/>
|
||||
<field name="notification_department_id"/>
|
||||
<field name="notification_employee_ids" widget="many2many_tags"/>
|
||||
<field name="description"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_message_template_search_view" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.search.view</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="模糊搜索"
|
||||
filter_domain="['|','|',('name','like',self),('type','like',self),('description','like',self)]"/>
|
||||
<field name="name"/>
|
||||
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--定义单证类型视图动作-->
|
||||
<record id="sf_message_template_action" model="ir.actions.act_window">
|
||||
<field name="name">消息模板</field>
|
||||
<field name="res_model">sf.message.template</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="sf_message_template_view_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="msg_set_menu" name="消息设置" parent="base.menu_administration" sequence="1"/>
|
||||
<menuitem id="sf_message_template_send_menu" name="消息模板" parent="msg_set_menu"
|
||||
action="sf_message_template_action" sequence="1"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -18,7 +18,8 @@ class sf_production_plan(models.Model):
|
||||
('draft', '待排程'),
|
||||
('done', '已排程'),
|
||||
('processing', '加工中'),
|
||||
('finished', '已完成')
|
||||
('finished', '已完成'),
|
||||
('cancel', '已取消')
|
||||
], string='状态', tracking=True)
|
||||
|
||||
state_order = fields.Integer(compute='_compute_state_order', store=True)
|
||||
@@ -37,7 +38,7 @@ class sf_production_plan(models.Model):
|
||||
_order = 'state_order asc, write_date desc'
|
||||
|
||||
name = fields.Char(string='制造订单')
|
||||
# active = fields.Boolean(string='已归档', default=True)
|
||||
active = fields.Boolean(string='已归档', default=True)
|
||||
# selected = fields.Boolean(default=False)
|
||||
# order_number = fields.Char(string='订单号')
|
||||
order_deadline = fields.Datetime(string='订单交期')
|
||||
@@ -191,7 +192,7 @@ class sf_production_plan(models.Model):
|
||||
|
||||
return num
|
||||
|
||||
def do_production_schedule(self):
|
||||
def do_production_schedule(self, date_planned_start):
|
||||
"""
|
||||
排程方法
|
||||
"""
|
||||
@@ -199,6 +200,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 +254,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工单的时间去计算之前的其他工单的开始结束时间
|
||||
@@ -361,3 +386,23 @@ class machine_work_schedule(models.Model):
|
||||
_description = '机台作业计划'
|
||||
|
||||
name = fields.Char(string='机台名')
|
||||
|
||||
|
||||
class MrpProductionInheritForPlan(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
def button_cancel(self):
|
||||
# 调用父类的取消操作
|
||||
res = super(MrpProductionInheritForPlan, self).button_cancel()
|
||||
# 更新 sf.production.plan 模型的 state 为 'cancel'
|
||||
self.env['sf.production.plan'].search([('production_id', '=', self.id)]).write({'state': 'cancel'})
|
||||
return res
|
||||
|
||||
def toggle_active(self):
|
||||
# 调用父类方法切换 active 状态
|
||||
res = super(MrpProductionInheritForPlan, self).toggle_active()
|
||||
self.env['sf.production.plan'].search(
|
||||
[('production_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)]).write(
|
||||
{'active': self.active})
|
||||
|
||||
return res
|
||||
|
||||
BIN
sf_plan/static/description/计划.png
Normal file
BIN
sf_plan/static/description/计划.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 673 B |
@@ -278,6 +278,7 @@
|
||||
sequence="150"
|
||||
action="sf_production_plan_action"
|
||||
groups="sf_base.group_plan_dispatch"
|
||||
web_icon="sf_plan,static/description/计划.png"
|
||||
/>
|
||||
|
||||
<!-- <record model="ir.ui.menu" id="mrp_custom_menu" inherit_id="mrp.menu_mrp_manufacturing"> -->
|
||||
@@ -291,6 +292,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">
|
||||
<!-- 自定义额外的动作 -->
|
||||
|
||||
@@ -36,7 +36,7 @@ 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.date_planned_start = self.date_planned_start
|
||||
plan_obj.do_production_schedule()
|
||||
plan_obj.do_production_schedule(self.date_planned_start)
|
||||
# plan_obj.state = 'done'
|
||||
print('处理计划:', plan.id, '完成')
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -184,5 +184,10 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="purchase.product_normal_action_puchased" model="ir.actions.act_window">
|
||||
<field name="context">
|
||||
{"search_default_categ_id":1,"search_default_filter_to_purchase":1, "purchase_product_template": 1}
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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="pdf_viewer"/>
|
||||
<field name="sale_order_id"
|
||||
attrs='{"invisible": [("sale_order_id","=",False)],"readonly": [("sale_order_id","!=",False)]}'/>
|
||||
</group>
|
||||
|
||||
@@ -100,8 +100,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<field name="property_supplier_payment_term_id" position="before">
|
||||
<field name="purchase_user_id" context="{'supplier_rank': supplier_rank }"
|
||||
widget="many2one_avatar_user"
|
||||
attrs="{'required' : [('supplier_rank','>', 0)]}"/>
|
||||
widget="many2one_avatar_user"/>
|
||||
</field>
|
||||
<xpath expr="//field[@name='property_account_position_id']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('id','!=', False)]}</attribute>
|
||||
|
||||
@@ -225,5 +225,10 @@
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record id="sale.product_template_action" model="ir.actions.act_window">
|
||||
<field name="context">{"search_default_categ_id":1,
|
||||
"search_default_filter_to_sell":1,"sale_multi_pricelist_product_template": 1}
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
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
|
||||
36
sf_stock/__manifest__.py
Normal file
36
sf_stock/__manifest__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# -*- 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',
|
||||
'views/stock_product_template.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
|
||||
108
sf_stock/models/stock_picking.py
Normal file
108
sf_stock/models/stock_picking.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# -*- 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'
|
||||
|
||||
# 重写验证,下发发货到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
|
||||
|
6
sf_stock/views/stock_picking.xml
Normal file
6
sf_stock/views/stock_picking.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
11
sf_stock/views/stock_product_template.xml
Normal file
11
sf_stock/views/stock_product_template.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="stock.product_template_action_product" model="ir.actions.act_window">
|
||||
<field name="context">
|
||||
{"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</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': [
|
||||
],
|
||||
|
||||
@@ -122,7 +122,8 @@ class Manufacturing_Connect(http.Controller):
|
||||
tool_assembly.write({
|
||||
'after_assembly_tool_loading_length': float(data_list[1] or "0"), # 高度(总长度)
|
||||
'after_assembly_functional_tool_diameter': float(data_list[2] or "0") * 2, # 直径
|
||||
'after_assembly_knife_tip_r_angle': float(data_list[3] or "0") # R角
|
||||
'after_assembly_knife_tip_r_angle': float(data_list[3] or "0"), # R角
|
||||
'bool_preset_parameter': True
|
||||
})
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
|
||||
@@ -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
|
||||
@@ -349,25 +349,100 @@ class CAMWorkOrderProgramKnifePlan(models.Model):
|
||||
|
||||
class FunctionalToolAssembly(models.Model):
|
||||
_name = 'sf.functional.tool.assembly'
|
||||
_inherit = ['mail.thread']
|
||||
_inherit = ['mail.thread', 'barcodes.barcode_events_mixin']
|
||||
_description = '功能刀具组装'
|
||||
_order = 'assemble_status, use_tool_time asc'
|
||||
_order = 'tool_loading_time desc, use_tool_time asc'
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
"""
|
||||
智能工厂组装单处扫码校验刀具物料
|
||||
"""
|
||||
for record in self:
|
||||
tool_assembly_id = self.env['sf.functional.tool.assembly'].browse(self.ids)
|
||||
lot_ids = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)])
|
||||
if lot_ids:
|
||||
for lot_id in lot_ids:
|
||||
if lot_id.tool_material_status != '可用':
|
||||
raise ValidationError(f'Rfid为【{barcode}】的刀柄已被占用,请重新扫描!!')
|
||||
if lot_id.product_id == record.handle_product_id:
|
||||
record.handle_code_id = lot_id.id
|
||||
tool_assembly_id.handle_code_id = lot_id.id
|
||||
else:
|
||||
raise ValidationError('刀柄选择错误,请重新确认!!!')
|
||||
else:
|
||||
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', barcode)])
|
||||
if location:
|
||||
if location == record.integral_freight_barcode_id:
|
||||
tool_assembly_id.integral_verify = True
|
||||
record.integral_verify = True
|
||||
elif location == record.blade_freight_barcode_id:
|
||||
tool_assembly_id.blade_verify = True
|
||||
record.blade_verify = True
|
||||
elif location == record.bar_freight_barcode_id:
|
||||
tool_assembly_id.bar_verify = True
|
||||
record.bar_verify = True
|
||||
elif location == record.pad_freight_barcode_id:
|
||||
tool_assembly_id.pad_verify = True
|
||||
record.pad_verify = True
|
||||
elif location == record.chuck_freight_barcode_id:
|
||||
tool_assembly_id.chuck_verify = True
|
||||
record.chuck_verify = True
|
||||
else:
|
||||
raise ValidationError('刀具选择错误,请重新确认!')
|
||||
else:
|
||||
raise ValidationError(f'扫描为【{barcode}】的货位不存在,请重新扫描!')
|
||||
|
||||
@api.depends('functional_tool_name')
|
||||
def _compute_name(self):
|
||||
for obj in self:
|
||||
obj.name = obj.assembly_order_code
|
||||
|
||||
code = fields.Char('功能刀具编码', readonly=True)
|
||||
rfid = fields.Char('Rfid', readonly=True)
|
||||
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', readonly=True)
|
||||
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', store=True, compute='_compute_inventory_num')
|
||||
name = fields.Char(string='名称', readonly=True, compute='_compute_name')
|
||||
assembly_order_code = fields.Char(string='组装单编码', readonly=True)
|
||||
|
||||
functional_tool_name_id = fields.Many2one('product.product', string='功能刀具', readonly=True)
|
||||
functional_tool_name = fields.Char(string='功能刀具名称', readonly=True)
|
||||
functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型', readonly=True,
|
||||
group_expand='_read_group_functional_tool_type_ids')
|
||||
functional_tool_name = fields.Char(string='功能刀具名称', readonly=True, required=True)
|
||||
tool_inventory_id = fields.Many2one('sf.tool.inventory', string='功能刀具清单', store=True,
|
||||
compute='_compute_functional_tool_name')
|
||||
|
||||
@api.depends('functional_tool_name')
|
||||
def _compute_functional_tool_name(self):
|
||||
for item in self:
|
||||
if item.functional_tool_name:
|
||||
inventory = self.env['sf.tool.inventory'].sudo().search([('name', '=', item.functional_tool_name)])
|
||||
if inventory:
|
||||
item.tool_inventory_id = inventory.id
|
||||
item.after_assembly_functional_tool_name = item.functional_tool_name # 组装后名称
|
||||
item.functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
|
||||
item.tool_groups_id = item.tool_inventory_id.tool_groups_id.id # 刀具组
|
||||
item.after_assembly_functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
|
||||
item.after_assembly_functional_tool_diameter = item.tool_inventory_id.diameter # 直径
|
||||
item.after_assembly_knife_tip_r_angle = item.tool_inventory_id.angle # R角
|
||||
item.after_assembly_tool_loading_length = item.tool_inventory_id.tool_length # 总长度
|
||||
item.after_assembly_functional_tool_length = item.tool_inventory_id.extension # 伸出长度
|
||||
item.after_assembly_max_lifetime_value = item.tool_inventory_id.life_span # 最大寿命
|
||||
|
||||
@api.depends('tool_inventory_id', 'tool_inventory_id.functional_cutting_tool_model_id', 'tool_inventory_id.angle',
|
||||
'tool_inventory_id.tool_groups_id', 'tool_inventory_id.diameter', 'tool_inventory_id.tool_length',
|
||||
'tool_inventory_id.extension', 'tool_inventory_id.life_span')
|
||||
def _compute_inventory_num(self):
|
||||
for item in self:
|
||||
if item.assemble_status != '01':
|
||||
return True
|
||||
if item.tool_inventory_id:
|
||||
item.functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
|
||||
item.tool_groups_id = item.tool_inventory_id.tool_groups_id.id # 刀具组
|
||||
item.after_assembly_functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
|
||||
item.after_assembly_functional_tool_diameter = item.tool_inventory_id.diameter # 直径
|
||||
item.after_assembly_knife_tip_r_angle = item.tool_inventory_id.angle # R角
|
||||
item.after_assembly_tool_loading_length = item.tool_inventory_id.tool_length # 总长度
|
||||
item.after_assembly_max_lifetime_value = item.tool_inventory_id.life_span # 最大寿命
|
||||
|
||||
functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型',
|
||||
group_expand='_read_group_functional_tool_type_ids', store=True,
|
||||
compute='_compute_inventory_num')
|
||||
functional_tool_diameter = fields.Float(string='功能刀具直径(mm)', readonly=True)
|
||||
knife_tip_r_angle = fields.Float(string='刀尖R角(mm)', readonly=True)
|
||||
coarse_middle_thin = fields.Selection([("1", "粗"), ('2', '中'), ('3', '精')], string='粗/中/精', readonly=True)
|
||||
@@ -375,16 +450,17 @@ class FunctionalToolAssembly(models.Model):
|
||||
tool_loading_length = fields.Float(string='总长度(mm)', readonly=True)
|
||||
functional_tool_length = fields.Float(string='伸出长(mm)', readonly=True)
|
||||
effective_length = fields.Float(string='有效长(mm)', readonly=True)
|
||||
loading_task_source = fields.Selection([('0', 'CAM装刀'), ('1', '机台换刀'), ('2', '按库存组装')],
|
||||
string='装刀任务来源', readonly=True)
|
||||
loading_task_source = fields.Selection(
|
||||
[('0', 'CAM装刀'), ('1', '机台换刀'), ('2', '按库存组装'), ('3', '寿命到期组装')],
|
||||
string='装刀任务来源', readonly=True)
|
||||
use_tool_time = fields.Datetime(string='用刀时间', readonly=True)
|
||||
production_line_name_id = fields.Many2one('sf.production.line', string='申请产线', readonly=True)
|
||||
machine_tool_name_id = fields.Many2one('maintenance.equipment', string='申请机台', readonly=True)
|
||||
machine_tool_code = fields.Char(string='机台号', readonly=True)
|
||||
applicant = fields.Char(string='申请人', readonly=True)
|
||||
apply_time = fields.Datetime(string='申请时间', default=fields.Datetime.now(), readonly=True)
|
||||
assemble_status = fields.Selection([('0', '待组装'), ('1', '已组装')], string='组装状态', default='0',
|
||||
tracking=True, readonly=True)
|
||||
assemble_status = fields.Selection([('0', '待组装'), ('01', '组装中'), ('1', '已组装'), ('3', '已取消')],
|
||||
string='组装状态', default='0', tracking=True, readonly=True)
|
||||
cutter_spacing_code_id = fields.Many2one('maintenance.equipment.tool', string='刀位号', readonly=True)
|
||||
whether_standard_knife = fields.Boolean(string='是否标准刀', default=True, readonly=True)
|
||||
reason_for_applying = fields.Char(string='申请原因', readonly=True)
|
||||
@@ -392,7 +468,7 @@ class FunctionalToolAssembly(models.Model):
|
||||
alarm_value = fields.Integer(string='报警值(min)', readonly=True)
|
||||
used_value = fields.Integer(string='已使用值(min)', readonly=True)
|
||||
|
||||
image = fields.Binary('图片', readonly=True)
|
||||
image = fields.Binary('图片', readonly=False)
|
||||
|
||||
@api.model
|
||||
def _read_group_functional_tool_type_ids(self, categories, domain, order):
|
||||
@@ -402,8 +478,9 @@ class FunctionalToolAssembly(models.Model):
|
||||
|
||||
# 刀具物料信息
|
||||
# ==============整体式刀具型号=============
|
||||
integral_freight_barcode_id = fields.Many2one('sf.shelf.location', string='整体式刀具货位')
|
||||
integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次')
|
||||
integral_freight_barcode_id = fields.Many2one('sf.shelf.location', string='整体式刀具货位', readonly=True,
|
||||
domain="[('product_id.cutting_tool_material_id.name', '=', '整体式刀具'),('product_num', '>', 0)]")
|
||||
integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次', readonly=True)
|
||||
integral_product_id = fields.Many2one('product.product', string='整体式刀具名称',
|
||||
compute='_compute_integral_product_id', store=True)
|
||||
cutting_tool_integral_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='整体式刀具型号',
|
||||
@@ -412,16 +489,20 @@ class FunctionalToolAssembly(models.Model):
|
||||
related='integral_product_id.specification_id')
|
||||
sf_tool_brand_id_1 = fields.Many2one('sf.machine.brand', string='整体式刀具品牌',
|
||||
related='integral_product_id.brand_id')
|
||||
integral_verify = fields.Boolean('整体刀校验', default=False)
|
||||
|
||||
@api.depends('integral_lot_id')
|
||||
def _compute_integral_product_id(self):
|
||||
for item in self:
|
||||
if item.integral_lot_id:
|
||||
item.integral_product_id = item.integral_lot_id.product_id.id
|
||||
else:
|
||||
item.integral_product_id = False
|
||||
|
||||
# =================刀片型号=============
|
||||
blade_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀片货位')
|
||||
blade_lot_id = fields.Many2one('stock.lot', string='刀片批次')
|
||||
blade_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀片货位', readonly=True,
|
||||
domain="[('product_id.cutting_tool_material_id.name', '=', '刀片'),('product_num', '>', 0)]")
|
||||
blade_lot_id = fields.Many2one('stock.lot', string='刀片批次', readonly=True)
|
||||
blade_product_id = fields.Many2one('product.product', string='刀片名称', compute='_compute_blade_product_id',
|
||||
store=True)
|
||||
cutting_tool_blade_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀片型号',
|
||||
@@ -429,16 +510,20 @@ class FunctionalToolAssembly(models.Model):
|
||||
blade_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀片规格',
|
||||
related='blade_product_id.specification_id')
|
||||
sf_tool_brand_id_2 = fields.Many2one('sf.machine.brand', '刀片品牌', related='blade_product_id.brand_id')
|
||||
blade_verify = fields.Boolean('刀片校验', default=False)
|
||||
|
||||
@api.depends('blade_lot_id')
|
||||
def _compute_blade_product_id(self):
|
||||
for item in self:
|
||||
if item.blade_lot_id:
|
||||
item.blade_product_id = item.blade_lot_id.product_id.id
|
||||
else:
|
||||
item.blade_product_id = False
|
||||
|
||||
# ==============刀杆型号================
|
||||
bar_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀杆货位')
|
||||
bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次')
|
||||
bar_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀杆货位', readonly=True,
|
||||
domain="[('product_id.cutting_tool_material_id.name', '=', '刀杆'),('product_num', '>', 0)]")
|
||||
bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次', readonly=True)
|
||||
bar_product_id = fields.Many2one('product.product', string='刀杆名称', compute='_compute_bar_product_id',
|
||||
store=True)
|
||||
cutting_tool_cutterbar_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀杆型号',
|
||||
@@ -446,16 +531,20 @@ class FunctionalToolAssembly(models.Model):
|
||||
bar_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀杆规格',
|
||||
related='bar_product_id.specification_id')
|
||||
sf_tool_brand_id_3 = fields.Many2one('sf.machine.brand', '刀杆品牌', related='bar_product_id.brand_id')
|
||||
bar_verify = fields.Boolean('刀杆校验', default=False)
|
||||
|
||||
@api.depends('bar_lot_id')
|
||||
def _compute_bar_product_id(self):
|
||||
for item in self:
|
||||
if item.bar_lot_id:
|
||||
item.bar_product_id = item.bar_lot_id.product_id.id
|
||||
else:
|
||||
item.bar_product_id = False
|
||||
|
||||
# =============刀盘型号================
|
||||
pad_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀盘货位')
|
||||
pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次')
|
||||
pad_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀盘货位', readonly=True,
|
||||
domain="[('product_id.cutting_tool_material_id.name', '=', '刀盘'),('product_num', '>', 0)]")
|
||||
pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次', readonly=True)
|
||||
pad_product_id = fields.Many2one('product.product', string='刀盘名称', compute='_compute_pad_product_id',
|
||||
store=True)
|
||||
cutting_tool_cutterpad_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀盘型号',
|
||||
@@ -463,37 +552,42 @@ class FunctionalToolAssembly(models.Model):
|
||||
pad_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀盘规格',
|
||||
related='pad_product_id.specification_id')
|
||||
sf_tool_brand_id_4 = fields.Many2one('sf.machine.brand', '刀盘品牌', related='pad_product_id.brand_id')
|
||||
pad_verify = fields.Boolean('刀盘校验', default=False)
|
||||
|
||||
@api.depends('pad_lot_id')
|
||||
def _compute_pad_product_id(self):
|
||||
for item in self:
|
||||
if item.pad_lot_id:
|
||||
item.pad_product_id = item.pad_lot_id.product_id.id
|
||||
else:
|
||||
item.pad_product_id = False
|
||||
|
||||
# ==============刀柄型号==============
|
||||
handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_handle_product_id', store=True)
|
||||
handle_code_id = fields.Many2one('stock.lot', '刀柄序列号')
|
||||
handle_product_id = fields.Many2one('product.product', string='刀柄名称', compute='_compute_handle_product_id',
|
||||
store=True)
|
||||
handle_code_id = fields.Many2one('stock.lot', '刀柄序列号', readonly=True,
|
||||
domain="[('product_id', '=', handle_product_id)]")
|
||||
handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_handle_rfid', store=True)
|
||||
handle_product_id = fields.Many2one('product.product', string='刀柄名称', readonly=True)
|
||||
cutting_tool_cutterhandle_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀柄型号',
|
||||
related='handle_product_id.cutting_tool_model_id')
|
||||
handle_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀柄规格',
|
||||
related='handle_product_id.specification_id')
|
||||
sf_tool_brand_id_5 = fields.Many2one('sf.machine.brand', '刀柄品牌', related='handle_product_id.brand_id')
|
||||
handle_verify = fields.Boolean('刀柄校验', default=False)
|
||||
|
||||
@api.depends('handle_code_id')
|
||||
def _compute_handle_product_id(self):
|
||||
def _compute_handle_rfid(self):
|
||||
for item in self:
|
||||
if item.handle_code_id:
|
||||
item.handle_product_id = item.handle_code_id.product_id.id
|
||||
item.handle_freight_rfid = item.handle_code_id.rfid
|
||||
item.rfid = item.handle_freight_rfid
|
||||
else:
|
||||
item.handle_product_id = False
|
||||
item.handle_freight_rfid = False
|
||||
item.rfid = False
|
||||
|
||||
# ==============夹头型号==============
|
||||
chuck_freight_barcode_id = fields.Many2one('sf.shelf.location', string='夹头货位')
|
||||
chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次')
|
||||
chuck_freight_barcode_id = fields.Many2one('sf.shelf.location', string='夹头货位', readonly=True,
|
||||
domain="[('product_id.cutting_tool_material_id.name', '=', '夹头'),('product_num', '>', 0)]")
|
||||
chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次', readonly=True)
|
||||
chuck_product_id = fields.Many2one('product.product', string='夹头名称', compute='_compute_chuck_product_id',
|
||||
store=True)
|
||||
cutting_tool_cutterhead_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='夹头型号',
|
||||
@@ -501,12 +595,15 @@ class FunctionalToolAssembly(models.Model):
|
||||
chuck_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='夹头规格',
|
||||
related='chuck_product_id.specification_id')
|
||||
sf_tool_brand_id_6 = fields.Many2one('sf.machine.brand', '夹头品牌', related='chuck_product_id.brand_id')
|
||||
chuck_verify = fields.Boolean('夹头校验', default=False)
|
||||
|
||||
@api.depends('chuck_lot_id')
|
||||
def _compute_chuck_product_id(self):
|
||||
for item in self:
|
||||
if item.chuck_lot_id:
|
||||
item.chuck_product_id = item.chuck_lot_id.product_id.id
|
||||
else:
|
||||
item.chuck_product_id = False
|
||||
|
||||
# ==================待删除字段==================
|
||||
integral_freight_barcode = fields.Char('整体式刀具货位')
|
||||
@@ -525,30 +622,39 @@ class FunctionalToolAssembly(models.Model):
|
||||
handle_name = fields.Char('')
|
||||
chuck_code_id = fields.Many2one('stock.lot', '夹头序列号')
|
||||
chuck_name = fields.Char('')
|
||||
# ====================暂时无用字段=========================
|
||||
after_assembly_used_value = fields.Integer(string='组装后已使用值(min)', readonly=True)
|
||||
# ==============================================
|
||||
# 组装功能刀具参数信息
|
||||
start_preset_bool = fields.Boolean('开始预调', default=False)
|
||||
barcode_id = fields.Many2one('stock.lot', string='功能刀具序列号', readonly=True)
|
||||
after_assembly_functional_tool_name = fields.Char(string='组装后功能刀具名称', readonly=True)
|
||||
after_assembly_functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model',
|
||||
string='组装后功能刀具类型', readonly=True)
|
||||
after_assembly_functional_tool_diameter = fields.Float(string='组装后功能刀具直径(mm)', readonly=True,
|
||||
digits=(10, 3))
|
||||
after_assembly_knife_tip_r_angle = fields.Float(string='组装后刀尖R角(mm)', readonly=True, digits=(10, 3))
|
||||
after_assembly_new_former = fields.Selection([('0', '新'), ('1', '旧')], string='组装后新/旧', readonly=True)
|
||||
after_assembly_functional_tool_name = fields.Char(string='组装后功能刀具名称', store=True,
|
||||
compute='_compute_functional_tool_name')
|
||||
after_assembly_functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', store=True,
|
||||
string='组装后功能刀具类型',
|
||||
compute='_compute_inventory_num')
|
||||
after_assembly_functional_tool_diameter = fields.Float(string='组装后功能刀具直径(mm)', digits=(10, 3), store=True,
|
||||
compute='_compute_inventory_num')
|
||||
after_assembly_knife_tip_r_angle = fields.Float(string='组装后刀尖R角(mm)', digits=(10, 3), store=True,
|
||||
compute='_compute_inventory_num')
|
||||
after_assembly_new_former = fields.Selection([('0', '新'), ('1', '旧')], string='组装后新/旧', default='0',
|
||||
store=True, compute='_compute_rota_tive')
|
||||
cut_time = fields.Integer(string='已切削时间(min)', readonly=True)
|
||||
cut_length = fields.Float(string='已切削长度(mm)', readonly=True)
|
||||
cut_number = fields.Integer(string='已切削次数', readonly=True)
|
||||
|
||||
after_assembly_whether_standard_knife = fields.Boolean(string='组装后是否标准刀', default=True, readonly=True)
|
||||
after_assembly_coarse_middle_thin = fields.Selection([("1", "粗"), ('2', '中'), ('3', '精')],
|
||||
string='组装后粗/中/精', readonly=True)
|
||||
after_assembly_max_lifetime_value = fields.Integer(string='组装后最大寿命值(min)', readonly=True)
|
||||
after_assembly_alarm_value = fields.Integer(string='组装后报警值(min)', readonly=True)
|
||||
after_assembly_used_value = fields.Integer(string='组装后已使用值(min)', readonly=True)
|
||||
after_assembly_tool_loading_length = fields.Float(string='组装后总长度(mm)', readonly=True, digits=(10, 3))
|
||||
after_assembly_handle_length = fields.Float(string='组装后刀柄长度(mm)', readonly=True, digits=(10, 3))
|
||||
after_assembly_functional_tool_length = fields.Float(string='组装后伸出长(mm)', readonly=True, digits=(10, 3))
|
||||
after_assembly_coarse_middle_thin = fields.Selection([("1", "粗"), ('2', '中'), ('3', '精')], store=True,
|
||||
string='组装后粗/中/精', default='3',
|
||||
compute='_compute_rota_tive')
|
||||
after_assembly_max_lifetime_value = fields.Integer(string='组装后最大寿命值(min)', store=True,
|
||||
compute='_compute_inventory_num')
|
||||
after_assembly_alarm_value = fields.Integer(string='组装后报警值(min)')
|
||||
after_assembly_tool_loading_length = fields.Float(string='组装后总长度(mm)', digits=(10, 3), store=True,
|
||||
compute='_compute_inventory_num')
|
||||
after_assembly_handle_length = fields.Float(string='组装后刀柄长度(mm)', digits=(10, 3))
|
||||
after_assembly_functional_tool_length = fields.Float(string='组装后伸出长(mm)', digits=(10, 3), store=True,
|
||||
compute='_compute_length')
|
||||
after_assembly_effective_length = fields.Float(string='组装后有效长(mm)', readonly=True)
|
||||
L_D_number = fields.Float(string='L/D值(mm)', readonly=True)
|
||||
hiding_length = fields.Float(string='避空长(mm)', readonly=True)
|
||||
@@ -562,10 +668,74 @@ class FunctionalToolAssembly(models.Model):
|
||||
sf_machine_table_tool_changing_apply_id = fields.Many2one('sf.machine.table.tool.changing.apply', '机床换刀申请',
|
||||
readonly=True)
|
||||
sf_cam_work_order_program_knife_plan_id = fields.Many2one('sf.cam.work.order.program.knife.plan',
|
||||
'CAM工单程序用刀计划', readonly=True, )
|
||||
'CAM工单程序用刀计划', readonly=True)
|
||||
|
||||
active = fields.Boolean(string='已归档', default=True)
|
||||
|
||||
code = fields.Char('功能刀具编码', compute='_compute_code')
|
||||
|
||||
@api.depends('after_assembly_tool_loading_length', 'after_assembly_handle_length')
|
||||
def _compute_length(self):
|
||||
for item in self:
|
||||
if item.after_assembly_tool_loading_length > item.after_assembly_handle_length:
|
||||
item.after_assembly_functional_tool_length = (
|
||||
item.after_assembly_tool_loading_length - item.after_assembly_handle_length)
|
||||
|
||||
@api.depends('integral_freight_barcode_id', 'blade_freight_barcode_id')
|
||||
def _compute_rota_tive(self):
|
||||
for item in self:
|
||||
rota_tive_boolean = False
|
||||
if item.integral_freight_barcode_id:
|
||||
# 整体刀
|
||||
if item.integral_freight_barcode_id.rotative_Boolean:
|
||||
rota_tive_boolean = True
|
||||
elif item.blade_freight_barcode_id:
|
||||
# 刀片
|
||||
if item.blade_freight_barcode_id.rotative_Boolean:
|
||||
rota_tive_boolean = True
|
||||
if rota_tive_boolean:
|
||||
item.after_assembly_coarse_middle_thin = '1'
|
||||
item.after_assembly_new_former = '1'
|
||||
else:
|
||||
item.after_assembly_coarse_middle_thin = '3'
|
||||
item.after_assembly_new_former = '0'
|
||||
|
||||
@api.onchange('handle_product_id')
|
||||
def _onchange_after_assembly_handle_length(self):
|
||||
for item in self:
|
||||
if item:
|
||||
if item.handle_product_id:
|
||||
item.after_assembly_handle_length = item.handle_product_id.cutting_tool_shank_length
|
||||
|
||||
@api.depends('after_assembly_functional_tool_type_id', 'cutting_tool_cutterhandle_model_id',
|
||||
'after_assembly_functional_tool_diameter', 'after_assembly_tool_loading_length',
|
||||
'after_assembly_knife_tip_r_angle', 'after_assembly_functional_tool_length',
|
||||
'after_assembly_handle_length')
|
||||
def _compute_code(self):
|
||||
for obj in self:
|
||||
if obj.cutting_tool_cutterhandle_model_id:
|
||||
code = obj.cutting_tool_cutterhandle_model_id.code.split('-', 1)[0]
|
||||
str_1 = '%s-GNDJ-%s-D%sL%sR%sB%sH%s' % (
|
||||
code, obj.after_assembly_functional_tool_type_id.code, obj.after_assembly_functional_tool_diameter,
|
||||
obj.after_assembly_tool_loading_length, obj.after_assembly_knife_tip_r_angle,
|
||||
round(obj.after_assembly_functional_tool_length, 3), obj.after_assembly_handle_length)
|
||||
obj.code = '%s-%s' % (str_1, self._get_code_1(str_1))
|
||||
else:
|
||||
obj.code = ''
|
||||
|
||||
def _get_code_1(self, str_2):
|
||||
functional_tool_assembly = self.env['sf.functional.cutting.tool.entity'].sudo().search(
|
||||
[('code', 'like', str_2)],
|
||||
limit=1,
|
||||
order="id desc"
|
||||
)
|
||||
if not functional_tool_assembly:
|
||||
num = "%03d" % 1
|
||||
else:
|
||||
m = int(functional_tool_assembly.code[-3:]) + 1
|
||||
num = "%03d" % m
|
||||
return num
|
||||
|
||||
def action_open_reference1(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
@@ -575,41 +745,326 @@ class FunctionalToolAssembly(models.Model):
|
||||
'res_id': self.id,
|
||||
}
|
||||
|
||||
def put_start_preset(self):
|
||||
self.search([('start_preset_bool', '=', True)]).write({'start_preset_bool': False})
|
||||
def start_preset(self):
|
||||
"""
|
||||
开始组装
|
||||
"""
|
||||
# 设置初始值
|
||||
self.start_preset_bool = True
|
||||
self.assemble_status = '01'
|
||||
self.after_assembly_coarse_middle_thin = '3'
|
||||
self.after_assembly_new_former = '0'
|
||||
|
||||
# 调用功能刀具名称对应的清单的BOM获取对应刀具物料信息
|
||||
bom = self._get_inventory_bom(self.tool_inventory_id)
|
||||
# 根据BOM自动配置物料的值
|
||||
self._set_tool_material(bom)
|
||||
logging.info('功能刀具开始组装初始化值成功!')
|
||||
|
||||
def _set_tool_material(self, bom):
|
||||
"""根据BOM对刀具物料进行初始配置"""
|
||||
options = bom.get('options')
|
||||
# 配置刀柄信息
|
||||
for handle_id in bom.get('handle_ids'):
|
||||
if handle_id:
|
||||
if not self.handle_product_id:
|
||||
self.handle_product_id = handle_id.id
|
||||
break
|
||||
|
||||
# 刀柄之外的物料配置
|
||||
if options == '刀柄+整体式刀具':
|
||||
# 配置整体式刀具
|
||||
integra_lot_id = self._get_old_tool_material_lot(bom.get('integral_ids'))
|
||||
integra_location_lot_id = self._get_shelf_location_lot(integra_lot_id)
|
||||
self.integral_freight_barcode_id = integra_location_lot_id.shelf_location_id.id
|
||||
self.integral_lot_id = integra_location_lot_id.lot_id.id
|
||||
else:
|
||||
# 配置刀片
|
||||
blade_lot_id = self._get_old_tool_material_lot(bom.get('blade_ids'))
|
||||
blade_location_lot_id = self._get_shelf_location_lot(blade_lot_id)
|
||||
self.blade_freight_barcode_id = blade_location_lot_id.shelf_location_id.id
|
||||
self.blade_lot_id = blade_location_lot_id.lot_id.id
|
||||
|
||||
if options == '刀柄+刀杆+刀片':
|
||||
# 配置刀杆
|
||||
bar_lot_id = self._get_old_tool_material_lot(bom.get('bar_ids'))
|
||||
bar_location_lot_id = self._get_shelf_location_lot(bar_lot_id)
|
||||
self.bar_freight_barcode_id = bar_location_lot_id.shelf_location_id.id
|
||||
self.bar_lot_id = bar_location_lot_id.lot_id.id
|
||||
elif options == '刀柄+刀盘+刀片':
|
||||
# 配置刀盘
|
||||
pad_lot_id = self._get_old_tool_material_lot(bom.get('pad_ids'))
|
||||
pad_location_lot_id = self._get_shelf_location_lot(pad_lot_id)
|
||||
self.pad_freight_barcode_id = pad_location_lot_id.shelf_location_id.id
|
||||
self.pad_lot_id = pad_location_lot_id.lot_id.id
|
||||
|
||||
def _get_old_tool_material_lot(self, material_ids):
|
||||
""" 根据先进先出原则选择物料批次 """
|
||||
location_id = self.env['stock.location'].search([('name', '=', '刀具房')])
|
||||
stock_quant = self.env['stock.quant'].sudo().search(
|
||||
[('location_id', '=', location_id.id), ('product_id', 'in', material_ids.ids), ('quantity', '>', '0')],
|
||||
order='lot_id', limit=1)
|
||||
if stock_quant:
|
||||
return stock_quant.lot_id
|
||||
else:
|
||||
raise ValidationError(f'【{material_ids[0].cutting_tool_material_id.name}】物料库存不足,请先进行盘点或采购')
|
||||
|
||||
def _get_shelf_location_lot(self, lot_id):
|
||||
"""根据所给的刀具物料批次号,返回一个刀具物料货位、批次信息"""
|
||||
location_lots = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', '=', lot_id.id)])
|
||||
if not location_lots:
|
||||
raise ValidationError(f'没有查询到批次为【{lot_id.name}】物料的货位信息!')
|
||||
else:
|
||||
return location_lots[0]
|
||||
|
||||
def _get_inventory_bom(self, inventory_id):
|
||||
"""获取BOM的刀具物料产品信息"""
|
||||
product_ids = inventory_id.jikimo_bom_ids.product_ids # BOM配置的物料产品
|
||||
options = inventory_id.jikimo_bom_ids.options # BOM产品组装类型
|
||||
|
||||
if not product_ids or not options:
|
||||
raise ValidationError('功能刀具清单的BOM未进行配置,请先配置BOM信息!')
|
||||
handle_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀柄')
|
||||
integral_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '整体式刀具')
|
||||
blade_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀片')
|
||||
bar_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀杆')
|
||||
pad_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀盘')
|
||||
|
||||
if not handle_ids:
|
||||
raise ValidationError('功能刀具清单的BOM未配置[刀柄]信息,请先配置BOM再开始组装!')
|
||||
|
||||
if options == '刀柄+整体式刀具':
|
||||
if not integral_ids:
|
||||
raise ValidationError('功能刀具清单的BOM未配置[刀柄]信息,请先配置BOM再开始组装!')
|
||||
return {'options': options, 'handle_ids': handle_ids, 'integral_ids': integral_ids}
|
||||
elif options == '刀柄+刀杆+刀片':
|
||||
if not blade_ids:
|
||||
raise ValidationError('功能刀具清单的BOM未配置[刀片]信息,请先配置BOM再开始组装!')
|
||||
if not bar_ids:
|
||||
raise ValidationError('功能刀具清单的BOM未配置[刀杆]信息,请先配置BOM再开始组装!')
|
||||
return {'options': options, 'handle_ids': handle_ids, 'blade_ids': blade_ids, 'bar_ids': bar_ids}
|
||||
elif options == '刀柄+刀盘+刀片':
|
||||
if not blade_ids:
|
||||
raise ValidationError('功能刀具清单的BOM未配置[刀片]信息,请先配置BOM再开始组装!')
|
||||
if not pad_ids:
|
||||
raise ValidationError('功能刀具清单的BOM未配置[刀盘]信息,请先配置BOM再开始组装!')
|
||||
return {'options': options, 'handle_ids': handle_ids, 'blade_ids': blade_ids, 'pad_ids': pad_ids}
|
||||
else:
|
||||
raise ValidationError(f'功能刀具清单BOM的组装方式错误:【{options}】')
|
||||
|
||||
def set_tool_lot(self):
|
||||
# 获取BOM的刀具物料产品信息
|
||||
tool_data = self._get_inventory_bom(self.tool_inventory_id)
|
||||
|
||||
# 获取刀具类型
|
||||
tool_type = self.env.context.get('tool_type')
|
||||
|
||||
if tool_type == '刀柄':
|
||||
tool_material_ids = tool_data.get('handle_ids')
|
||||
tool_material_tree_id = self.env.ref('sf_tool_management.view_tool_product_tree')
|
||||
elif tool_type == '整体式刀具':
|
||||
tool_material_ids = self._get_all_material_location_lot(tool_data.get('integral_ids'))
|
||||
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_1')
|
||||
elif tool_type == '刀片':
|
||||
tool_material_ids = self._get_all_material_location_lot(tool_data.get('blade_ids'))
|
||||
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_2')
|
||||
elif tool_type == '刀杆':
|
||||
tool_material_ids = self._get_all_material_location_lot(tool_data.get('bar_ids'))
|
||||
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_3')
|
||||
else:
|
||||
tool_material_ids = self._get_all_material_location_lot(tool_data.get('pad_ids'))
|
||||
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_4')
|
||||
|
||||
if tool_type == '刀柄':
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "product.product",
|
||||
"views": [[tool_material_tree_id.id, "tree"],
|
||||
[self.env.ref('sf_tool_management.view_tool_product_search').id, "search"]],
|
||||
"target": "new",
|
||||
"domain": [('id', 'in', tool_material_ids.ids)],
|
||||
"context": {'tool_id': self.id}
|
||||
}
|
||||
elif tool_type in ['整体式刀具', '刀片', '刀杆', '刀盘']:
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "sf.shelf.location.lot",
|
||||
"views": [[tool_material_tree_id.id, "tree"]],
|
||||
"target": "new",
|
||||
"domain": [('id', 'in', tool_material_ids.ids)],
|
||||
"context": {'tool_id': self.id}
|
||||
}
|
||||
|
||||
def _get_all_material_location_lot(self, material_ids):
|
||||
""" 获取所有满足条件 """
|
||||
location_id = self.env['stock.location'].search([('name', '=', '刀具房')])
|
||||
stock_quant_ids = self.env['stock.quant'].sudo().search(
|
||||
[('location_id', '=', location_id.id), ('product_id', 'in', material_ids.ids if material_ids else []),
|
||||
('quantity', '>', '0')])
|
||||
lot_ids = []
|
||||
for stock_quant_id in stock_quant_ids:
|
||||
lot_ids.append(stock_quant_id.lot_id.id)
|
||||
location_lots = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', 'in', lot_ids)])
|
||||
return location_lots
|
||||
|
||||
def functional_tool_assembly(self):
|
||||
"""
|
||||
功能刀具确认组装
|
||||
:return:
|
||||
"""
|
||||
logging.info('功能刀具开始组装!')
|
||||
# 对物料做必填判断
|
||||
self.materials_must_be_judged()
|
||||
|
||||
product_id = self.env['product.product']
|
||||
# 创建组装入库单
|
||||
# 创建功能刀具批次/序列号记录
|
||||
stock_lot = product_id.create_assemble_warehouse_receipt(self)
|
||||
# 封装功能刀具数据,用于创建功能刀具记录
|
||||
desc_2 = self.get_desc(stock_lot, self)
|
||||
# 创建功能刀具组装入库单
|
||||
self.env['stock.picking'].create_tool_stocking_picking(stock_lot, self)
|
||||
# 创建刀具物料出库单
|
||||
self.env['stock.picking'].create_tool_stocking_picking1(self)
|
||||
|
||||
# ============================创建功能刀具列表、安全库存记录===============================
|
||||
# 创建功能刀具列表记录
|
||||
record_1 = self.env['sf.functional.cutting.tool.entity'].create(desc_2)
|
||||
# 创建安全库存信息
|
||||
self.env['sf.real.time.distribution.of.functional.tools'].create_or_edit_safety_stock({
|
||||
'functional_name_id': self.tool_inventory_id.id
|
||||
}, record_1)
|
||||
|
||||
# =====================修改功能刀具组装单、机床换刀申请、CAM工单程序用刀计划的状态==============
|
||||
if self.sf_machine_table_tool_changing_apply_id:
|
||||
# 修改机床换刀申请的状态
|
||||
self.env['sf.machine.table.tool.changing.apply'].sudo().search([
|
||||
('id', '=', self.sf_machine_table_tool_changing_apply_id.id)
|
||||
]).write({'status': '3'})
|
||||
elif self.sf_cam_work_order_program_knife_plan_id:
|
||||
# 修改CAM工单程序用刀计划状态
|
||||
cam_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().search([
|
||||
('id', '=', self.sf_cam_work_order_program_knife_plan_id.id)
|
||||
])
|
||||
cam_plan.write({'plan_execute_status': '2'})
|
||||
|
||||
# ============修改组装单状态为已组装=================================‘
|
||||
self.write({
|
||||
'after_assembly_tool_loading_length': 0,
|
||||
'after_assembly_functional_tool_diameter': 0,
|
||||
'after_assembly_knife_tip_r_angle': 0,
|
||||
'start_preset_bool': True
|
||||
'assemble_status': '1',
|
||||
'start_preset_bool': False,
|
||||
'tool_loading_person': self.env.user.name,
|
||||
'tool_loading_time': fields.Datetime.now()
|
||||
})
|
||||
|
||||
logging.info('功能刀具组装完成!')
|
||||
|
||||
def materials_must_be_judged(self):
|
||||
"""
|
||||
功能刀具组装必填判断
|
||||
"""
|
||||
# 物料准确性校验
|
||||
|
||||
# 物料必填校验
|
||||
if not self.handle_code_id:
|
||||
raise ValidationError('缺少【刀柄】物料信息!')
|
||||
if self.integral_lot_id:
|
||||
if not self.integral_verify:
|
||||
raise ValidationError('【整体式刀具】未进行验证!')
|
||||
elif self.blade_lot_id:
|
||||
if not self.blade_verify:
|
||||
raise ValidationError('【刀片】未进行验证!')
|
||||
if self.bar_lot_id:
|
||||
if not self.bar_verify:
|
||||
raise ValidationError('【刀杆】未进行验证!')
|
||||
elif self.pad_lot_id:
|
||||
if not self.pad_verify:
|
||||
raise ValidationError('【刀盘】未进行验证!')
|
||||
# 组装参数必填校验
|
||||
if self.after_assembly_max_lifetime_value == 0:
|
||||
raise ValidationError('组装参数信息【最大寿命值】不能为0!')
|
||||
if self.after_assembly_functional_tool_diameter <= 0:
|
||||
raise ValidationError('组装参数信息【刀具直径】不能小于等于0!')
|
||||
if self.after_assembly_tool_loading_length == 0:
|
||||
raise ValidationError('组装参数信息【总长度】不能为0!!!')
|
||||
if self.after_assembly_handle_length == 0:
|
||||
raise ValidationError('组装参数信息【刀柄长度】不能为0!')
|
||||
if self.after_assembly_tool_loading_length < self.after_assembly_handle_length:
|
||||
raise ValidationError('组装参数信息【刀柄长度】不能大于【总长度】!')
|
||||
|
||||
def get_desc(self, stock_lot, functional_tool_assembly_id):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'sf.functional.tool.assembly.order',
|
||||
'name': '功能刀具组装单',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_name': self.name,
|
||||
'default_assembly_order_code': self.assembly_order_code,
|
||||
'default_production_line_name_id': self.production_line_name_id.id,
|
||||
'default_machine_tool_name_id': self.machine_tool_name_id.id,
|
||||
'default_cutter_spacing_code_id': self.cutter_spacing_code_id.id,
|
||||
'default_functional_tool_name': self.functional_tool_name,
|
||||
'default_functional_tool_type_id': self.functional_tool_type_id.id,
|
||||
'default_tool_groups_id': self.tool_groups_id.id,
|
||||
'default_functional_tool_diameter': self.functional_tool_diameter,
|
||||
'default_knife_tip_r_angle': self.knife_tip_r_angle,
|
||||
'default_tool_loading_length': self.tool_loading_length,
|
||||
'default_functional_tool_length': self.functional_tool_length,
|
||||
'default_effective_length': self.effective_length,
|
||||
'default_whether_standard_knife': self.whether_standard_knife,
|
||||
'default_coarse_middle_thin': self.coarse_middle_thin,
|
||||
'default_new_former': self.new_former,
|
||||
'default_use_tool_time': self.use_tool_time,
|
||||
'default_reason_for_applying': self.reason_for_applying
|
||||
}
|
||||
'barcode_id': stock_lot.id,
|
||||
'code': self.code,
|
||||
'name': self.tool_inventory_id.name,
|
||||
'tool_name_id': self.tool_inventory_id.id,
|
||||
'rfid': self.rfid,
|
||||
'tool_groups_id': self.tool_groups_id.id,
|
||||
'functional_tool_name_id': functional_tool_assembly_id.id,
|
||||
'sf_cutting_tool_type_id': self.after_assembly_functional_tool_type_id.id,
|
||||
'cutting_tool_integral_model_id': self.integral_product_id.id,
|
||||
'cutting_tool_blade_model_id': self.blade_product_id.id,
|
||||
'cutting_tool_cutterbar_model_id': self.bar_product_id.id,
|
||||
'cutting_tool_cutterpad_model_id': self.pad_product_id.id,
|
||||
'cutting_tool_cutterhandle_model_id': self.handle_product_id.id,
|
||||
'cutting_tool_cutterhead_model_id': self.chuck_product_id.id,
|
||||
|
||||
'functional_tool_diameter': self.after_assembly_functional_tool_diameter,
|
||||
'knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
|
||||
'coarse_middle_thin': self.after_assembly_coarse_middle_thin,
|
||||
'new_former': self.after_assembly_new_former,
|
||||
'tool_loading_length': self.after_assembly_tool_loading_length,
|
||||
'handle_length': self.after_assembly_handle_length,
|
||||
'functional_tool_length': self.after_assembly_functional_tool_length,
|
||||
'effective_length': self.after_assembly_effective_length,
|
||||
|
||||
'max_lifetime_value': self.after_assembly_max_lifetime_value,
|
||||
'alarm_value': self.after_assembly_alarm_value,
|
||||
'used_value': self.after_assembly_used_value,
|
||||
'whether_standard_knife': self.after_assembly_whether_standard_knife,
|
||||
'L_D_number': self.L_D_number,
|
||||
'hiding_length': self.hiding_length,
|
||||
'cut_time': self.cut_time,
|
||||
'cut_length': self.cut_length,
|
||||
'cut_number': self.cut_number,
|
||||
'image': self.image,
|
||||
}
|
||||
|
||||
# def put_start_preset(self):
|
||||
# # 打开组装弹窗开始组装
|
||||
# self.search([('start_preset_bool', '=', True)]).write({'start_preset_bool': False})
|
||||
# self.write({
|
||||
# 'after_assembly_tool_loading_length': 0,
|
||||
# 'after_assembly_functional_tool_diameter': 0,
|
||||
# 'after_assembly_knife_tip_r_angle': 0,
|
||||
# 'start_preset_bool': True
|
||||
# })
|
||||
# return {
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# 'res_model': 'sf.functional.tool.assembly.order',
|
||||
# 'name': '功能刀具组装单',
|
||||
# 'view_mode': 'form',
|
||||
# 'target': 'new',
|
||||
# 'context': {'default_name': self.name,
|
||||
# 'default_assembly_order_code': self.assembly_order_code,
|
||||
# 'default_production_line_name_id': self.production_line_name_id.id,
|
||||
# 'default_machine_tool_name_id': self.machine_tool_name_id.id,
|
||||
# 'default_cutter_spacing_code_id': self.cutter_spacing_code_id.id,
|
||||
# 'default_functional_tool_name': self.functional_tool_name,
|
||||
# 'default_functional_tool_type_id': self.functional_tool_type_id.id,
|
||||
# 'default_tool_groups_id': self.tool_groups_id.id,
|
||||
# 'default_functional_tool_diameter': self.functional_tool_diameter,
|
||||
# 'default_knife_tip_r_angle': self.knife_tip_r_angle,
|
||||
# 'default_tool_loading_length': self.tool_loading_length,
|
||||
# 'default_functional_tool_length': self.functional_tool_length,
|
||||
# 'default_effective_length': self.effective_length,
|
||||
# 'default_whether_standard_knife': self.whether_standard_knife,
|
||||
# 'default_coarse_middle_thin': self.coarse_middle_thin,
|
||||
# 'default_new_former': self.new_former,
|
||||
# 'default_use_tool_time': self.use_tool_time,
|
||||
# 'default_reason_for_applying': self.reason_for_applying
|
||||
# }
|
||||
# }
|
||||
|
||||
def _get_code(self, loading_task_source):
|
||||
"""
|
||||
自动生成组装单编码
|
||||
@@ -622,6 +1077,8 @@ class FunctionalToolAssembly(models.Model):
|
||||
code = 'J' + datetime
|
||||
elif loading_task_source == '2':
|
||||
code = 'K' + datetime
|
||||
elif loading_task_source == '3':
|
||||
code = 'S' + datetime
|
||||
else:
|
||||
code = False
|
||||
functional_tool_assembly = self.env['sf.functional.tool.assembly'].sudo().search(
|
||||
@@ -648,6 +1105,13 @@ class FunctionalToolAssembly(models.Model):
|
||||
return functional_tool
|
||||
return False
|
||||
|
||||
bool_preset_parameter = fields.Boolean('', default=False)
|
||||
|
||||
def get_tool_preset_parameter(self):
|
||||
if not self.bool_preset_parameter:
|
||||
raise ValidationError('没有获取到测量数据, 请确认刀具预调仪操作是否正确!')
|
||||
return True
|
||||
|
||||
def assemble_single_print(self):
|
||||
"""
|
||||
todo 组装单打印
|
||||
@@ -1032,6 +1496,47 @@ class FunctionalToolDismantle(models.Model):
|
||||
})
|
||||
logging.info('【%s】刀具拆解成功!' % self.name)
|
||||
|
||||
# ==================根据条件创建功能刀具组装单=======================
|
||||
# 如果报废原因为【寿命到期报废】并且刀柄不报废时, 创建功能刀具组装单
|
||||
if self.dismantle_cause in ['寿命到期报废'] and not self.scrap_boolean:
|
||||
# 创建组装单
|
||||
assembly_id = self.env['sf.functional.tool.assembly'].sudo().create({
|
||||
'functional_tool_name': self.functional_tool_id.name,
|
||||
'handle_code_id': self.handle_lot_id.id,
|
||||
'handle_product_id': self.handle_product_id.id,
|
||||
'loading_task_source': '3',
|
||||
'use_tool_time': fields.Datetime.now() + timedelta(hours=4),
|
||||
'reason_for_applying': '刀具寿命到期'
|
||||
})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'sf.functional.tool.assembly',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_id': assembly_id.id,
|
||||
# 'target': 'new'
|
||||
}
|
||||
# {
|
||||
# 'type': 'ir.actions.client',
|
||||
# 'tag': 'display_notification',
|
||||
# 'params': {
|
||||
# 'title': '组装单创建完成',
|
||||
# 'message': '请组装同名称的功能刀具',
|
||||
# 'type': 'info'
|
||||
# }
|
||||
# }
|
||||
|
||||
# 'params': {
|
||||
# 'title': _('The following replenishment order has been generated'),
|
||||
# 'message': '%s',
|
||||
# 'links': [{
|
||||
# 'label': order.display_name,
|
||||
# 'url': f'#action={action.id}&id={order.id}&model=purchase.order',
|
||||
# }],
|
||||
# 'sticky': False,
|
||||
# }
|
||||
|
||||
def create_tool_picking_scrap(self, datas):
|
||||
scrap_data = datas['scrap']
|
||||
picking_data = datas['picking']
|
||||
|
||||
@@ -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)
|
||||
@@ -1,4 +1,9 @@
|
||||
import logging
|
||||
|
||||
from datetime import timedelta, datetime, date
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ShelfLocation(models.Model):
|
||||
@@ -58,3 +63,289 @@ class StockPicking(models.Model):
|
||||
if move_lines:
|
||||
self.env['stock.move.line'].sudo().button_function_tool_use_verify(move_lines)
|
||||
return res
|
||||
|
||||
def create_tool_stocking_picking(self, stock_lot, obj):
|
||||
"""
|
||||
创建功能刀具组装入库单
|
||||
"""
|
||||
# 获取名称为刀具组装入库的作业类型
|
||||
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '刀具组装入库')])
|
||||
# 创建刀具组装入库单
|
||||
picking_id = self.env['stock.picking'].create({
|
||||
'name': self._get_name_stock(picking_type_id),
|
||||
'picking_type_id': picking_type_id.id,
|
||||
'location_id': picking_type_id.default_location_src_id.id,
|
||||
'location_dest_id': picking_type_id.default_location_dest_id.id,
|
||||
'origin': obj.assembly_order_code
|
||||
})
|
||||
# 创建作业详情对象记录,并绑定到刀具组装入库单
|
||||
self.env['stock.move.line'].create({
|
||||
'picking_id': picking_id.id,
|
||||
'product_id': stock_lot.product_id.id,
|
||||
'location_id': picking_id.location_id.id,
|
||||
'location_dest_id': picking_id.location_dest_id.id,
|
||||
'lot_id': stock_lot.id,
|
||||
'install_tool_time': fields.Datetime.now(),
|
||||
'qty_done': 1,
|
||||
'functional_tool_name_id': obj.id,
|
||||
'functional_tool_type_id': obj.functional_tool_type_id.id,
|
||||
'diameter': obj.after_assembly_functional_tool_diameter,
|
||||
'knife_tip_r_angle': obj.after_assembly_knife_tip_r_angle,
|
||||
'code': obj.code,
|
||||
'rfid': obj.rfid,
|
||||
'functional_tool_name': obj.after_assembly_functional_tool_name,
|
||||
'tool_groups_id': obj.tool_groups_id.id
|
||||
})
|
||||
# 将刀具组装入库单的状态更改为就绪
|
||||
picking_id.action_confirm()
|
||||
picking_id.button_validate()
|
||||
|
||||
def _get_name_stock(self, picking_type_id):
|
||||
name = picking_type_id.sequence_id.prefix + str(
|
||||
datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d"))
|
||||
stock_id = self.env['stock.picking'].sudo().search(
|
||||
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
|
||||
limit=1,
|
||||
order="id desc"
|
||||
)
|
||||
if not stock_id:
|
||||
num = "%03d" % 1
|
||||
else:
|
||||
m = int(stock_id.name[-3:]) + 1
|
||||
num = "%03d" % m
|
||||
return name + str(num)
|
||||
|
||||
def tool_location_num(self, freight_barcode_id, lot_id):
|
||||
location_lot = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', '=', lot_id.id), (
|
||||
'shelf_location_id', '=', freight_barcode_id.id)])
|
||||
if not location_lot:
|
||||
raise ValidationError(
|
||||
f'[{freight_barcode_id.barcode}]货位的[{lot_id.name}]批次物料已用完,请重新选择!')
|
||||
|
||||
def create_tool_stocking_picking1(self, obj):
|
||||
"""
|
||||
创建刀具物料出库单
|
||||
"""
|
||||
# 获取名称为内部调拨的作业类型
|
||||
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '内部调拨')])
|
||||
# 创建刀具物料出库单
|
||||
picking_id = self.env['stock.picking'].create({
|
||||
'name': self._get_name_stock1(picking_type_id),
|
||||
'picking_type_id': picking_type_id.id,
|
||||
'location_id': self.env['stock.location'].search([('name', '=', '刀具房')]).id,
|
||||
'location_dest_id': self.env['stock.location'].search([('name', '=', '刀具组装位置')]).id,
|
||||
'origin': obj.assembly_order_code
|
||||
})
|
||||
# =============刀具物料出库===================
|
||||
stock_move_id = self.env['stock.move']
|
||||
datas = {'data': [], 'picking_id': picking_id}
|
||||
if obj.handle_code_id:
|
||||
if obj.handle_code_id.tool_material_status == '在用':
|
||||
raise ValidationError(f'Rfid为{obj.handle_code_id.rfid}的刀柄已被使用,请重新选择!')
|
||||
# 修改刀柄序列号状态为【在用】
|
||||
obj.handle_code_id.sudo().write({'tool_material_status': '在用'})
|
||||
datas['data'].append(
|
||||
{'current_location_id': self.env['sf.shelf.location'], 'lot_id': obj.handle_code_id})
|
||||
if obj.integral_product_id:
|
||||
self.tool_location_num(obj.integral_freight_barcode_id, obj.integral_lot_id)
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.integral_freight_barcode_id, 'lot_id': obj.integral_lot_id})
|
||||
if obj.blade_product_id:
|
||||
self.tool_location_num(obj.blade_freight_barcode_id, obj.blade_lot_id)
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.blade_freight_barcode_id, 'lot_id': obj.blade_lot_id})
|
||||
if obj.bar_product_id:
|
||||
self.tool_location_num(obj.bar_freight_barcode_id, obj.bar_lot_id)
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.bar_freight_barcode_id, 'lot_id': obj.bar_lot_id})
|
||||
if obj.pad_product_id:
|
||||
self.tool_location_num(obj.pad_freight_barcode_id, obj.pad_lot_id)
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.pad_freight_barcode_id, 'lot_id': obj.pad_lot_id})
|
||||
if obj.chuck_product_id:
|
||||
self.tool_location_num(obj.chuck_freight_barcode_id, obj.chuck_lot_id)
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.chuck_freight_barcode_id, 'lot_id': obj.chuck_lot_id})
|
||||
# 创建刀具物料出库库存移动记录
|
||||
stock_move_id.create_tool_material_stock_moves(datas)
|
||||
# 将刀具物料出库库单的状态更改为就绪
|
||||
picking_id.action_confirm()
|
||||
# 修改刀具物料出库移动历史记录
|
||||
stock_move_id.write_tool_material_stock_move_lines(datas)
|
||||
# 设置数量,并验证完成
|
||||
picking_id.action_set_quantities_to_reservation()
|
||||
picking_id.button_validate()
|
||||
logging.info(f'刀具物料调拨单状态:{picking_id.state}')
|
||||
|
||||
def _get_name_stock1(self, picking_type_id):
|
||||
name = f'{picking_type_id.sequence_id.prefix}DJ/{date.today().strftime("%y")}'
|
||||
stock_id = self.env['stock.picking'].sudo().search(
|
||||
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
|
||||
limit=1,
|
||||
order="id desc"
|
||||
)
|
||||
if not stock_id:
|
||||
num = "%05d" % 1
|
||||
else:
|
||||
m = int(stock_id.name[-5:]) + 1
|
||||
num = "%05d" % m
|
||||
return name + str(num)
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
|
||||
def create_tool_material_stock_moves(self, datas):
|
||||
picking_id = datas['picking_id']
|
||||
data = datas['data']
|
||||
stock_move_ids = []
|
||||
for res in data:
|
||||
if res:
|
||||
if res['lot_id'].product_qty <= 0:
|
||||
raise ValidationError(
|
||||
f'[{res["lot_id"].product_id.name}产品的{res["lot_id"].name}]批次/序列号库存不足!')
|
||||
# 创建库存移动记录
|
||||
stock_move_id = self.env['stock.move'].sudo().create({
|
||||
'name': picking_id.name,
|
||||
'picking_id': picking_id.id,
|
||||
'product_id': res['lot_id'].product_id.id,
|
||||
'location_id': picking_id.location_id.id,
|
||||
'location_dest_id': picking_id.location_dest_id.id,
|
||||
'product_uom_qty': 1.00,
|
||||
'reserved_availability': 1.00
|
||||
})
|
||||
stock_move_ids.append(stock_move_id)
|
||||
return stock_move_ids
|
||||
|
||||
def write_tool_material_stock_move_lines(self, datas):
|
||||
picking_id = datas['picking_id']
|
||||
data = datas['data']
|
||||
move_line_ids = picking_id.move_line_ids
|
||||
for move_line_id in move_line_ids:
|
||||
for res in data:
|
||||
if move_line_id.lot_id.product_id == res['lot_id'].product_id:
|
||||
move_line_id.write({
|
||||
'current_location_id': res.get('current_location_id').id,
|
||||
'lot_id': res.get('lot_id').id
|
||||
})
|
||||
return True
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
def create_assemble_warehouse_receipt(self, obj):
|
||||
"""
|
||||
创建功能刀具批次/序列号记录
|
||||
"""
|
||||
product_id = self.env['product.product'].search([('categ_type', '=', '功能刀具'), ('tracking', '=', 'serial')])
|
||||
if not product_id:
|
||||
logging.info('没有搜索到功能刀具产品:%s' % product_id)
|
||||
raise ValidationError('没有找到按唯一序列号追溯的功能刀具产品信息!')
|
||||
stock_lot = self.env['stock.lot'].create({
|
||||
'name': self.get_stock_lot_name(obj),
|
||||
'product_id': product_id[0].id,
|
||||
'company_id': self.env.company.id
|
||||
})
|
||||
return stock_lot
|
||||
|
||||
def get_stock_lot_name(self, obj):
|
||||
"""
|
||||
生成功能刀具序列号
|
||||
"""
|
||||
company = obj.cutting_tool_cutterhandle_model_id.code.split('-', 1)[0]
|
||||
new_time = datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d")
|
||||
code = '%s-GNDJ-%s-%s' % (company, obj.after_assembly_functional_tool_type_id.code, new_time)
|
||||
stock_lot_id = self.env['stock.lot'].sudo().search(
|
||||
[('name', 'like', code)], limit=1, order="id desc")
|
||||
if not stock_lot_id:
|
||||
num = "%03d" % 1
|
||||
else:
|
||||
m = int(stock_lot_id.name[-3:]) + 1
|
||||
num = "%03d" % m
|
||||
return '%s-%s' % (code, num)
|
||||
|
||||
def set_tool_material(self):
|
||||
tool_id = self.env.context.get('tool_id')
|
||||
tool_assembly_id = self.env['sf.functional.tool.assembly'].sudo().search([('id', '=', tool_id)])
|
||||
if len(self) > 1:
|
||||
raise ValidationError('请不要多选')
|
||||
else:
|
||||
tool_assembly_id.handle_product_id = self.id
|
||||
tool_assembly_id.handle_code_id = False
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'message': '刀柄信息更改成功',
|
||||
'type': 'success',
|
||||
'next': {'type': 'ir.actions.act_window_close'}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SfShelfLocationLot(models.Model):
|
||||
_inherit = 'sf.shelf.location.lot'
|
||||
|
||||
product_id = fields.Many2one('product.product', '产品', compute='_compute_product_id', store=True)
|
||||
cutting_tool_type = fields.Char(string="刀具物料类型", compute='_compute_product_id', store=True)
|
||||
cutting_tool_type_id = fields.Many2one('sf.cutting.tool.type', string='类型',
|
||||
related='product_id.cutting_tool_type_id')
|
||||
cutting_tool_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='型号名称',
|
||||
related='product_id.cutting_tool_model_id')
|
||||
specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='物料号',
|
||||
related='product_id.specification_id')
|
||||
brand_id = fields.Many2one('sf.machine.brand', '品牌', related='product_id.brand_id')
|
||||
|
||||
cutting_tool_blade_diameter = fields.Float('刃部直径(mm)', related='product_id.cutting_tool_blade_diameter')
|
||||
cutting_tool_blade_tip_working_size = fields.Char('刀尖R角(mm)',
|
||||
related='product_id.cutting_tool_blade_tip_working_size')
|
||||
cutting_tool_blade_radius = fields.Char('刀尖圆弧半径(mm)',
|
||||
related='product_id.cutting_tool_blade_tip_circular_arc_radius')
|
||||
cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)',
|
||||
related='product_id.cutting_tool_cutter_arbor_diameter')
|
||||
cutting_tool_cutter_head_diameter = fields.Float('刀盘直径(mm)',
|
||||
related='product_id.cutting_tool_cutter_head_diameter')
|
||||
|
||||
fit_blade_shape_id = fields.Many2one('maintenance.equipment.image', '适配刀片形状',
|
||||
related='product_id.fit_blade_shape_id')
|
||||
|
||||
@api.depends('lot_id')
|
||||
def _compute_product_id(self):
|
||||
for item in self:
|
||||
if item.lot_id:
|
||||
item.product_id = item.lot_id.product_id.id
|
||||
item.cutting_tool_type = item.lot_id.product_id.cutting_tool_type
|
||||
|
||||
def set_tool_material(self):
|
||||
tool_type = self.env.context.get('tool_type')
|
||||
tool_id = self.env.context.get('tool_id')
|
||||
tool_assembly_id = self.env['sf.functional.tool.assembly'].sudo().search([('id', '=', tool_id)])
|
||||
if len(self) > 1:
|
||||
raise ValidationError('请不要多选')
|
||||
if tool_type == '整体式刀具':
|
||||
tool_assembly_id.integral_freight_barcode_id = self.shelf_location_id.id
|
||||
tool_assembly_id.integral_lot_id = self.lot_id.id
|
||||
tool_assembly_id.integral_verify = False
|
||||
elif tool_type == '刀片':
|
||||
tool_assembly_id.blade_freight_barcode_id = self.shelf_location_id.id
|
||||
tool_assembly_id.blade_lot_id = self.lot_id.id
|
||||
tool_assembly_id.blade_verify = False
|
||||
elif tool_type == '刀杆':
|
||||
tool_assembly_id.bar_freight_barcode_id = self.shelf_location_id.id
|
||||
tool_assembly_id.bar_lot_id = self.lot_id.id
|
||||
tool_assembly_id.bar_verify = False
|
||||
elif tool_type == '刀盘':
|
||||
tool_assembly_id.pad_freight_barcode_id = self.shelf_location_id.id
|
||||
tool_assembly_id.pad_lot_id = self.lot_id.id
|
||||
tool_assembly_id.pad_verify = False
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'message': f'[{tool_type}]物料信息更改成功',
|
||||
'type': 'success',
|
||||
'next': {'type': 'ir.actions.act_window_close'}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -11,4 +11,131 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_tool_product_tree" model="ir.ui.view">
|
||||
<field name="name">刀柄</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" export_xlsx="0" delete="0">
|
||||
<header>
|
||||
<button string="确认" name="set_tool_material" type="object"
|
||||
class="treeHeaderBtn"/>
|
||||
</header>
|
||||
<field name="name"/>
|
||||
<field name="cutting_tool_type_id"/>
|
||||
<field name="cutting_tool_model_id"/>
|
||||
<field name="specification_id"/>
|
||||
<field name="cutting_tool_shank_length"/>
|
||||
<field name="cutting_tool_taper_shank_model"/>
|
||||
<field name="brand_id"/>
|
||||
<field name="qty_available" string="数量"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_tool_product_search" model="ir.ui.view">
|
||||
<field name="model">product.product</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_shelf_location_lot_tree_1" model="ir.ui.view">
|
||||
<field name="name">sf.shelf.location.lot.tree</field>
|
||||
<field name="model">sf.shelf.location.lot</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" export_xlsx="0" delete="0">
|
||||
<header>
|
||||
<button string="确认" name="set_tool_material" type="object"
|
||||
class="treeHeaderBtn"/>
|
||||
</header>
|
||||
<field name="product_id"/>
|
||||
<field name="cutting_tool_type_id"/>
|
||||
<field name="cutting_tool_model_id"/>
|
||||
<field name="specification_id"/>
|
||||
|
||||
<field name="cutting_tool_blade_diameter"/>
|
||||
<field name="cutting_tool_blade_tip_working_size"/>
|
||||
|
||||
<field name="brand_id"/>
|
||||
<field name="shelf_location_id"/>
|
||||
<field name="lot_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_shelf_location_lot_tree_2" model="ir.ui.view">
|
||||
<field name="name">sf.shelf.location.lot.tree</field>
|
||||
<field name="model">sf.shelf.location.lot</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" export_xlsx="0" delete="0">
|
||||
<header>
|
||||
<button string="确认" name="set_tool_material" type="object"
|
||||
class="treeHeaderBtn"/>
|
||||
</header>
|
||||
<field name="product_id"/>
|
||||
<field name="cutting_tool_type_id"/>
|
||||
<field name="cutting_tool_model_id"/>
|
||||
<field name="specification_id"/>
|
||||
|
||||
<field name="fit_blade_shape_id"/>
|
||||
<field name="cutting_tool_blade_radius"/>
|
||||
|
||||
<field name="brand_id"/>
|
||||
<field name="shelf_location_id"/>
|
||||
<field name="lot_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_shelf_location_lot_tree_3" model="ir.ui.view">
|
||||
<field name="name">sf.shelf.location.lot.tree</field>
|
||||
<field name="model">sf.shelf.location.lot</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" export_xlsx="0" delete="0">
|
||||
<header>
|
||||
<button string="确认" name="set_tool_material" type="object"
|
||||
class="treeHeaderBtn"/>
|
||||
</header>
|
||||
<field name="product_id"/>
|
||||
<field name="cutting_tool_type_id"/>
|
||||
<field name="cutting_tool_model_id"/>
|
||||
<field name="specification_id"/>
|
||||
|
||||
<field name="cutting_tool_cutter_arbor_diameter"/>
|
||||
<field name="fit_blade_shape_id"/>
|
||||
|
||||
<field name="brand_id"/>
|
||||
<field name="shelf_location_id"/>
|
||||
<field name="lot_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_shelf_location_lot_tree_4" model="ir.ui.view">
|
||||
<field name="name">sf.shelf.location.lot.tree</field>
|
||||
<field name="model">sf.shelf.location.lot</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="0" export_xlsx="0" delete="0">
|
||||
<header>
|
||||
<button string="确认" name="set_tool_material" type="object"
|
||||
class="treeHeaderBtn"/>
|
||||
</header>
|
||||
<field name="product_id"/>
|
||||
<field name="cutting_tool_type_id"/>
|
||||
<field name="cutting_tool_model_id"/>
|
||||
<field name="specification_id"/>
|
||||
|
||||
<field name="cutting_tool_cutter_head_diameter"/>
|
||||
<field name="fit_blade_shape_id"/>
|
||||
|
||||
<field name="brand_id"/>
|
||||
<field name="shelf_location_id"/>
|
||||
<field name="lot_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -446,19 +446,23 @@
|
||||
<field name="use_tool_time"/>
|
||||
<field name="production_line_name_id" optional="hide"/>
|
||||
<field name="machine_tool_name_id" optional="hide"/>
|
||||
<field name="applicant"/>
|
||||
<field name="applicant" optional="hide"/>
|
||||
<field name="apply_time"/>
|
||||
<field name="assemble_status" optional="hide"/>
|
||||
<field name="assemble_status" widget='badge'
|
||||
decoration-info="assemble_status == '0'"
|
||||
decoration-warning="assemble_status == '01'"
|
||||
decoration-success="assemble_status == '1'"
|
||||
/>
|
||||
|
||||
<field name="name" invisible="True"/>
|
||||
<field name="machine_tool_code" invisible="True"/>
|
||||
<field name="cutter_spacing_code_id" invisible="True"/>
|
||||
<field name="whether_standard_knife" invisible="True"/>
|
||||
<field name="reason_for_applying" invisible="True"/>
|
||||
<field name="functional_tool_type_id" invisible="True"/>
|
||||
<button string="组装" name="put_start_preset" type="object"
|
||||
attrs="{'invisible': [('assemble_status', '!=', '0')]}"
|
||||
class="btn-primary"/>
|
||||
<!-- <field name="name" invisible="True"/>-->
|
||||
<!-- <field name="machine_tool_code" invisible="True"/>-->
|
||||
<!-- <field name="cutter_spacing_code_id" invisible="True"/>-->
|
||||
<!-- <field name="whether_standard_knife" invisible="True"/>-->
|
||||
<!-- <field name="reason_for_applying" invisible="True"/>-->
|
||||
<!-- <field name="functional_tool_type_id" invisible="True"/>-->
|
||||
<!-- <button string="组装" name="put_start_preset" type="object"-->
|
||||
<!-- attrs="{'invisible': [('assemble_status', '!=', '0')]}"-->
|
||||
<!-- class="btn-primary"/>-->
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -467,19 +471,23 @@
|
||||
<field name="name">功能刀具组装</field>
|
||||
<field name="model">sf.functional.tool.assembly</field>
|
||||
<field name="arch" type="xml">
|
||||
<form create="0" delete="0" edit="0">
|
||||
<form create="0" delete="0" edit="1">
|
||||
<header>
|
||||
<!-- <button string="修改编码" name="put_assembly_order_code" type="object"-->
|
||||
<!-- class="btn-primary" confirm="是否确认修改编码"/>-->
|
||||
<button string="组装" name="put_start_preset" type="object"
|
||||
attrs="{'invisible': [('assemble_status', '!=', '0')]}"
|
||||
<button string="开始组装" name="start_preset" type="object"
|
||||
attrs="{'invisible': [('assemble_status', 'not in', ['0'])]}"
|
||||
class="btn-primary"/>
|
||||
<field name="assemble_status" widget="statusbar" statusbar_visible="0,1"/>
|
||||
<button string="确认组装" name="functional_tool_assembly" type="object"
|
||||
attrs="{'invisible': [('assemble_status', 'not in', ['01'])]}"
|
||||
class="btn-primary"/>
|
||||
<button name="get_tool_preset_parameter" string="获取测量值"
|
||||
type="object" class="btn-primary"
|
||||
attrs="{'invisible': [('assemble_status', 'in', ['0','1','2'])]}"
|
||||
/>
|
||||
<field name="assemble_status" widget="statusbar" statusbar_visible="0,01,1"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="open_tool_stock_picking" icon="fa-truck" type="object"
|
||||
attrs="{'invisible': [('assemble_status', '!=', '1')]}">
|
||||
<button class="oe_stat_button" name="open_tool_stock_picking" icon="fa-truck" type="object">
|
||||
<div name="delivery_count" class="o_field_widget o_readonly_modifier o_field_statinfo">
|
||||
<span class="o_stat_info o_stat_value">
|
||||
<field name="picking_num"/>
|
||||
@@ -488,6 +496,10 @@
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_employee_avatar">
|
||||
<field name="image" widget="image" class="oe_avatar m-0"
|
||||
attrs="{'readonly': [('assemble_status', '!=', '01')]}"/>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="assembly_order_code"/>
|
||||
@@ -496,116 +508,77 @@
|
||||
<field name="name" invisible="1"/>
|
||||
<group>
|
||||
<group>
|
||||
<field name="production_line_name_id"/>
|
||||
<field name="machine_tool_name_id"/>
|
||||
<field name="cutter_spacing_code_id"/>
|
||||
<field name="sf_machine_table_tool_changing_apply_id"
|
||||
attrs="{'invisible': [('sf_machine_table_tool_changing_apply_id','=',False)]}"/>
|
||||
<field name="sf_cam_work_order_program_knife_plan_id"
|
||||
attrs="{'invisible': [('sf_cam_work_order_program_knife_plan_id','=',False)]}"/>
|
||||
<field name="functional_tool_name"/>
|
||||
<field name="functional_tool_type_id"/>
|
||||
<field name="tool_groups_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="image" nolabel="1" widget="image"/>
|
||||
<field name="functional_tool_diameter" string="刀具直径(mm)"/>
|
||||
<field name="knife_tip_r_angle"/>
|
||||
<field name="tool_loading_length" string="总长度(mm)"/>
|
||||
<field name="functional_tool_length"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="组装信息" attrs="{'invisible': [('assemble_status', '=', '0')]}">
|
||||
<group col="1">
|
||||
<page string="组装信息">
|
||||
<field name="_barcode_scanned" widget="barcode_handler"/>
|
||||
<group attrs="{'invisible': [('assemble_status', '=', '0')]}">
|
||||
<group col="1">
|
||||
<group>
|
||||
<group col="1">
|
||||
<div>
|
||||
<separator string="刀柄:" style="font-size: 13px;"/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="barcode_id" invisible="True"/>
|
||||
<field name="rfid" string="功能刀具rfid"/>
|
||||
<field name="code"/>
|
||||
<field name="after_assembly_functional_tool_name"
|
||||
string="功能刀具名称"/>
|
||||
<field name="after_assembly_functional_tool_type_id"
|
||||
string="功能刀具类型"/>
|
||||
<field name="tool_groups_id"/>
|
||||
<field name="after_assembly_whether_standard_knife"
|
||||
string="是否标准刀"/>
|
||||
<field name="after_assembly_coarse_middle_thin" string="粗/中/精"/>
|
||||
<field name="after_assembly_new_former" string="新/旧"/>
|
||||
<field name="cut_time"
|
||||
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
|
||||
<field name="cut_length"
|
||||
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
|
||||
<field name="cut_number"
|
||||
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="after_assembly_functional_tool_diameter"
|
||||
string="刀具直径(mm)"/>
|
||||
<field name="after_assembly_knife_tip_r_angle" string="刀尖R角(mm)"/>
|
||||
<field name="after_assembly_tool_loading_length" string="总长度(mm)"/>
|
||||
<field name="after_assembly_handle_length" string="刀柄长度(mm)"/>
|
||||
<field name="after_assembly_functional_tool_length"
|
||||
string="伸出长(mm)"/>
|
||||
<field name="after_assembly_max_lifetime_value"
|
||||
string="最大寿命值(min)"/>
|
||||
<field name="after_assembly_alarm_value" string="报警值(min)"/>
|
||||
<field name="after_assembly_used_value" string="已使用值(min)"
|
||||
invisible="1"/>
|
||||
<field name="after_assembly_effective_length" string="有效长(mm)"
|
||||
invisible="1"/>
|
||||
<field name="L_D_number" invisible="1"/>
|
||||
<field name="hiding_length" invisible="1"/>
|
||||
<field name="handle_code_id" string="序列号" placeholder="请选择"
|
||||
options="{'no_create': True, 'no_quick_create': True}"/>
|
||||
<field name="handle_freight_rfid" string="Rfid"/>
|
||||
<field name="handle_product_id" string="名称"/>
|
||||
<field name="cutting_tool_cutterhandle_model_id" string="型号"/>
|
||||
<field name="handle_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_5" string="品牌"/>
|
||||
</group>
|
||||
<div>
|
||||
<button string="更多" name="set_tool_lot" type="object"
|
||||
class="btn-primary" context="{'tool_type': '刀柄'}"
|
||||
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group col="1" attrs="{'invisible': [('handle_code_id', '=', False)]}">
|
||||
<div>
|
||||
<separator string="刀柄:" style="font-size: 13px;"/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="handle_code_id" string="序列号" placeholder="请选择"
|
||||
options="{'no_create': True, 'no_quick_create': True}"/>
|
||||
<field name="handle_freight_rfid" string="Rfid"/>
|
||||
<field name="handle_product_id" string="名称"/>
|
||||
<field name="cutting_tool_cutterhandle_model_id" string="型号"/>
|
||||
<field name="handle_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_5" string="品牌"/>
|
||||
</group>
|
||||
</group>
|
||||
<group col="1" attrs="{'invisible': [('chuck_freight_barcode_id', '=', False)]}">
|
||||
<div>
|
||||
<separator string="夹头:" style="font-size: 13px;"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="chuck_freight_barcode_id" string="货位"/>
|
||||
<field name="chuck_lot_id" string="批次"/>
|
||||
<field name="chuck_product_id" string="名称"/>
|
||||
<field name="cutting_tool_cutterhead_model_id" string="型号"/>
|
||||
<field name="chuck_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_6" string="品牌"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group col="1">
|
||||
<group col="1"
|
||||
attrs="{'invisible': [('integral_freight_barcode_id', '=', False)]}">
|
||||
attrs="{'invisible': [('integral_lot_id', '=', False),'|','|','|', ('blade_lot_id', '!=', False),('bar_lot_id', '!=', False),('pad_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
|
||||
<div>
|
||||
<separator string="整体式刀具:" style="font-size: 13px;"/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="integral_freight_barcode_id" string="货位"/>
|
||||
<field name="integral_lot_id" string="批次"/>
|
||||
<field name="integral_product_id" string="名称"/>
|
||||
<field name="cutting_tool_integral_model_id" string="型号"/>
|
||||
<field name="integral_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_1" string="品牌"/>
|
||||
<group>
|
||||
<field name="integral_freight_barcode_id" string="货位"/>
|
||||
<field name="integral_lot_id" string="批次"/>
|
||||
<field name="integral_product_id" string="名称"/>
|
||||
<field name="cutting_tool_integral_model_id" string="型号"/>
|
||||
<field name="integral_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_1" string="品牌"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="integral_verify" string="" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group col="1"
|
||||
attrs="{'invisible': [('blade_freight_barcode_id', '=', False)]}">
|
||||
<div>
|
||||
<separator string="刀片:" style="font-size: 13px;"/>
|
||||
<button string="更多" name="set_tool_lot" type="object"
|
||||
class="btn-primary" context="{'tool_type': '整体式刀具'}"
|
||||
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
|
||||
</div>
|
||||
</group>
|
||||
|
||||
</group>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('assemble_status', '=', '0')]}">
|
||||
<group col="1"
|
||||
attrs="{'invisible': [('blade_lot_id', '=', False),'|', ('integral_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
|
||||
<div>
|
||||
<separator string="刀片:" style="font-size: 13px;"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="blade_freight_barcode_id" string="货位"/>
|
||||
<field name="blade_lot_id" string="批次"/>
|
||||
@@ -614,10 +587,19 @@
|
||||
<field name="blade_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_2" string="品牌"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="blade_verify" string="" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<div>
|
||||
<button string="更多" name="set_tool_lot" type="object"
|
||||
class="btn-primary" context="{'tool_type': '刀片'}"
|
||||
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
|
||||
</div>
|
||||
</group>
|
||||
<group col="1">
|
||||
<group col="1" attrs="{'invisible': [('bar_freight_barcode_id', '=', False)]}">
|
||||
<group col="1"
|
||||
attrs="{'invisible': [('bar_lot_id', '=', False),'|','|',('integral_lot_id', '!=', False),('pad_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
|
||||
<div>
|
||||
<separator string="刀杆:" style="font-size: 13px;"/>
|
||||
</div>
|
||||
@@ -630,9 +612,18 @@
|
||||
<field name="bar_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_3" string="品牌"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="bar_verify" string="" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<div>
|
||||
<button string="更多" name="set_tool_lot" type="object"
|
||||
class="btn-primary" context="{'tool_type': '刀杆'}"
|
||||
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
|
||||
</div>
|
||||
</group>
|
||||
<group col="1" attrs="{'invisible': [('pad_freight_barcode_id', '=', False)]}">
|
||||
<group col="1"
|
||||
attrs="{'invisible': [('pad_lot_id', '=', False),'|','|',('integral_lot_id', '!=', False),('bar_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
|
||||
<div>
|
||||
<separator string="刀盘:" style="font-size: 13px;"/>
|
||||
</div>
|
||||
@@ -645,30 +636,69 @@
|
||||
<field name="pad_specification_id" string="规格"/>
|
||||
<field name="sf_tool_brand_id_4" string="品牌"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="pad_verify" string="" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<div>
|
||||
<button string="更多" name="set_tool_lot" type="object"
|
||||
class="btn-primary" context="{'tool_type': '刀盘'}"
|
||||
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="申请信息">
|
||||
<group>
|
||||
<group>
|
||||
<field name="functional_tool_name"/>
|
||||
<field name="functional_tool_type_id"/>
|
||||
<field name="tool_groups_id"/>
|
||||
<field name="functional_tool_diameter" string="刀具直径(mm)"/>
|
||||
<field name="knife_tip_r_angle"/>
|
||||
<field name="tool_loading_length" string="总长度(mm)"/>
|
||||
<field name="functional_tool_length"/>
|
||||
<field name="effective_length"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="whether_standard_knife"/>
|
||||
<field name="coarse_middle_thin"/>
|
||||
<field name="new_former"/>
|
||||
<field name="use_tool_time"/>
|
||||
<field name="reason_for_applying"/>
|
||||
<!-- <field name="functional_tool_cutting_type"/>-->
|
||||
<group col="1" attrs="{'invisible': [('assemble_status', '=', '0')]}">
|
||||
<group col="1">
|
||||
<group string="组装参数信息">
|
||||
<group>
|
||||
<field name="barcode_id" invisible="True"/>
|
||||
<field name="rfid" string="功能刀具rfid"/>
|
||||
<field name="code"/>
|
||||
<field name="after_assembly_functional_tool_name"
|
||||
string="功能刀具名称"/>
|
||||
<field name="after_assembly_functional_tool_type_id"
|
||||
string="功能刀具类型"/>
|
||||
<field name="tool_groups_id"/>
|
||||
<field name="after_assembly_whether_standard_knife"
|
||||
string="是否标准刀" invisible="1"/>
|
||||
<field name="after_assembly_coarse_middle_thin" string="粗/中/精"
|
||||
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
|
||||
<field name="after_assembly_new_former" string="新/旧"
|
||||
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
|
||||
<field name="cut_time"
|
||||
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
|
||||
<field name="cut_length"
|
||||
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
|
||||
<field name="cut_number"
|
||||
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="after_assembly_functional_tool_diameter" readonly="0"
|
||||
string="刀具直径(mm)"
|
||||
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
|
||||
<field name="after_assembly_knife_tip_r_angle" readonly="0"
|
||||
string="刀尖R角(mm)"
|
||||
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
|
||||
<field name="after_assembly_tool_loading_length" readonly="0"
|
||||
string="总长度(mm)"
|
||||
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
|
||||
<field name="after_assembly_handle_length" string="刀柄长度(mm)"
|
||||
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
|
||||
<field name="after_assembly_functional_tool_length"
|
||||
string="伸出长(mm)"/>
|
||||
<field name="after_assembly_max_lifetime_value"
|
||||
string="最大寿命值(min)"/>
|
||||
<field name="after_assembly_alarm_value" string="报警值(min)"
|
||||
invisible="1"/>
|
||||
<field name="after_assembly_used_value" string="已使用值(min)"
|
||||
invisible="1"/>
|
||||
<field name="after_assembly_effective_length" string="有效长(mm)"
|
||||
invisible="1"/>
|
||||
<field name="L_D_number" invisible="1"/>
|
||||
<field name="hiding_length" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
@@ -685,9 +715,27 @@
|
||||
<page string="其他">
|
||||
<group>
|
||||
<group>
|
||||
<field name="production_line_name_id"
|
||||
attrs="{'invisible': [('production_line_name_id','=',False)]}"/>
|
||||
<field name="machine_tool_name_id"
|
||||
attrs="{'invisible': [('machine_tool_name_id','=',False)]}"/>
|
||||
<field name="cutter_spacing_code_id"
|
||||
attrs="{'invisible': [('cutter_spacing_code_id','=',False)]}"/>
|
||||
<field name="use_tool_time"/>
|
||||
<field name="reason_for_applying"/>
|
||||
<field name="sf_machine_table_tool_changing_apply_id"
|
||||
attrs="{'invisible': [('sf_machine_table_tool_changing_apply_id','=',False)]}"/>
|
||||
<field name="sf_cam_work_order_program_knife_plan_id"
|
||||
attrs="{'invisible': [('sf_cam_work_order_program_knife_plan_id','=',False)]}"/>
|
||||
<field name="check_box_1" invisible="True"/>
|
||||
<field name="remark"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="effective_length"/>
|
||||
<field name="whether_standard_knife"/>
|
||||
<field name="coarse_middle_thin" invisible="1"/>
|
||||
<field name="new_former" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
@@ -705,20 +753,19 @@
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="functional_tool_name"/>
|
||||
<field name="tool_groups_id"/>
|
||||
<field name="functional_tool_diameter"/>
|
||||
<field name="knife_tip_r_angle"/>
|
||||
<field name="assembly_order_code"/>
|
||||
<field name="code" string="功能刀具编码"/>
|
||||
<field name="barcode_id"/>
|
||||
<field name="functional_tool_type_id"/>
|
||||
<field name="tool_groups_id"/>
|
||||
<field name="loading_task_source" string="任务来源"/>
|
||||
<field name="production_line_name_id"/>
|
||||
<field name="machine_tool_name_id"/>
|
||||
<field name="applicant"/>
|
||||
<filter name="no_assemble_status" string="未组装" domain="[('assemble_status', '=', '0')]"/>
|
||||
|
||||
<filter name="no_assemble_status" string="未组装" domain="[('assemble_status', 'in', ['0','01'])]"/>
|
||||
<filter name="yes_assemble_status" string="已组装" domain="[('assemble_status', '=', '1')]"/>
|
||||
<separator/>
|
||||
<filter string="已归档" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<searchpanel>
|
||||
<field name="assemble_status" enable_counters="1" icon="fa-filter"/>
|
||||
<field name="functional_tool_type_id" enable_counters="1" icon="fa-filter"/>
|
||||
</searchpanel>
|
||||
|
||||
@@ -743,7 +790,7 @@
|
||||
<field name="view_mode">tree,form,search</field>
|
||||
<!-- <field name="view_id" ref="sf_functional_tool_assembly_tree"/>-->
|
||||
<field name="search_view_id" ref="sf_functional_tool_assembly_search"/>
|
||||
<field name="context">{'search_default_no_assemble_status':1}</field>
|
||||
<field name="context">{'search_default_no_assemble_status':[1,01]}</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -844,7 +891,8 @@
|
||||
<group attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])]}">
|
||||
<!-- <group col="3">-->
|
||||
<group>
|
||||
<field name="scrap_boolean" string="是否报废" readonly="0"/>
|
||||
<field name="scrap_boolean" string="是否报废"
|
||||
attrs="{'readonly': [('state' , '=', '已拆解')]}"/>
|
||||
</group>
|
||||
<!-- <group></group>-->
|
||||
<!-- <group>-->
|
||||
|
||||
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>
|
||||
@@ -598,361 +598,175 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
|
||||
else:
|
||||
record.L_D_number = 0
|
||||
|
||||
def functional_tool_assembly(self):
|
||||
"""
|
||||
功能刀具组装
|
||||
:return:
|
||||
"""
|
||||
logging.info('功能刀具开始组装!')
|
||||
# 获取组装单对象
|
||||
functional_tool_assembly = self.env['sf.functional.tool.assembly'].search([
|
||||
('assembly_order_code', '=', self.assembly_order_code),
|
||||
('machine_tool_name_id', '=', self.machine_tool_name_id.id),
|
||||
('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id),
|
||||
('assemble_status', '=', '0'),
|
||||
])
|
||||
# 对物料做必填判断
|
||||
self.materials_must_be_judged()
|
||||
|
||||
product_id = self.env['product.product']
|
||||
# 创建组装入库单
|
||||
# 创建功能刀具批次/序列号记录
|
||||
stock_lot = product_id.create_assemble_warehouse_receipt(self.id, functional_tool_assembly, self)
|
||||
# 封装功能刀具数据,用于更新组装单信息
|
||||
desc_1 = self.get_desc_1(stock_lot)
|
||||
# 封装功能刀具数据,用于创建功能刀具记录
|
||||
desc_2 = self.get_desc_2(stock_lot, functional_tool_assembly)
|
||||
# 创建功能刀具组装入库单
|
||||
self.env['stock.picking'].create_tool_stocking_picking(stock_lot, functional_tool_assembly, self)
|
||||
# 创建刀具物料出库单
|
||||
self.env['stock.picking'].create_tool_stocking_picking1(self)
|
||||
|
||||
# ============================创建功能刀具列表、安全库存记录===============================
|
||||
# 创建功能刀具列表记录
|
||||
record_1 = self.env['sf.functional.cutting.tool.entity'].create(desc_2)
|
||||
# 创建安全库存信息
|
||||
self.env['sf.real.time.distribution.of.functional.tools'].create_or_edit_safety_stock({
|
||||
'functional_name_id': self.after_name_id.id
|
||||
}, record_1)
|
||||
|
||||
# =====================修改功能刀具组装单、机床换刀申请、CAM工单程序用刀计划的状态==============
|
||||
# 修改功能刀具组装单信息
|
||||
functional_tool_assembly.write(desc_1)
|
||||
if functional_tool_assembly.sf_machine_table_tool_changing_apply_id:
|
||||
# 修改机床换刀申请的状态
|
||||
self.env['sf.machine.table.tool.changing.apply'].sudo().search([
|
||||
('id', '=', functional_tool_assembly.sf_machine_table_tool_changing_apply_id.id)
|
||||
]).write({'status': '3'})
|
||||
elif functional_tool_assembly.sf_cam_work_order_program_knife_plan_id:
|
||||
# 修改CAM工单程序用刀计划状态
|
||||
cam_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().search([
|
||||
('id', '=', functional_tool_assembly.sf_cam_work_order_program_knife_plan_id.id)
|
||||
])
|
||||
cam_plan.write({'plan_execute_status': '2'})
|
||||
|
||||
logging.info('功能刀具组装完成!')
|
||||
|
||||
# 关闭弹出窗口
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def materials_must_be_judged(self):
|
||||
"""
|
||||
功能刀具组装必填判断
|
||||
"""
|
||||
# 物料必填校验
|
||||
if not self.handle_code_id:
|
||||
raise ValidationError('缺少【刀柄】物料信息!')
|
||||
if not self.integral_product_id and not self.blade_product_id:
|
||||
raise ValidationError('【整体式刀具】和【刀片】必须填写一个!')
|
||||
if self.blade_product_id:
|
||||
if not self.bar_product_id and not self.pad_product_id:
|
||||
raise ValidationError('【刀盘】和【刀杆】必须填写一个!')
|
||||
# 组装参数必填校验
|
||||
if self.after_assembly_functional_tool_length == 0:
|
||||
raise ValidationError('组装参数信息【伸出长】不能为0!')
|
||||
if self.after_assembly_max_lifetime_value == 0:
|
||||
raise ValidationError('组装参数信息【最大寿命值】不能为0!')
|
||||
if self.after_assembly_alarm_value == 0:
|
||||
raise ValidationError('组装参数信息【报警值】不能为0!')
|
||||
# if self.after_assembly_effective_length == 0:
|
||||
# raise ValidationError('组装参数信息【有效长】不能为0!!!')
|
||||
# if self.hiding_length == 0:
|
||||
# raise ValidationError('组装参数信息【避空长】不能为0!!!')
|
||||
if self.after_assembly_functional_tool_diameter == 0:
|
||||
raise ValidationError('组装参数信息【刀具直径】不能为0!')
|
||||
if self.after_assembly_tool_loading_length == 0:
|
||||
raise ValidationError('组装参数信息【总长度】不能为0!!!')
|
||||
if self.after_assembly_handle_length == 0:
|
||||
raise ValidationError('组装参数信息【刀柄长度】不能为0!')
|
||||
if self.after_assembly_tool_loading_length < self.after_assembly_handle_length:
|
||||
raise ValidationError('组装参数信息【刀柄长度】不能大于【总长度】!')
|
||||
|
||||
def get_desc_1(self, stock_lot):
|
||||
return {
|
||||
'start_preset_bool': False,
|
||||
'barcode_id': stock_lot.id,
|
||||
'code': self.code,
|
||||
'rfid': self.rfid,
|
||||
'tool_groups_id': self.after_tool_groups_id.id,
|
||||
'handle_code_id': self.handle_code_id.id,
|
||||
'integral_freight_barcode_id': self.integral_freight_barcode_id.id,
|
||||
'integral_lot_id': self.integral_freight_lot_id.lot_id.id,
|
||||
'blade_freight_barcode_id': self.blade_freight_barcode_id.id,
|
||||
'blade_lot_id': self.blade_freight_lot_id.lot_id.id,
|
||||
'bar_freight_barcode_id': self.bar_freight_barcode_id.id,
|
||||
'bar_lot_id': self.bar_freight_lot_id.lot_id.id,
|
||||
'pad_freight_barcode_id': self.pad_freight_barcode_id.id,
|
||||
'pad_lot_id': self.pad_freight_lot_id.lot_id.id,
|
||||
'chuck_freight_barcode_id': self.chuck_freight_barcode_id.id,
|
||||
'chuck_lot_id': self.chuck_freight_lot_id.lot_id.id,
|
||||
|
||||
'after_assembly_functional_tool_name': self.after_assembly_functional_tool_name,
|
||||
'after_assembly_functional_tool_type_id': self.after_assembly_functional_tool_type_id.id,
|
||||
'after_assembly_functional_tool_diameter': self.after_assembly_functional_tool_diameter,
|
||||
'after_assembly_knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
|
||||
'after_assembly_new_former': self.after_assembly_new_former,
|
||||
'cut_time': self.cut_time,
|
||||
'cut_length': self.cut_length,
|
||||
'cut_number': self.cut_number,
|
||||
'after_assembly_whether_standard_knife': self.after_assembly_whether_standard_knife,
|
||||
'after_assembly_coarse_middle_thin': self.after_assembly_coarse_middle_thin,
|
||||
'after_assembly_max_lifetime_value': self.after_assembly_max_lifetime_value,
|
||||
'after_assembly_alarm_value': self.after_assembly_alarm_value,
|
||||
'after_assembly_used_value': self.after_assembly_used_value,
|
||||
'after_assembly_tool_loading_length': self.after_assembly_tool_loading_length,
|
||||
'after_assembly_handle_length': self.after_assembly_handle_length,
|
||||
'after_assembly_functional_tool_length': self.after_assembly_functional_tool_length,
|
||||
'after_assembly_effective_length': self.after_assembly_effective_length,
|
||||
'L_D_number': self.L_D_number,
|
||||
'hiding_length': self.hiding_length,
|
||||
'assemble_status': '1',
|
||||
'tool_loading_person': self.env.user.name,
|
||||
'image': self.image,
|
||||
'tool_loading_time': fields.Datetime.now()
|
||||
}
|
||||
|
||||
def get_desc_2(self, stock_lot, functional_tool_assembly_id):
|
||||
return {
|
||||
'barcode_id': stock_lot.id,
|
||||
'code': self.code,
|
||||
'name': self.after_name_id.name,
|
||||
'tool_name_id': self.after_name_id.id,
|
||||
'rfid': self.rfid,
|
||||
'tool_groups_id': self.after_tool_groups_id.id,
|
||||
'functional_tool_name_id': functional_tool_assembly_id.id,
|
||||
'sf_cutting_tool_type_id': self.after_assembly_functional_tool_type_id.id,
|
||||
'cutting_tool_integral_model_id': self.integral_product_id.id,
|
||||
'cutting_tool_blade_model_id': self.blade_product_id.id,
|
||||
'cutting_tool_cutterbar_model_id': self.bar_product_id.id,
|
||||
'cutting_tool_cutterpad_model_id': self.pad_product_id.id,
|
||||
'cutting_tool_cutterhandle_model_id': self.handle_product_id.id,
|
||||
'cutting_tool_cutterhead_model_id': self.chuck_product_id.id,
|
||||
|
||||
'functional_tool_diameter': self.after_assembly_functional_tool_diameter,
|
||||
'knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
|
||||
'coarse_middle_thin': self.after_assembly_coarse_middle_thin,
|
||||
'new_former': self.after_assembly_new_former,
|
||||
'tool_loading_length': self.after_assembly_tool_loading_length,
|
||||
'handle_length': self.after_assembly_handle_length,
|
||||
'functional_tool_length': self.after_assembly_functional_tool_length,
|
||||
'effective_length': self.after_assembly_effective_length,
|
||||
|
||||
'max_lifetime_value': self.after_assembly_max_lifetime_value,
|
||||
'alarm_value': self.after_assembly_alarm_value,
|
||||
'used_value': self.after_assembly_used_value,
|
||||
'whether_standard_knife': self.after_assembly_whether_standard_knife,
|
||||
'L_D_number': self.L_D_number,
|
||||
'hiding_length': self.hiding_length,
|
||||
'cut_time': self.cut_time,
|
||||
'cut_length': self.cut_length,
|
||||
'cut_number': self.cut_number,
|
||||
'image': self.image,
|
||||
}
|
||||
# def functional_tool_assembly(self):
|
||||
# """
|
||||
# 功能刀具组装
|
||||
# :return:
|
||||
# """
|
||||
# logging.info('功能刀具开始组装!')
|
||||
# # 获取组装单对象
|
||||
# functional_tool_assembly = self.env['sf.functional.tool.assembly'].search([
|
||||
# ('assembly_order_code', '=', self.assembly_order_code),
|
||||
# ('machine_tool_name_id', '=', self.machine_tool_name_id.id),
|
||||
# ('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id),
|
||||
# ('assemble_status', '=', '0'),
|
||||
# ])
|
||||
# # 对物料做必填判断
|
||||
# self.materials_must_be_judged()
|
||||
#
|
||||
# product_id = self.env['product.product']
|
||||
# # 创建组装入库单
|
||||
# # 创建功能刀具批次/序列号记录
|
||||
# stock_lot = product_id.create_assemble_warehouse_receipt(self.id, functional_tool_assembly, self)
|
||||
# # 封装功能刀具数据,用于更新组装单信息
|
||||
# desc_1 = self.get_desc_1(stock_lot)
|
||||
# # 封装功能刀具数据,用于创建功能刀具记录
|
||||
# desc_2 = self.get_desc_2(stock_lot, functional_tool_assembly)
|
||||
# # 创建功能刀具组装入库单
|
||||
# self.env['stock.picking'].create_tool_stocking_picking(stock_lot, functional_tool_assembly, self)
|
||||
# # 创建刀具物料出库单
|
||||
# self.env['stock.picking'].create_tool_stocking_picking1(self)
|
||||
#
|
||||
# # ============================创建功能刀具列表、安全库存记录===============================
|
||||
# # 创建功能刀具列表记录
|
||||
# record_1 = self.env['sf.functional.cutting.tool.entity'].create(desc_2)
|
||||
# # 创建安全库存信息
|
||||
# self.env['sf.real.time.distribution.of.functional.tools'].create_or_edit_safety_stock({
|
||||
# 'functional_name_id': self.after_name_id.id
|
||||
# }, record_1)
|
||||
#
|
||||
# # =====================修改功能刀具组装单、机床换刀申请、CAM工单程序用刀计划的状态==============
|
||||
# # 修改功能刀具组装单信息
|
||||
# functional_tool_assembly.write(desc_1)
|
||||
# if functional_tool_assembly.sf_machine_table_tool_changing_apply_id:
|
||||
# # 修改机床换刀申请的状态
|
||||
# self.env['sf.machine.table.tool.changing.apply'].sudo().search([
|
||||
# ('id', '=', functional_tool_assembly.sf_machine_table_tool_changing_apply_id.id)
|
||||
# ]).write({'status': '3'})
|
||||
# elif functional_tool_assembly.sf_cam_work_order_program_knife_plan_id:
|
||||
# # 修改CAM工单程序用刀计划状态
|
||||
# cam_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().search([
|
||||
# ('id', '=', functional_tool_assembly.sf_cam_work_order_program_knife_plan_id.id)
|
||||
# ])
|
||||
# cam_plan.write({'plan_execute_status': '2'})
|
||||
#
|
||||
# logging.info('功能刀具组装完成!')
|
||||
#
|
||||
# # 关闭弹出窗口
|
||||
# return {'type': 'ir.actions.act_window_close'}
|
||||
#
|
||||
# def materials_must_be_judged(self):
|
||||
# """
|
||||
# 功能刀具组装必填判断
|
||||
# """
|
||||
# # 物料必填校验
|
||||
# if not self.handle_code_id:
|
||||
# raise ValidationError('缺少【刀柄】物料信息!')
|
||||
# if not self.integral_product_id and not self.blade_product_id:
|
||||
# raise ValidationError('【整体式刀具】和【刀片】必须填写一个!')
|
||||
# if self.blade_product_id:
|
||||
# if not self.bar_product_id and not self.pad_product_id:
|
||||
# raise ValidationError('【刀盘】和【刀杆】必须填写一个!')
|
||||
# # 组装参数必填校验
|
||||
# if self.after_assembly_functional_tool_length == 0:
|
||||
# raise ValidationError('组装参数信息【伸出长】不能为0!')
|
||||
# if self.after_assembly_max_lifetime_value == 0:
|
||||
# raise ValidationError('组装参数信息【最大寿命值】不能为0!')
|
||||
# if self.after_assembly_alarm_value == 0:
|
||||
# raise ValidationError('组装参数信息【报警值】不能为0!')
|
||||
# # if self.after_assembly_effective_length == 0:
|
||||
# # raise ValidationError('组装参数信息【有效长】不能为0!!!')
|
||||
# # if self.hiding_length == 0:
|
||||
# # raise ValidationError('组装参数信息【避空长】不能为0!!!')
|
||||
# if self.after_assembly_functional_tool_diameter == 0:
|
||||
# raise ValidationError('组装参数信息【刀具直径】不能为0!')
|
||||
# if self.after_assembly_tool_loading_length == 0:
|
||||
# raise ValidationError('组装参数信息【总长度】不能为0!!!')
|
||||
# if self.after_assembly_handle_length == 0:
|
||||
# raise ValidationError('组装参数信息【刀柄长度】不能为0!')
|
||||
# if self.after_assembly_tool_loading_length < self.after_assembly_handle_length:
|
||||
# raise ValidationError('组装参数信息【刀柄长度】不能大于【总长度】!')
|
||||
#
|
||||
# def get_desc_1(self, stock_lot):
|
||||
# return {
|
||||
# 'start_preset_bool': False,
|
||||
# 'barcode_id': stock_lot.id,
|
||||
# 'code': self.code,
|
||||
# 'rfid': self.rfid,
|
||||
# 'tool_groups_id': self.after_tool_groups_id.id,
|
||||
# 'handle_code_id': self.handle_code_id.id,
|
||||
# 'integral_freight_barcode_id': self.integral_freight_barcode_id.id,
|
||||
# 'integral_lot_id': self.integral_freight_lot_id.lot_id.id,
|
||||
# 'blade_freight_barcode_id': self.blade_freight_barcode_id.id,
|
||||
# 'blade_lot_id': self.blade_freight_lot_id.lot_id.id,
|
||||
# 'bar_freight_barcode_id': self.bar_freight_barcode_id.id,
|
||||
# 'bar_lot_id': self.bar_freight_lot_id.lot_id.id,
|
||||
# 'pad_freight_barcode_id': self.pad_freight_barcode_id.id,
|
||||
# 'pad_lot_id': self.pad_freight_lot_id.lot_id.id,
|
||||
# 'chuck_freight_barcode_id': self.chuck_freight_barcode_id.id,
|
||||
# 'chuck_lot_id': self.chuck_freight_lot_id.lot_id.id,
|
||||
#
|
||||
# 'after_assembly_functional_tool_name': self.after_assembly_functional_tool_name,
|
||||
# 'after_assembly_functional_tool_type_id': self.after_assembly_functional_tool_type_id.id,
|
||||
# 'after_assembly_functional_tool_diameter': self.after_assembly_functional_tool_diameter,
|
||||
# 'after_assembly_knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
|
||||
# 'after_assembly_new_former': self.after_assembly_new_former,
|
||||
# 'cut_time': self.cut_time,
|
||||
# 'cut_length': self.cut_length,
|
||||
# 'cut_number': self.cut_number,
|
||||
# 'after_assembly_whether_standard_knife': self.after_assembly_whether_standard_knife,
|
||||
# 'after_assembly_coarse_middle_thin': self.after_assembly_coarse_middle_thin,
|
||||
# 'after_assembly_max_lifetime_value': self.after_assembly_max_lifetime_value,
|
||||
# 'after_assembly_alarm_value': self.after_assembly_alarm_value,
|
||||
# 'after_assembly_used_value': self.after_assembly_used_value,
|
||||
# 'after_assembly_tool_loading_length': self.after_assembly_tool_loading_length,
|
||||
# 'after_assembly_handle_length': self.after_assembly_handle_length,
|
||||
# 'after_assembly_functional_tool_length': self.after_assembly_functional_tool_length,
|
||||
# 'after_assembly_effective_length': self.after_assembly_effective_length,
|
||||
# 'L_D_number': self.L_D_number,
|
||||
# 'hiding_length': self.hiding_length,
|
||||
# 'assemble_status': '1',
|
||||
# 'tool_loading_person': self.env.user.name,
|
||||
# 'image': self.image,
|
||||
# 'tool_loading_time': fields.Datetime.now()
|
||||
# }
|
||||
#
|
||||
# def get_desc_2(self, stock_lot, functional_tool_assembly_id):
|
||||
# return {
|
||||
# 'barcode_id': stock_lot.id,
|
||||
# 'code': self.code,
|
||||
# 'name': self.after_name_id.name,
|
||||
# 'tool_name_id': self.after_name_id.id,
|
||||
# 'rfid': self.rfid,
|
||||
# 'tool_groups_id': self.after_tool_groups_id.id,
|
||||
# 'functional_tool_name_id': functional_tool_assembly_id.id,
|
||||
# 'sf_cutting_tool_type_id': self.after_assembly_functional_tool_type_id.id,
|
||||
# 'cutting_tool_integral_model_id': self.integral_product_id.id,
|
||||
# 'cutting_tool_blade_model_id': self.blade_product_id.id,
|
||||
# 'cutting_tool_cutterbar_model_id': self.bar_product_id.id,
|
||||
# 'cutting_tool_cutterpad_model_id': self.pad_product_id.id,
|
||||
# 'cutting_tool_cutterhandle_model_id': self.handle_product_id.id,
|
||||
# 'cutting_tool_cutterhead_model_id': self.chuck_product_id.id,
|
||||
#
|
||||
# 'functional_tool_diameter': self.after_assembly_functional_tool_diameter,
|
||||
# 'knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
|
||||
# 'coarse_middle_thin': self.after_assembly_coarse_middle_thin,
|
||||
# 'new_former': self.after_assembly_new_former,
|
||||
# 'tool_loading_length': self.after_assembly_tool_loading_length,
|
||||
# 'handle_length': self.after_assembly_handle_length,
|
||||
# 'functional_tool_length': self.after_assembly_functional_tool_length,
|
||||
# 'effective_length': self.after_assembly_effective_length,
|
||||
#
|
||||
# 'max_lifetime_value': self.after_assembly_max_lifetime_value,
|
||||
# 'alarm_value': self.after_assembly_alarm_value,
|
||||
# 'used_value': self.after_assembly_used_value,
|
||||
# 'whether_standard_knife': self.after_assembly_whether_standard_knife,
|
||||
# 'L_D_number': self.L_D_number,
|
||||
# 'hiding_length': self.hiding_length,
|
||||
# 'cut_time': self.cut_time,
|
||||
# 'cut_length': self.cut_length,
|
||||
# 'cut_number': self.cut_number,
|
||||
# 'image': self.image,
|
||||
# }
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
def create_tool_stocking_picking(self, stock_lot, functional_tool_assembly, obj):
|
||||
"""
|
||||
创建功能刀具组装入库单
|
||||
"""
|
||||
# 获取名称为刀具组装入库的作业类型
|
||||
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '刀具组装入库')])
|
||||
# 创建刀具组装入库单
|
||||
picking_id = self.env['stock.picking'].create({
|
||||
'name': self._get_name_stock(picking_type_id),
|
||||
'picking_type_id': picking_type_id.id,
|
||||
'location_id': picking_type_id.default_location_src_id.id,
|
||||
'location_dest_id': picking_type_id.default_location_dest_id.id,
|
||||
'origin': obj.assembly_order_code
|
||||
})
|
||||
# 创建作业详情对象记录,并绑定到刀具组装入库单
|
||||
self.env['stock.move.line'].create({
|
||||
'picking_id': picking_id.id,
|
||||
'product_id': stock_lot.product_id.id,
|
||||
'location_id': picking_id.location_id.id,
|
||||
'location_dest_id': picking_id.location_dest_id.id,
|
||||
'lot_id': stock_lot.id,
|
||||
'install_tool_time': fields.Datetime.now(),
|
||||
'qty_done': 1,
|
||||
'functional_tool_name_id': functional_tool_assembly.id,
|
||||
'functional_tool_type_id': obj.functional_tool_type_id.id,
|
||||
'diameter': obj.after_assembly_functional_tool_diameter,
|
||||
'knife_tip_r_angle': obj.after_assembly_knife_tip_r_angle,
|
||||
'code': obj.code,
|
||||
'rfid': obj.rfid,
|
||||
'functional_tool_name': obj.after_assembly_functional_tool_name,
|
||||
'tool_groups_id': obj.after_tool_groups_id.id
|
||||
})
|
||||
# 将刀具组装入库单的状态更改为就绪
|
||||
picking_id.action_confirm()
|
||||
picking_id.button_validate()
|
||||
|
||||
def _get_name_stock(self, picking_type_id):
|
||||
name = picking_type_id.sequence_id.prefix + str(
|
||||
datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d"))
|
||||
stock_id = self.env['stock.picking'].sudo().search(
|
||||
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
|
||||
limit=1,
|
||||
order="id desc"
|
||||
)
|
||||
if not stock_id:
|
||||
num = "%03d" % 1
|
||||
else:
|
||||
m = int(stock_id.name[-3:]) + 1
|
||||
num = "%03d" % m
|
||||
return name + str(num)
|
||||
|
||||
def create_tool_stocking_picking1(self, obj):
|
||||
"""
|
||||
创建刀具物料出库单
|
||||
"""
|
||||
# 获取名称为内部调拨的作业类型
|
||||
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '内部调拨')])
|
||||
# 创建刀具物料出库单
|
||||
picking_id = self.env['stock.picking'].create({
|
||||
'name': self._get_name_stock1(picking_type_id),
|
||||
'picking_type_id': picking_type_id.id,
|
||||
'location_id': self.env['stock.location'].search([('name', '=', '刀具房')]).id,
|
||||
'location_dest_id': self.env['stock.location'].search([('name', '=', '刀具组装位置')]).id,
|
||||
'origin': obj.assembly_order_code
|
||||
})
|
||||
# =============刀具物料出库===================
|
||||
stock_move_id = self.env['stock.move']
|
||||
datas = {'data': [], 'picking_id': picking_id}
|
||||
if obj.handle_code_id:
|
||||
# 修改刀柄序列号状态为【在用】
|
||||
obj.handle_code_id.sudo().write({'tool_material_status': '在用'})
|
||||
datas['data'].append(
|
||||
{'current_location_id': self.env['sf.shelf.location'], 'lot_id': obj.handle_code_id})
|
||||
if obj.integral_product_id:
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.integral_freight_barcode_id, 'lot_id': obj.integral_freight_lot_id.lot_id})
|
||||
if obj.blade_product_id:
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.blade_freight_barcode_id, 'lot_id': obj.blade_freight_lot_id.lot_id})
|
||||
if obj.bar_product_id:
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.bar_freight_barcode_id, 'lot_id': obj.bar_freight_lot_id.lot_id})
|
||||
if obj.pad_product_id:
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.pad_freight_barcode_id, 'lot_id': obj.pad_freight_lot_id.lot_id})
|
||||
if obj.chuck_product_id:
|
||||
datas['data'].append(
|
||||
{'current_location_id': obj.chuck_freight_barcode_id, 'lot_id': obj.chuck_freight_lot_id.lot_id})
|
||||
# 创建刀具物料出库库存移动记录
|
||||
stock_move_id.create_tool_material_stock_moves(datas)
|
||||
# 将刀具物料出库库单的状态更改为就绪
|
||||
picking_id.action_confirm()
|
||||
# 修改刀具物料出库移动历史记录
|
||||
stock_move_id.write_tool_material_stock_move_lines(datas)
|
||||
# 设置数量,并验证完成
|
||||
picking_id.action_set_quantities_to_reservation()
|
||||
picking_id.button_validate()
|
||||
logging.info(f'刀具物料调拨单状态:{picking_id.state}')
|
||||
|
||||
def _get_name_stock1(self, picking_type_id):
|
||||
name = f'{picking_type_id.sequence_id.prefix}DJ/{date.today().strftime("%y")}'
|
||||
stock_id = self.env['stock.picking'].sudo().search(
|
||||
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
|
||||
limit=1,
|
||||
order="id desc"
|
||||
)
|
||||
if not stock_id:
|
||||
num = "%05d" % 1
|
||||
else:
|
||||
m = int(stock_id.name[-5:]) + 1
|
||||
num = "%05d" % m
|
||||
return name + str(num)
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = 'stock.move'
|
||||
|
||||
def create_tool_material_stock_moves(self, datas):
|
||||
picking_id = datas['picking_id']
|
||||
data = datas['data']
|
||||
stock_move_ids = []
|
||||
for res in data:
|
||||
if res:
|
||||
# 创建库存移动记录
|
||||
stock_move_id = self.env['stock.move'].sudo().create({
|
||||
'name': picking_id.name,
|
||||
'picking_id': picking_id.id,
|
||||
'product_id': res['lot_id'].product_id.id,
|
||||
'location_id': picking_id.location_id.id,
|
||||
'location_dest_id': picking_id.location_dest_id.id,
|
||||
'product_uom_qty': 1.00,
|
||||
'reserved_availability': 1.00
|
||||
})
|
||||
stock_move_ids.append(stock_move_id)
|
||||
return stock_move_ids
|
||||
|
||||
def write_tool_material_stock_move_lines(self, datas):
|
||||
picking_id = datas['picking_id']
|
||||
data = datas['data']
|
||||
move_line_ids = picking_id.move_line_ids
|
||||
for move_line_id in move_line_ids:
|
||||
for res in data:
|
||||
if move_line_id.lot_id.product_id == res['lot_id'].product_id:
|
||||
move_line_id.write({
|
||||
'current_location_id': res.get('current_location_id').id,
|
||||
'lot_id': res.get('lot_id').id
|
||||
})
|
||||
return True
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
def create_assemble_warehouse_receipt(self, tool_assembly_order_id, functional_tool_assembly, obj):
|
||||
"""
|
||||
创建功能刀具批次/序列号记录
|
||||
"""
|
||||
product_id = self.env['product.product'].search([('categ_type', '=', '功能刀具'), ('tracking', '=', 'serial')])
|
||||
if not product_id:
|
||||
logging.info('没有搜索到功能刀具产品:%s' % product_id)
|
||||
raise ValidationError('没有找到按唯一序列号追溯的功能刀具产品信息!')
|
||||
stock_lot = self.env['stock.lot'].create({
|
||||
'name': self.get_stock_lot_name(obj),
|
||||
'product_id': product_id[0].id,
|
||||
'company_id': self.env.company.id
|
||||
})
|
||||
return stock_lot
|
||||
|
||||
def get_stock_lot_name(self, obj):
|
||||
"""
|
||||
生成功能刀具序列号
|
||||
"""
|
||||
company = obj.cutting_tool_cutterhandle_model_id.code.split('-', 1)[0]
|
||||
new_time = datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d")
|
||||
code = '%s-GNDJ-%s-%s' % (company, obj.after_assembly_functional_tool_type_id.code, new_time)
|
||||
stock_lot_id = self.env['stock.lot'].sudo().search(
|
||||
[('name', 'like', code)], limit=1, order="id desc")
|
||||
if not stock_lot_id:
|
||||
num = "%03d" % 1
|
||||
else:
|
||||
m = int(stock_lot_id.name[-3:]) + 1
|
||||
num = "%03d" % m
|
||||
return '%s-%s' % (code, num)
|
||||
|
||||
@@ -427,10 +427,10 @@
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button string="确定" name="functional_tool_assembly" type="object" class="btn-primary"
|
||||
attrs="{'invisible': [('obtain_measurement_status', '=', False),('enable_tool_presetter', '=', True)]}"
|
||||
confirm="是否确认申请组装"/>
|
||||
<button string="取消" class="btn-secondary" special="cancel"/>
|
||||
<!-- <button string="确定" name="functional_tool_assembly" type="object" class="btn-primary"-->
|
||||
<!-- attrs="{'invisible': [('obtain_measurement_status', '=', False),('enable_tool_presetter', '=', True)]}"-->
|
||||
<!-- confirm="是否确认申请组装"/>-->
|
||||
<!-- <button string="取消" class="btn-secondary" special="cancel"/>-->
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -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