diff --git a/jikimo_frontend/static/src/js/custom_form_status_indicator.js b/jikimo_frontend/static/src/js/custom_form_status_indicator.js index 2bed0804..35f0f3e2 100644 --- a/jikimo_frontend/static/src/js/custom_form_status_indicator.js +++ b/jikimo_frontend/static/src/js/custom_form_status_indicator.js @@ -4,6 +4,7 @@ import {patch} from '@web/core/utils/patch'; // import { Dialog } from "@web/core/dialog/dialog"; import {_t} from "@web/core/l10n/translation"; import {FormStatusIndicator} from "@web/views/form/form_status_indicator/form_status_indicator"; +import {ListRenderer} from "@web/views/list/list_renderer"; import {Field} from "@web/views/fields/field"; @@ -42,7 +43,14 @@ const filedRequiredList = { // 制造大模块 'production_line_id': { multiple: false, noLabel: false }, 'date_approve': { multiple: false, noLabel: false }, + 'date_planned_start': { multiple: false, noLabel: false }, + 'date_planned_finished': { multiple: false, noLabel: false }, } +const tableRequiredList = [ + 'product_template_id', 'product_uom_qty', 'price_unit','product_id','product_qty', + 'name', 'fault_type', 'maintenance_standards', 'Period' +] + patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', { // 你可以重写或者添加一些方法和属性 async _onDiscardChanges() { @@ -83,12 +91,8 @@ patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', { ); patch(Field.prototype, 'jikimo_frontend.Field', { setup() { - this.FieldComponent = this.props.fieldInfo.FieldComponent; - if (!this.FieldComponent) { - const fieldType = this.props.record.fields[this.props.name].type; - this.FieldComponent = getFieldClassFromRegistry(fieldType, this.props.type); - } owl.onMounted(this.setRequired); + return this._super(...arguments); }, setRequired() { const id = this.props.id @@ -112,7 +116,39 @@ patch(Field.prototype, 'jikimo_frontend.Field', { } } }) - +patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { + setup(){ + owl.onMounted(() => { + this.activeElement = this.uiService.activeElement; + this.setRequired() + }) + return this._super(...arguments); + }, + setRequired() { + this.allColumns.forEach(_ => { + if( tableRequiredList.indexOf(_.name) >= 0 ) { + const dom = $(`th[data-name=${_.name}]`) + dom.addClass('addRequired') + } + }) + try { + const dom = this.tableRef.el + if(dom ) { + const tfoot = $(dom).children('tfoot') + const tbooy = $(dom).children('tbody') + if(tfoot.length) { + const tfoot_tr = tfoot.children().eq(0) + const tbody_tr = tbooy.children().eq(0) + if(tfoot_tr.children().length < tbody_tr.children().length) { + tfoot_tr.prepend('') + } + } + } + } catch (e) { + console.log(e) + } + } +}) $(function () { document.addEventListener('click', function () { @@ -162,45 +198,13 @@ $(function () { }, 500) } - function setRequired(dom = {label: [], table: []}) { - let domTimer = null - let timer_count = 0 - clearInterval(domTimer) - domTimer = setInterval(() => { - timer_count++ - const lint = $('.o_form_view_container') - if (lint.length) { - clearInterval(domTimer) - const { table} = dom - - if (table.length) { - table.forEach(_ => { - const th = $(`th[data-name=${_}]`) - const t = th.find('span').eq(0).text().replace('*','') - th.find('span').eq(0).html('*' + t) - - }) - - } - } - if (timer_count == 20) { - clearInterval(domTimer) - } - }, 500) - } - var currentUrl = location.href - const customRequiredDom = { - table: ['product_template_id', 'product_uom_qty', 'price_unit','product_id','product_qty', 'name', 'fault_type', 'maintenance_standards', 'Period'] - } const listenerUrl = setInterval(() => { const isChange = currentUrl != location.href if (isChange) { currentUrl = location.href customRequired() - setRequired(customRequiredDom) } }, 500) customRequired() - setRequired(customRequiredDom) }) diff --git a/jikimo_frontend/static/src/js/custom_image_temp.js b/jikimo_frontend/static/src/js/custom_image_temp.js index fba97121..16f7dab0 100644 --- a/jikimo_frontend/static/src/js/custom_image_temp.js +++ b/jikimo_frontend/static/src/js/custom_image_temp.js @@ -3,6 +3,13 @@ import { registry } from "@web/core/registry"; import { url } from "@web/core/utils/urls"; import { ImageField, imageCacheKey } from '@web/views/fields/image/image_field'; +import { isBinarySize } from "@web/core/utils/binary"; +export const fileTypeMagicWordMap = { + "/": "jpg", + R: "gif", + i: "png", + P: "svg+xml", +}; const placeholder = "/web/static/img/placeholder.png"; @@ -15,7 +22,7 @@ export class CustomImageField extends ImageField { getUrl(previewFieldName) { console.log('8888888888886666666666666666666') if (this.state.isValid && this.props.value) { - if (1) { + if (isBinarySize(this.props.value) || this.props.value.length < 50) { if (!this.rawCacheKey) { this.rawCacheKey = this.props.record.data.__last_update; } diff --git a/jikimo_frontend/static/src/scss/custom_style.scss b/jikimo_frontend/static/src/scss/custom_style.scss index bc1be730..d3a44da4 100644 --- a/jikimo_frontend/static/src/scss/custom_style.scss +++ b/jikimo_frontend/static/src/scss/custom_style.scss @@ -467,3 +467,28 @@ div:has(.o_required_modifier) > label::before { background: #71639e; color: #fff } + +// 修改时间输入框宽度 +.o_datepicker_input.o_input.datetimepicker-input { + width: 200px; +} + + +.o_form_view .o_form_editable .o_row > .o_field_widget, .o_form_view .o_form_editable .o_row > .o_field_widget.o_field_float_time { + width: auto !important; + flex: unset; +} +.addRequired { + padding-left: calc(0.3rem + 2px)!important; +} +.addRequired:before { + content: '*'; + color: red; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); +} +.o_list_renderer .o_list_table tfoot .o_list_number { + text-align: left; +} \ No newline at end of file diff --git a/sf_base/models/tool_base_new.py b/sf_base/models/tool_base_new.py index a3a9e172..3aa85ceb 100644 --- a/sf_base/models/tool_base_new.py +++ b/sf_base/models/tool_base_new.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from datetime import date from odoo import fields, models, api +from odoo.exceptions import UserError class CuttingToolMaterial(models.Model): @@ -308,3 +309,41 @@ class ToolGroups(models.Model): # records = super(ToolGroups, self).create(vals_list) # self._register_tool_groups(records) # return records + + +class ToolInventory(models.Model): + _name = 'sf.tool.inventory' + _description = '功能刀具清单' + + name = fields.Char('功能刀具名称', required=True) + type = fields.Char('类型') + functional_cutting_tool_model_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型') + prefix = fields.Char('前缀') + postfix = fields.Char('后缀') + diameter = fields.Float('直径(mm)') + angle = fields.Float('R角(mm)') + tool_length = fields.Float('刀具总长(mm)') + blade_length = fields.Float('避空长/刃长(mm)') + knife_head_name = fields.Char('刀头名称') + cutter_number = fields.Char('刀号') + blade_number = fields.Integer('刃数(个)') + extension = fields.Float('伸出长度(mm)') + work_material = fields.Selection([('钢', '钢'), ('铝', '铝')], string='加工材料') + life_span = fields.Float('寿命(h)') + + tool_groups_id = fields.Many2one('sf.tool.groups', string='刀具组') + + active = fields.Boolean('已归档', default=True) + + @api.model_create_multi + def create(self, vals_list): + # 名称重复校验 + name_list = [] + for val in vals_list: + tool_inventory = self.search([('name', '=', val['name'])]) + if tool_inventory: + name_list.append(val['name']) + if name_list: + raise UserError("功能刀具名称%s已存在,请重新输入" % name_list) + records = super(ToolInventory, self).create(vals_list) + return records diff --git a/sf_base/security/group_security.xml b/sf_base/security/group_security.xml index 478d41b9..1321bae2 100644 --- a/sf_base/security/group_security.xml +++ b/sf_base/security/group_security.xml @@ -65,7 +65,7 @@ 计划调度岗 - + diff --git a/sf_base/security/ir.model.access.csv b/sf_base/security/ir.model.access.csv index 4eaddff0..3795e69b 100644 --- a/sf_base/security/ir.model.access.csv +++ b/sf_base/security/ir.model.access.csv @@ -9,16 +9,25 @@ access_sf_machine_brand_tags,sf_machine_brand_tags,model_sf_machine_brand_tags,b access_sf_machine_brand_tags_admin,sf_machine_brand_tags_admin,model_sf_machine_brand_tags,base.group_system,1,1,1,0 access_sf_machine_control_system,sf_machine_control_system,model_sf_machine_control_system,base.group_user,1,1,1,0 access_sf_machine_control_system_admin,sf_machine_control_system_admin,model_sf_machine_control_system,base.group_system,1,1,1,0 +access_sf_production_process_group_sale_director,sf_production_process_group_sale_director,model_sf_production_process,sf_base.group_sale_director,1,0,0,0 +access_sf_production_process_group_sale_salemanager,sf_production_process_group_sale_salemanager,model_sf_production_process,sf_base.group_sale_salemanager,1,0,0,0 access_sf_production_process,sf_production_process,model_sf_production_process,base.group_user,1,1,1,0 access_sf_production_process_admin,sf_production_process_admin,model_sf_production_process,base.group_system,1,1,1,0 access_sf_production_materials,sf_production_materials,model_sf_production_materials,base.group_user,1,1,1,0 +access_sf_production_materials_group_sale_director,sf_production_materials_group_sale_director,model_sf_production_materials,sf_base.group_sale_director,1,0,0,0 +access_sf_production_materials_group_sale_salemanager,sf_production_materials_group_sale_salemanager,model_sf_production_materials,sf_base.group_sale_salemanager,1,0,0,0 + access_sf_production_materials_admin,sf_production_materials_admin,model_sf_production_materials,base.group_system,1,1,1,0 access_sf_materials_model,sf_materials_model,model_sf_materials_model,base.group_user,1,1,1,0 access_sf_materials_model_admin,sf_materials_model_admin,model_sf_materials_model,base.group_system,1,1,1,0 access_sf_supplier_sort,sf_supplier_sort,model_sf_supplier_sort,base.group_user,1,1,1,0 access_sf_supplier_sort_admin,sf_supplier_sort_admin,model_sf_supplier_sort,base.group_system,1,1,1,0 access_sf_production_process_parameter,sf_production_process_parameter,model_sf_production_process_parameter,base.group_user,1,1,1,0 +access_sf_production_process_parameter_group_sale_director,sf_production_process_parameter_group_sale_director,model_sf_production_process_parameter,sf_base.group_sale_director,1,0,0,0 +access_sf_production_process_parameter_group_sale_salemanager,sf_production_process_parameter_group_sale_salemanager,model_sf_production_process_parameter,sf_base.group_sale_salemanager,1,0,0,0 + + access_sf_production_process_parameter_group_plan_director,sf_production_process_parameter_group_plan_director,model_sf_production_process_parameter,sf_base.group_plan_director,1,0,0,0 access_sf_production_process_parameter_group_purchase_director,sf_production_process_parameter_group_purchase_director,model_sf_production_process_parameter,sf_base.group_purchase_director,1,0,0,0 access_sf_production_process_parameter_group_sale_director,sf_production_process_parameter_group_sale_director,model_sf_production_process_parameter,sf_base.group_sale_director,1,0,0,0 @@ -101,6 +110,10 @@ access_sf_production_materials_group_plan_director,sf_production_materials_group access_sf_production_materials_group_purchase_director,sf_production_materials_group_purchase_director,model_sf_production_materials,sf_base.group_purchase_director,1,1,0,0 access_sf_production_materials_group_sale_director,sf_production_materials_group_sale_director,model_sf_production_materials,sf_base.group_sale_director,1,1,0,0 access_sf_materials_model,sf_materials_model,model_sf_materials_model,sf_base.group_sf_mrp_user,1,0,0,0 +access_sf_materials_model_group_sale_salemanager,sf_materials_model_group_sale_salemanager,model_sf_materials_model,sf_base.group_sale_salemanager,1,0,0,0 +access_sf_materials_model_group_sale_director,sf_materials_model_group_sale_director,model_sf_materials_model,sf_base.group_sale_director,1,0,0,0 + + access_sf_materials_model_group_plan_director,sf_materials_model_group_plan_director,model_sf_materials_model,sf_base.group_plan_director,1,0,0,0 access_sf_materials_model_group_purchase_director,sf_materials_model_group_purchase_director,model_sf_materials_model,sf_base.group_purchase_director,1,0,0,0 access_sf_materials_model_group_sale_director,sf_materials_model_group_sale_director,model_sf_materials_model,sf_base.group_sale_director,1,0,0,0 @@ -145,6 +158,7 @@ access_purchase_order_group_purchase,access_purchase_order_group_purchase,purcha access_purchase_order_line_group_purchase,access_purchase_order_line_group_purchase,purchase.model_purchase_order_line,sf_base.group_purchase,1,1,1,0 access_spindle_taper_type,spindle_taper_type,model_spindle_taper_type,base.group_user,1,1,1,1 access_sf_tool_groups_group_plan_dispatch,sf_tool_groups,model_sf_tool_groups,sf_base.group_plan_dispatch,1,0,0,0 +access_sf_tool_groups_group_plan_director,sf_tool_groups,model_sf_tool_groups,sf_base.group_plan_director,1,1,1,0 access_sf_tool_groups_group_sf_tool_user,sf_tool_groups,model_sf_tool_groups,sf_base.group_sf_tool_user,1,1,1,1 access_purchase_order,purchase.order,purchase.model_purchase_order,sf_base.group_plan_dispatch,1,0,0,0 access_res_partner,res.partner,base.model_res_partner,sf_base.group_plan_dispatch,1,0,0,0 @@ -191,4 +205,25 @@ access_sf_machine_brand_tags_group_purchase_director,sf_machine_brand_tags_group access_printer,printer,model_printer,base.group_user,1,1,1,1 -access_printer_configuration,printer.configuration,model_printer_configuration,base.group_user,1,1,1,1 \ No newline at end of file +access_printer_configuration,printer.configuration,model_printer_configuration,base.group_user,1,1,1,1 + +access_group_sf_mrp_user,sf_tool_inventory,model_sf_tool_inventory,base.group_user,1,1,1,0 +access_group_sf_mrp_user_admin,sf_tool_inventory_admin,model_sf_tool_inventory,base.group_system,1,1,1,0 +access_group_sf_mrp_user_group_purchase_director,sf_tool_inventory_group_purchase_director,model_sf_tool_inventory,sf_base.group_purchase_director,1,0,1,0 +access_group_sf_mrp_user_group_sale_director,sf_tool_inventory_group_sale_director,model_sf_tool_inventory,sf_base.group_sale_director,1,0,1,0 +access_sf_cutting_tool_material_group_plan_director,sf_tool_inventory_group_plan_director,model_sf_tool_inventory,sf_base.group_plan_director,1,0,1,0 +access_group_sf_mrp_user_group_sf_mrp_user,sf_tool_inventory_group_sf_mrp_user,model_sf_tool_inventory,sf_base.group_sf_mrp_user,1,1,0,0 + + +access_sf_fixture_material_group_purchase_director,sf_fixture_material_group_purchase_director,model_sf_fixture_material,sf_base.group_purchase_director,1,0,0,0 +access_sf_multi_mounting_type_group_purchase_director,sf_multi_mounting_type_group_purchase_director,model_sf_multi_mounting_type,sf_base.group_purchase_director,1,0,0,0 +access_sf_fixture_model_group_purchase_director,sf_fixture_model_group_purchase_director,model_sf_fixture_model,sf_base.group_purchase_director,1,0,0,0 +access_sf_fixture_materials_basic_parameters_group_purchase_director,sf_fixture_materials_basic_parameters_group_purchase_director,model_sf_fixture_materials_basic_parameters,sf_base.group_purchase_director,1,0,0,0 +access_sf_machine_tool_type_group_purchase_director,sf_machine_tool_type_group_purchase_director,model_sf_machine_tool_type,sf_base.group_purchase_director,1,0,0,0 + +access_sf_fixture_material_group_sale_director,sf_fixture_material_group_sale_director,model_sf_fixture_material,sf_base.group_sale_director,1,0,0,0 +access_sf_multi_mounting_type_group_sale_director,sf_multi_mounting_type_group_sale_director,model_sf_multi_mounting_type,sf_base.group_sale_director,1,0,0,0 +access_sf_fixture_model_group_sale_director,sf_fixture_model_group_sale_director,model_sf_fixture_model,sf_base.group_sale_director,1,0,0,0 +access_sf_fixture_materials_basic_parameters_group_sale_director,sf_fixture_materials_basic_parameters_group_sale_director,model_sf_fixture_materials_basic_parameters,sf_base.group_sale_director,1,0,0,0 +access_sf_machine_tool_type_group_sale_director,sf_machine_tool_type_group_sale_director,model_sf_machine_tool_type,sf_base.group_sale_director,1,0,0,0 + diff --git a/sf_base/static/src/scss/test.scss b/sf_base/static/src/scss/test.scss index fdc5821e..c91a8a77 100644 --- a/sf_base/static/src/scss/test.scss +++ b/sf_base/static/src/scss/test.scss @@ -189,3 +189,6 @@ td.o_required_modifier { flex-direction: row !important; } +.supplier_ids_set_css thead th[data-name=partner_id]{ + width: 500px!important; +} \ No newline at end of file diff --git a/sf_base/views/common_view.xml b/sf_base/views/common_view.xml index bf9201b8..e829274a 100644 --- a/sf_base/views/common_view.xml +++ b/sf_base/views/common_view.xml @@ -27,7 +27,7 @@ - + @@ -231,6 +231,9 @@ + + + @@ -266,7 +269,7 @@ - + diff --git a/sf_base/views/tool_menu.xml b/sf_base/views/tool_menu.xml index 506a73df..263d11fa 100644 --- a/sf_base/views/tool_menu.xml +++ b/sf_base/views/tool_menu.xml @@ -21,7 +21,7 @@ 刀具标准库 ir.actions.act_window sf.cutting_tool.standard.library - + tree,form @@ -59,46 +59,54 @@ id="menu_sf_cutting_tool_type" parent="menu_sf_cutting_tool" name="刀具类型" - sequence="2" + sequence="10" action="action_sf_cutting_tool_type" /> + + - - - - - - - + + + + + + + + id="menu_sf_maintenance_equipment_image" + name="能力特征库" + parent="menu_sf_cutting_tool" + action="action_maintenance_equipment_image" + sequence="40"/> + id="menu_sf_tool_groups" + name="刀具组" + parent="menu_sf_cutting_tool" + action="sf_tool_groups_view_act" + sequence="50"/> diff --git a/sf_base/views/tool_views.xml b/sf_base/views/tool_views.xml index 34b87a41..1e82b8c2 100644 --- a/sf_base/views/tool_views.xml +++ b/sf_base/views/tool_views.xml @@ -123,7 +123,7 @@
+ required="1"/>

