Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# 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):
|
class MrpProduction(models.Model):
|
||||||
@@ -12,7 +12,8 @@ class MrpProduction(models.Model):
|
|||||||
check_ids = fields.One2many('quality.check', 'production_id', string="Checks")
|
check_ids = fields.One2many('quality.check', 'production_id', string="Checks")
|
||||||
|
|
||||||
def _split_productions(self, amounts=False, cancel_remaining_qty=False, set_consumed_qty=False):
|
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:]
|
backorders = productions[1:]
|
||||||
if not backorders:
|
if not backorders:
|
||||||
return productions
|
return productions
|
||||||
@@ -20,3 +21,4 @@ class MrpProduction(models.Model):
|
|||||||
if wo.current_quality_check_id.component_id:
|
if wo.current_quality_check_id.component_id:
|
||||||
wo.current_quality_check_id._update_component_quantity()
|
wo.current_quality_check_id._update_component_quantity()
|
||||||
return productions
|
return productions
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,44 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Manufacturing Order for Planing view -->
|
<!-- Manufacturing Order for Planing view -->
|
||||||
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
|
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
|
||||||
<field name="name">mrp.production.tree.inherit.planning</field>
|
<field name="name">mrp.production.tree.inherit.planning</field>
|
||||||
<field name="model">mrp.production</field>
|
<field name="model">mrp.production</field>
|
||||||
<field name="arch" type="xml">
|
<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">
|
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'"
|
||||||
<field name="message_needaction" invisible="1"/>
|
decoration-danger="date_planned_start<current_date and state not in ('done','cancel')"
|
||||||
<field name="name"/>
|
decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
|
||||||
<field name="date_planned_start"/>
|
<field name="message_needaction" invisible="1"/>
|
||||||
<field name="product_id"/>
|
<field name="name"/>
|
||||||
<field name="product_qty" sum="Total Qty" string="Quantity"/>
|
<field name="date_planned_start"/>
|
||||||
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
|
<field name="product_id"/>
|
||||||
<field name="reservation_state" string="Availability"/>
|
<field name="product_qty" sum="Total Qty" string="Quantity"/>
|
||||||
<field name="origin"/>
|
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}"
|
||||||
<field name="state"/>
|
groups="uom.group_uom"/>
|
||||||
</tree>
|
<field name="reservation_state" string="Availability"/>
|
||||||
</field>
|
<field name="origin"/>
|
||||||
</record>
|
<field name="state"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
|
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
|
||||||
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
|
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
|
||||||
<!-- <field name="model">mrp.production</field>-->
|
<!-- <field name="model">mrp.production</field>-->
|
||||||
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
|
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
|
||||||
<!-- <field name="arch" type="xml">-->
|
<!-- <field name="arch" type="xml">-->
|
||||||
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
|
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
|
||||||
<!-- <!– 这里放置替换后的内容 –>-->
|
<!-- <!– 这里放置替换后的内容 –>-->
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
<!-- <xpath expr="//notebook" position="after">-->
|
<!-- <xpath expr="//notebook" position="after">-->
|
||||||
<!-- <div class="oe_chatter">-->
|
<!-- <div class="oe_chatter">-->
|
||||||
<!-- <field name="message_follower_ids"/>-->
|
<!-- <field name="message_follower_ids"/>-->
|
||||||
<!-- <field name="activity_ids"/>-->
|
<!-- <field name="activity_ids"/>-->
|
||||||
<!-- <field name="message_ids"/>-->
|
<!-- <field name="message_ids"/>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- </xpath>-->
|
<!-- </xpath>-->
|
||||||
<!-- </field>-->
|
<!-- </field>-->
|
||||||
<!-- </record>-->
|
<!-- </record>-->
|
||||||
|
|
||||||
<record id="mrp_production_view_search_inherit_planning" model="ir.ui.view">
|
<record id="mrp_production_view_search_inherit_planning" model="ir.ui.view">
|
||||||
<field name="name">mrp.production.search.view.inherit.planning</field>
|
<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="inherit_id" ref="mrp.view_mrp_production_filter"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<filter name="filter_planned" position="attributes">
|
<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>
|
</filter>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -51,30 +56,33 @@
|
|||||||
<record id="production_order_unplan_server_action" model="ir.actions.server">
|
<record id="production_order_unplan_server_action" model="ir.actions.server">
|
||||||
<field name="name">Unplan orders</field>
|
<field name="name">Unplan orders</field>
|
||||||
<field name="model_id" ref="mrp.model_mrp_production"/>
|
<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="binding_view_types">list</field>
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code">records.button_unplan()</field>
|
<field name="code">records.button_unplan()</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="mrp.act_product_mrp_production_workcenter" model="ir.actions.act_window">
|
<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"/>
|
<field name="view_id" ref="mrp_production_tree_view_planning"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
<menuitem id="mrp_workorder_menu_planning"
|
<menuitem id="mrp_workorder_menu_planning"
|
||||||
name="Work Orders"
|
name="Work Orders"
|
||||||
sequence="2"
|
sequence="2"
|
||||||
parent="mrp.mrp_planning_menu_root"
|
parent="mrp.mrp_planning_menu_root"
|
||||||
groups="mrp.group_mrp_routings"/>
|
groups="mrp.group_mrp_routings"/>
|
||||||
<menuitem id="menu_mrp_workorder_production"
|
<menuitem id="menu_mrp_workorder_production"
|
||||||
name="Planning by Production"
|
name="Planning by Production"
|
||||||
sequence="1"
|
sequence="1"
|
||||||
action="mrp.action_mrp_workorder_production"
|
action="mrp.action_mrp_workorder_production"
|
||||||
parent="mrp_workorder_menu_planning"/>
|
parent="mrp_workorder_menu_planning"/>
|
||||||
<menuitem id="menu_mrp_workorder_workcenter"
|
<menuitem id="menu_mrp_workorder_workcenter"
|
||||||
name="Planning by Workcenter"
|
name="Planning by Workcenter"
|
||||||
sequence="2"
|
sequence="2"
|
||||||
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
|
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
|
||||||
parent="mrp_workorder_menu_planning"/>
|
parent="mrp_workorder_menu_planning"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
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 { formView } from '@web/views/form/form_view';
|
||||||
import { FormController } from '@web/views/form/form_controller';
|
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";
|
import { onRendered, onMounted } from "@odoo/owl";
|
||||||
|
|
||||||
export class RemoveFocusController extends FormController {
|
export class RemoveFocusFormController extends FormController {
|
||||||
setup() {
|
setup() {
|
||||||
super.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,
|
...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>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
|
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
|
||||||
<field name="model">sf.production.process.parameter</field>
|
<field name="model">sf.production.process.parameter</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
@@ -26,19 +25,19 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="code" readonly="1"/>
|
<field name="code" readonly="1"/>
|
||||||
<field name="process_id" readonly="1"/>
|
<field name="process_id" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||||
<field name="process_description" readonly="1"/>
|
<field name="process_description" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||||
<field name="gain_way"/>
|
<field name="gain_way"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="processing_day" readonly="1"/>
|
<field name="processing_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||||
<field name="travel_day" readonly="1"/>
|
<field name="travel_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||||
<field name="processing_mm" readonly="1"/>
|
<field name="processing_mm" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="适用材料">
|
<page string="适用材料">
|
||||||
<field name="materials_model_ids" readonly="1"></field>
|
<field name="materials_model_ids" attrs="{'readonly': [('code', '!=', False)]}"></field>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|||||||
@@ -80,10 +80,10 @@
|
|||||||
<field name="name">sf.cutter.function.tree</field>
|
<field name="name">sf.cutter.function.tree</field>
|
||||||
<field name="model">sf.functional.cutting.tool.model</field>
|
<field name="model">sf.functional.cutting.tool.model</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="功能刀具类型" create="0" delete="0" edit="1">
|
<tree string="功能刀具类型" create="0" delete="0" edit="1" editable="bottom">
|
||||||
<field name="name" string="名称"/>
|
<field name="name" string="名称" readonly="1"/>
|
||||||
<field name="code"/>
|
<field name="code" readonly="1"/>
|
||||||
<field name="remark"/>
|
<field name="remark" readonly="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class StatusChange(models.Model):
|
|||||||
if not ret.get('error'):
|
if not ret.get('error'):
|
||||||
logging.info('接口已经执行=============')
|
logging.info('接口已经执行=============')
|
||||||
else:
|
else:
|
||||||
logging.error('工厂加工同步订单状态失败 {}'.format(ret.text))
|
logging.error('工厂加工同步订单状态失败 {}'.format(ret))
|
||||||
raise UserError('工厂加工同步订单状态失败')
|
raise UserError('工厂加工同步订单状态失败')
|
||||||
except UserError as e:
|
except UserError as e:
|
||||||
logging.error('工厂加工同步订单状态失败 {}'.format(e))
|
logging.error('工厂加工同步订单状态失败 {}'.format(e))
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class SfEquipmentSaintenanceStandards(models.Model):
|
|||||||
remark = fields.Char('备注')
|
remark = fields.Char('备注')
|
||||||
maintenance_type = fields.Selection([('保养', '保养'), ("检修", "检修")], string='类型', default='保养')
|
maintenance_type = fields.Selection([('保养', '保养'), ("检修", "检修")], string='类型', default='保养')
|
||||||
name = fields.Char(string='名称')
|
name = fields.Char(string='名称')
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
|
|||||||
@@ -6,15 +6,16 @@
|
|||||||
<field name="name">equipment.maintenance.standards.form</field>
|
<field name="name">equipment.maintenance.standards.form</field>
|
||||||
<field name="model">equipment.maintenance.standards</field>
|
<field name="model">equipment.maintenance.standards</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="设备维保标准">
|
<form string="设备维保标准" delete="false" duplicate="false">
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="code" readonly="1" force_save="1"/>
|
<field name="active" invisible="1"/>
|
||||||
<field name="name" readonly="1" force_save="1"/>
|
<field name="code" readonly="1" force_save="1"/>
|
||||||
<field name="maintenance_equipment_category_id" required="1" />
|
<field name="name" readonly="1" force_save="1"/>
|
||||||
<field name="eq_maintenance_ids" invisible='1'/>
|
<field name="maintenance_equipment_category_id" required="1"/>
|
||||||
<field name="overhaul_ids" invisible='1'/>
|
<field name="eq_maintenance_ids" invisible='1'/>
|
||||||
|
<field name="overhaul_ids" invisible='1'/>
|
||||||
|
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
@@ -50,7 +51,8 @@
|
|||||||
<field name="name">equipment.maintenance.standards.tree</field>
|
<field name="name">equipment.maintenance.standards.tree</field>
|
||||||
<field name="model">equipment.maintenance.standards</field>
|
<field name="model">equipment.maintenance.standards</field>
|
||||||
<field name="arch" type="xml">
|
<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="code" readonly="1" force_save="1"/>
|
||||||
<field name="maintenance_type" required="1"/>
|
<field name="maintenance_type" required="1"/>
|
||||||
<field name="name" required="1"/>
|
<field name="name" required="1"/>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
'sf_manufacturing/static/src/scss/kanban_change.scss',
|
'sf_manufacturing/static/src/scss/kanban_change.scss',
|
||||||
'sf_manufacturing/static/src/xml/button_show_on_tree.xml',
|
'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/workpiece_delivery_wizard_confirm.js',
|
||||||
|
'sf_manufacturing/static/src/js/custom_barcode_handlers.js',
|
||||||
]
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -668,7 +668,7 @@ class Manufacturing_Connect(http.Controller):
|
|||||||
logging.info('AGVDownProduct error:%s' % e)
|
logging.info('AGVDownProduct error:%s' % e)
|
||||||
return json.JSONEncoder().encode(res)
|
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="*")
|
cors="*")
|
||||||
def AGVStationState(self, **kw):
|
def AGVStationState(self, **kw):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -265,3 +265,18 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
'sf_agv_scheduling_mrp_workorder_ref',
|
'sf_agv_scheduling_mrp_workorder_ref',
|
||||||
string='AGV调度',
|
string='AGV调度',
|
||||||
domain=[('state', '!=', '已取消')])
|
domain=[('state', '!=', '已取消')])
|
||||||
|
|
||||||
|
def get_down_product_agv_scheduling(self):
|
||||||
|
"""
|
||||||
|
获取关联的制造订单下产线的agv任务
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
workorder_ids = self.production_id.workorder_ids
|
||||||
|
cnc_workorder = workorder_ids.filtered(
|
||||||
|
lambda w: w.routing_type == 'CNC加工' and w.state == 'done' and w.processing_panel == self.processing_panel
|
||||||
|
)
|
||||||
|
if cnc_workorder:
|
||||||
|
agv_schedulingss = cnc_workorder.agv_scheduling_ids
|
||||||
|
return agv_schedulingss.filtered(lambda a: a.state == '已配送' and a.agv_route_type == '下产线')
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
from datetime import timedelta, time
|
||||||
from collections import defaultdict
|
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.addons.resource.models.resource import Intervals
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
class ResWorkcenter(models.Model):
|
class ResWorkcenter(models.Model):
|
||||||
@@ -41,14 +44,16 @@ class ResWorkcenter(models.Model):
|
|||||||
|
|
||||||
oee_target = fields.Float(
|
oee_target = fields.Float(
|
||||||
string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True)
|
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_start = fields.Float('Setup Time', tracking=True)
|
||||||
time_stop = fields.Float('Cleanup 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)
|
costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True)
|
||||||
|
|
||||||
equipment_status = fields.Selection(
|
equipment_status = fields.Selection(
|
||||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
|
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||||
|
("封存(报废)", "封存(报废)")],
|
||||||
string="设备状态", related='equipment_id.state')
|
string="设备状态", related='equipment_id.state')
|
||||||
|
|
||||||
# @api.depends('equipment_id')
|
# @api.depends('equipment_id')
|
||||||
@@ -127,6 +132,106 @@ class ResWorkcenter(models.Model):
|
|||||||
|
|
||||||
# AGV是否可配送
|
# AGV是否可配送
|
||||||
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
|
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):
|
class ResWorkcenterProductivity(models.Model):
|
||||||
_inherit = 'mrp.workcenter.productivity'
|
_inherit = 'mrp.workcenter.productivity'
|
||||||
|
|||||||
@@ -1340,7 +1340,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
'name': 'button_delivery',
|
'name': 'button_delivery',
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'string': '解除装夹',
|
'string': '解除装夹',
|
||||||
'class': 'btn-primary',
|
'class': 'btn-primary jikimo_button_confirm',
|
||||||
# 'className': 'btn-primary',
|
# 'className': 'btn-primary',
|
||||||
'modifiers': '{"force_show": 1}'
|
'modifiers': '{"force_show": 1}'
|
||||||
})
|
})
|
||||||
@@ -1351,18 +1351,23 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def button_delivery(self):
|
def button_delivery(self):
|
||||||
production_ids = []
|
# production_ids = []
|
||||||
workorder_ids = []
|
# workorder_ids = []
|
||||||
delivery_type = '运送空料架'
|
delivery_type = '运送空料架'
|
||||||
max_num = 4 # 最大配送数量
|
# max_num = 4 # 最大配送数量
|
||||||
if len(self) > max_num:
|
# feeder_station_start_id = False
|
||||||
raise UserError('仅限于拆卸1-4个制造订单,请重新选择')
|
# if len(self) > max_num:
|
||||||
for item in self:
|
# raise UserError('仅限于拆卸1-4个制造订单,请重新选择')
|
||||||
if item.state != 'ready':
|
# for item in self:
|
||||||
raise UserError('请选择状态为【就绪】的工单进行解除装夹')
|
# if item.state != 'ready':
|
||||||
|
# raise UserError('请选择状态为【就绪】的工单进行解除装夹')
|
||||||
production_ids.append(item.production_id.id)
|
#
|
||||||
workorder_ids.append(item.id)
|
# 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 {
|
return {
|
||||||
'name': _('确认'),
|
'name': _('确认'),
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
@@ -1371,11 +1376,12 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
'target': 'new',
|
'target': 'new',
|
||||||
'context': {
|
'context': {
|
||||||
# 'default_delivery_ids': [(6, 0, delivery_ids)],
|
# '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_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_workcenter_id': self.env.context.get('default_workcenter_id'),
|
||||||
'default_confirm_button': '确认解除'
|
'default_confirm_button': '确认解除',
|
||||||
|
# 'default_feeder_station_start_id': feeder_station_start_id,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) {
|
|||||||
title: "确认",
|
title: "确认",
|
||||||
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
|
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
|
||||||
buttons: [
|
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 },
|
{ text: "取消", close: true },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<field name="model">mrp.production</field>
|
<field name="model">mrp.production</field>
|
||||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<!-- <button name="action_cancel" position="before"> -->
|
<!-- <button name="action_cancel" position="before"> -->
|
||||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||||
<!-- </button> -->
|
<!-- </button> -->
|
||||||
<div name="button_box" position="inside">
|
<div name="button_box" position="inside">
|
||||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||||
@@ -25,38 +25,81 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="custom_model_form_view_inherit" model="ir.ui.view">
|
<record id="custom_model_form_view_inherit" model="ir.ui.view">
|
||||||
<field name="name">custom.model.form.view.inherit</field>
|
<field name="name">custom.model.form.view.inherit</field>
|
||||||
<field name="model">mrp.workcenter</field>
|
<field name="model">mrp.workcenter</field>
|
||||||
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr='//form//sheet' position="after">
|
<xpath expr='//form//sheet' position="after">
|
||||||
<div class="oe_chatter">
|
<div class="oe_chatter">
|
||||||
<field name="message_follower_ids"/>
|
<field name="message_follower_ids"/>
|
||||||
<field name="message_ids"/>
|
<field name="message_ids"/>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
<xpath expr="//field[@name='default_capacity'][last()]" position="attributes">
|
||||||
</record>
|
<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">
|
<label for="default_capacity"/>
|
||||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
<div class="o_row">
|
||||||
<field name="model">mrp.workcenter</field>
|
<field name="default_capacity" string="产线日产能"/>
|
||||||
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
|
台
|
||||||
<field name="arch" type="xml">
|
</div>
|
||||||
<!-- Desktop view -->
|
</xpath>
|
||||||
<xpath expr='(//field[@name="name"])[1]' position="after">
|
<xpath expr="//field[@name='default_capacity'][last()]" position="before">
|
||||||
<field name="equipment_status" />
|
<label for="available_machine_number"/>
|
||||||
<field name="equipment_image" />
|
<div class="o_row">
|
||||||
</xpath>
|
<field name="available_machine_number"/>
|
||||||
<xpath expr='(//field[@name="name"])[2]' position="after">
|
台
|
||||||
<field name="equipment_status" />
|
</div>
|
||||||
<field name="equipment_image" widget="image" />
|
<label for="single_machine_capacity"/>
|
||||||
</xpath>
|
<div class="o_row">
|
||||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
<field name="single_machine_capacity"/>
|
||||||
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
|
件/小时
|
||||||
</xpath>
|
</div>
|
||||||
</field>
|
<label for="production_line_hour_capacity"/>
|
||||||
</record>
|
<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">
|
<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="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||||
@@ -73,11 +116,11 @@
|
|||||||
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||||
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<!-------------------->
|
<!-------------------->
|
||||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||||
<field name="model">mrp.workcenter</field>
|
<field name="model">mrp.workcenter</field>
|
||||||
@@ -99,7 +142,7 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- 继承原有的看板视图 -->
|
<!-- 继承原有的看板视图 -->
|
||||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||||
<field name="model">mrp.workcenter</field>
|
<field name="model">mrp.workcenter</field>
|
||||||
@@ -391,9 +434,9 @@
|
|||||||
<field name="model">mrp.production</field>
|
<field name="model">mrp.production</field>
|
||||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<!-- <button name="action_cancel" position="before"> -->
|
<!-- <button name="action_cancel" position="before"> -->
|
||||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||||
<!-- </button> -->
|
<!-- </button> -->
|
||||||
<div name="button_box" position="inside">
|
<div name="button_box" position="inside">
|
||||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<field name="date_planned_start" string="计划开始日期" optional="show"/>
|
<field name="date_planned_start" string="计划开始日期" optional="show"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='date_planned_start']" position="before">
|
<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>
|
||||||
<xpath expr="//field[@name='date_planned_finished']" position="replace">
|
<xpath expr="//field[@name='date_planned_finished']" position="replace">
|
||||||
<field name="date_planned_finished" string="计划结束日期" optional="hide"/>
|
<field name="date_planned_finished" string="计划结束日期" optional="hide"/>
|
||||||
@@ -659,9 +659,9 @@
|
|||||||
<field name="name">工件配送</field>
|
<field name="name">工件配送</field>
|
||||||
<field name="model">sf.workpiece.delivery</field>
|
<field name="model">sf.workpiece.delivery</field>
|
||||||
<field name="arch" type="xml">
|
<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>
|
<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>
|
</header>
|
||||||
<field name="status" widget="badge"
|
<field name="status" widget="badge"
|
||||||
decoration-success="status == '已配送'"
|
decoration-success="status == '已配送'"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
|
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
|
||||||
<field name="model">sf.workpiece.delivery.wizard</field>
|
<field name="model">sf.workpiece.delivery.wizard</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form js_class="remove_focus_view">
|
<form js_class="remove_focus_form_view">
|
||||||
<sheet>
|
<sheet>
|
||||||
<field name="delivery_ids" invisible="True"/>
|
<field name="delivery_ids" invisible="True"/>
|
||||||
<field name="workorder_ids" invisible="True"/>
|
<field name="workorder_ids" invisible="True"/>
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
<field name="workcenter_id" options="{'no_create': True}"/>
|
<field name="workcenter_id" options="{'no_create': True}"/>
|
||||||
</group>
|
</group>
|
||||||
<footer>
|
<footer>
|
||||||
<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="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="取消" class="btn btn-secondary" special="cancel"/>
|
<button string="取消" class="btn btn-secondary" special="cancel"/>
|
||||||
</footer>
|
</footer>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
|
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from odoo.exceptions import UserError, ValidationError
|
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
|
from odoo.exceptions import UserError
|
||||||
from odoo import models, api, fields
|
from odoo import models, api, fields
|
||||||
|
|
||||||
|
|
||||||
@@ -79,29 +78,18 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
|||||||
'tag': 'display_notification',
|
'tag': 'display_notification',
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
'params': {
|
'params': {
|
||||||
'message': '任务下发成功!AGV任务调度编号为【%s】' % scheduling['name'],
|
'message': f'任务下发成功!AGV任务调度编号为【{scheduling["name"]}】',
|
||||||
'type': 'success',
|
'type': 'success',
|
||||||
'sticky': False,
|
'sticky': False,
|
||||||
'next': {'type': 'ir.actions.act_window_close'},
|
'next': {'type': 'ir.actions.act_window_close'},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def confirm(self):
|
def confirm(self):
|
||||||
try:
|
try:
|
||||||
# if self.workorder_id:
|
if not self.workorder_ids:
|
||||||
# self.workorder_id.workpiece_delivery_ids[0].agv_scheduling_id()
|
raise UserError('制造订单不能为空')
|
||||||
# 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:
|
|
||||||
scheduling = self.env['sf.agv.scheduling'].add_scheduling(
|
scheduling = self.env['sf.agv.scheduling'].add_scheduling(
|
||||||
agv_start_site_name=self.feeder_station_start_id.name,
|
agv_start_site_name=self.feeder_station_start_id.name,
|
||||||
agv_route_type=self.delivery_type,
|
agv_route_type=self.delivery_type,
|
||||||
@@ -129,10 +117,21 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
|||||||
item.button_start()
|
item.button_start()
|
||||||
item.button_finish()
|
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:
|
except Exception as e:
|
||||||
logging.info('%s任务下发失败:%s' % (self.delivery_type, e))
|
logging.info('%s任务下发失败:%s', self.delivery_type, e)
|
||||||
raise UserError('%s任务下发失败:%s' % (self.delivery_type, e))
|
raise UserError(f'{self.delivery_type}任务下发失败:{e}') from e
|
||||||
|
|
||||||
# def recognize_production(self):
|
# def recognize_production(self):
|
||||||
# # production_ids = []
|
# # production_ids = []
|
||||||
@@ -180,6 +179,17 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
|||||||
self.feeder_station_destination_id = self.route_id.end_site_id.id
|
self.feeder_station_destination_id = self.route_id.end_site_id.id
|
||||||
|
|
||||||
def on_barcode_scanned(self, barcode):
|
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')
|
delivery_type = self.env.context.get('default_delivery_type')
|
||||||
if delivery_type == '上产线':
|
if delivery_type == '上产线':
|
||||||
workorder = self.env['mrp.workorder'].search(
|
workorder = self.env['mrp.workorder'].search(
|
||||||
@@ -198,11 +208,22 @@ class WorkpieceDeliveryWizard(models.TransientModel):
|
|||||||
if workorder:
|
if workorder:
|
||||||
if (len(self.production_ids) > 0 and
|
if (len(self.production_ids) > 0 and
|
||||||
workorder.production_line_id.id != self.production_ids[0].production_line_id.id):
|
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.production_ids |= workorder.production_id
|
||||||
self.workorder_ids |= workorder
|
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:
|
else:
|
||||||
raise UserError('该rfid码对应的工单不存在')
|
raise UserError('该rfid码对应的工单不存在')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
|
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError, UserError
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
_logger.info("同步刀具物料每齿走刀量完成")
|
_logger.info("同步刀具物料每齿走刀量完成")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.info("捕获错误信息:%s" % e)
|
_logger.info("sf_all_sync error: %s" % e)
|
||||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||||
|
|
||||||
@api.model
|
@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_user", self.ftp_user or "")
|
||||||
ir_config.set_param("ftp_password", self.ftp_password or "")
|
ir_config.set_param("ftp_password", self.ftp_password or "")
|
||||||
ir_config.set_param("enable_tool_presetter", self.enable_tool_presetter or False)
|
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 logging
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
import traceback
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from odoo import models
|
from odoo import models
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
@@ -73,7 +75,8 @@ class MrStaticResourceDataSync(models.Model):
|
|||||||
self.env['sf.feed.per.tooth'].sync_feed_per_tooth_yesterday()
|
self.env['sf.feed.per.tooth'].sync_feed_per_tooth_yesterday()
|
||||||
_logger.info("同步刀具物料每齿走刀量完成")
|
_logger.info("同步刀具物料每齿走刀量完成")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("捕获错误信息:%s" % e)
|
traceback_error = traceback.format_exc()
|
||||||
|
logging.error("同步静态资源库失败:%s" % traceback_error)
|
||||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||||
|
|
||||||
|
|
||||||
@@ -2759,8 +2762,9 @@ class CuttingToolBasicParameters(models.Model):
|
|||||||
if result['status'] == 1:
|
if result['status'] == 1:
|
||||||
if 'basic_parameters_integral_tool' in result['cutting_tool_basic_parameters_yesterday_list']:
|
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']:
|
if result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool']:
|
||||||
basic_parameters_integral_tool_list = json.loads(
|
cutting_tool_basic_parameters_yesterday_list= result['cutting_tool_basic_parameters_yesterday_list']
|
||||||
result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool'])
|
basic_parameters_integral_tool_list = cutting_tool_basic_parameters_yesterday_list['basic_parameters_integral_tool']
|
||||||
|
|
||||||
if basic_parameters_integral_tool_list:
|
if basic_parameters_integral_tool_list:
|
||||||
for integral_tool_item in basic_parameters_integral_tool_list:
|
for integral_tool_item in basic_parameters_integral_tool_list:
|
||||||
integral_tool = self.search(
|
integral_tool = self.search(
|
||||||
|
|||||||
@@ -129,6 +129,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class sf_production_plan(models.Model):
|
|||||||
|
|
||||||
return num
|
return num
|
||||||
|
|
||||||
def do_production_schedule(self):
|
def do_production_schedule(self, date_planned_start):
|
||||||
"""
|
"""
|
||||||
排程方法
|
排程方法
|
||||||
"""
|
"""
|
||||||
@@ -199,6 +199,10 @@ class sf_production_plan(models.Model):
|
|||||||
if not record.production_line_id:
|
if not record.production_line_id:
|
||||||
raise ValidationError("未选择生产线")
|
raise ValidationError("未选择生产线")
|
||||||
else:
|
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
|
workorder_id_list = record.production_id.workorder_ids.ids
|
||||||
if record.production_id:
|
if record.production_id:
|
||||||
if record.production_id.workorder_ids:
|
if record.production_id.workorder_ids:
|
||||||
@@ -249,6 +253,26 @@ class sf_production_plan(models.Model):
|
|||||||
'target': 'current', # 跳转的目标窗口,可以是'current'或'new'
|
'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):
|
def calculate_plan_time_before(self, item, workorder_id_list):
|
||||||
"""
|
"""
|
||||||
根据CNC工单的时间去计算之前的其他工单的开始结束时间
|
根据CNC工单的时间去计算之前的其他工单的开始结束时间
|
||||||
|
|||||||
@@ -291,6 +291,7 @@
|
|||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="res_model">mrp.production</field>
|
<field name="res_model">mrp.production</field>
|
||||||
<field name="view_mode">tree,form</field>
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="domain">[('picking_type_id.active', '=', True)]</field>
|
||||||
</record>
|
</record>
|
||||||
<record model="ir.actions.act_window" id="sale_custom_action">
|
<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 = self.env['sf.production.plan'].browse(plan.id)
|
||||||
plan_obj.production_line_id = self.production_line_id.id
|
plan_obj.production_line_id = self.production_line_id.id
|
||||||
plan.date_planned_start = self.date_planned_start
|
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'
|
# plan_obj.state = 'done'
|
||||||
print('处理计划:', plan.id, '完成')
|
print('处理计划:', plan.id, '完成')
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,18 @@ class QuickEasyOrder(models.Model):
|
|||||||
processing_time = fields.Integer('加工时长(min)')
|
processing_time = fields.Integer('加工时长(min)')
|
||||||
sale_order_id = fields.Many2one('sale.order', '销售订单号')
|
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')
|
@api.depends('unit_price', 'quantity')
|
||||||
def _compute_total_amount(self):
|
def _compute_total_amount(self):
|
||||||
for item in self:
|
for item in self:
|
||||||
|
|||||||
@@ -73,12 +73,14 @@
|
|||||||
<field name="material_model_id" options="{'no_create': True}" required="1"/>
|
<field name="material_model_id" options="{'no_create': True}" required="1"/>
|
||||||
<!-- <field name="process_id"/>-->
|
<!-- <field name="process_id"/>-->
|
||||||
<field name="parameter_ids" widget="many2many_tags" string="表面工艺参数"
|
<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="machining_precision" required="1"/>
|
||||||
<field name="processing_time"/>
|
<field name="processing_time"/>
|
||||||
<field name="quantity" options="{'format': false}"/>
|
<field name="quantity" options="{'format': false}"/>
|
||||||
<field name="unit_price"/>
|
<field name="unit_price"/>
|
||||||
<field name="price" options="{'format': false}"/>
|
<field name="price" options="{'format': false}"/>
|
||||||
|
<field name="part_drawing_number"/>
|
||||||
|
<field name="machining_drawings" widget="image"/>
|
||||||
<field name="sale_order_id"
|
<field name="sale_order_id"
|
||||||
attrs='{"invisible": [("sale_order_id","=",False)],"readonly": [("sale_order_id","!=",False)]}'/>
|
attrs='{"invisible": [("sale_order_id","=",False)],"readonly": [("sale_order_id","!=",False)]}'/>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
4
sf_stock/__init__.py
Normal file
4
sf_stock/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
35
sf_stock/__manifest__.py
Normal file
35
sf_stock/__manifest__.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "sf_stock",
|
||||||
|
|
||||||
|
'summary': """
|
||||||
|
处理代发货业务""",
|
||||||
|
|
||||||
|
'description': """
|
||||||
|
Long description of module's purpose
|
||||||
|
""",
|
||||||
|
|
||||||
|
'author': "My Company",
|
||||||
|
'website': "https://www.yourcompany.com",
|
||||||
|
|
||||||
|
# Categories can be used to filter modules in modules listing
|
||||||
|
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||||
|
# for the full list
|
||||||
|
'category': 'Uncategorized',
|
||||||
|
'version': '0.1',
|
||||||
|
|
||||||
|
# any module necessary for this one to work correctly
|
||||||
|
'depends': ['sf_sale', 'stock'],
|
||||||
|
|
||||||
|
# always loaded
|
||||||
|
'data': [
|
||||||
|
# 'security/ir.model.access.csv',
|
||||||
|
'views/stock_picking.xml',
|
||||||
|
],
|
||||||
|
# only loaded in demonstration mode
|
||||||
|
'demo': [
|
||||||
|
'demo/demo.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
}
|
||||||
3
sf_stock/controllers/__init__.py
Normal file
3
sf_stock/controllers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import controllers
|
||||||
21
sf_stock/controllers/controllers.py
Normal file
21
sf_stock/controllers/controllers.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# from odoo import http
|
||||||
|
|
||||||
|
|
||||||
|
# class SfStock(http.Controller):
|
||||||
|
# @http.route('/sf_stock/sf_stock', auth='public')
|
||||||
|
# def index(self, **kw):
|
||||||
|
# return "Hello, world"
|
||||||
|
|
||||||
|
# @http.route('/sf_stock/sf_stock/objects', auth='public')
|
||||||
|
# def list(self, **kw):
|
||||||
|
# return http.request.render('sf_stock.listing', {
|
||||||
|
# 'root': '/sf_stock/sf_stock',
|
||||||
|
# 'objects': http.request.env['sf_stock.sf_stock'].search([]),
|
||||||
|
# })
|
||||||
|
|
||||||
|
# @http.route('/sf_stock/sf_stock/objects/<model("sf_stock.sf_stock"):obj>', auth='public')
|
||||||
|
# def object(self, obj, **kw):
|
||||||
|
# return http.request.render('sf_stock.object', {
|
||||||
|
# 'object': obj
|
||||||
|
# })
|
||||||
30
sf_stock/demo/demo.xml
Normal file
30
sf_stock/demo/demo.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!--
|
||||||
|
<record id="object0" model="sf_stock.sf_stock">
|
||||||
|
<field name="name">Object 0</field>
|
||||||
|
<field name="value">0</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object1" model="sf_stock.sf_stock">
|
||||||
|
<field name="name">Object 1</field>
|
||||||
|
<field name="value">10</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object2" model="sf_stock.sf_stock">
|
||||||
|
<field name="name">Object 2</field>
|
||||||
|
<field name="value">20</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object3" model="sf_stock.sf_stock">
|
||||||
|
<field name="name">Object 3</field>
|
||||||
|
<field name="value">30</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object4" model="sf_stock.sf_stock">
|
||||||
|
<field name="name">Object 4</field>
|
||||||
|
<field name="value">40</field>
|
||||||
|
</record>
|
||||||
|
-->
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
3
sf_stock/models/__init__.py
Normal file
3
sf_stock/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import stock_picking
|
||||||
110
sf_stock/models/stock_picking.py
Normal file
110
sf_stock/models/stock_picking.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
import logging
|
||||||
|
from odoo.tools import date_utils
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class StockPicking(models.Model):
|
||||||
|
_inherit = 'stock.picking'
|
||||||
|
|
||||||
|
cancel_backorder_ids = fields.Boolean(default=False, string='是否取消后置单据')
|
||||||
|
|
||||||
|
# 重写验证,下发发货到bfm
|
||||||
|
def button_validate(self):
|
||||||
|
info = super(StockPicking, self).button_validate()
|
||||||
|
if self.picking_type_code == 'outgoing':
|
||||||
|
self.send_to_bfm()
|
||||||
|
return info
|
||||||
|
|
||||||
|
def deal_move_ids(self, send_move_ids, send_move_line_ids):
|
||||||
|
move_ids = [] # 本次发货单
|
||||||
|
move_line_ids = [] # 本次发货单行
|
||||||
|
if send_move_ids:
|
||||||
|
for item in send_move_ids:
|
||||||
|
val = {
|
||||||
|
'name': item.product_id.upload_model_file.display_name,
|
||||||
|
'quantity_done': item.quantity_done,
|
||||||
|
'date': date_utils.json_default(item.date) if item.date else None,
|
||||||
|
'description_picking': item.description_picking,
|
||||||
|
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
|
||||||
|
'product_uom_qty': item.product_uom_qty,
|
||||||
|
'sequence': item.sequence,
|
||||||
|
'price_unit': item.price_unit,
|
||||||
|
'priority': item.priority,
|
||||||
|
'state': item.state,
|
||||||
|
}
|
||||||
|
move_ids.append(val)
|
||||||
|
for item in send_move_line_ids:
|
||||||
|
val = {
|
||||||
|
'qty_done': item.qty_done,
|
||||||
|
'reserved_qty': item.reserved_qty,
|
||||||
|
'reserved_uom_qty': item.reserved_uom_qty,
|
||||||
|
'date': date_utils.json_default(item.date) if item.date else None,
|
||||||
|
'description_picking': item.description_picking,
|
||||||
|
'state': item.state,
|
||||||
|
}
|
||||||
|
move_line_ids.append(val)
|
||||||
|
return move_ids, move_line_ids
|
||||||
|
|
||||||
|
def deal_send_backorder_id(self, backorder_ids1):
|
||||||
|
backorder_ids = []
|
||||||
|
|
||||||
|
if backorder_ids1:
|
||||||
|
for item in backorder_ids1:
|
||||||
|
move_ids, move_line_ids = self.deal_move_ids(item.move_ids, item.move_line_ids)
|
||||||
|
val = {
|
||||||
|
'receiverName': item.receiverName,
|
||||||
|
'name': item.sale_id.default_code,
|
||||||
|
'send_no': item.name,
|
||||||
|
'scheduled_date': date_utils.json_default(item.scheduled_date) if item.scheduled_date else None,
|
||||||
|
'date': date_utils.json_default(item.date) if item.date else None,
|
||||||
|
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
|
||||||
|
'date_done': date_utils.json_default(item.date_done) if item.date_done else None,
|
||||||
|
'move_ids': move_ids,
|
||||||
|
'move_line_ids': move_line_ids,
|
||||||
|
'state': item.state,
|
||||||
|
'move_type': item.move_type,
|
||||||
|
}
|
||||||
|
backorder_ids.append(val)
|
||||||
|
return backorder_ids
|
||||||
|
|
||||||
|
def send_to_bfm(self):
|
||||||
|
skip_backorder = self.env.context.get('skip_backorder')
|
||||||
|
# 下发发货到bfm
|
||||||
|
config = self.env['res.config.settings'].get_values()
|
||||||
|
move_ids, move_line_ids = self.deal_move_ids(self.move_ids, self.move_line_ids)
|
||||||
|
data = {
|
||||||
|
'params': {
|
||||||
|
'receiverName': self.receiverName,
|
||||||
|
'priority': self.priority,
|
||||||
|
'name': self.sale_id.default_code,
|
||||||
|
'send_no': self.name,
|
||||||
|
'scheduled_date': date_utils.json_default(self.scheduled_date) if self.scheduled_date else None,
|
||||||
|
'date': date_utils.json_default(self.date) if self.date else None,
|
||||||
|
'date_deadline': date_utils.json_default(self.date_deadline) if self.date_deadline else None,
|
||||||
|
'date_done': date_utils.json_default(self.date_done) if self.date_done else None,
|
||||||
|
'move_ids': move_ids,
|
||||||
|
'move_line_ids': move_line_ids,
|
||||||
|
'state': self.state,
|
||||||
|
'backorder_id': self.deal_send_backorder_id(self.backorder_id),
|
||||||
|
'backorder_ids': self.deal_send_backorder_id(self.backorder_ids),
|
||||||
|
'cancel_backorder_ids': skip_backorder,
|
||||||
|
'move_type': self.move_type,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url1 = config['bfm_url_new'] + '/api/stock/deliver_goods'
|
||||||
|
json_str = json.dumps(data)
|
||||||
|
print('json_str', json_str)
|
||||||
|
r = requests.post(url1, json=data, data=None)
|
||||||
|
if r.status_code == 200:
|
||||||
|
result = json.loads(r.json()['result'])
|
||||||
|
if result['code'] != 200:
|
||||||
|
raise UserError(result['message'] or '工厂发货下发bfm失败')
|
||||||
|
else:
|
||||||
|
raise UserError('工厂发货下发bfm失败')
|
||||||
2
sf_stock/security/ir.model.access.csv
Normal file
2
sf_stock/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_sf_stock_sf_stock,sf_stock.sf_stock,model_sf_stock_sf_stock,base.group_user,1,1,1,1
|
||||||
|
5
sf_stock/views/stock_picking.xml
Normal file
5
sf_stock/views/stock_picking.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
""",
|
""",
|
||||||
'category': 'sf',
|
'category': 'sf',
|
||||||
'website': 'https://www.sf.jikimo.com',
|
'website': 'https://www.sf.jikimo.com',
|
||||||
'depends': ['sf_manufacturing'],
|
'depends': ['sf_manufacturing', 'sf_base'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/group_security.xml',
|
'security/group_security.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
@@ -24,6 +24,11 @@
|
|||||||
'views/menu_view.xml',
|
'views/menu_view.xml',
|
||||||
'views/stock.xml',
|
'views/stock.xml',
|
||||||
'data/tool_data.xml',
|
'data/tool_data.xml',
|
||||||
|
'wizard/jikimo_bom_wizard.xml',
|
||||||
|
'views/tool_inventory.xml',
|
||||||
|
'views/jikimo_bom.xml',
|
||||||
|
'views/tool_views.xml',
|
||||||
|
|
||||||
],
|
],
|
||||||
'demo': [
|
'demo': [
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -8,4 +8,7 @@ from . import fixture_material_search
|
|||||||
from . import fixture_enroll
|
from . import fixture_enroll
|
||||||
from . import temporary_data_processing_methods
|
from . import temporary_data_processing_methods
|
||||||
from . import stock
|
from . import stock
|
||||||
|
from . import jikimo_bom
|
||||||
|
from . import tool_inventory
|
||||||
|
from . import functional_cutting_tool_model
|
||||||
|
# from . import product_product
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class SyncFunctionalCuttingToolModel(models.Model):
|
||||||
|
_inherit = 'sf.functional.cutting.tool.model'
|
||||||
|
cutting_tool_type_ids = fields.Many2many('sf.cutting.tool.type', string='适用刀具物料类型', required=True)
|
||||||
@@ -50,7 +50,7 @@ class ToolDatasync(models.Model):
|
|||||||
# self.env['sf.real.time.distribution.of.functional.tools'].sudo().sync_enroll_functional_tool_real_time_distribution_all()
|
# self.env['sf.real.time.distribution.of.functional.tools'].sudo().sync_enroll_functional_tool_real_time_distribution_all()
|
||||||
# logging.info("功能刀具安全库存每日同步成功")
|
# logging.info("功能刀具安全库存每日同步成功")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("捕获错误信息:%s" % e)
|
logging.info("刀具物料、刀具信息同步失败:%s" % e)
|
||||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||||
|
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@ class FunctionalToolWarning(models.Model):
|
|||||||
else:
|
else:
|
||||||
logging.info('没有注册功能刀具预警信息')
|
logging.info('没有注册功能刀具预警信息')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("捕获错误信息:%s" % e)
|
logging.info("功能刀具预警同步失败:%s" % e)
|
||||||
|
|
||||||
|
|
||||||
class StockMoveLine(models.Model):
|
class StockMoveLine(models.Model):
|
||||||
@@ -373,7 +373,7 @@ class StockMoveLine(models.Model):
|
|||||||
else:
|
else:
|
||||||
logging.info('没有注册功能刀具出入库记录信息')
|
logging.info('没有注册功能刀具出入库记录信息')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("捕获错误信息:%s" % e)
|
logging.info("出入库记录信息同步失败:%s" % e)
|
||||||
|
|
||||||
|
|
||||||
class RealTimeDistributionFunctionalTools(models.Model):
|
class RealTimeDistributionFunctionalTools(models.Model):
|
||||||
@@ -446,4 +446,4 @@ class RealTimeDistributionFunctionalTools(models.Model):
|
|||||||
else:
|
else:
|
||||||
logging.info('没有注册功能刀具出入库记录信息')
|
logging.info('没有注册功能刀具出入库记录信息')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("捕获错误信息:%s" % e)
|
logging.info("实时功能刀具同步失败:%s" % e)
|
||||||
|
|||||||
134
sf_tool_management/models/jikimo_bom.py
Normal file
134
sf_tool_management/models/jikimo_bom.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from collections import Counter
|
||||||
|
from xml import etree
|
||||||
|
|
||||||
|
from odoo import models, fields, api, Command
|
||||||
|
from odoo.exceptions import UserError, AccessError
|
||||||
|
from odoo.http import request
|
||||||
|
|
||||||
|
|
||||||
|
class jikimo_bom(models.Model):
|
||||||
|
_name = 'jikimo.bom'
|
||||||
|
_description = '功能刀具物料清单'
|
||||||
|
tool_inventory_id = fields.Many2one('sf.tool.inventory', '功能刀具清单')
|
||||||
|
tool_name = fields.Char(related="tool_inventory_id.name", string='功能刀具名称')
|
||||||
|
functional_cutting_tool_model_id = fields.Many2one(related='tool_inventory_id.functional_cutting_tool_model_id',
|
||||||
|
string='功能刀具类型')
|
||||||
|
tool_groups_id = fields.Many2one(related='tool_inventory_id.tool_groups_id', string='刀具组')
|
||||||
|
tool_length = fields.Float(related='tool_inventory_id.tool_length', string='刀具总长(mm)')
|
||||||
|
diameter = fields.Float(related='tool_inventory_id.diameter', string='直径(mm)')
|
||||||
|
angle = fields.Float(related='tool_inventory_id.angle', string='R角(mm)')
|
||||||
|
extension = fields.Float(related='tool_inventory_id.extension', string='伸出长度(mm)')
|
||||||
|
product_ids = fields.Many2many('product.product', string='产品')
|
||||||
|
knife_handle_model = fields.Selection(related='tool_inventory_id.knife_handle_model', string='使用刀柄型号')
|
||||||
|
options = fields.Char('产品清单')
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
result = []
|
||||||
|
for bom in self:
|
||||||
|
result.append((bom.id, '功能刀具物料清单'))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def check_types_in_list(self):
|
||||||
|
# 统计每个元素的类型
|
||||||
|
type_counts = Counter(item.cutting_tool_material_id.name for item in self.product_ids)
|
||||||
|
return all(count > 0 for count in type_counts.values()) and len(type_counts) == self.options.split('+')
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
# 在更新模型时记录旧的 Many2many ID 列表
|
||||||
|
if 'product_ids' in vals:
|
||||||
|
old_product_counter = Counter(self.product_ids.ids)
|
||||||
|
super(jikimo_bom, self).write(vals)
|
||||||
|
new_product_counter = Counter(self.product_ids.ids)
|
||||||
|
delete_product_counter = old_product_counter - new_product_counter
|
||||||
|
if delete_product_counter:
|
||||||
|
# 删除操作
|
||||||
|
if self.check_types_in_list():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise UserError('每种物料最少要有一个')
|
||||||
|
return super(jikimo_bom, self).write(vals)
|
||||||
|
|
||||||
|
def bom_product_domains(self, assembly_options):
|
||||||
|
self.options = assembly_options
|
||||||
|
cutting_tool_materials = self.env['sf.cutting.tool.material'].search(
|
||||||
|
[('name', 'in', assembly_options.split('+'))])
|
||||||
|
domains = []
|
||||||
|
for index, option in enumerate(cutting_tool_materials):
|
||||||
|
domain = ['&', ('cutting_tool_material_id', '=', option.id),
|
||||||
|
("cutting_tool_type_id", "in",
|
||||||
|
self.tool_inventory_id.functional_cutting_tool_model_id.cutting_tool_type_ids.ids)]
|
||||||
|
if option.name == '刀柄':
|
||||||
|
domain = ['&'] + domain + [
|
||||||
|
("cutting_tool_taper_shank_model", "=", self.tool_inventory_id.knife_handle_model)]
|
||||||
|
|
||||||
|
if option.name == '整体式刀具':
|
||||||
|
domain = ['&'] + domain + [
|
||||||
|
'|',
|
||||||
|
# 刀具直径
|
||||||
|
('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
|
||||||
|
|
||||||
|
# r角
|
||||||
|
('cutting_tool_blade_tip_working_size', '=', self.tool_inventory_id.angle)]
|
||||||
|
if option.name == '刀杆':
|
||||||
|
domain = ['&'] + domain + [
|
||||||
|
("cutting_tool_cutter_arbor_diameter", "=", self.tool_inventory_id.diameter)]
|
||||||
|
if option.name == '刀片':
|
||||||
|
domain = ['&'] + domain + [
|
||||||
|
("cutting_tool_blade_tip_circular_arc_radius", "=", self.tool_inventory_id.angle)]
|
||||||
|
if option.name == '刀盘':
|
||||||
|
domain = ['&'] + domain + [
|
||||||
|
("cutting_tool_cutter_head_diameter", "=", self.tool_inventory_id.diameter)]
|
||||||
|
domains = domains + domain
|
||||||
|
if index != 0:
|
||||||
|
domains = ['|'] + domains
|
||||||
|
# wqwqwe = self.env['product.product'].search(ddd)
|
||||||
|
# product = self.env['product.product'].search(domain)
|
||||||
|
# if product:
|
||||||
|
# products = products + product
|
||||||
|
domains = domains + [('stock_move_count', '>', 0)]
|
||||||
|
return domains
|
||||||
|
|
||||||
|
def generate_bill_materials(self, assembly_options):
|
||||||
|
domains = self.bom_product_domains(assembly_options)
|
||||||
|
products = self.env['product.product'].search(domains)
|
||||||
|
if products:
|
||||||
|
self.product_ids = [Command.set(products.ids)]
|
||||||
|
# if option.name == '刀盘':
|
||||||
|
# hilt = self.env['product.product'].search(
|
||||||
|
# [('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
|
||||||
|
# ('cutting_tool_material_id', '=', option.id)])
|
||||||
|
# self.product_ids = [Command.set(hilt.ids)]k
|
||||||
|
|
||||||
|
|
||||||
|
class jikimo_bom_line(models.Model):
|
||||||
|
_name = 'jikimo.bom.line'
|
||||||
|
_description = 'jikimo.bom.line'
|
||||||
|
|
||||||
|
name = fields.Char()
|
||||||
|
|
||||||
|
|
||||||
|
class ProductProduct(models.Model):
|
||||||
|
_inherit = 'product.product'
|
||||||
|
_order = 'cutting_tool_material_id, cutting_tool_type_id'
|
||||||
|
stock_move_count = fields.Integer(string='stock_move count', compute='_compute_stock_move_count', store=True)
|
||||||
|
|
||||||
|
@api.depends('stock_move_ids')
|
||||||
|
def _compute_stock_move_count(self):
|
||||||
|
for record in self:
|
||||||
|
if record.stock_move_ids:
|
||||||
|
record.stock_move_count = len(record.stock_move_ids)
|
||||||
|
else:
|
||||||
|
record.stock_move_count = 0
|
||||||
|
|
||||||
|
def search(self, args, offset=0, limit=None, order=None, count=False):
|
||||||
|
# 你可以在这里修改 `args` 以调整搜索条件
|
||||||
|
# 例如,添加额外的搜索条件
|
||||||
|
if self.env.context.get('jikimo_bom_product'):
|
||||||
|
bom_id = self.env['jikimo.bom'].browse(request.session.get('jikimo_bom_product').get('bom_id'))
|
||||||
|
|
||||||
|
if not bom_id.options:
|
||||||
|
raise UserError('请先选择组装方式')
|
||||||
|
domains = bom_id.bom_product_domains(bom_id.options)
|
||||||
|
args = args + domains
|
||||||
|
return super(ProductProduct, self).search(args, offset=offset, limit=limit, order=order, count=count)
|
||||||
34
sf_tool_management/models/tool_inventory.py
Normal file
34
sf_tool_management/models/tool_inventory.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from odoo import models, fields
|
||||||
|
from odoo.http import request
|
||||||
|
|
||||||
|
|
||||||
|
class ToolInventory(models.Model):
|
||||||
|
_inherit = 'sf.tool.inventory'
|
||||||
|
_description = '功能刀具清单'
|
||||||
|
knife_handle_model = fields.Selection([('BT30', 'BT30'), ('BT40', 'BT40'), ('BT50', 'BT50'), ('GSK30', 'GSK30'), ('GSK40', 'GSK40'), ('GSK50', 'GSK50')], string='使用刀柄型号', required=True)
|
||||||
|
jikimo_bom_ids = fields.One2many('jikimo.bom','tool_inventory_id', 'bom单')
|
||||||
|
blade_length = fields.Float('刃长(mm)')
|
||||||
|
def bom_mainfest(self):
|
||||||
|
jikimo_bom_ids = self.mapped('jikimo_bom_ids')
|
||||||
|
if not jikimo_bom_ids:
|
||||||
|
self._bom_mainfest()
|
||||||
|
return self.bom_mainfest()
|
||||||
|
request.session['jikimo_bom_product'] = {'bom_id': int(self.jikimo_bom_ids)}
|
||||||
|
# context = dict(self.env.context)
|
||||||
|
# context.update({'jikimo_bom_product': self.jikimo_bom_ids.options})
|
||||||
|
# if self.functional_cutting_tool_model_id.cutting_tool_type_ids:
|
||||||
|
# context.update({'jikimo_bom_product_cutting_tool_type': self.functional_cutting_tool_model_id.cutting_tool_type_ids.ids})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': '刀具组装清单',
|
||||||
|
'res_model': 'jikimo.bom',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'view_id': self.env.ref('sf_tool_management.view_jikimo_bom_form').id,
|
||||||
|
'res_id': int(self.jikimo_bom_ids),
|
||||||
|
'target': 'current', # Use 'new' to open in a new window/tab
|
||||||
|
# {'jikimo_bom_product': self.jikimo_bom_ids.options}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建bom单
|
||||||
|
def _bom_mainfest(self):
|
||||||
|
self.env['jikimo.bom'].create({'tool_inventory_id':self.id})
|
||||||
@@ -38,3 +38,6 @@ access_sf_fixture_material_search_group_plan_dispatch,sf.fixture.material.search
|
|||||||
access_sf_functional_tool_dismantle,sf.functional.tool.dismantle,model_sf_functional_tool_dismantle,base.group_user,1,1,1,0
|
access_sf_functional_tool_dismantle,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_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_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>
|
||||||
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 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>
|
||||||
@@ -139,5 +139,5 @@ class MrsShelfLocationDataSync(models.Model):
|
|||||||
location_id.product_sn_id = False
|
location_id.product_sn_id = False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("捕获错误信息:%s" % e)
|
logging.info("库区信息同步失败:%s" % e)
|
||||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||||
|
|||||||
Reference in New Issue
Block a user