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 5d8b2fbf..3296b127 100644 --- a/jikimo_frontend/static/src/js/custom_form_status_indicator.js +++ b/jikimo_frontend/static/src/js/custom_form_status_indicator.js @@ -115,9 +115,20 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { this.setRequired() this.listherHeaderBodyNum() }) - owl.onPatched(() => { + owl.onPatched(() => { this.listherHeaderBodyNum() }) + const treeModifiers = this.getFieldModifiers(this.props.archInfo.__rawArch); + // console.log('treeModifiers', treeModifiers); + if(treeModifiers) { + this.props.merge_key = treeModifiers.merge_key; + this.props.merge_fields = treeModifiers.merge_fields.split(','); + const data = this.setColumns(this.props.merge_key); + console.log('data', data); + owl.onMounted(() => { + this.mergeColumns(this.props.merge_fields, data) + }) + } return this._super(...arguments); }, setRequired() { @@ -163,7 +174,58 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { } catch (e) { console.log(e) } + }, + setColumns( merge_key) { + const data = this.props.list.records + let sourceIndex = 0; + let sourceValue = '' + data.forEach((item, index) => { + if(!item.colspan) { + item.colspan = 1; + } + if(item.data[merge_key] === sourceValue) { + data[sourceIndex].colspan ++ ; + item.colspan = 0; + } else { + sourceIndex = index; + sourceValue = item.data[merge_key]; + } + }) + return data + }, + getFieldModifiers(xmlString) { + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlString, "text/xml"); + + // 提取 的 modifiers + const treeElement = xmlDoc.querySelector("tree"); + const treeModifiers = treeElement.getAttribute("modifiers"); + if (treeModifiers) { + const parsedTreeModifiers = JSON.parse(treeModifiers); + console.log("Tree Modifiers:", parsedTreeModifiers); + return parsedTreeModifiers; + } + return null; + }, + mergeColumns(merge_fields, data) { + const dom = this.tableRef.el + const thead = $(dom).children('thead') + const tbody = $(dom).children('tbody') + tbody.children('tr.o_data_row').each(function () { + const tr = $(this) + const td = tr.children('td') + const index = $(this).index() + td.each(function () { + if(merge_fields.indexOf($(this).attr('name')) >= 0) { + $(this).attr('rowspan', data[index].colspan) + if(data[index].colspan == 0) { + $(this).remove() + } + } + }) + }) } + }) patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', { get className() { @@ -176,7 +238,6 @@ patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', { ); const classes = this.props.className ? [this.props.className] : []; const otherRequired = filedRequiredList[this.props.fieldName] - if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0 || otherRequired) { classes.push('custom_required_add') } @@ -193,35 +254,6 @@ patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', { } }) -// 根据进度条设置水印 -// const statusbar_params = { -// '已完工': 'bg-primary', -// '完成': 'bg-primary', -// '采购订单': 'bg-primary', -// '作废': 'bg-danger', -// '封存(报废)': 'bg-danger', -// } -// patch(StatusBarField.prototype, 'jikimo_frontend.StatusBarField', { -// setup() { -// owl.onMounted(this.ribbons); -// return this._super(...arguments); -// }, -// ribbons() { -// try { -// const dom = $('.o_form_sheet.position-relative') -// const status = statusbar_params[this.currentName] -// if(status && dom.length) { -// dom.prepend(`
-//
-// ${this.currentName} -//
-//
`) -// } -// } catch (e) { -// console.log(e) -// } -// } -// }) $(function () { diff --git a/jikimo_purchase_tier_validation/models/models.py b/jikimo_purchase_tier_validation/models/models.py index 018766eb..ecb7ed73 100644 --- a/jikimo_purchase_tier_validation/models/models.py +++ b/jikimo_purchase_tier_validation/models/models.py @@ -9,6 +9,8 @@ class jikimo_purchase_tier_validation(models.Model): _name = 'purchase.order' _inherit = ['purchase.order', 'tier.validation'] _description = "采购订单" + _state_from = ["draft", "to approve", "rejected"] + _state_to = ["approved"] _tier_validation_buttons_xpath = "/form/header/button[@id='draft_confirm'][1]" @@ -21,7 +23,7 @@ class jikimo_purchase_tier_validation(models.Model): def button_confirm(self): for record in self: - if record.state in ['to approve']: + if record.need_validation and not record.validation_status == 'validated': raise ValidationError(_('请先完成审批。')) res = super(jikimo_purchase_tier_validation, self).button_confirm() for record in self: @@ -68,11 +70,6 @@ class jikimo_purchase_tier_validation(models.Model): return res - def _rejected_tier(self, tiers=False): - res = super(jikimo_purchase_tier_validation, self)._rejected_tier(tiers) - self.state = 'draft' - return res - @api.model def _get_under_validation_exceptions(self): res = super(jikimo_purchase_tier_validation, self)._get_under_validation_exceptions() diff --git a/quality_control/models/stock_picking.py b/quality_control/models/stock_picking.py index f6d2b07c..243ffd18 100644 --- a/quality_control/models/stock_picking.py +++ b/quality_control/models/stock_picking.py @@ -81,18 +81,38 @@ class StockPicking(models.Model): return quality_pickings def action_cancel(self): + """ + 调拨单取消后,关联取消质量检查单 + """ + context = self.env.context + if not context.get('cancel_check_picking') and self.sudo().mapped('check_ids').filtered( + lambda x: x.quality_state in ['pass', 'fail']): + self.env.cr.rollback() + return { + 'type': 'ir.actions.act_window', + 'res_model': 'picking.check.cancel.wizard', + 'name': '取消质检单', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_picking_id': self.id, + 'cancel_check_picking': True} + } + elif self.check_ids.filtered(lambda x: x.quality_state != 'cancel'): + self.sudo().mapped('check_ids').filtered(lambda x: x.quality_state != 'cancel').write({ + 'quality_state': 'cancel' + }) res = super(StockPicking, self).action_cancel() - self.sudo().mapped('check_ids').filtered(lambda x: x.quality_state == 'none').unlink() + # self.sudo().mapped('check_ids').filtered(lambda x: x.quality_state == 'none').unlink() return res def action_open_quality_check_picking(self): action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_check_action_picking") - action['context'] = self.env.context.copy() - action['context'].update({ + action['context'] = { 'search_default_picking_id': [self.id], 'default_picking_id': self.id, 'show_lots_text': self.show_lots_text, - }) + } return action def button_quality_alert(self): diff --git a/quality_control/security/ir.model.access.csv b/quality_control/security/ir.model.access.csv index e6911403..178283e1 100644 --- a/quality_control/security/ir.model.access.csv +++ b/quality_control/security/ir.model.access.csv @@ -3,4 +3,5 @@ access_quality_check_wizard,access.quality_check_wizard,model_quality_check_wiza access_quality_check_measure_line,quality.check.measure.line,model_quality_check_measure_line,base.group_user,1,1,1,0 access_quality_check_import_complex_model_wizard,quality.check.import.complex.model.wizard,model_quality_check_import_complex_model_wizard,quality.group_quality_user,1,1,1,0 access_quality_check_report_history,quality.check.report.history,model_quality_check_report_history,quality.group_quality_user,1,1,1,0 -access_quality_check_publish_wizard,quality.check.publish.wizard,model_quality_check_publish_wizard,quality.group_quality_user,1,1,1,0 \ No newline at end of file +access_quality_check_publish_wizard,quality.check.publish.wizard,model_quality_check_publish_wizard,quality.group_quality_user,1,1,1,0 +access_picking_check_cancel_wizard,access.picking_check_cancel_wizard,model_picking_check_cancel_wizard,quality.group_quality_user,1,1,1,0 diff --git a/quality_control/wizard/quality_check_wizard.py b/quality_control/wizard/quality_check_wizard.py index 7be548c1..502bacc1 100644 --- a/quality_control/wizard/quality_check_wizard.py +++ b/quality_control/wizard/quality_check_wizard.py @@ -62,7 +62,8 @@ class QualityCheckWizard(models.TransientModel): def do_pass(self): if self.test_type == 'picture' and not self.picture: - raise UserError('You must provide a picture before validating') + raise UserError('请先上传照片') + # raise UserError('You must provide a picture before validating') self.current_check_id.do_pass() return self.action_generate_next_window() @@ -116,3 +117,18 @@ class QualityCheckWizard(models.TransientModel): # 对于成品出库的出厂检验报告,增加发布按钮 def publish(self): self.current_check_id._do_publish_implementation() + + +class PickingCheckCancelWizard(models.TransientModel): + _name = 'picking.check.cancel.wizard' + _description = 'picking check cancel wizard' + + picking_id = fields.Many2one('stock.picking', 'stock picking') + + def confirm_picking_check(self): + res = self.picking_id.action_cancel() + return res + + def cancel_picking_check(self): + # 这里是取消后的逻辑 + return {'type': 'ir.actions.act_window_close'} diff --git a/quality_control/wizard/quality_check_wizard_views.xml b/quality_control/wizard/quality_check_wizard_views.xml index 8631b500..26d4ed8e 100644 --- a/quality_control/wizard/quality_check_wizard_views.xml +++ b/quality_control/wizard/quality_check_wizard_views.xml @@ -121,4 +121,21 @@ {} new + + + + + picking.check.cancel.wizard + picking.check.cancel.wizard + +
+
质量检查单已完成,继续取消吗?
+
注意:关联质量检查单也将被取消。
+
+
+
+
+
diff --git a/sf_base/models/common.py b/sf_base/models/common.py index 4152c119..25c79f72 100644 --- a/sf_base/models/common.py +++ b/sf_base/models/common.py @@ -57,15 +57,24 @@ class MrsMaterialModel(models.Model): remark = fields.Text("备注") gain_way = fields.Selection( [("自加工", "自加工"), ("外协", "委外加工"), ("采购", "采购")], - default="", string="获取方式") + default="采购", string="获取方式") supplier_ids = fields.One2many('sf.supplier.sort', 'materials_model_id', string='供应商') active = fields.Boolean('有效', default=True) - @api.constrains("gain_way") - def _check_supplier_ids(self): - for item in self: - if item.gain_way in ('外协', '采购') and not item.supplier_ids: - raise UserError("请添加供应商") + @api.model + def create(self, vals): + res = super(MrsMaterialModel, self).create(vals) + if not vals.get('supplier_ids'): + supplier_id = self.env['res.partner'].search([('name', '=', '湖南傲派自动化设备有限公司')], limit=1) + res.supplier_ids = [(0, 0, {'materials_model_id': res.id, 'partner_id': supplier_id.id or False})] + return res + else: + return res + # @api.constrains("gain_way") + # def _check_supplier_ids(self): + # for item in self: + # if item.gain_way in ('外协', '采购') and not item.supplier_ids: + # raise UserError("请添加供应商") class MrsProductionProcessCategory(models.Model): diff --git a/sf_base/views/common_view.xml b/sf_base/views/common_view.xml index e49e25ba..74b8f177 100644 --- a/sf_base/views/common_view.xml +++ b/sf_base/views/common_view.xml @@ -262,13 +262,13 @@ - - - + + + @@ -297,7 +297,7 @@ sf.materials.model - + diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index f8a22b28..433feccc 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -1574,7 +1574,7 @@ class MrpProduction(models.Model): vals['picking_type_id'] = picking_type_id vals['name'] = self.env['stock.picking.type'].browse(picking_type_id).sequence_id.next_by_id() product_id = self.env['product.product'].browse(vals['product_id']) - is_self_process = product_id.materials_type_id and product_id.materials_type_id.gain_way and product_id.materials_type_id.gain_way != '自加工' + is_self_process = product_id.materials_type_id.gain_way if product_id.materials_type_id else None is_customer_provided = product_id.is_customer_provided key = f"{is_self_process}_{is_customer_provided}" if not is_custemer_group_id.get(key): diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index c0c45ac8..d8d69f37 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -1198,11 +1198,7 @@ class ResMrpWorkOrder(models.Model): 'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids, }] return workorders_values_str - - @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state', - 'production_id.tool_state', 'production_id.schedule_state', 'sequence', - 'production_id.programming_state') - def _compute_state(self): + def _process_compute_state(self): for workorder in self: # 如果工单的工序没有进行排序则跳出循环 if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0): @@ -1289,7 +1285,20 @@ class ResMrpWorkOrder(models.Model): mo.get_move_line(workorder.production_id, workorder)) else: workorder.state = 'waiting' - + @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state', + 'production_id.tool_state', 'production_id.schedule_state', 'sequence', + 'production_id.programming_state') + def _compute_state(self): + self._process_compute_state() + for workorder in self: + if workorder.state == 'waiting' or workorder.state == 'pending': + for check_id in workorder.check_ids: + if not check_id.is_inspect: + check_id.quality_state = 'waiting' + if workorder.state == 'ready': + for check_id in workorder.check_ids: + if not check_id.is_inspect: + check_id.quality_state = 'none' # 重写工单开始按钮方法 def button_start(self): # 判断工单状态是否为等待组件 @@ -1510,8 +1519,12 @@ class ResMrpWorkOrder(models.Model): for workorder in record.production_id.workorder_ids: if workorder.processing_panel == record.processing_panel: rfid_code = workorder.rfid_code - workorder.write({'rfid_code_old': rfid_code, - 'rfid_code': False}) + if record.is_rework is not True: + workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False}) + elif workorder.routing_type != '装夹预调' and workorder.state != 'rework': + workorder.write({'rfid_code_old': False, 'rfid_code': False}) + elif workorder.routing_type == '装夹预调' and workorder.state != 'rework': + workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False}) self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write( {'tool_material_status': '可用'}) if workorder.rfid_code: @@ -1928,7 +1941,8 @@ class SfWorkOrderBarcodes(models.Model): self.write(val) workorder_rfid = self.env['mrp.workorder'].search( [('production_id', '=', workorder.production_id.id), - ('processing_panel', '=', workorder.processing_panel)]) + ('processing_panel', '=', workorder.processing_panel), + ('state', '!=', 'rework')]) if workorder_rfid: for item in workorder_rfid: item.write({'rfid_code': barcode}) diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index 307fbbc2..fbea675c 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -4,6 +4,7 @@ import requests import base64 import hashlib import os +import re from odoo import models, fields, api, _ from odoo.exceptions import ValidationError, UserError from odoo.modules import get_resource_path @@ -776,10 +777,33 @@ class ResProductMo(models.Model): manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) machining_drawings = fields.Binary('2D加工图纸', readonly=True) quality_standard = fields.Binary('质检标准', readonly=True) - part_name = fields.Char(string='零件名称', readonly=True) - part_number = fields.Char(string='零件图号', readonly=True) + part_name = fields.Char(string='零件名称', compute='_compute_related_product', readonly=True, store=True) + part_number = fields.Char(string='零件图号', compute='_compute_related_product', readonly=True, store=True) machining_drawings_name = fields.Char(string='零件图号名称', readonly=True) machining_drawings_mimetype = fields.Char(string='零件图号类型', readonly=True) + + @api.depends('name') + def _compute_related_product(self): + for record in self: + if record.categ_id.name == '坯料': + product_name = '' + match = re.search(r'(S\d{5}-\d)', record.name) + # 如果匹配成功,提取结果 + if match: + product_name = match.group(0) + sale_order_name = '' + match_sale = re.search(r'S(\d+)', record.name) + if match_sale: + sale_order_name = match_sale.group(0) + sale_order = self.env['sale.order'].sudo().search( + [('name', '=', sale_order_name)]) + if sale_order: + filtered_order_line = sale_order.order_line.filtered( + lambda order_line: re.search(f'{product_name}$', order_line.product_id.name) + ) + record.part_number = filtered_order_line.product_id.part_number if filtered_order_line else None + record.part_name = filtered_order_line.product_id.part_name if filtered_order_line else None + @api.constrains('tool_length') def _check_tool_length_size(self): if self.tool_length > 1000000: diff --git a/sf_manufacturing/models/purchase_order.py b/sf_manufacturing/models/purchase_order.py index 398db85b..39e8f23d 100644 --- a/sf_manufacturing/models/purchase_order.py +++ b/sf_manufacturing/models/purchase_order.py @@ -117,6 +117,8 @@ class PurchaseOrderLine(models.Model): @api.depends('product_id') def _compute_related_product(self): for record in self: + if record.part_number or record.part_name: + continue if record.product_id.categ_id.name == '坯料': product_name = '' match = re.search(r'(S\d{5}-\d)', record.product_id.name) diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 30485988..02018a11 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -1032,6 +1032,8 @@ class ReStockMove(models.Model): productions = self.env['mrp.production'].search( [('origin', '=', production.origin), ('product_id', '=', production.product_id.id)]) res['origin'] = ','.join(productions.mapped('name')) + if self.picking_type_id.name == '客供料入库': + self.picking_id.sudo().write({'origin': res['origin'] if res.get('origin') else self[0].picking_id.origin}) return res def _get_new_picking_values(self): diff --git a/sf_manufacturing/views/stock_picking_view.xml b/sf_manufacturing/views/stock_picking_view.xml index 458494b9..3cb877a9 100644 --- a/sf_manufacturing/views/stock_picking_view.xml +++ b/sf_manufacturing/views/stock_picking_view.xml @@ -67,6 +67,16 @@ + + + + diff --git a/sf_manufacturing/wizard/production_wizard.py b/sf_manufacturing/wizard/production_wizard.py index aa2a4075..abd8b6cc 100644 --- a/sf_manufacturing/wizard/production_wizard.py +++ b/sf_manufacturing/wizard/production_wizard.py @@ -46,8 +46,12 @@ class ProductionWizard(models.TransientModel): mrp_workorder_list = self.mrp_production_id.workorder_ids.filtered(lambda kw: kw.rfid_code) for workorder in mrp_workorder_list: rfid_code = workorder.rfid_code - workorder.write({'rfid_code_old': rfid_code, - 'rfid_code': False}) + workorder.filtered(lambda wo: wo.routing_type == '装夹预调' and wo.rfid_code is not False).write( + {'rfid_code_old': rfid_code, 'rfid_code': False}) + workorder.filtered(lambda wo: (wo.routing_type != '装夹预调' and + (wo.rfid_code_old is not False or wo.rfid_code is not False))).write( + {'rfid_code_old': False, 'rfid_code': False}) + if self.is_remanufacture is True: ret = {'programming_list': [], 'is_reprogramming': self.is_reprogramming} if self.is_reprogramming is True: diff --git a/sf_manufacturing/wizard/rework_wizard.py b/sf_manufacturing/wizard/rework_wizard.py index 11827819..6061dda8 100644 --- a/sf_manufacturing/wizard/rework_wizard.py +++ b/sf_manufacturing/wizard/rework_wizard.py @@ -140,7 +140,7 @@ class ReworkWizard(models.TransientModel): and item.process_parameters_id == work.surface_technics_parameters_id) or (item.route_id.name == work.name and item.panel and item.panel == work.processing_panel) or - (item.route_id == work.routing_workcenter_id + (item.route_id == work.routing_work_center_id and not work.processing_panel and not work.surface_technics_parameters_id)) if route: diff --git a/sf_manufacturing/wizard/sale_order_cancel_views.xml b/sf_manufacturing/wizard/sale_order_cancel_views.xml index 134c524b..6848faf6 100644 --- a/sf_manufacturing/wizard/sale_order_cancel_views.xml +++ b/sf_manufacturing/wizard/sale_order_cancel_views.xml @@ -12,18 +12,18 @@ - - + + + + - -