@@ -547,7 +547,66 @@ - + ' + + + sf.tool.inventory.tree + sf.tool.inventory + + + + + + + + + + + + + + + + + + + + + + + + + + sf.tool.inventory.search + sf.tool.inventory + + + + + + + + + + + + + + + + + + + + + + + + 功能刀具清单 + ir.actions.act_window + sf.tool.inventory + tree + diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml index 264a1c95..94fc6b45 100644 --- a/sf_dlm_management/views/product_template_management_view.xml +++ b/sf_dlm_management/views/product_template_management_view.xml @@ -10,6 +10,7 @@ + @@ -41,14 +42,15 @@ context="{'default_cutting_tool_type': cutting_tool_type,'default_standard_library_id':cutting_tool_model_id}" attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}" domain="[('standard_library_id','=',cutting_tool_model_id)]"/> - diff --git a/sf_maintenance/models/sf_maintenance.py b/sf_maintenance/models/sf_maintenance.py index 5b0db796..74da7742 100644 --- a/sf_maintenance/models/sf_maintenance.py +++ b/sf_maintenance/models/sf_maintenance.py @@ -148,7 +148,7 @@ class SfMaintenanceEquipment(models.Model): MTcode = fields.Char("机台编码") created_user = fields.Many2one('res.users', string='创建人', default=lambda self: self.env.user) equipment_type = fields.Selection([('机床', '机床'), ('机器人', '机器人'), ('AGV小车', 'AGV小车'), - ('检测设备', '检测设备')], compute='_compute_category_id') + ('检测设备', '检测设备'), ('其他', '其他')], compute='_compute_category_id') @api.depends('category_id') def _compute_category_id(self): diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index 6a122097..1c49c88b 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -24,6 +24,7 @@ 'views/mrp_workcenter_views.xml', 'views/mrp_workorder_view.xml', 'views/model_type_view.xml', + 'views/agv_setting_views.xml', 'views/sf_maintenance_equipment.xml', ], diff --git a/sf_manufacturing/controllers/controllers.py b/sf_manufacturing/controllers/controllers.py index c548222a..b9cef58e 100644 --- a/sf_manufacturing/controllers/controllers.py +++ b/sf_manufacturing/controllers/controllers.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import logging import json +from datetime import datetime from odoo import http from odoo.http import request @@ -114,7 +115,7 @@ class Manufacturing_Connect(http.Controller): logging.info('RfidCode:%s' % ret['RfidCode']) if 'RfidCode' in ret: workorder = request.env['mrp.workorder'].sudo().search( - [('routing_type', '=', '装夹预调'), ('rfid_code', '=', ret['RfidCode'])]) + [('routing_type', '=', '装夹预调'), ('rfid_code', '=', ret['RfidCode'])], limit=1, order='id asc') if workorder: for item in workorder: if item.material_center_point: @@ -122,8 +123,8 @@ class Manufacturing_Connect(http.Controller): res['Datas'].append({ 'XOffset': 0 if not item.material_center_point else offset[0], 'YOffset': 0 if not item.material_center_point else offset[1], - 'ZOffet': 0 if not item.material_center_point else offset[2], - 'COffset': 0 if not item.X_deviation_angle else item.X_deviation_angle, + 'ZOffset': 0 if not item.material_center_point else offset[2], + 'COffset': 0, 'Coordinate': 'G54' }) else: @@ -238,7 +239,7 @@ class Manufacturing_Connect(http.Controller): workorder = request.env['mrp.workorder'].sudo().search( [('production_id', '=', production_id), ('routing_type', '=', routing_type)], limit=1) if workorder: - workorder.test_result = ret['Quality'] + # workorder.test_results = ret['Quality'] logging.info('制造订单:%s' % workorder.production_id.name) if 'ReportPaht' in ret: download_state = request.env['mrp.workorder'].with_user( @@ -261,8 +262,7 @@ class Manufacturing_Connect(http.Controller): ('picking_id', '=', stock_picking.id)]) if quality_check: logging.info('质检单:%s' % quality_check.name) - quality_check.write({'report_pdf': workorder.detection_report, - 'report_result': workorder.test_result}) + quality_check.write({'report_pdf': workorder.detection_report}) elif download_state == 2: res = {'Succeed': False, 'ErrorCode': 205, 'Error': 'ReportPaht中的工件号与制造订单%s不匹配,请检查ReportPaht是否正确' % workorder.production_id.name} @@ -431,14 +431,15 @@ class Manufacturing_Connect(http.Controller): if 'DeviceId' in ret: logging.info('DeviceId:%s' % ret['DeviceId']) workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search( - [('feeder_station_destination', '=', ret['DeviceId'])]) + [('feeder_station_start_id.name', '=', ret['DeviceId']), + ('status', '=', '待配送')], limit=1, order='id asc') if workpiece_delivery: for wd in workpiece_delivery: - logging.info('wd.workorder_id:%s' % wd.workorder_id.name) + logging.info('wd.production_id:%s' % wd.production_id.name) if wd.workorder_id.state == 'done' and wd.production_id.production_line_state == '待上产线': - logging.info('wd.production_id:%s' % wd.production_id.name) logging.info('wd.production_line_state:%s' % wd.production_id.production_line_state) wd.production_id.write({'production_line_state': '已上产线'}) + wd.write({'production_line_state': '已上产线'}) else: res = {'Succeed': False, 'ErrorCode': 203, 'Error': '该DeviceId没有对应的工件配送数据'} else: @@ -465,14 +466,28 @@ class Manufacturing_Connect(http.Controller): if 'DeviceId' in ret: logging.info('DeviceId:%s' % ret['DeviceId']) workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search( - [('feeder_station_destination', '=', ret['DeviceId'])]) + [('feeder_station_destination_id.name', '=', ret['DeviceId']), + ('status', '=', '待配送')], limit=1, order='id asc') if workpiece_delivery: for wd in workpiece_delivery: - logging.info('wd.workorder_id:%s' % wd.workorder_id.name) + logging.info('wd.production_id:%s' % wd.production_id.name) if wd.workorder_id.state == 'done' and wd.production_id.production_line_state == '已上产线': - logging.info('wd.production_id:%s' % wd.production_id.name) logging.info('wd.production_line_state:%s' % wd.production_id.production_line_state) + workpiece_delivery_off = request.env['sf.workpiece.delivery'].sudo().create({ + 'production_id': wd.production_id.id, + 'feeder_station_start_id': workpiece_delivery.feeder_station_start_id.id, + 'feeder_station_destination_id': '', + 'workorder_id': workpiece_delivery.workorder_id.id, + 'workpiece_code': workpiece_delivery.workpiece_code, + 'production_line_id': workpiece_delivery.production_line_id.id, + 'task_delivery_time': datetime.now(), + 'production_line_state': '已下产线' + }) wd.production_id.write({'production_line_state': '已下产线'}) + logging.info('开始向agv下发下产线任务') + workpiece_delivery_off._delivery_avg() + logging.info('agv下发下产线任务已配送') + else: res = {'Succeed': False, 'ErrorCode': 203, 'Error': '该DeviceId没有对应的工件配送数据'} else: @@ -481,3 +496,35 @@ class Manufacturing_Connect(http.Controller): res = {'Succeed': False, 'ErrorCode': 202, 'Error': e} logging.info('AGVDownProduct error:%s' % e) return json.JSONEncoder().encode(res) + + @http.route('/AutoDeviceApi/EquipmentBaseCoordinate', type='json', auth='none', methods=['GET', 'POST'], csrf=False, + cors="*") + def PutEquipmentBaseCoordinate(self, **kw): + """ + 获取机床基坐标 + :param kw: + :return: + """ + logging.info('PutEquipmentBaseCoordinate:%s' % kw) + try: + res = {'Succeed': True} + datas = request.httprequest.data + ret = json.loads(datas) + if 'DeviceId' in ret: + equipment = request.env['maintenance.equipment'].sudo().search('name', '=', ret['DeviceId']) + if equipment: + equipment.sudo().write({ + 'base_coordinate_fixture_model_id': ret['base_coordinate_fixture_model_id'], + 'base_coordinate_g_coordinate': ret['base_coordinate_g_coordinate'], + 'base_coordinate_x': ret['base_coordinate_x'], + 'base_coordinate_y': ret['base_coordinate_y'], + 'base_coordinate_z': ret['base_coordinate_z'], + }) + else: + res = {'Succeed': False, 'ErrorCode': 203, 'Error': 'DeviceId为%s的设备不存在!' % ret['DeviceId']} + else: + res = {'Succeed': False, 'ErrorCode': 201, 'Error': '未传DeviceId字段'} + except Exception as e: + res = {'Succeed': False, 'ErrorCode': 202, 'Error': e} + logging.info('AGVDownProduct error:%s' % e) + return json.JSONEncoder().encode(res) diff --git a/sf_manufacturing/controllers/workpiece.py b/sf_manufacturing/controllers/workpiece.py index e2f60421..fcdcf606 100644 --- a/sf_manufacturing/controllers/workpiece.py +++ b/sf_manufacturing/controllers/workpiece.py @@ -25,7 +25,7 @@ class Workpiece(http.Controller): if 'method' in ret: if ret['method'] == 'end': workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search( - [('production_id.name', '=', ret['reqCode'])]) + [('production_id.name', '=', ret['reqCode']), ('agv_task_code'), '=', ret['taskCode']]) if workpiece_delivery: workpiece_delivery.write({'status': '已配送', 'task_completion_time': ret['reqTime']}) else: diff --git a/sf_manufacturing/data/stock_data.xml b/sf_manufacturing/data/stock_data.xml index 6bd0fe53..72559691 100644 --- a/sf_manufacturing/data/stock_data.xml +++ b/sf_manufacturing/data/stock_data.xml @@ -67,5 +67,21 @@ search="[('barcode','=','WH-PREPRODUCTION')]"/> + + + 表面工艺外协 + + True + 11 + + + + + + + + + + diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index 3ca83b27..3de23ef3 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -8,3 +8,4 @@ from . import mrp_routing_workcenter from . import stock from . import res_user from . import production_line_base +from . import agv_setting diff --git a/sf_manufacturing/models/agv_setting.py b/sf_manufacturing/models/agv_setting.py new file mode 100644 index 00000000..22c486d1 --- /dev/null +++ b/sf_manufacturing/models/agv_setting.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from odoo import fields, models + + +class AgvSetting(models.Model): + _name = 'sf.agv.site' + _description = 'agv站点' + + number = fields.Integer('序号') + name = fields.Char('位置编号') + owning_region = fields.Char('所属区域') + state = fields.Selection([ + ('占用', '占用'), + ('空闲', '空闲')], string='状态') + divide_the_work = fields.Char('主要分工') + + +class AgvTaskRoute(models.Model): + _name = 'sf.agv.task.route' + _description = 'agv任务路线' + + name = fields.Char('名称') + type = fields.Selection([ + ('F01', '搬运'), ], string='类型', default="F01") + start_site_id = fields.Many2one('sf.agv.site', '起点接驳站位置编号') + end_site_id = fields.Many2one('sf.agv.site', '终点接驳站位置编号') + destination_production_line_id = fields.Many2one('sf.production.line', '目的生产线') + priority = fields.Selection([ + ('0', '正常'), + ('1', '低'), + ('2', '中'), + ('3', '高'), + ('4', '紧急'), + ], string='优先级', default='0') + active = fields.Boolean('有效', default=True) diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index dff2763b..4b915392 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -54,8 +54,11 @@ class MrpProduction(models.Model): glb_file = fields.Binary("glb模型文件") production_line_id = fields.Many2one('sf.production.line', string='生产线') plan_start_processing_time = fields.Datetime('计划开始加工时间') - production_line_state = fields.Selection([('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')], - string='上/下产线', default='待上产线') + production_line_state = fields.Selection( + [('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')], + string='上/下产线', default='待上产线') + + manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) @api.depends( 'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', @@ -132,6 +135,15 @@ class MrpProduction(models.Model): # cnc程序获取 def fetchCNC(self): cnc = self.env['mrp.production'].search([('id', '=', self.id)]) + quick_order = self.env['quick.easy.order'].search( + [('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])]) + programme_way = False + if cnc.manual_quotation is True: + programme_way = 'manual operation' + else: + programme_way = 'auto' + if quick_order: + programme_way = 'manual operation' try: res = {'model_code': '' if not cnc.product_id.model_code else cnc.product_id.model_code, 'production_no': cnc.name, @@ -146,8 +158,9 @@ class MrpProduction(models.Model): 'embryo_height': cnc.product_id.bom_ids.bom_line_ids.product_id.height, 'embryo_width': cnc.product_id.bom_ids.bom_line_ids.product_id.width, 'order_no': cnc.origin, - 'model_order_no': cnc.product_id.default_code.rsplit(' -', 1)[0], + 'model_order_no': cnc.product_id.default_code, 'user': cnc.env.user.name, + 'programme_way': programme_way, 'model_file': '' if not cnc.product_id.model_file else base64.b64encode( cnc.product_id.model_file).decode('utf-8') } diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index dfb064cb..ab520434 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -1,10 +1,14 @@ +import re + import logging import base64 +import urllib.parse from datetime import date from datetime import datetime, timedelta import requests import os import math +from lxml import etree from dateutil.relativedelta import relativedelta # import subprocess from odoo import api, fields, models, SUPERUSER_ID, _ @@ -42,6 +46,8 @@ class ResMrpWorkOrder(models.Model): ], string="工序类型") results = fields.Char('结果') + manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) + @api.onchange('users_ids') def get_user_permissions(self): uid = self.env.uid @@ -95,11 +101,12 @@ class ResMrpWorkOrder(models.Model): Y10_axis = fields.Float(default=0) Z10_axis = fields.Float(default=0) X_deviation_angle = fields.Integer(string="X轴偏差度", default=0) - test_result = fields.Char("检测结果") + test_results = fields.Selection([("合格", "合格"), ("返工", "返工"), ("报废", "报废")], default='合格', + string="检测结果") cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工程序") cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序") tray_code = fields.Char(string="托盘编码") - glb_file = fields.Binary("glb模型文件") + glb_file = fields.Binary("glb模型文件", related='production_id.model_file') is_subcontract = fields.Boolean(string='是否外协') surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数") picking_in_id = fields.Many2one('stock.picking', string='外协入库单') @@ -111,6 +118,19 @@ class ResMrpWorkOrder(models.Model): processing_user_id = fields.Many2one('res.users', string='加工人') # 检测人 inspection_user_id = fields.Many2one('res.users', string='检测人') + # 保存名称 + save_name = fields.Char(string='检测文件保存名称', compute='_compute_save_name') + # 获取数据状态 + data_state = fields.Boolean(string='获取数据状态', default=False) + + @api.depends('production_id') + def _compute_save_name(self): + """ + 保存名称 + """ + for record in self: + record.save_name = record.production_id.name.replace('/', '_') + schedule_state = fields.Selection(related='production_id.schedule_state', store=True) # 工件装夹信息 functional_fixture_code = fields.Char(string="功能夹具编码", readonly=True) @@ -140,6 +160,7 @@ class ResMrpWorkOrder(models.Model): production_line_state = fields.Selection(related='production_id.production_line_state', string='上/下产线', store=True) detection_report = fields.Binary('检测报告', readonly=True) + is_remanufacture = fields.Boolean(string='是否重新生成制造订单', default=True) def get_plan_workorder(self, production_line): tomorrow = (date.today() + timedelta(days=+1)).strftime("%Y-%m-%d") @@ -182,6 +203,149 @@ class ResMrpWorkOrder(models.Model): [('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_id)]) return process_parameter_workorder + # 获取三次元检测点数据 + def get_three_check_datas(self): + factory_nick_name = 'XT' + ftp_resconfig = self.env['res.config.settings'].get_values() + ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']), + ftp_resconfig['ftp_user'], + ftp_resconfig['ftp_password']) + # ftp.connect() + + local_dir_path = '/ftp/before' + os.makedirs(local_dir_path, exist_ok=True) + local_filename = self.save_name + '.xls' + local_file_path = os.path.join(local_dir_path, local_filename) + logging.info('local_file_path:%s' % local_file_path) + remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename + logging.info('remote_path:%s' % remote_path) + + if not ftp.file_exists(remote_path): + raise UserError(f"文件不存在: {remote_path}") + + with open(local_file_path, 'wb') as local_file: + ftp.ftp.retrbinary('RETR ' + remote_path, local_file.write) + logging.info('下载文件成功') + # 解析本地文件 + # file_path = 'WH_MO_00099.xls' # 使用下载的实际文件路径 + parser = etree.XMLParser(recover=True) # Using recover to handle errors + tree = etree.parse(local_file_path, parser) + logging.info('tree:%s' % tree) + root = tree.getroot() + logging.info('root:%s' % root) + + # 准备一个外部字典来存储以PT为键的坐标字典 + pt_coordinates = {} + # 遍历每个工作表和行 + for worksheet in root.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Worksheet'): + sheet_name = worksheet.attrib.get('{urn:schemas-microsoft-com:office:spreadsheet}Name') + logging.info('sheet_name:%s' % sheet_name) + if sheet_name == "Sheet1": # 确保我们只查看包含数据的工作表 + current_pt = None + for row in worksheet.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Row'): + cells = list(row.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Cell')) + for i, cell in enumerate(cells): + data_cell = cell.find('.//{urn:schemas-microsoft-com:office:spreadsheet}Data') + if data_cell is not None and data_cell.text is not None: # 添加检查以确保data_cell.text不为空 + # 检查是否是PT标识 + logging.info(f"Data in cell: {data_cell.text}") # 输出单元格数据 + if "PT" in data_cell.text: + current_pt = data_cell.text + pt_coordinates[current_pt] = [] + elif data_cell.text in ["X", "Y", "Z"] and current_pt is not None: + # 确保当前单元格后面还有单元格存在,以获取理论值 + if i + 1 < len(cells): + next_cell = cells[i + 1] + theory_value = next_cell.find( + './/{urn:schemas-microsoft-com:office:spreadsheet}Data') + if theory_value is not None: + # 为当前PT键添加坐标数据 + pt_coordinates[current_pt].append({ + data_cell.text: float(theory_value.text) + }) + logging.info(f"PT: {current_pt} - {data_cell.text}: {theory_value.text}") + logging.info('pt_coordinates=====%s' % pt_coordinates) + # pt_coordinates:{'PT1': [{'X': 38.9221}, {'Y': -18.7304}, {'Z': 128.0783}], + # 'PT2': [{'X': 39.2456}, {'Y': -76.9169}, {'Z': 123.7541}]} + + # 检查是否存在PT1等键 + if 'PT1' in pt_coordinates and pt_coordinates['PT1']: + self.X1_axis = pt_coordinates['PT3'][0]['X'] + self.Y1_axis = pt_coordinates['PT3'][1]['Y'] + self.Z1_axis = pt_coordinates['PT3'][2]['Z'] + else: + raise UserError('PT1点未测或数据错误') + if 'PT2' in pt_coordinates and pt_coordinates['PT2']: + self.X2_axis = pt_coordinates['PT4'][0]['X'] + self.Y2_axis = pt_coordinates['PT4'][1]['Y'] + self.Z2_axis = pt_coordinates['PT4'][2]['Z'] + else: + raise UserError('PT2点未测或数据错误') + if 'PT3' in pt_coordinates and pt_coordinates['PT3']: + self.X3_axis = pt_coordinates['PT5'][0]['X'] + self.Y3_axis = pt_coordinates['PT5'][1]['Y'] + self.Z3_axis = pt_coordinates['PT5'][2]['Z'] + else: + raise UserError('PT3点未测或数据错误') + if 'PT4' in pt_coordinates and pt_coordinates['PT4']: + self.X4_axis = pt_coordinates['PT6'][0]['X'] + self.Y4_axis = pt_coordinates['PT6'][1]['Y'] + self.Z4_axis = pt_coordinates['PT6'][2]['Z'] + else: + raise UserError('PT4点未测或数据错误') + if 'PT5' in pt_coordinates and pt_coordinates['PT5']: + self.X5_axis = pt_coordinates['PT7'][0]['X'] + self.Y5_axis = pt_coordinates['PT7'][1]['Y'] + self.Z5_axis = pt_coordinates['PT7'][2]['Z'] + else: + raise UserError('PT5点未测或数据错误') + if 'PT6' in pt_coordinates and pt_coordinates['PT6']: + self.X6_axis = pt_coordinates['PT8'][0]['X'] + self.Y6_axis = pt_coordinates['PT8'][1]['Y'] + self.Z6_axis = pt_coordinates['PT8'][2]['Z'] + else: + raise UserError('PT6点未测或数据错误') + if 'PT7' in pt_coordinates and pt_coordinates['PT7']: + self.X7_axis = pt_coordinates['PT9'][0]['X'] + self.Y7_axis = pt_coordinates['PT9'][1]['Y'] + self.Z7_axis = pt_coordinates['PT9'][2]['Z'] + else: + raise UserError('PT7点未测或数据错误') + if 'PT8' in pt_coordinates and pt_coordinates['PT8']: + self.X8_axis = pt_coordinates['PT10'][0]['X'] + self.Y8_axis = pt_coordinates['PT10'][1]['Y'] + self.Z8_axis = pt_coordinates['PT10'][2]['Z'] + else: + raise UserError('PT8点未测或数据错误') + if 'PT9' in pt_coordinates and pt_coordinates['PT9']: + self.X9_axis = pt_coordinates['PT1'][0]['X'] + self.Y9_axis = pt_coordinates['PT1'][1]['Y'] + self.Z9_axis = pt_coordinates['PT1'][2]['Z'] + else: + raise UserError('PT9点未测或数据错误') + if 'PT10' in pt_coordinates and pt_coordinates['PT10']: + self.X10_axis = pt_coordinates['PT2'][0]['X'] + self.Y10_axis = pt_coordinates['PT2'][1]['Y'] + self.Z10_axis = pt_coordinates['PT2'][2]['Z'] + else: + raise UserError('PT10点未测或数据错误') + + self.data_state = True + + return True + + # ftp.download_file('three_check_datas.xls', '/home/ftpuser/three_check_datas.xls') + # ftp.close() + # data = xlrd.open_workbook('/home/ftpuser/three_check_datas.xls') + # table = data.sheets()[0] + # nrows = table.nrows + # # 点坐标列表 + # point_list = [] + # datas = [] + # for i in range(1, nrows): + # datas.append(table.row_values(i)) + # return datas + # 计算配料中心点和与x轴倾斜度方法 def getcenter(self): try: @@ -202,6 +366,7 @@ class ResMrpWorkOrder(models.Model): y7 = self.Y7_axis y8 = self.Y8_axis z1 = self.Z9_axis + z2 = self.Z10_axis x0 = ((x3 - x4) * (x2 * y1 - x1 * y2) - (x1 - x2) * (x4 * y3 - x3 * y4)) / ( (x3 - x4) * (y1 - y2) - (x1 - x2) * (y3 - y4)) y0 = ((y3 - y4) * (y2 * x1 - y1 * x2) - (y1 - y2) * (y4 * x3 - y3 * x4)) / ( @@ -212,7 +377,7 @@ class ResMrpWorkOrder(models.Model): (y7 - y8) * (x5 - x6) - (y5 - y6) * (x7 - x8)) x = (x0 + x1) / 2 y = (y0 + y1) / 2 - z = z1 / 2 + z = (z1 + z2) / 2 jd = math.atan2((x5 - x6), (y5 - y6)) jdz = jd * 180 / math.pi @@ -225,27 +390,31 @@ class ResMrpWorkOrder(models.Model): work.compensation_value_x = eval(self.material_center_point)[0] work.compensation_value_y = eval(self.material_center_point)[1] workorder.button_finish() - except: - raise UserError("参数计算有误") + except Exception as e: + # 重新抛出捕获到的异常信息 + raise UserError(str(e)) def button_workpiece_delivery(self): if self.routing_type == '装夹预调': for item in self.workpiece_delivery_ids: - if not item.feeder_station_start: - raise UserError('【工件配送】明细中请输入起点接驳站') + if not item.route_id: + raise UserError('【工件配送】明细中请选择【任务路线】') # if not item.workpiece_code: # raise UserError('请对【同运工件】进行扫描') else: - if item.status == '待下发': - return { - 'name': _('确认'), - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'sf.workpiece.delivery.wizard', - 'target': 'new', - 'context': { - 'default_workorder_id': self.id, - }} + if self.cnc_program_down_state == '已下发': + if item.status == '待下发': + return { + 'name': _('确认'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'sf.workpiece.delivery.wizard', + 'target': 'new', + 'context': { + 'default_workorder_id': self.id, + }} + else: + raise UserError(_("该制造订单还未下发CNC程序单,无法进行工件配送")) # 拼接工单对象属性值 def json_workorder_str(self, k, production, route): @@ -443,7 +612,7 @@ class ResMrpWorkOrder(models.Model): """ 重新生成制造订单或者重新生成工单 """ - if self.test_result == '报废': + if self.test_results == '报废': values = self.env['mrp.production'].create_production1_values(self.production_id) productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company( self.production_id.company_id).create( @@ -475,7 +644,7 @@ class ResMrpWorkOrder(models.Model): 'mail.message_origin_link', values={'self': production, 'origin': origin_production}, subtype_id=self.env.ref('mail.mt_note').id) - if self.test_result == '返工': + if self.test_results == '返工': productions = self.production_id # self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) @@ -483,47 +652,6 @@ class ResMrpWorkOrder(models.Model): else: self.results = '合格' - # cnc程序获取 - def fetchCNC(self): - try: - cnc = self.env['mrp.workorder'].search( - [('routing_type', '=', 'CNC加工'), ('production_id', '=', self.production_id.id)], limit=1) - res = {'model_code': '' if not cnc.product_id.model_code else cnc.product_id.model_code, - 'production_no': self.production_id.name, - 'machine_tool_code': cnc.workcenter_id.equipment_id.code, - 'material_code': cnc.env['sf.production.materials'].search( - [('id', '=', cnc.product_id.materials_id.id)]).materials_no, - 'material_type_code': cnc.env['sf.materials.model'].search( - [('id', '=', cnc.product_id.materials_type_id.id)]).materials_no, - 'machining_processing_panel': cnc.product_id.model_processing_panel, - 'machining_precision': cnc.product_id.model_machining_precision, - 'embryo_long': cnc.product_id.bom_ids.bom_line_ids.product_id.length, - 'embryo_height': cnc.product_id.bom_ids.bom_line_ids.product_id.height, - 'embryo_width': cnc.product_id.bom_ids.bom_line_ids.product_id.width, - 'order_no': cnc.production_id.origin, - 'model_order_no': cnc.product_id.default_code.rsplit(' -', 1)[0], - 'user': self.env.user.name, - 'model_file': '' if not cnc.product_id.model_file else base64.b64encode( - cnc.product_id.model_file).decode('utf-8') - } - logging.info('res:%s' % res) - configsettings = self.env['res.config.settings'].get_values() - config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) - url = '/api/intelligent_programming/create' - config_url = configsettings['sf_url'] + url - # res_str = json.dumps(res) - ret = requests.post(config_url, json={}, data=res, headers=config_header) - ret = ret.json() - logging.info('fetchCNC-ret:%s' % ret) - if ret['status'] == 1: - self.write( - {'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'}) - else: - raise UserError(ret['message']) - except Exception as e: - logging.info('fetchCNC error:%s' % e) - raise UserError("cnc程序获取编程单失败,请联系管理员") - def json_workorder_str1(self, k, production, route): workorders_values_str = [0, '', { 'product_uom_id': production.product_uom_id.id, @@ -555,6 +683,12 @@ class ResMrpWorkOrder(models.Model): picking_out.write({'state': 'assigned'}) if self.state == 'waiting' or self.state == 'ready' or self.state == 'progress': self.move_raw_ids = self.production_id.move_raw_ids + self.move_raw_ids[0].write({ + 'materiel_length': self.move_raw_ids[0].product_id.length, + 'materiel_width': self.move_raw_ids[0].product_id.width, + 'materiel_height': self.move_raw_ids[0].product_id.height + }) + self.ensure_one() if any(not time.date_end for time in self.time_ids.filtered(lambda t: t.user_id.id == self.env.user.id)): return True @@ -605,6 +739,8 @@ class ResMrpWorkOrder(models.Model): if self.routing_type == '装夹预调': if not self.material_center_point and self.X_deviation_angle > 0: raise UserError("请对前置三元检测定位参数进行计算定位") + if not self.rfid_code: + raise UserError("请扫RFID码进行绑定") if self.picking_out_id: picking_out = self.env['stock.picking'].search([('id', '=', self.picking_out_id.id)]) if picking_out.workorder_out_id: @@ -647,7 +783,7 @@ class ResMrpWorkOrder(models.Model): production_no_ftp = reportpath.split('/') production_no = workorder.production_id.name.replace('/', '_') # ftp地址 - remotepath = os.path.join('/', production_no_ftp[1], 'detection') + remotepath = os.path.join('/NC', production_no_ftp[1], 'detection') logging.info('ftp地址:%s' % remotepath) if reportpath.find(production_no) != -1: # 服务器内临时地址 @@ -726,7 +862,7 @@ class CNCprocessing(models.Model): 'cutting_tool_handle_type': obj['cutting_tool_handle_type'], 'estimated_processing_time': obj['estimated_processing_time'], 'remark': obj['remark'], - 'program_path': program_path.replace('/tmp', '') + 'program_path': program_path.replace('/tmp', '/home/ftp/ftp_root/NC') }) cnc_processing.get_cnc_processing_file(program_path_tmp, cnc_processing, program_path) # cnc_workorder.state = 'done' @@ -735,7 +871,7 @@ class CNCprocessing(models.Model): # cnc_workorder.time_ids.date_end = datetime.now() # cnc_workorder.button_finish() - # 根据程序名和加工面匹配到ftp里对应的Nc程序名 + # 根据程序名和加工面匹配到ftp里对应的Nc程序名,可优化为根据cnc_processing.program_path进行匹配 def get_cnc_processing_file(self, serverdir, cnc_processing, program_path): logging.info('serverdir:%s' % serverdir) for root, dirs, files in os.walk(serverdir): @@ -765,7 +901,7 @@ class CNCprocessing(models.Model): # 将FTP的nc文件下载到临时目录 def download_file_tmp(self, production_no, processing_panel): - remotepath = os.path.join('/', production_no, 'return', processing_panel) + remotepath = os.path.join('/NC', production_no, 'return', processing_panel) serverdir = os.path.join('/tmp', production_no, 'return', processing_panel) ftp_resconfig = self.env['res.config.settings'].get_values() ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']), ftp_resconfig['ftp_user'], @@ -777,7 +913,7 @@ class CNCprocessing(models.Model): # 将nc文件存到attach的datas里 def write_file(self, nc_file_path, cnc): nc_file_name = nc_file_path.split('/') - logging.info('nc_file_name:%s' % nc_file_name[-1]) + logging.info('file_name:%s' % nc_file_name[-1]) if os.path.exists(nc_file_path): with open(nc_file_path, 'rb') as file: data_bytes = file.read() @@ -818,25 +954,35 @@ class SfWorkOrderBarcodes(models.Model): workorder = self.env['mrp.workorder'].browse(self.ids) # workorder = self.env['mrp.workorder'].search( # [('routing_type', '=', '装夹预调'), ('production_id', '=', self.production_id.id)]) + workorder_old = self.env['mrp.workorder'].search([('rfid_code', '=', barcode)]) + if workorder_old: + raise UserError('该托盘已绑定工件,请先解除绑定!!!') if workorder: if workorder.routing_type == '装夹预调': - stock_move_line = self.env['stock.move.line'].search([('lot_name', '=', barcode)]) - if stock_move_line.product_id.categ_type == '夹具': - workorder.write({ - 'tray_serial_number': stock_move_line.lot_name, - 'tray_product_id': stock_move_line.product_id.id, - 'tray_brand_id': stock_move_line.product_id.brand_id.id, - 'tray_type_id': stock_move_line.product_id.fixture_material_id.id, - 'tray_model_id': stock_move_line.product_id.fixture_model_id.id - }) - workorder.button_start() - # return { - # 'type': 'ir.actions.act_window', - # 'res_model': 'mrp.workorder', - # 'view_mode': 'form', - # 'domain': [('id', 'in', workorder.id)], - # 'target': 'current' - # } + if workorder.state in ['done']: + work_state = {'done': '已完工'} + raise UserError('装夹%s,请勿重复扫码' % work_state.get(workorder.state)) + lots = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)]) + logging.info("托盘信息:%s" % lots) + if lots: + for lot in lots: + if lot.product_id.categ_type == '夹具': + val = { + 'tray_serial_number': lot.name, + 'tray_product_id': lot.product_id.id, + 'tray_brand_id': lot.product_id.brand_id.id, + 'tray_type_id': lot.product_id.fixture_material_id.id, + 'tray_model_id': lot.product_id.fixture_model_id.id, + 'rfid_code': barcode + } + workorder.write(val) + self.write(val) + workorder_rfid = self.env['mrp.workorder'].search( + [('production_id', '=', workorder.production_id.id)]) + if workorder_rfid: + for item in workorder_rfid: + item.write({'rfid_code': barcode}) + logging.info("Rfid绑定成功!!!") else: embryo_stock_lot = self.env['stock.lot'].search([('name', '=', barcode)]) if embryo_stock_lot: @@ -868,7 +1014,58 @@ class SfWorkOrderBarcodes(models.Model): [('production_id', '=', workorder.production_id.id)]) if workorder_rfid: for item in workorder_rfid: - item.write({'rfid_code': barcode}) + if item.state == "progress": + item.write({'rfid_code': barcode}) + raise UserError('该托盘信息不存在!!!') + # stock_move_line = self.env['stock.move.line'].search([('lot_name', '=', barcode)]) + # if stock_move_line.product_id.categ_type == '夹具': + # workorder.write({ + # 'tray_serial_number': stock_move_line.lot_name, + # 'tray_product_id': stock_move_line.product_id.id, + # 'tray_brand_id': stock_move_line.product_id.brand_id.id, + # 'tray_type_id': stock_move_line.product_id.fixture_material_id.id, + # 'tray_model_id': stock_move_line.product_id.fixture_model_id.id + # }) + # workorder.button_start() + # # return { + # # 'type': 'ir.actions.act_window', + # # 'res_model': 'mrp.workorder', + # # 'view_mode': 'form', + # # 'domain': [('id', 'in', workorder.id)], + # # 'target': 'current' + # # } + # else: + # embryo_stock_lot = self.env['stock.lot'].search([('name', '=', barcode)]) + # if embryo_stock_lot: + # embryo_stock_move_line = self.env['stock.move.line'].search( + # [('product_id', '=', embryo_stock_lot.product_id.id), + # ('reference', '=', workorder.production_id.name), + # ('lot_id', '=', embryo_stock_lot.id), + # ('product_category_name', '=', '坯料')]) + # if embryo_stock_move_line: + # bom_production = self.env['mrp.production'].search( + # [('product_id', '=', embryo_stock_lot.product_id.id), + # ('origin', '=', workorder.production_id.name)], limit=1, order='id asc') + # workpiece_delivery = self.env['sf.workpiece.delivery'].search( + # [('workorder_id', '=', workorder.id)], limit=1, order='id asc') + # if workpiece_delivery: + # embryo_workpiece_code = workpiece_delivery.workpiece_code + # if bom_production: + # if workpiece_delivery.workpiece_code and bom_production.name not in \ + # workpiece_delivery.workpiece_code: + # embryo_workpiece_code = workpiece_delivery.workpiece_code + ',' + \ + # bom_production.name + # if not workpiece_delivery.workpiece_code: + # embryo_workpiece_code = bom_production.name + # workpiece_delivery.write({'workpiece_code': embryo_workpiece_code}) + # else: + # raise UserError('工件生产线不一致,请重新确认') + # else: + # workorder_rfid = self.env['mrp.workorder'].search( + # [('production_id', '=', workorder.production_id.id)]) + # if workorder_rfid: + # for item in workorder_rfid: + # item.write({'rfid_code': barcode}) class WorkPieceDelivery(models.Model): @@ -882,53 +1079,88 @@ class WorkPieceDelivery(models.Model): store=True) plan_start_processing_time = fields.Datetime('计划开始加工时间', readonly=True) workpiece_code = fields.Char('同运工件编码') - feeder_station_start = fields.Char('起点接驳站') - feeder_station_destination = fields.Char('目的接驳站') + route_id = fields.Many2one('sf.agv.task.route', '任务路线') + feeder_station_start_id = fields.Many2one('sf.agv.site', '起点接驳站') + feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站') task_delivery_time = fields.Datetime('任务下发时间') task_completion_time = fields.Datetime('任务完成时间') delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration') status = fields.Selection( [('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送')], string='状态', default='待下发') + production_line_state = fields.Selection( + [('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')], + string='上/下产线', default='待上产线') + cnc_program_down_state = fields.Selection([('待下发', '待下发'), ('已下发', '已下发')], + string='CNC程序下发状态', default='待下发') + agv_task_code = fields.Char('agv任务单号') + + @api.onchange('route_id') + def onchage_route(self): + if self.route_id: + self.feeder_station_start_id = self.route_id.start_site_id + self.feeder_station_destination_id = self.route_id.end_site_id # 工件配送 def button_delivery(self): - if self.status == '待下发': - return { - 'name': _('确认'), - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'sf.workpiece.delivery.wizard', - 'target': 'new', - 'context': { - 'default_delivery_id': self.id, - }} + if self.cnc_program_down_state == '待下发': + if self.route_id: + if self.status == '待下发': + return { + 'name': _('确认'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'sf.workpiece.delivery.wizard', + 'target': 'new', + 'context': { + 'default_delivery_id': self.id, + }} + else: + raise UserError('状态为【待下发】的工件记录可进行配送') + else: + raise UserError('请选择任务路线再进行配送') else: - raise UserError('状态为【待下发】的工件记录可进行配送') + raise UserError(_("该制造订单还未下发CNC程序单,无法进行工件配送")) # 配送至avg小车 def _delivery_avg(self): + config = self.env['res.config.settings'].get_values() + positionCode_Arr = [] + if self.feeder_station_start_id: + positionCode_Arr.append({ + 'positionCode': self.feeder_station_start_id.name, + 'code': '00' + }) + if self.feeder_station_destination_id: + positionCode_Arr.append({ + 'positionCode': self.feeder_station_destination_id.name, + 'code': '00' + }) res = {'reqCode': self.production_id.name, 'reqTime': '', 'clientCode': '', 'tokenCode': '', - 'taskTyp': 'F01', 'ctnrTyp': '', 'ctnrCode': '', 'wbCode': '', 'positionCodePath': [], 'podCode': '', + 'taskTyp': 'F01', 'ctnrTyp': '', 'ctnrCode': '', 'wbCode': config['wbcode'], + 'positionCodePath': positionCode_Arr, + 'podCode': '', 'podDir': '', 'materialLot': '', 'priority': '', 'taskCode': '', 'agvCode': '', 'materialLot': '', 'data': ''} - config = self.env['res.config.settings'].get_values() try: - logging.info('config-AGV请求路径:%s' % config['agv_rcms_url']) - logging.info('config-json:%s' % res) - ret = requests.post((config['agv_rcms_url']), json=res) + config['agv_rcs_url'] = 'http://172.16.10.114:8182/rcms/services/rest/hikRpcService/genAgvSchedulingTask' + logging.info('AGV请求路径:%s' % config['agv_rcs_url']) + logging.info('AGV-json:%s' % res) + headers = {'Content-Type': 'application/json'} + ret = requests.post((config['agv_rcs_url']), json=res, headers=headers) ret = ret.json() logging.info('config-ret:%s' % ret) if ret['code'] == 0: if self.production_id.name == ret['reqCode']: - self.write({'task_delivery_time': fields.Datetime.now(), 'status': '待配送'}) + self.write( + {'task_delivery_time': fields.Datetime.now(), 'status': '待配送', 'agv_task_code': ret['data']}) else: raise UserError(ret['message']) except Exception as e: logging.info('config-e:%s' % e) raise UserError("工件配送请求agv失败") - @api.depends('production_id.production_line_id') + @api.onchange('production_id.production_line_id') def _compute_production_line_id(self): if self.production_id.production_line_id: self.production_line_id = self.production_id.production_line_id.id @@ -948,7 +1180,77 @@ class CMMprogram(models.Model): _name = 'sf.cmm.program' _description = "CMM程序" - program_path = fields.Char('程序文件路径') - post_processing_name = fields.Char('后处理名称') - program_date = fields.Datetime('程序日期') + cmm_id = fields.Many2one('ir.attachment') + sequence_number = fields.Integer('序号') + program_name = fields.Char('程序名') + cutting_tool_name = fields.Char('刀具名称') + cutting_tool_no = fields.Char('刀号') + processing_type = fields.Char('加工类型') + margin_x_y = fields.Char('余量_X/Y') + margin_z = fields.Char('余量_Z') + depth_of_processing_z = fields.Char('加工深度(Z)') + cutting_tool_extension_length = fields.Char('刀具伸出长度') + cutting_tool_handle_type = fields.Char('刀柄型号') + estimated_processing_time = fields.Char('预计加工时间') + remark = fields.Text('备注') workorder_id = fields.Many2one('mrp.workorder', string="工单") + production_id = fields.Many2one('mrp.production', string="制造订单") + program_path = fields.Char('程序文件路径') + + def cmm_program_create(self, ret, program_path, program_path_tmp): + for obj in ret['programming_list']: + workorder = self.env['mrp.workorder'].search([('production_id.name', '=', ret['production_order_no']), + ('processing_panel', '=', obj['processing_panel']), + ('routing_type', '=', 'CNC加工')]) + if obj['program_name'] in program_path: + logging.info('obj:%s' % obj['program_name']) + cmm_program = self.sudo().create({ + 'workorder_id': workorder.id, + 'sequence_number': obj['sequence_number'], + 'program_name': obj['program_name'], + 'cutting_tool_name': obj['cutting_tool_name'], + 'cutting_tool_no': obj['cutting_tool_no'], + 'processing_type': obj['processing_type'], + 'margin_x_y': obj['margin_x_y'], + 'margin_z': obj['margin_z'], + 'depth_of_processing_z': obj['depth_of_processing_z'], + 'cutting_tool_extension_length': obj['cutting_tool_extension_length'], + 'cutting_tool_handle_type': obj['cutting_tool_handle_type'], + 'estimated_processing_time': obj['estimated_processing_time'], + 'remark': obj['remark'], + 'program_path': program_path.replace('/tmp', '') + }) + cmm_program.get_cmm_program_file(program_path_tmp, cmm_program, program_path) + + # 根据程序名和加工面匹配到ftp里对应的cmm程序名 + def get_cmm_program_file(self, serverdir, cmm_program, program_path): + logging.info('cmm-serverdir:%s' % serverdir) + for root, dirs, files in os.walk(serverdir): + for f in files: + if f in program_path: + cmm_program_file_path = os.path.join(serverdir, root, f) + self.write_file_cmm(cmm_program_file_path, cmm_program) + + # 创建附件(nc文件) + def attachment_create(self, name, data): + attachment = self.env['ir.attachment'].create({ + 'datas': base64.b64encode(data), + 'type': 'binary', + 'public': True, + 'description': '程序文件', + 'name': name + }) + return attachment + + # 将cmm文件存到attach的datas里 + def write_file_cmm(self, cmm_file_path, cnc): + cmm_file_name = cmm_file_path.split('/') + logging.info('cmm_file_name:%s' % cmm_file_name[-1]) + if os.path.exists(cmm_file_path): + with open(cmm_file_path, 'rb') as file: + data_bytes = file.read() + attachment = self.attachment_create(cnc.program_name + cmm_file_name[-1], data_bytes) + cnc.write({'cmm_id': attachment.id}) + file.close() + else: + return False diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index be0c09ae..d766629c 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -1,15 +1,14 @@ # -*- coding: utf-8 -*- import logging +import requests import base64 import hashlib import os from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from odoo.modules import get_resource_path - - -from OCC.Extend.DataExchange import read_step_file -from OCC.Extend.DataExchange import write_stl_file +# from OCC.Extend.DataExchange import read_step_file +# from OCC.Extend.DataExchange import write_stl_file class ResProductMo(models.Model): @@ -107,7 +106,10 @@ class ResProductMo(models.Model): @api.onchange('cutting_tool_model_id') def _onchange_cutting_tool_model_id(self): - self.specification_id = False + for item in self: + if item: + item.specification_id = False + item.cutting_tool_chuck_id = item.cutting_tool_model_id.chuck_id @api.onchange('cutting_tool_material_id') def _onchange_cutting_tool_material_id(self): @@ -310,7 +312,6 @@ class ResProductMo(models.Model): self.cutting_tool_is_safety_lock = self.specification_id.is_safe_lock self.cutting_tool_fit_nut_model = self.specification_id.nut self.cutting_tool_wrench = self.specification_id.spanner - self.cutting_tool_chuck_id = self.specification_id.chuck_id.id self.cutting_tool_jump_accuracy = self.specification_id.diameter_slip_accuracy self.cutting_tool_taper_shank_model = self.specification_id.taper_shank_model self.cutting_tool_cooling_type = self.specification_id.cooling_model @@ -521,6 +522,9 @@ class ResProductMo(models.Model): string='注册状态', default='未注册') industry_code = fields.Char('行业编码', readonly=True) + # bfm下单 + manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) + @api.constrains('tool_length') def _check_tool_length_size(self): if self.tool_length > 1000000: @@ -615,6 +619,7 @@ class ResProductMo(models.Model): 'process_parameters_code') else self.get_process_parameters_id(item['process_parameters_code']), 'model_remark': item['remark'], 'default_code': '%s-%s' % (order_number, i), + 'manual_quotation': item['manual_quotation'] or False, 'active': True, } copy_product_id.sudo().write(vals) @@ -888,6 +893,33 @@ class SfMaintenanceEquipmentAndProductTemplate(models.Model): vals.append(res) return vals[0] + base_coordinate_fixture_model_id = fields.Many2one('sf.fixture.model', '基坐标卡盘型号', + domain=[('fixture_material_id', '=', '零点卡盘')]) + base_coordinate_g_coordinate = fields.Char('G坐标') + base_coordinate_x = fields.Float('x轴', digits=(12, 3)) + base_coordinate_y = fields.Float('y轴', digits=(12, 3)) + base_coordinate_z = fields.Float('z轴', digits=(12, 3)) + + # ==========获取机床基坐标接口========== + def get_equipment_base_coordinate(self): + headers = {'Authorization': 'Ba F2CF5DCC-1A00-4234-9E95-65603F70CC8A'} + crea_url = "https://x24467i973.zicp.fun/AutoDeviceApi/EquipmentBaseCoordinate" + params = {"DeviceId": self.name} + r = requests.get(crea_url, params=params, headers=headers) + ret = r.json() + logging.info('register_equipment_tool:%s' % ret) + self.write({ + 'base_coordinate_fixture_model_id': ret['base_coordinate_fixture_model_id'], + 'base_coordinate_g_coordinate': ret['base_coordinate_g_coordinate'], + 'base_coordinate_x': ret['base_coordinate_x'], + 'base_coordinate_y': ret['base_coordinate_y'], + 'base_coordinate_z': ret['base_coordinate_z'], + }) + if ret['Succeed']: + return "机床基坐标获取成功" + else: + raise ValidationError("机床基坐标获取失败") + class SfMaintenanceEquipmentTool(models.Model): _name = 'maintenance.equipment.tool' diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 3166441a..94a9d646 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -17,6 +17,50 @@ from io import BytesIO from odoo.exceptions import ValidationError +class stockWarehouse(models.Model): + _inherit = 'stock.warehouse' + + subcontracting_surface_technology_pull_out_id = fields.Many2one( + 'stock.rule', '表面工艺规则1') + subcontracting_surface_technology_pull_in_id = fields.Many2one( + 'stock.rule', '表面工艺规则2' + ) + + def _get_global_route_rules_values(self): + rules = super(stockWarehouse, self)._get_global_route_rules_values() + location_virtual_id = self.env.ref( + 'sf_manufacturing.stock_location_locations_virtual_outcontract').id, + location_pre_id = self.env['stock.location'].search( + [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id, + rules.update({ + 'subcontracting_surface_technology_pull_in_id': { + 'create_values': { + 'action': 'pull', + 'picking_type_id': self.env.ref('sf_manufacturing.outcontract_picking_in').id, + 'group_propagation_option': 'none', + 'company_id': self.company_id.id, + 'location_src_id': location_virtual_id, + 'location_dest_id': location_pre_id, + 'route_id': self._find_global_route('sf_manufacturing.route_surface_technology_outsourcing', + _('表面工艺外协')).id, + } + }, + 'subcontracting_surface_technology_pull_out_id': { + 'create_values': { + 'action': 'pull', + 'picking_type_id': self.env.ref('sf_manufacturing.outcontract_picking_out').id, + 'group_propagation_option': 'none', + 'company_id': self.company_id.id, + 'location_src_id': location_pre_id, + 'location_dest_id': location_virtual_id, + 'route_id': self._find_global_route('sf_manufacturing.route_surface_technology_outsourcing', + _('表面工艺外协')).id, + } + } + }) + return rules + + class StockRule(models.Model): _inherit = 'stock.rule' @@ -222,6 +266,21 @@ class ProductionLot(models.Model): _name = 'stock.lot' _inherit = ['stock.lot', 'printing.utils'] + rfid = fields.Char('Rfid', readonly=True) + product_specification = fields.Char('规格', compute='_compute_product_specification', store=True) + + @api.depends('product_id') + def _compute_product_specification(self): + for stock in self: + if stock: + if stock.product_id: + if stock.product_id.categ_id.name in '刀具': + stock.product_specification = stock.product_id.specification_id.name + elif stock.product_id.categ_id.name in '夹具': + stock.product_specification = stock.product_id.specification_fixture_id.name + else: + stock.product_specification = stock.product_id.default_code + @api.model def generate_lot_names1(self, display_name, first_lot, count): """Generate `lot_names` from a string.""" @@ -258,13 +317,15 @@ class ProductionLot(models.Model): """ now = datetime.now().strftime("%Y%m%d") last_serial = self.env['stock.lot'].search( - [('company_id', '=', company.id), ('product_id', '=', product.id), ('name', 'like', now)], + [('company_id', '=', company.id), ('product_id', '=', product.id)], limit=1, order='id DESC') if product.cutting_tool_model_id: + split_codes = product.cutting_tool_model_id.code.split('-') if not last_serial: - return "%s-%s%03d" % (product.cutting_tool_model_id.code[:-12], now, 1) + return "%s-T-%s-%s-%03d" % (split_codes[0], now, product.specification_id.name, 1) else: - return "%s-%s%03d" % (product.cutting_tool_model_id.code[:-12], now, int(last_serial.name[-3:]) + 2) + return "%s-T-%s-%s-%03d" % ( + split_codes[0], now, product.specification_id.name, int(last_serial.name[-3:]) + 1) else: raise ValidationError('该刀具物料产品的型号字段为空,请补充完整!!!') @@ -282,7 +343,8 @@ class ProductionLot(models.Model): return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name, 2)[1] now = datetime.now().strftime("%Y%m%d") if product.cutting_tool_model_id: - return "%s-%s%03d" % (product.cutting_tool_model_id.code[:-12], now, 1) + split_codes = product.cutting_tool_model_id.code.split('-') + return "%s-T-%s-%s-%03d" % (split_codes[0], now, product.specification_id.name, 1) return "%s-%03d" % (product.name, 1) qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code') @@ -319,7 +381,7 @@ class ProductionLot(models.Model): # port = 9100 # 可以根据实际情况修改 # 获取默认打印机配置 - printer_config = self.env['printer.configuration'].search([('model', '=', self._name)], limit=1) + printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name)], limit=1) if not printer_config: raise UserError('请先配置打印机') host = printer_config.printer_id.ip_address @@ -366,10 +428,10 @@ class StockPicking(models.Model): # 设置外协出入单的名称 def _get_name_Res(self, rescode): last_picking = self.sudo().search([('name', 'like', rescode)], order='create_date DESC', limit=1) - logging.info('编号:' + last_picking.name) if not last_picking: num = "%04d" % 1 else: + logging.info('编号:' + last_picking.name) m = int(last_picking.name[-3:]) + 1 num = "%04d" % m return '%s%s' % (rescode, num) @@ -419,7 +481,7 @@ class StockPicking(models.Model): location_id = self.env.ref( 'sf_manufacturing.stock_location_locations_virtual_outcontract').id, location_dest_id = self.env['stock.location'].search( - [('barcode', '=', 'WH-PREPRODUCTION')]).id, + [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id, outcontract_picking_type_in = self.env.ref( 'sf_manufacturing.outcontract_picking_in').id, outcontract_picking_type_out = self.env.ref( @@ -480,6 +542,65 @@ class ReStockMove(models.Model): else: raise UserError(_("没有可打印的标签数据")) + def action_show_details(self): + """ Returns an action that will open a form view (in a popup) allowing to work on all the + move lines of a particular move. This form view is used when "show operations" is not + checked on the picking type. + """ + self.ensure_one() + + # If "show suggestions" is not checked on the picking type, we have to filter out the + # reserved move lines. We do this by displaying `move_line_nosuggest_ids`. We use + # different views to display one field or another so that the webclient doesn't have to + # fetch both. + if self.picking_type_id.show_reserved: + view = self.env.ref('stock.view_stock_move_operations') + else: + view = self.env.ref('stock.view_stock_move_nosuggest_operations') + + if self.product_id.tracking == "serial" and self.state == "assigned": + print(self.origin) + if self.product_id.categ_id.name == '刀具': + self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) + else: + self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) + + return { + 'name': _('Detailed Operations'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'stock.move', + 'views': [(view.id, 'form')], + 'view_id': view.id, + 'target': 'new', + 'res_id': self.id, + 'context': dict( + self.env.context, + show_owner=self.picking_type_id.code != 'incoming', + show_lots_m2o=self.has_tracking != 'none' and ( + self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), + # able to create lots, whatever the value of ` use_create_lots`. + show_lots_text=self.has_tracking != 'none' and self.picking_type_id.use_create_lots and not self.picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id, + show_source_location=self.picking_type_id.code != 'incoming', + show_destination_location=self.picking_type_id.code != 'outgoing', + show_package=not self.location_id.usage == 'supplier', + show_reserved_quantity=self.state != 'done' and not self.picking_id.immediate_transfer and self.picking_type_id.code != 'incoming' + ), + } + + def _get_tool_next_serial(self, company, product, origin): + """Return the next serial number to be attributed to the product.""" + if product.tracking == "serial": + last_serial = self.env['stock.lot'].search( + [('company_id', '=', company.id), ('product_id', '=', product.id), ('name', 'ilike', origin)], + limit=1, order='id DESC') + split_codes = product.cutting_tool_model_id.code.split('-') + if last_serial: + return "%s-T-%s-%s-%03d" % ( + split_codes[0], origin, product.specification_id.name, int(last_serial.name[-3:]) + 1) + else: + return "%s-T-%s-%s-%03d" % (split_codes[0], origin, product.specification_id.name, 1) + class ReStockQuant(models.Model): _inherit = 'stock.quant' diff --git a/sf_manufacturing/security/img.png b/sf_manufacturing/security/img.png new file mode 100644 index 00000000..130affd5 Binary files /dev/null and b/sf_manufacturing/security/img.png differ diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index 8272af30..e8c2449c 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -129,4 +129,7 @@ access_sf_cmm_program_group_plan_dispatch,sf_cmm_program_group_plan_dispatch,mod access_mrp_workcenter_productivity,mrp.workcenter.productivity,mrp.model_mrp_workcenter_productivity,sf_base.group_plan_dispatch,1,0,0,0 access_maintenance_equipment_tool_group_plan_dispatch,maintenance.equipment.tool,sf_manufacturing.model_maintenance_equipment_tool,sf_base.group_plan_dispatch,1,0,0,0 -access_sf_workpiece_delivery_group_plan_dispatch,sf.workpiece.delivery,sf_manufacturing.model_sf_workpiece_delivery,sf_base.group_plan_dispatch,1,0,0,0 \ No newline at end of file +access_sf_workpiece_delivery_group_plan_dispatch,sf.workpiece.delivery,sf_manufacturing.model_sf_workpiece_delivery,sf_base.group_plan_dispatch,1,0,0,0 + +access_sf_agv_site_group_sf_order_user,sf_agv_site_group_sf_order_user,model_sf_agv_site,sf_base.group_sf_order_user,1,1,1,0 +access_sf_agv_task_route_group_sf_order_user,sf_agv_task_route_group_sf_order_user,model_sf_agv_task_route,sf_base.group_sf_order_user,1,1,1,0 \ No newline at end of file diff --git a/sf_manufacturing/views/agv_setting_views.xml b/sf_manufacturing/views/agv_setting_views.xml new file mode 100644 index 00000000..dea85e6a --- /dev/null +++ b/sf_manufacturing/views/agv_setting_views.xml @@ -0,0 +1,59 @@ + + + + + + agv站点 + sf.agv.site + + + + + + + + + + + + + AGV站点 + sf.agv.site + tree + + + + + + + AGV任务路线 + sf.agv.task.route + + + + + + + + + + + + + + AGV任务路线 + sf.agv.task.route + tree + + + + + \ No newline at end of file diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index 0ae0d95f..6ba78573 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -422,7 +422,8 @@
规格: - + +
[] diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index 308ba2f1..0cc541e7 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -46,6 +46,9 @@ 1
+ + + @@ -104,22 +107,20 @@ - +

- + + + + + + @@ -242,6 +243,7 @@ + @@ -250,7 +252,6 @@ - @@ -407,8 +408,11 @@
+ +
@@ -421,8 +425,10 @@ - - + + + + @@ -437,16 +443,17 @@ - + + -
-
+ + + + +
@@ -476,9 +483,19 @@ - - - + + + + + + + + + + + + + @@ -510,6 +527,8 @@ +
@@ -540,7 +559,7 @@ 工件配送 sf.workpiece.delivery - +
@@ -550,11 +569,13 @@ decoration-danger="status == '待配送'"/> - - - - - + + + + + + +
@@ -567,16 +588,19 @@ - + - + + + +
diff --git a/sf_manufacturing/views/sf_maintenance_equipment.xml b/sf_manufacturing/views/sf_maintenance_equipment.xml index b398f994..5ddf59b0 100644 --- a/sf_manufacturing/views/sf_maintenance_equipment.xml +++ b/sf_manufacturing/views/sf_maintenance_equipment.xml @@ -1,23 +1,38 @@ - 设备增加刀具库位table + sf_manufacturing_equipment.form maintenance.equipment - + - - - - - - - - - - + + + + + +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + sf.functional.cutting.tool.entity.list.tree + sf.functional.cutting.tool.entity + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 功能刀具列表 + ir.actions.act_window + sf.functional.cutting.tool.entity + tree,form,search + + + + + + sf.functional.tool.warning.tree + sf.functional.tool.warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sf.functional.tool.warning.search + sf.functional.tool.warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 功能刀具预警 + ir.actions.act_window + sf.functional.tool.warning + tree,search + + + + + + 功能刀具安全库存 + sf.real.time.distribution.of.functional.tools + + + + + + + + + + + + + + + + + + + + + + + 功能刀具安全库存 + sf.real.time.distribution.of.functional.tools + +
+ + + + + +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + 功能刀具安全库存 + sf.real.time.distribution.of.functional.tools + + + + + + + + + + + + + + + + + + + + + + + + 功能刀具安全库存 + ir.actions.act_window + sf.real.time.distribution.of.functional.tools + tree,form,search + + + + + + + 功能刀具出入库记录 + stock.move.line + + + + + + + + + + + + + + + + + + + + + + + 功能刀具出入库记录 + stock.move.line + + + + + + + + + + + + + + + + + + + + + + + 功能刀具出入库记录 + ir.actions.act_window + stock.move.line + tree,search + + + [('functional_tool_name_id', '!=', False)] + + + + \ No newline at end of file diff --git a/sf_tool_management/views/menu_view.xml b/sf_tool_management/views/menu_view.xml index ea2e0fbd..13a12725 100644 --- a/sf_tool_management/views/menu_view.xml +++ b/sf_tool_management/views/menu_view.xml @@ -7,6 +7,13 @@ groups="mrp.group_mrp_routings" parent="mrp.menu_mrp_root" sequence="20"/> + + + - - - sf.functional.cutting.tool.entity.list.tree - sf.functional.cutting.tool.entity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sf.functional.cutting.tool.entity.list.form - sf.functional.cutting.tool.entity - -
-
- - - -
- -
- - - -
-
-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- - - sf.functional.cutting.tool.entity.list.tree - sf.functional.cutting.tool.entity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 功能刀具列表 - ir.actions.act_window - sf.functional.cutting.tool.entity - tree,form,search - - - - - - sf.functional.tool.warning.tree - sf.functional.tool.warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - sf.functional.tool.warning.search - sf.functional.tool.warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 功能刀具预警 - ir.actions.act_window - sf.functional.tool.warning - tree,search - - - - - - 功能刀具安全库存 - sf.real.time.distribution.of.functional.tools - - - - - - - - - - - - - - - - - - - - - - 功能刀具安全库存 - sf.real.time.distribution.of.functional.tools - -
- - - - - -
-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- - - 功能刀具安全库存 - sf.real.time.distribution.of.functional.tools - - - - - - - - - - - - - - - - - - - - - - - - 功能刀具安全库存 - ir.actions.act_window - sf.real.time.distribution.of.functional.tools - tree,form,search - - - - - - - 功能刀具出入库记录 - stock.move.line - - - - - - - - - - - - - - - - - - - - - - 功能刀具出入库记录 - stock.move.line - - - - - - - - - - - - - - - - - - - - - - - 功能刀具出入库记录 - ir.actions.act_window - stock.move.line - tree,search - - - [('functional_tool_name_id', '!=', False)] - - - 机床换刀申请 @@ -995,7 +491,7 @@ 功能刀具组装 sf.functional.tool.assembly -
+
@@ -23,12 +24,21 @@ - - - - - - + + + - - - - - + + + + + + @@ -162,20 +185,20 @@ shelf.location.kanban sf.shelf.location - +
- +
- +
@@ -186,31 +209,31 @@
- - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -222,9 +245,10 @@ sf.shelf.location +
diff --git a/sf_warehouse/views/view.xml b/sf_warehouse/views/view.xml index 39d5a8c0..aa3293ce 100644 --- a/sf_warehouse/views/view.xml +++ b/sf_warehouse/views/view.xml @@ -67,16 +67,16 @@
- -
- -
+ + + + + + + + -
+ @@ -188,23 +188,23 @@ - - stock.warehouse.form.sf.inherit - stock.warehouse - - - -
- -
+ + + + + + + + + + + + + -
-
-
+ + + @@ -220,23 +220,23 @@ - - stock.route.form.sf.inherit - stock.route - - - -
- -
+ + + + + + + + + + + + + -
-
-
+ + + @@ -251,23 +251,23 @@ - - stock.rule.form.sf.inherit - stock.rule - - - -
- -
+ + + + + + + + + + + + + -
-
-
+ + + @@ -282,23 +282,23 @@ - - stock.picking.type.form.sf.inherit - stock.picking.type - - - -
- -
+ + + + + + + + + + + + + -
-
-
+ + + diff --git a/sf_warehouse/wizard/__init__.py b/sf_warehouse/wizard/__init__.py new file mode 100644 index 00000000..20a7f5c4 --- /dev/null +++ b/sf_warehouse/wizard/__init__.py @@ -0,0 +1 @@ +from . import wizard \ No newline at end of file diff --git a/sf_warehouse/wizard/wizard.py b/sf_warehouse/wizard/wizard.py new file mode 100644 index 00000000..db64fe07 --- /dev/null +++ b/sf_warehouse/wizard/wizard.py @@ -0,0 +1,64 @@ +from odoo import fields, models, api +from odoo.exceptions import UserError, ValidationError + + +class ShelfLocationWizard(models.TransientModel): + _name = 'sf.shelf.location.wizard' + _description = '货位变更' + + name = fields.Char('') + + current_location_id = fields.Many2one('stock.location', string='所属库区', readonly=True) + + current_shelf_id = fields.Many2one('sf.shelf', string='当前货架', readonly=True) + current_barcode = fields.Char('当前货位编码', readonly=True) + current_name = fields.Char('当前货位名称', readonly=True) + current_product_id = fields.Many2one('product.product', string='产品', readonly=True) + + destination_shelf_id = fields.Many2one('sf.shelf', string='目标货架', compute='_compute_destination_name') + destination_barcode_id = fields.Many2one('sf.shelf.location', string='目标货位编码', required=True, + domain="") + destination_name = fields.Char('目标货位名称', compute='_compute_destination_name') + + def return_domain(self): + val = [('location_status', '=', '空闲')] + if self.current_product_id: + val = ['|', ('location_status', '=', '空闲'), ('product_id', '=', self.current_product_id)] + if self.destination_shelf_id: + val.append(('shelf_id', '=', self.destination_shelf_id)) + return "%s" % val + + @api.depends('destination_barcode_id') + def _compute_destination_name(self): + if self.destination_barcode_id: + self.destination_name = self.destination_barcode_id.name + self.destination_shelf_id = self.destination_barcode_id.shelf_id.id + else: + self.destination_name = '' + self.destination_shelf_id = False + + # + # @api.onchange('destination_barcode_id') + # def _onchange_destination_shelf_id(self): + # if self.destination_barcode_id: + # self.destination_shelf_id = self.destination_barcode_id.shelf_id.id + + def confirm_the_change(self): + shelf_location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.current_barcode)]) + # 变更货位 + if self.destination_barcode_id and shelf_location: + if self.destination_barcode_id.product_id and self.destination_barcode_id.product_id == shelf_location.product_id and not self.destination_barcode_id.product_sn_id: + self.destination_barcode_id.product_num += shelf_location.product_num + else: + self.destination_barcode_id.product_sn_id = shelf_location.product_sn_id.id + self.destination_barcode_id.product_id = shelf_location.product_id.id + self.destination_barcode_id.product_num = shelf_location.product_num + + shelf_location.product_sn_id = False + shelf_location.product_id = False + shelf_location.product_num = 0 + else: + raise ValidationError('目标货位出错,请联系管理员!') + + # 关闭弹出窗口 + return {'type': 'ir.actions.act_window_close'} diff --git a/sf_warehouse/wizard/wizard_view.xml b/sf_warehouse/wizard/wizard_view.xml new file mode 100644 index 00000000..1bb95e15 --- /dev/null +++ b/sf_warehouse/wizard/wizard_view.xml @@ -0,0 +1,49 @@ + + + + 货位变更 + sf.shelf.location.wizard + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + 货位变更 + ir.actions.act_window + sf.shelf.location.wizard + form + + new + +
\ No newline at end of file