From 05dac9fb0ca8683b45b41bc107524541ffed3ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Thu, 20 Feb 2025 13:36:05 +0800 Subject: [PATCH 001/189] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=9A=84=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product_template_management_view.xml | 12 +++-- sf_manufacturing/models/mrp_production.py | 6 ++- sf_manufacturing/models/product_template.py | 49 ++++++++++++------- sf_manufacturing/models/sale_order.py | 4 +- sf_manufacturing/views/sale_order_views.xml | 4 -- sf_sale/models/sale_order.py | 10 +++- sf_sale/views/sale_order_view.xml | 6 +-- .../static/src/js/3d_viewer.js | 15 +++--- 8 files changed, 66 insertions(+), 40 deletions(-) diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml index 1f3dc50b..f575b9cf 100644 --- a/sf_dlm_management/views/product_template_management_view.xml +++ b/sf_dlm_management/views/product_template_management_view.xml @@ -16,13 +16,16 @@ + - + - + + @@ -68,6 +71,7 @@ + - - + + diff --git a/web_widget_model_viewer/static/src/js/3d_viewer.js b/web_widget_model_viewer/static/src/js/3d_viewer.js index 4ed9dcc1..8cd9ba87 100644 --- a/web_widget_model_viewer/static/src/js/3d_viewer.js +++ b/web_widget_model_viewer/static/src/js/3d_viewer.js @@ -27,7 +27,10 @@ export class StepViewer extends Component { formatUrl() { var url = ''; if (this.props.value) { - if (this.props.value.slice(-1) == 'b' && !isNaN(this.props.value.split(' ')[0])) { + if (this.props.value.startsWith('http')) { + // 从url读取文件内容 + url = this.props.value; + } else if (this.props.value.slice(-1) == 'b' && !isNaN(this.props.value.split(' ')[0])) { var url_props = { base_url: session['web.base.url'], model: this.props.record.resModel, @@ -37,21 +40,19 @@ export class StepViewer extends Component { url = url_props['base_url'].replace('http://', 'https://') + '/web/content/' + url_props['model'] + '/' + url_props['id'] + '/' + url_props['field'] + '?download=true'; // url = 'http://localhost:8069'+'/web/content/'+url_props['model']+'/'+url_props['id']+'/'+url_props['field']+'?download=true' // console.log('url111111', url) - return url; + } else { url = "data:model/gltf-binary;base64," + this.props.value; // console.log('url2', url) - return url; + // localStorage.setItem('url',url) // let new_url = localStorage.getItem(('url')) // var oViewer = document.getElementsByTagName('model-viewer')[0]; // return new_url // url = "web_widget_model_viewer/static/src/images/not_model.png"; } - } else { - // var oImg = document.getElementsByClassName('test')[0] - // console.log(oImg) - } + } + return url; } } From 7e93586f69b700d0541726459af9308c3f6b87b0 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Tue, 4 Mar 2025 11:40:39 +0800 Subject: [PATCH 002/189] =?UTF-8?q?=E4=BA=BA=E5=B7=A5=E5=BB=BA=E7=AB=8B?= =?UTF-8?q?=E9=94=80=E5=94=AE=E5=8D=95=E5=8F=96=E7=AC=AC=E4=B8=80=E4=BB=BD?= =?UTF-8?q?bom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/mrp_production.py | 26 +++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 8b629440..17c1803e 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -684,7 +684,6 @@ class MrpProduction(models.Model): logging.info('change_programming_state error:%s' % e) raise UserError("修改编程单状态失败,请联系管理员") - # cnc程序获取 def fetchCNC(self, production_names): cnc = self.env['mrp.production'].search([('id', '=', self.id)]) @@ -714,9 +713,9 @@ class MrpProduction(models.Model): [('id', '=', cnc.product_id.materials_type_id.id)]).materials_no, 'machining_processing_panel': cnc.product_id.model_processing_panel, '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, + 'embryo_long': cnc.product_id.bom_ids[0].bom_line_ids.product_id.length, + 'embryo_height': cnc.product_id.bom_ids[0].bom_line_ids.product_id.height, + 'embryo_width': cnc.product_id.bom_ids[0].bom_line_ids.product_id.width, 'order_no': cnc.origin, 'model_order_no': cnc.product_id.default_code, 'user': cnc.env.user.name, @@ -1298,12 +1297,14 @@ class MrpProduction(models.Model): 'target': 'new', 'context': { 'default_production_id': self.id, - 'default_is_clamping': True if self.workorder_ids.filtered(lambda wk: wk.routing_type == '装夹预调') else False, + 'default_is_clamping': True if self.workorder_ids.filtered( + lambda wk: wk.routing_type == '装夹预调') else False, 'default_workorder_ids': workorder_ids.ids if workorder_ids.ids != [] else self.workorder_ids.ids, 'default_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '', 'default_reprogramming_num': cloud_programming.get('reprogramming_num') if cloud_programming else '', 'default_programming_state': cloud_programming.get('programming_state') if cloud_programming else '', - 'default_is_reprogramming': True if cloud_programming and (cloud_programming.get('programming_state') in ['已下发']) else False + 'default_is_reprogramming': True if cloud_programming and ( + cloud_programming.get('programming_state') in ['已下发']) else False } } @@ -1337,7 +1338,8 @@ class MrpProduction(models.Model): for rework_item in rework_workorder: pending_workorder = production.workorder_ids.filtered( lambda m1: m1.state in [ - 'pending'] and m1.processing_panel == rework_item.processing_panel and m1.routing_type in ['CNC加工', '人工线下加工']) + 'pending'] and m1.processing_panel == rework_item.processing_panel and m1.routing_type in [ + 'CNC加工', '人工线下加工']) if not pending_workorder.cnc_ids: production.get_new_program(rework_item.processing_panel) # production.write({'state': 'progress', 'programming_state': '已编程', 'is_rework': False}) @@ -1380,8 +1382,9 @@ class MrpProduction(models.Model): if productions: for production in productions: panel_workorder = production.workorder_ids.filtered(lambda - pw: pw.processing_panel == processing_panel and pw.routing_type in ['CNC加工', '人工线下加工'] and pw.state not in ( - 'rework', 'done')) + pw: pw.processing_panel == processing_panel and pw.routing_type in [ + 'CNC加工', '人工线下加工'] and pw.state not in ( + 'rework', 'done')) if panel_workorder: if panel_workorder.cmm_ids: panel_workorder.cmm_ids.sudo().unlink() @@ -1406,8 +1409,9 @@ class MrpProduction(models.Model): 'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) logging.info('len(cnc_worksheet):%s' % len(panel_workorder.cnc_worksheet)) pre_workorder = production.workorder_ids.filtered(lambda - ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.processing_panel == processing_panel and ap.state not in ( - 'rework', 'done')) + ap: ap.routing_type in ['装夹预调', + '人工线下加工'] and ap.processing_panel == processing_panel and ap.state not in ( + 'rework', 'done')) if pre_workorder: pre_workorder.write( {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())}) From 7d4314abc764dcfbcda2b63b6570bdd2f65e67e1 Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Wed, 5 Mar 2025 11:24:48 +0800 Subject: [PATCH 003/189] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E5=AD=90=E9=A1=B5=E7=AD=BE=E5=8F=AF=E5=85=A8=E9=87=8F=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/mrp_workorder_views.xml | 2 +- sf_dlm/views/product_workorder.xml | 2 +- sf_machine_connect/views/delivery_record.xml | 2 +- .../views/machine_info_present.xml | 2 +- .../data/sf_work_individuation_page.xml | 58 +++++++++++++++++++ sf_manufacturing/models/mrp_production.py | 2 +- sf_manufacturing/models/mrp_workorder.py | 13 +++-- sf_manufacturing/views/mrp_workorder_view.xml | 37 +++++++----- sf_quality/models/quality.py | 4 +- sf_quality/views/quality_check_view.xml | 2 +- 10 files changed, 94 insertions(+), 30 deletions(-) diff --git a/jikimo_workorder_exception/views/mrp_workorder_views.xml b/jikimo_workorder_exception/views/mrp_workorder_views.xml index 55ee0aa7..1ce168dc 100644 --- a/jikimo_workorder_exception/views/mrp_workorder_views.xml +++ b/jikimo_workorder_exception/views/mrp_workorder_views.xml @@ -8,7 +8,7 @@ - + diff --git a/sf_dlm/views/product_workorder.xml b/sf_dlm/views/product_workorder.xml index 4c5f2a10..769e2369 100644 --- a/sf_dlm/views/product_workorder.xml +++ b/sf_dlm/views/product_workorder.xml @@ -6,7 +6,7 @@ - + diff --git a/sf_machine_connect/views/delivery_record.xml b/sf_machine_connect/views/delivery_record.xml index b614a7bb..d3d84cec 100644 --- a/sf_machine_connect/views/delivery_record.xml +++ b/sf_machine_connect/views/delivery_record.xml @@ -7,7 +7,7 @@ - + diff --git a/sf_machine_connect/views/machine_info_present.xml b/sf_machine_connect/views/machine_info_present.xml index b25a3515..78571a47 100644 --- a/sf_machine_connect/views/machine_info_present.xml +++ b/sf_machine_connect/views/machine_info_present.xml @@ -6,7 +6,7 @@ - + diff --git a/sf_manufacturing/data/sf_work_individuation_page.xml b/sf_manufacturing/data/sf_work_individuation_page.xml index 89bf333b..0b23324d 100644 --- a/sf_manufacturing/data/sf_work_individuation_page.xml +++ b/sf_manufacturing/data/sf_work_individuation_page.xml @@ -4,5 +4,63 @@ PTD 后置三元检测 + + WCP + 工件装夹 + + + ITD_PP + 前置三元检测定位参数 + + + 2D_MD + 2D加工图纸 + + + QIS + 质检标准 + + + WD + 工件配送 + + + CNC_P + CNC程序 + + + CMM_P + CMM程序 + + + MTI + 机床信息 + + + HDR + 下发记录 + + + ER + 异常记录 + + + DCP + 解除装夹 + + + CMR + 开料要求 + + + + + + + + + + + \ No newline at end of file diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 17c1803e..4f9571c8 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -365,7 +365,7 @@ class MrpProduction(models.Model): and production.schedule_state == '已排' and production.is_rework is False): production.state = 'pending_cam' if any((wo.test_results == '返工' and wo.state == 'done' and - (production.programming_state in ['已编程'] or wo.individuation_page_PTD is True)) + (production.programming_state in ['已编程'] or 'PTD' in wo.individuation_page_list)) or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程']) for wo in production.workorder_ids) or production.is_rework is True: production.state = 'rework' diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index a2bae378..728123e0 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -1442,7 +1442,7 @@ class ResMrpWorkOrder(models.Model): record.production_id.process_state = '待加工' # 生成工件配送单 record.workpiece_delivery_ids = record._json_workpiece_delivery_list() - if record.routing_type == 'CNC加工' or record.individuation_page_PTD is True: + if record.routing_type == 'CNC加工' or 'PTD' in record.individuation_page_list: if record.routing_type == 'CNC加工': record.process_state = '待解除装夹' # record.write({'process_state': '待加工'}) @@ -1676,10 +1676,10 @@ class ResMrpWorkOrder(models.Model): # ==============================配置化页签--个性化记录=================================== routing_workcenter_id = fields.Many2one('mrp.routing.workcenter', compute='_compute_routing_workcenter_id', - store=True) + store=True, string='工序作业') individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录', store=True, compute='_compute_individuation_page_ids') - individuation_page_PTD = fields.Boolean('个性化记录(是否显示后置三元检测[PTD]页签)', default=False) + individuation_page_list = fields.Char('个性化记录', default=None) @api.depends('name') def _compute_routing_workcenter_id(self): @@ -1695,10 +1695,11 @@ class ResMrpWorkOrder(models.Model): if mw.routing_workcenter_id: mw.individuation_page_ids = mw.routing_workcenter_id.individuation_page_ids.ids # 初始化页签配置 - mw.individuation_page_PTD = False + mw.individuation_page_list = None # 根据工单对应的【作业_个性化记录】配置页签 - if any(item.code == 'PTD' for item in mw.routing_workcenter_id.individuation_page_ids): - mw.individuation_page_PTD = True + individuation_page_list = [item.code for item in mw.routing_workcenter_id.individuation_page_ids] + if individuation_page_list: + mw.individuation_page_list = list(set(individuation_page_list)) # ============================================================================================= diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index ff6b0a20..23c67c71 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -227,8 +227,7 @@ - + @@ -325,7 +324,7 @@ - + @@ -347,7 +346,7 @@ placeholder="如有预调程序信息请在此处输入....."/> - +
左面:
@@ -504,17 +503,15 @@ - + - + - + @@ -542,11 +539,19 @@ attrs='{"invisible": ["|", ("state","!=","progress"), ("routing_type","!=","装夹预调")]}'/> + + + + + + + + - - + + --> - + - + - + @@ -601,7 +606,7 @@ - + @@ -614,7 +619,7 @@ - + diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index ebe30a71..498ec402 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -18,7 +18,7 @@ class QualityCheck(models.Model): ('cancel', '已取消'), ], string='状态', tracking=True, store=True, default='none', copy=False, compute='_compute_quality_state') - individuation_page_PTD = fields.Boolean('个性化记录(是否显示后置三元检测[PTD]页签)', related='workorder_id.individuation_page_PTD') + individuation_page_list = fields.Char('个性化记录', related='workorder_id.individuation_page_list') work_state = fields.Selection(related='workorder_id.state', string='工单状态') processing_panel = fields.Char(related='workorder_id.processing_panel', string='加工面') @@ -93,7 +93,7 @@ class QualityCheck(models.Model): raise ValidationError('请填写【判定结果】里的信息') if self.test_results == '合格': raise ValidationError('请重新选择【判定结果】-【检测结果】') - if self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_PTD is False: + if self.workorder_id.routing_type != 'CNC加工' and 'PTD' not in self.workorder_id.individuation_page_list: self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, { 'rework_reason': self.reason, 'detailed_reason': self.detailed_reason, diff --git a/sf_quality/views/quality_check_view.xml b/sf_quality/views/quality_check_view.xml index 00b139de..83ffcbca 100644 --- a/sf_quality/views/quality_check_view.xml +++ b/sf_quality/views/quality_check_view.xml @@ -8,7 +8,7 @@ - + Date: Wed, 5 Mar 2025 14:27:41 +0800 Subject: [PATCH 004/189] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=9D=9E=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E5=8E=9F=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/sale_order.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sf_manufacturing/models/sale_order.py b/sf_manufacturing/models/sale_order.py index 39066f14..dbd06e6a 100644 --- a/sf_manufacturing/models/sale_order.py +++ b/sf_manufacturing/models/sale_order.py @@ -190,3 +190,16 @@ class SaleOrderLine(models.Model): if vals['supply_method'] == 'purchase' and line.is_incoming_material: raise UserError('当前(%s)产品为客供料,不能选择外购' % ','.join(line.mapped('product_id.name'))) return super(SaleOrderLine, self).write(vals) + + cancel_auto_machining = fields.Boolean('是否取消自动化加工', compute='_compute_cancel_auto_machining', store=True) + cancel_auto_machining_reason = fields.Char('非自动化加工原因') + + def _compute_cancel_auto_machining(self): + for line in self: + line.cancel_auto_machining = True if line.product_id.auto_machining \ + and line.supply_method != 'automation' else False + + @api.onchange('product_id') + def _onchange_product_id_set_supply_method(self): + if self.product_id.auto_machining: + self.supply_method = 'automation' From 0a65c292860500a8177d07c167bc1daeae46ba98 Mon Sep 17 00:00:00 2001 From: hyyy <123@qq.com> Date: Wed, 5 Mar 2025 17:24:42 +0800 Subject: [PATCH 005/189] =?UTF-8?q?=E5=8F=96=E6=B6=88=E9=94=80=E5=94=AE?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=9A=84=E4=B8=8B=E6=B8=B8=E6=B8=85=E5=8D=95?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/js/custom_form_status_indicator.js | 94 +++++++++++++------ .../wizard/sale_order_cancel_views.xml | 8 +- 2 files changed, 67 insertions(+), 35 deletions(-) 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/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 @@ - - + + + + - -
From 9c5ecdfe76718fe1469eaa59f1aab21b694edc8c Mon Sep 17 00:00:00 2001 From: liaodanlong Date: Thu, 6 Mar 2025 10:25:56 +0800 Subject: [PATCH 006/189] =?UTF-8?q?sf=20=E6=9D=90=E6=96=99=E5=9E=8B?= =?UTF-8?q?=E5=8F=B7=E9=A1=B5=E9=9D=A2=E4=BF=AE=E6=94=B9=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_base/models/common.py | 21 +++++++++++++++------ sf_base/views/common_view.xml | 8 ++++---- sf_mrs_connect/models/res_config_setting.py | 4 +++- 3 files changed, 22 insertions(+), 11 deletions(-) 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_mrs_connect/models/res_config_setting.py b/sf_mrs_connect/models/res_config_setting.py index eb853186..c387f765 100644 --- a/sf_mrs_connect/models/res_config_setting.py +++ b/sf_mrs_connect/models/res_config_setting.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of SmartGo. See LICENSE file for full copyright and licensing details. import logging +import traceback import requests @@ -90,7 +91,8 @@ class ResConfigSettings(models.TransientModel): _logger.info("同步坯料冗余完成") except Exception as e: - _logger.info("sf_all_sync error: %s" % e) + traceback_error = traceback.format_exc() + _logger.error("sf_all_sync error:%s" % traceback_error) raise ValidationError("数据错误导致同步失败,请联系管理员") @api.model From 826d5b1d584e006796d1350598f8f0b64defebca Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Thu, 6 Mar 2025 13:37:52 +0800 Subject: [PATCH 007/189] =?UTF-8?q?1=E3=80=81=E4=BC=98=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E5=AD=90=E9=A1=B5=E7=AD=BE=E5=8F=AF=E5=85=A8=E9=87=8F?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=A4=84=E7=90=86=E6=96=B9=E6=B3=95=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/mrp_production.py | 2 +- sf_manufacturing/models/mrp_workorder.py | 44 +++++++++---------- sf_manufacturing/views/mrp_workorder_view.xml | 8 ---- sf_quality/__init__.py | 1 + sf_quality/models/__init__.py | 1 + sf_quality/models/quality.py | 3 +- sf_quality/models/stock.py | 21 +++++++++ sf_quality/wizard/__init__.py | 0 8 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 sf_quality/models/stock.py create mode 100644 sf_quality/wizard/__init__.py diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 4f9571c8..f8a22b28 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -365,7 +365,7 @@ class MrpProduction(models.Model): and production.schedule_state == '已排' and production.is_rework is False): production.state = 'pending_cam' if any((wo.test_results == '返工' and wo.state == 'done' and - (production.programming_state in ['已编程'] or 'PTD' in wo.individuation_page_list)) + (production.programming_state in ['已编程'] or(wo.individuation_page_list and 'PTD' in wo.individuation_page_list))) or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程']) for wo in production.workorder_ids) or production.is_rework is True: production.state = 'rework' diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 728123e0..c0c45ac8 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -1442,7 +1442,8 @@ class ResMrpWorkOrder(models.Model): record.production_id.process_state = '待加工' # 生成工件配送单 record.workpiece_delivery_ids = record._json_workpiece_delivery_list() - if record.routing_type == 'CNC加工' or 'PTD' in record.individuation_page_list: + if (record.routing_type == 'CNC加工' or + (record.individuation_page_list and 'PTD' in record.individuation_page_list)): if record.routing_type == 'CNC加工': record.process_state = '待解除装夹' # record.write({'process_state': '待加工'}) @@ -1675,32 +1676,31 @@ class ResMrpWorkOrder(models.Model): move_subcontract_workorder_ids = fields.One2many('stock.move', 'subcontract_workorder_id', string='组件') # ==============================配置化页签--个性化记录=================================== - routing_workcenter_id = fields.Many2one('mrp.routing.workcenter', compute='_compute_routing_workcenter_id', - store=True, string='工序作业') - individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录', store=True, - compute='_compute_individuation_page_ids') - individuation_page_list = fields.Char('个性化记录', default=None) + routing_work_center_id = fields.Many2one('mrp.routing.workcenter', compute='_compute_routing_work_center_id', + store=True, string='工序作业') + individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录', + related='routing_work_center_id.individuation_page_ids') + individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids', store=True) @api.depends('name') - def _compute_routing_workcenter_id(self): + def _compute_routing_work_center_id(self): for mw in self: - routing_workcenter_id = self.env['mrp.routing.workcenter'].sudo().search( - [('name', '=', mw.name), ('routing_type', '=', mw.routing_type)]) - if routing_workcenter_id: - mw.routing_workcenter_id = routing_workcenter_id.id + if not mw.routing_work_center_id and mw.name: + routing_work_center_id = self.env['mrp.routing.workcenter'].sudo().search( + [('name', 'in', mw.name.split('-')), ('routing_type', '=', mw.routing_type)]) + if routing_work_center_id: + mw.routing_work_center_id = routing_work_center_id.id - @api.depends('routing_workcenter_id.individuation_page_ids') + @api.depends('individuation_page_ids') def _compute_individuation_page_ids(self): for mw in self: - if mw.routing_workcenter_id: - mw.individuation_page_ids = mw.routing_workcenter_id.individuation_page_ids.ids - # 初始化页签配置 - mw.individuation_page_list = None - # 根据工单对应的【作业_个性化记录】配置页签 - individuation_page_list = [item.code for item in mw.routing_workcenter_id.individuation_page_ids] - if individuation_page_list: - mw.individuation_page_list = list(set(individuation_page_list)) - + mw.individuation_page_list = '[]' + if mw.routing_work_center_id: + if mw.individuation_page_ids: + # 根据工单对应的【作业_个性化记录】配置页签 + individuation_page_list = [item.code for item in mw.individuation_page_ids] + if individuation_page_list: + mw.individuation_page_list = list(set(individuation_page_list)) # ============================================================================================= is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False) @@ -2010,7 +2010,7 @@ class WorkPieceDelivery(models.Model): feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站') task_delivery_time = fields.Datetime('任务下发时间') task_completion_time = fields.Datetime('任务完成时间') - + def _get_agv_route_type_selection(self): return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection'] type = fields.Selection(selection=_get_agv_route_type_selection, string='类型') diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index 23c67c71..64c1c7a8 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -503,14 +503,6 @@ - - - - - - - - diff --git a/sf_quality/__init__.py b/sf_quality/__init__.py index 8134f974..52497701 100644 --- a/sf_quality/__init__.py +++ b/sf_quality/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models +from . import wizard diff --git a/sf_quality/models/__init__.py b/sf_quality/models/__init__.py index 60081c7b..4fcdb16f 100644 --- a/sf_quality/models/__init__.py +++ b/sf_quality/models/__init__.py @@ -5,3 +5,4 @@ from . import custom_quality from . import quality from . import quality_cnc_test from . import mrp_workorder +# from . import stock diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index 498ec402..970292b7 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -93,7 +93,8 @@ class QualityCheck(models.Model): raise ValidationError('请填写【判定结果】里的信息') if self.test_results == '合格': raise ValidationError('请重新选择【判定结果】-【检测结果】') - if self.workorder_id.routing_type != 'CNC加工' and 'PTD' not in self.workorder_id.individuation_page_list: + if (self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_list + and 'PTD' not in self.workorder_id.individuation_page_list): self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, { 'rework_reason': self.reason, 'detailed_reason': self.detailed_reason, diff --git a/sf_quality/models/stock.py b/sf_quality/models/stock.py new file mode 100644 index 00000000..c2eefa68 --- /dev/null +++ b/sf_quality/models/stock.py @@ -0,0 +1,21 @@ +from odoo import api, models + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + def button_validate(self): + """ + 调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示: + “警告:存在不合格产品XXXX n 件、YYYYY m件,继续调拨请点“确认”,否则请取消?” + """ + if self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail'): + return { + 'type': 'ir.actions.act_window', + 'res_model': 'sf.functional.tool.assembly.order', + 'name': ' ', + 'view_mode': 'form', + 'target': 'new', + 'context': {'': True} + } + return super(StockPicking, self).button_validate() diff --git a/sf_quality/wizard/__init__.py b/sf_quality/wizard/__init__.py new file mode 100644 index 00000000..e69de29b From e12755783c31addf5f9675c0f9fa760db1105fd2 Mon Sep 17 00:00:00 2001 From: guanhuan Date: Thu, 6 Mar 2025 13:46:18 +0800 Subject: [PATCH 008/189] =?UTF-8?q?=E5=AE=A1=E6=89=B9=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_purchase_tier_validation/models/models.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/jikimo_purchase_tier_validation/models/models.py b/jikimo_purchase_tier_validation/models/models.py index 018766eb..4462e962 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]" @@ -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() From cb645aa1b9a6c632be8314d56816e60196bf693b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Thu, 6 Mar 2025 13:59:14 +0800 Subject: [PATCH 009/189] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=E4=BA=A7=E7=BA=BF=E7=9A=84=E9=9B=B6=E4=BB=B6=E4=BE=9B?= =?UTF-8?q?=E8=B4=A7=E8=B7=AF=E7=BA=BF=E5=88=9D=E5=A7=8B=E5=8C=96=E4=B8=BA?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E4=BA=A7=E7=BA=BF=E5=8A=A0=E5=B7=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/sale_order.py | 11 ++++++----- sf_manufacturing/views/sale_order_views.xml | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/sf_manufacturing/models/sale_order.py b/sf_manufacturing/models/sale_order.py index dbd06e6a..d46c1baf 100644 --- a/sf_manufacturing/models/sale_order.py +++ b/sf_manufacturing/models/sale_order.py @@ -22,6 +22,9 @@ class SaleOrder(models.Model): def confirm_to_supply_method(self): self.state = 'supply method' + for line in self.order_line: + if line.product_id.auto_machining: + line.supply_method = 'automation' def action_confirm(self): if self._get_forbidden_state_confirm() & set(self.mapped('state')): @@ -192,14 +195,12 @@ class SaleOrderLine(models.Model): return super(SaleOrderLine, self).write(vals) cancel_auto_machining = fields.Boolean('是否取消自动化加工', compute='_compute_cancel_auto_machining', store=True) - cancel_auto_machining_reason = fields.Char('非自动化加工原因') + cancel_auto_machining_reason = fields.Char('更改供货原因') + @api.depends('product_id', 'supply_method') def _compute_cancel_auto_machining(self): for line in self: line.cancel_auto_machining = True if line.product_id.auto_machining \ and line.supply_method != 'automation' else False - @api.onchange('product_id') - def _onchange_product_id_set_supply_method(self): - if self.product_id.auto_machining: - self.supply_method = 'automation' + diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index 4d7a0c41..e0f9e097 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -21,6 +21,10 @@ + + + + From b276f616e59b1d7d304bc6f3dd9b76dad8898e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Thu, 6 Mar 2025 14:03:31 +0800 Subject: [PATCH 010/189] =?UTF-8?q?=E6=88=90=E5=93=81=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86=E9=BB=98=E8=AE=A4=E5=8F=96=E6=A8=A1=E6=9D=BF=E4=B8=AD?= =?UTF-8?q?=E6=9C=80=E5=90=8E=E4=B8=80=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_sale_multiple_supply_methods/models/mrp_bom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jikimo_sale_multiple_supply_methods/models/mrp_bom.py b/jikimo_sale_multiple_supply_methods/models/mrp_bom.py index 2528d0a4..aa9d6c60 100644 --- a/jikimo_sale_multiple_supply_methods/models/mrp_bom.py +++ b/jikimo_sale_multiple_supply_methods/models/mrp_bom.py @@ -9,5 +9,6 @@ class MrpBom(models.Model): # 成品的供应商从模板中获取 if product_type == 'product': - bom_id.subcontractor_id = product.product_tmpl_id.seller_ids.partner_id.id + if product.product_tmpl_id.seller_ids: + bom_id.subcontractor_id = product.product_tmpl_id.seller_ids[-1].partner_id.id return bom_id From 674af1d11d34c5f2fe2415bf8c3bfa2b1f9d5191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=B0=A7?= Date: Thu, 6 Mar 2025 16:02:19 +0800 Subject: [PATCH 011/189] =?UTF-8?q?=E6=88=90=E5=93=81=EF=BC=8C=E5=9D=AF?= =?UTF-8?q?=E6=96=99=E5=A4=8D=E5=88=B6=E6=A8=A1=E6=9D=BF=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E9=87=87=E8=B4=AD=E7=94=B3=E8=AF=B7=E5=AD=97=E6=AE=B5=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_purchase_request/__init__.py | 2 ++ jikimo_purchase_request/__manifest__.py | 22 +++++++++++++++++++ jikimo_purchase_request/models/__init__.py | 2 ++ .../models/product_template.py | 17 ++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 jikimo_purchase_request/__init__.py create mode 100644 jikimo_purchase_request/__manifest__.py create mode 100644 jikimo_purchase_request/models/__init__.py create mode 100644 jikimo_purchase_request/models/product_template.py diff --git a/jikimo_purchase_request/__init__.py b/jikimo_purchase_request/__init__.py new file mode 100644 index 00000000..a0fdc10f --- /dev/null +++ b/jikimo_purchase_request/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/jikimo_purchase_request/__manifest__.py b/jikimo_purchase_request/__manifest__.py new file mode 100644 index 00000000..f2054556 --- /dev/null +++ b/jikimo_purchase_request/__manifest__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +{ + 'name': '机企猫 采购申请', + 'version': '16.0.1.0.0', + 'summary': """ 机企猫 采购申请 """, + 'author': '机企猫', + 'website': 'https://bfw.jikimo.com', + 'category': 'purchase', + 'depends': ['sf_manufacturing', 'purchase_request'], + 'data': [ + + ], + # 'assets': { + # 'web.assets_backend': [ + # 'jikimo_purchase_request/static/src/**/*' + # ], + # }, + 'application': True, + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/jikimo_purchase_request/models/__init__.py b/jikimo_purchase_request/models/__init__.py new file mode 100644 index 00000000..1c32c33a --- /dev/null +++ b/jikimo_purchase_request/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import product_template \ No newline at end of file diff --git a/jikimo_purchase_request/models/product_template.py b/jikimo_purchase_request/models/product_template.py new file mode 100644 index 00000000..e6e96f10 --- /dev/null +++ b/jikimo_purchase_request/models/product_template.py @@ -0,0 +1,17 @@ +from odoo import models, fields + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + purchase_request_id = fields.Many2one('purchase.request', string='采购申请') + + def no_bom_product_create(self, product_id, item, order_id, route_type, i, finish_product): + """ 创建坯料时,复制采购申请 """ + template_id = super(ProductTemplate, self).no_bom_product_create(product_id, item, order_id, route_type, i, finish_product) + template_id.purchase_request = product_id.purchase_request + return template_id + + def copy_template(self, product_template_id): + """ 复制成品模板时,复制采购申请 """ + super(ProductTemplate, self).copy_template(product_template_id) + self.purchase_request = product_template_id.purchase_request \ No newline at end of file From 0d7f34819461e9b583ea5fb912673d426705fd6d Mon Sep 17 00:00:00 2001 From: guanhuan Date: Fri, 7 Mar 2025 11:29:32 +0800 Subject: [PATCH 012/189] =?UTF-8?q?=E8=B0=83=E6=8B=A8=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E9=87=87=E8=B4=AD=E8=AE=A2=E5=8D=95=E8=AF=A2=E4=BB=B7=E5=8D=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/models.py | 2 +- sf_manufacturing/views/stock_picking_view.xml | 10 +++++++ sf_sale/views/purchase_order_view.xml | 28 +++++++++++-------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/jikimo_purchase_tier_validation/models/models.py b/jikimo_purchase_tier_validation/models/models.py index 4462e962..ecb7ed73 100644 --- a/jikimo_purchase_tier_validation/models/models.py +++ b/jikimo_purchase_tier_validation/models/models.py @@ -23,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: 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_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml index 209fd4fa..6624c7fa 100644 --- a/sf_sale/views/purchase_order_view.xml +++ b/sf_sale/views/purchase_order_view.xml @@ -42,20 +42,20 @@ - -
+ +
+ From 34456c3506e54f8f1fff6f2d10088ba8052583e6 Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Mon, 10 Mar 2025 16:54:15 +0800 Subject: [PATCH 015/189] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E8=B0=83=E6=8B=A8=E5=8D=95=E6=97=B6=EF=BC=8C=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E4=B8=8D=E5=90=88=E6=A0=BC=E4=BA=A7=E5=93=81=EF=BC=8C=E5=A6=82?= =?UTF-8?q?=E6=9E=9C=E5=AD=98=E5=9C=A8=E4=B8=8D=E5=90=88=E6=A0=BC=E8=B4=A8?= =?UTF-8?q?=E6=A3=80=E5=8D=95=E5=88=99=E7=BB=99=E4=B8=8E=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_quality/__manifest__.py | 3 ++- sf_quality/models/__init__.py | 2 +- sf_quality/models/stock.py | 26 ++++++++++++++++--- sf_quality/security/ir.model.access.csv | 2 +- sf_quality/wizard/__init__.py | 1 + sf_quality/wizard/check_picking_wizard.py | 17 ++++++++++++ .../wizard/check_picking_wizard_view.xml | 15 +++++++++++ 7 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 sf_quality/wizard/check_picking_wizard.py create mode 100644 sf_quality/wizard/check_picking_wizard_view.xml diff --git a/sf_quality/__manifest__.py b/sf_quality/__manifest__.py index 9c2c3138..e44a6f68 100644 --- a/sf_quality/__manifest__.py +++ b/sf_quality/__manifest__.py @@ -19,7 +19,8 @@ 'views/view.xml', 'views/quality_cnc_test_view.xml', 'views/mrp_workorder.xml', - 'views/quality_check_view.xml' + 'views/quality_check_view.xml', + 'wizard/check_picking_wizard_view.xml', ], 'assets': { diff --git a/sf_quality/models/__init__.py b/sf_quality/models/__init__.py index 4fcdb16f..17ad5fab 100644 --- a/sf_quality/models/__init__.py +++ b/sf_quality/models/__init__.py @@ -5,4 +5,4 @@ from . import custom_quality from . import quality from . import quality_cnc_test from . import mrp_workorder -# from . import stock +from . import stock diff --git a/sf_quality/models/stock.py b/sf_quality/models/stock.py index c2eefa68..c9788a51 100644 --- a/sf_quality/models/stock.py +++ b/sf_quality/models/stock.py @@ -9,13 +9,31 @@ class StockPicking(models.Model): 调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示: “警告:存在不合格产品XXXX n 件、YYYYY m件,继续调拨请点“确认”,否则请取消?” """ - if self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail'): + context = self.env.context + if not context.get('again_validate') and self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail'): + # 回滚事务,为二次确认/取消做准备 + self.env.cr.rollback() + quality_check_ids = self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail') + product_list = list(set([quality_check_id.product_id for quality_check_id in quality_check_ids])) + fail_check_text = '' + for product_id in product_list: + check_ids = quality_check_ids.filtered(lambda qc: qc.product_id == product_id) + number = sum(check_ids.mapped('qty_line')) + if number != 0: + fail_check_text = (f'{fail_check_text}、{product_id.name} {number}件' + if fail_check_text != '' else f'{product_id.name} {number}件') + else: + fail_check_text = (f'{fail_check_text}、{product_id.name}' + if fail_check_text != '' else f'{product_id.name}') return { 'type': 'ir.actions.act_window', - 'res_model': 'sf.functional.tool.assembly.order', - 'name': ' ', + 'res_model': 'picking.validate.check.wizard', + 'name': '质检不合格提示', 'view_mode': 'form', 'target': 'new', - 'context': {'': True} + 'context': { + 'default_picking_id': self.id, + 'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?', + 'again_validate': True} } return super(StockPicking, self).button_validate() diff --git a/sf_quality/security/ir.model.access.csv b/sf_quality/security/ir.model.access.csv index 907c115b..fa1ec3fa 100644 --- a/sf_quality/security/ir.model.access.csv +++ b/sf_quality/security/ir.model.access.csv @@ -73,6 +73,6 @@ access_quality_cnc_test_group_quality_director,quality_cnc_test_group_quality_di access_quality_cnc_test_group_sf_equipment_user,quality_cnc_test_group_sf_equipment_user,model_quality_cnc_test,sf_base.group_sf_equipment_user,1,1,0,0 - +access_picking_validate_check_wizard,access.picking_validate_check_wizard,model_picking_validate_check_wizard,quality.group_quality_user,1,1,1,0 diff --git a/sf_quality/wizard/__init__.py b/sf_quality/wizard/__init__.py index e69de29b..2c90d07b 100644 --- a/sf_quality/wizard/__init__.py +++ b/sf_quality/wizard/__init__.py @@ -0,0 +1 @@ +from . import check_picking_wizard diff --git a/sf_quality/wizard/check_picking_wizard.py b/sf_quality/wizard/check_picking_wizard.py new file mode 100644 index 00000000..1cb25756 --- /dev/null +++ b/sf_quality/wizard/check_picking_wizard.py @@ -0,0 +1,17 @@ +from odoo import api, models,fields + + +class PickingValidateCheckWizard(models.TransientModel): + _name = 'picking.validate.check.wizard' + _description = '调拨质检不合格二次验证' + + picking_id = fields.Many2one('stock.picking', '调拨单') + fail_check_text = fields.Text('提示信息') + + def confirm_picking_validate_check(self): + self.picking_id.button_validate() + return {'type': 'ir.actions.act_window_close'} + + def cancel_picking_validate_check(self): + # 这里是取消后的逻辑 + return {'type': 'ir.actions.act_window_close'} diff --git a/sf_quality/wizard/check_picking_wizard_view.xml b/sf_quality/wizard/check_picking_wizard_view.xml new file mode 100644 index 00000000..702b2578 --- /dev/null +++ b/sf_quality/wizard/check_picking_wizard_view.xml @@ -0,0 +1,15 @@ + + + picking.validate.check.wizard + picking.validate.check.wizard + +
+ +
+
+ +
+
+
\ No newline at end of file From 2cc7386027366060ddc24e2a1eea768efffc8091 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 10 Mar 2025 17:10:27 +0800 Subject: [PATCH 016/189] =?UTF-8?q?=E8=B4=A8=E9=87=8F=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quality_control/models/quality.py | 80 +++++++++++++++++-------- quality_control/views/quality_views.xml | 1 + sf_quality/__manifest__.py | 1 + sf_quality/data/check_standards.xml | 11 ++++ sf_quality/models/custom_quality.py | 1 + 5 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 sf_quality/data/check_standards.xml diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py index 011c7914..f64864f7 100644 --- a/quality_control/models/quality.py +++ b/quality_control/models/quality.py @@ -34,7 +34,8 @@ class QualityPoint(models.Model): ('day', 'Days'), ('week', 'Weeks'), ('month', 'Months')], default="day") # TDE RENAME ? - is_lot_tested_fractionally = fields.Boolean(string="Lot Tested Fractionally", help="Determines if only a fraction of the lot should be tested") + is_lot_tested_fractionally = fields.Boolean(string="Lot Tested Fractionally", + help="Determines if only a fraction of the lot should be tested") testing_percentage_within_lot = fields.Float(help="Defines the percentage within a lot that should be tested") norm = fields.Float('Norm', digits='Quality Tests') # TDE RENAME ? tolerance_min = fields.Float('Min Tolerance', digits='Quality Tests') @@ -63,7 +64,7 @@ class QualityPoint(models.Model): if n > 1: point.average = mean - point.standard_deviation = sqrt( s / ( n - 1)) + point.standard_deviation = sqrt(s / (n - 1)) elif n == 1: point.average = mean point.standard_deviation = 0.0 @@ -94,7 +95,7 @@ class QualityPoint(models.Model): checks = self.env['quality.check'].search([ ('point_id', '=', self.id), ('create_date', '>=', date_previous.strftime(DEFAULT_SERVER_DATETIME_FORMAT))], limit=1) - return not(bool(checks)) + return not (bool(checks)) return super(QualityPoint, self).check_execute_now() def _get_type_default_domain(self): @@ -123,13 +124,31 @@ class QualityPoint(models.Model): class QualityCheck(models.Model): _inherit = "quality.check" - part_name = fields.Char('零件名称', compute='_compute_part_name_number', readonly=True) + part_name = fields.Char('零件名称', compute='_compute_part_name_number', readonly=True) part_number = fields.Char('零件图号', compute='_compute_part_name_number', readonly=True) + # 材料 + material_name = fields.Char('材料名称', compute='_compute_material_name', readonly=True) + + # # 总数量,值未调拨单_产品明细_数量 + # total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True) + # # 检验数 + # check_qty = fields.Float('检验数', compute='_compute_check_qty', readonly=True) + # # 出厂检验报告编号 + # report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True) + @depends('product_id') def _compute_part_name_number(self): for record in self: record.part_number = record.product_id.part_number record.part_name = record.product_id.part_name + + @depends('product_id') + def _compute_material_name(self): + for record in self: + materials_id_name = record.product_id.materials_id.name if record.product_id.materials_id else '' + materials_type_name = record.product_id.materials_type_id.name if record.product_id.materials_type_id else '' + record.material_name = materials_id_name + ' ' + materials_type_name + failure_message = fields.Html(related='point_id.failure_message', readonly=True) measure = fields.Float('Measure', default=0.0, digits='Quality Tests', tracking=True) measure_success = fields.Selection([ @@ -141,7 +160,8 @@ class QualityCheck(models.Model): tolerance_max = fields.Float('Max Tolerance', related='point_id.tolerance_max', readonly=True) warning_message = fields.Text(compute='_compute_warning_message') norm_unit = fields.Char(related='point_id.norm_unit', readonly=True) - qty_to_test = fields.Float(compute="_compute_qty_to_test", string="Quantity to Test", help="Quantity of product to test within the lot") + qty_to_test = fields.Float(compute="_compute_qty_to_test", string="Quantity to Test", + help="Quantity of product to test within the lot") qty_tested = fields.Float(string="Quantity Tested", help="Quantity of product tested within the lot") measure_on = fields.Selection([ ('operation', 'Operation'), @@ -150,7 +170,8 @@ class QualityCheck(models.Model): help="""Operation = One quality check is requested at the operation level. Product = A quality check is requested per product. Quantity = A quality check is requested for each new product quantity registered, with partial quantity checks also possible.""") - move_line_id = fields.Many2one('stock.move.line', 'Stock Move Line', check_company=True, help="In case of Quality Check by Quantity, Move Line on which the Quality Check applies") + move_line_id = fields.Many2one('stock.move.line', 'Stock Move Line', check_company=True, + help="In case of Quality Check by Quantity, Move Line on which the Quality Check applies") lot_name = fields.Char('Lot/Serial Number Name') lot_line_id = fields.Many2one('stock.lot', store=True, compute='_compute_lot_line_id') qty_line = fields.Float(compute='_compute_qty_line', string="Quantity") @@ -231,7 +252,8 @@ class QualityCheck(models.Model): def _compute_qty_to_test(self): for qc in self: if qc.is_lot_tested_fractionally: - qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100, precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP") + qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100, + precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP") else: qc.qty_to_test = qc.qty_line @@ -386,7 +408,8 @@ class QualityAlert(models.Model): class ProductTemplate(models.Model): _inherit = "product.template" - quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') + quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty', + groups='quality.group_quality_user') quality_pass_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_fail_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') @@ -394,14 +417,16 @@ class ProductTemplate(models.Model): def _compute_quality_check_qty(self): for product_tmpl in self: product_tmpl.quality_fail_qty, product_tmpl.quality_pass_qty = product_tmpl.product_variant_ids._count_quality_checks() - product_tmpl.quality_control_point_qty = product_tmpl.with_context(active_test=product_tmpl.active).product_variant_ids._count_quality_points() + product_tmpl.quality_control_point_qty = product_tmpl.with_context( + active_test=product_tmpl.active).product_variant_ids._count_quality_points() def action_see_quality_control_points(self): self.ensure_one() action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_point_action") action['context'] = dict(self.env.context, default_product_ids=self.product_variant_ids.ids) - domain_in_products_or_categs = ['|', ('product_ids', 'in', self.product_variant_ids.ids), ('product_category_ids', 'parent_of', self.categ_id.ids)] + domain_in_products_or_categs = ['|', ('product_ids', 'in', self.product_variant_ids.ids), + ('product_category_ids', 'parent_of', self.categ_id.ids)] domain_no_products_and_categs = [('product_ids', '=', False), ('product_category_ids', '=', False)] action['domain'] = OR([domain_in_products_or_categs, domain_no_products_and_categs]) return action @@ -412,10 +437,10 @@ class ProductTemplate(models.Model): action['context'] = dict(self.env.context, default_product_id=self.product_variant_id.id, create=False) action['domain'] = [ '|', - ('product_id', 'in', self.product_variant_ids.ids), - '&', - ('measure_on', '=', 'operation'), - ('picking_id.move_ids.product_tmpl_id', '=', self.id), + ('product_id', 'in', self.product_variant_ids.ids), + '&', + ('measure_on', '=', 'operation'), + ('picking_id.move_ids.product_tmpl_id', '=', self.id), ] return action @@ -423,7 +448,8 @@ class ProductTemplate(models.Model): class ProductProduct(models.Model): _inherit = "product.product" - quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') + quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty', + groups='quality.group_quality_user') quality_pass_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_fail_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') @@ -437,10 +463,10 @@ class ProductProduct(models.Model): quality_pass_qty = 0 domain = [ '|', - ('product_id', 'in', self.ids), - '&', - ('measure_on', '=', 'operation'), - ('picking_id.move_ids.product_id', 'in', self.ids), + ('product_id', 'in', self.ids), + '&', + ('measure_on', '=', 'operation'), + ('picking_id.move_ids.product_id', 'in', self.ids), ('company_id', '=', self.env.company.id), ('quality_state', '!=', 'none') ] @@ -464,7 +490,8 @@ class ProductProduct(models.Model): _, where_clause, where_clause_args = query.get_sql() additional_where_clause = self._additional_quality_point_where_clause() where_clause += additional_where_clause - parent_category_ids = [int(parent_id) for parent_id in self.categ_id.parent_path.split('/')[:-1]] if self.categ_id else [] + parent_category_ids = [int(parent_id) for parent_id in + self.categ_id.parent_path.split('/')[:-1]] if self.categ_id else [] self.env.cr.execute(""" SELECT COUNT(*) @@ -485,7 +512,7 @@ class ProductProduct(models.Model): ) ) """ % (where_clause,), where_clause_args + [self.ids, parent_category_ids] - ) + ) return self.env.cr.fetchone()[0] def action_see_quality_control_points(self): @@ -493,7 +520,8 @@ class ProductProduct(models.Model): action = self.product_tmpl_id.action_see_quality_control_points() action['context'].update(default_product_ids=self.ids) - domain_in_products_or_categs = ['|', ('product_ids', 'in', self.ids), ('product_category_ids', 'parent_of', self.categ_id.ids)] + domain_in_products_or_categs = ['|', ('product_ids', 'in', self.ids), + ('product_category_ids', 'parent_of', self.categ_id.ids)] domain_no_products_and_categs = [('product_ids', '=', False), ('product_category_ids', '=', False)] action['domain'] = OR([domain_in_products_or_categs, domain_no_products_and_categs]) return action @@ -504,10 +532,10 @@ class ProductProduct(models.Model): action['context'] = dict(self.env.context, default_product_id=self.id, create=False) action['domain'] = [ '|', - ('product_id', '=', self.id), - '&', - ('measure_on', '=', 'operation'), - ('picking_id.move_ids.product_id', '=', self.id), + ('product_id', '=', self.id), + '&', + ('measure_on', '=', 'operation'), + ('picking_id.move_ids.product_id', '=', self.id), ] return action diff --git a/quality_control/views/quality_views.xml b/quality_control/views/quality_views.xml index c40d42b2..1fd82ee3 100644 --- a/quality_control/views/quality_views.xml +++ b/quality_control/views/quality_views.xml @@ -268,6 +268,7 @@ + diff --git a/sf_quality/__manifest__.py b/sf_quality/__manifest__.py index 9c2c3138..9ca7128a 100644 --- a/sf_quality/__manifest__.py +++ b/sf_quality/__manifest__.py @@ -16,6 +16,7 @@ 'depends': ['quality_control', 'web_widget_model_viewer', 'sf_manufacturing','jikimo_attachment_viewer'], 'data': [ 'security/ir.model.access.csv', + 'data/check_standards.xml', 'views/view.xml', 'views/quality_cnc_test_view.xml', 'views/mrp_workorder.xml', diff --git a/sf_quality/data/check_standards.xml b/sf_quality/data/check_standards.xml new file mode 100644 index 00000000..ace59578 --- /dev/null +++ b/sf_quality/data/check_standards.xml @@ -0,0 +1,11 @@ + + + + + 出厂检验报告 + + product + all + + + diff --git a/sf_quality/models/custom_quality.py b/sf_quality/models/custom_quality.py index 8bf10c3e..f2277b7e 100644 --- a/sf_quality/models/custom_quality.py +++ b/sf_quality/models/custom_quality.py @@ -4,6 +4,7 @@ from odoo import models, fields class SfQualityPoint(models.Model): _inherit = 'quality.point' + _rec_name = 'title' product_ids = fields.Many2many( 'product.product', string='适用产品', From e6e740a6c7cf539b3e8918167e515f2657f18649 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Tue, 11 Mar 2025 12:54:59 +0800 Subject: [PATCH 017/189] =?UTF-8?q?=E8=B4=A8=E9=87=8F=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E4=BC=98=E5=8C=96-=E7=95=8C=E9=9D=A2=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quality_control/__manifest__.py | 3 +- quality_control/models/quality.py | 70 ++++++++++++++++++- quality_control/security/ir.model.access.csv | 1 + .../views/quality.check.measures.line.xml | 21 ++++++ quality_control/views/quality_views.xml | 41 +++++++++-- 5 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 quality_control/views/quality.check.measures.line.xml diff --git a/quality_control/__manifest__.py b/quality_control/__manifest__.py index 59fbbb98..3b080984 100644 --- a/quality_control/__manifest__.py +++ b/quality_control/__manifest__.py @@ -8,7 +8,7 @@ 'sequence': 120, 'summary': 'Control the quality of your products', 'website': 'https://www.odoo.com/app/quality', - 'depends': ['quality', 'sf_manufacturing'], + 'depends': ['quality', 'sf_manufacturing', 'base_import'], 'description': """ Quality Control =============== @@ -26,6 +26,7 @@ Quality Control 'views/product_views.xml', 'views/stock_move_views.xml', 'views/stock_picking_views.xml', + 'views/quality.check.measures.line.xml', 'wizard/quality_check_wizard_views.xml', 'security/ir.model.access.csv', ], diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py index f64864f7..9af5c24c 100644 --- a/quality_control/models/quality.py +++ b/quality_control/models/quality.py @@ -10,6 +10,7 @@ from odoo import api, models, fields, _ from odoo.api import depends from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_round from odoo.osv.expression import OR +from odoo.exceptions import UserError class QualityPoint(models.Model): @@ -129,12 +130,33 @@ class QualityCheck(models.Model): # 材料 material_name = fields.Char('材料名称', compute='_compute_material_name', readonly=True) - # # 总数量,值未调拨单_产品明细_数量 + # # 总数量,值为调拨单_产品明细_数量 # total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True) # # 检验数 # check_qty = fields.Float('检验数', compute='_compute_check_qty', readonly=True) # # 出厂检验报告编号 # report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True) + # 总数量,值为调拨单_产品明细_数量 + total_qty = fields.Float('总数量', readonly=True) + # 检验数 + check_qty = fields.Float('检验数', creadonly=True) + # 出厂检验报告编号 + report_number_id = fields.Many2one('document.document', string='出厂检验报告编号', readonly=True) + is_out_check = fields.Boolean(string='是否出库检验', compute='_compute_is_out_check', readonly=True) + + measure_line_ids = fields.One2many('quality.check.measure.line', 'check_id', string='测量明细') + + def add_measure_line(self): + ''' + 新增测量值,如果测量值有5列了,则提示“最多只能有5列测量值” + ''' + self.ensure_one() + if len(self.measure_line_ids) >= 5: + raise UserError('最多只能有5列测量值') + self.env['quality.check.measure.line'].create({ + 'check_id': self.id, + 'sequence': len(self.measure_line_ids) + 1, + }) @depends('product_id') def _compute_part_name_number(self): @@ -149,6 +171,14 @@ class QualityCheck(models.Model): materials_type_name = record.product_id.materials_type_id.name if record.product_id.materials_type_id else '' record.material_name = materials_id_name + ' ' + materials_type_name + @depends('point_id') + def _compute_is_out_check(self): + for record in self: + if record.point_id.title == '出厂检验报告': + record.is_out_check = True + else: + record.is_out_check = False + failure_message = fields.Html(related='point_id.failure_message', readonly=True) measure = fields.Float('Measure', default=0.0, digits='Quality Tests', tracking=True) measure_success = fields.Selection([ @@ -541,3 +571,41 @@ class ProductProduct(models.Model): def _additional_quality_point_where_clause(self): return "" + + +class QualityCheckMeasureLine(models.Model): + _name = 'quality.check.measure.line' + _description = '质检测量明细' + _order = 'sequence, id' + + sequence = fields.Integer('序号', default=10) + check_id = fields.Many2one('quality.check', string='质检单', required=True, ondelete='cascade') + + # 基本信息 + product_name = fields.Char('产品名称', related='check_id.product_id.name', readonly=True) + drawing_no = fields.Char('图号') + measure_item = fields.Char('检测项目') + + # 测量值 + measure_value1 = fields.Char('测量值1') + measure_value2 = fields.Char('测量值2') + measure_value3 = fields.Char('测量值3') + measure_value4 = fields.Char('测量值4') + measure_value5 = fields.Char('测量值5') + + # 展示列数 + show_colomn_number = fields.Integer('展示列数', default=1) + + # 判定结果 + measure_result = fields.Selection([ + ('OK', 'OK'), + ('NG', 'NG') + ], string='判定', default='OK') + + remark = fields.Char('备注') + + def del_measure_value(self): + self.ensure_one() + self.sudo().unlink() + + \ No newline at end of file diff --git a/quality_control/security/ir.model.access.csv b/quality_control/security/ir.model.access.csv index d700da50..9a6ff768 100644 --- a/quality_control/security/ir.model.access.csv +++ b/quality_control/security/ir.model.access.csv @@ -1,2 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_quality_check_wizard,access.quality_check_wizard,model_quality_check_wizard,quality.group_quality_user,1,1,1,0 +access_quality_check_measure_line,quality.check.measure.line,model_quality_check_measure_line,base.group_user,1,1,1,0 diff --git a/quality_control/views/quality.check.measures.line.xml b/quality_control/views/quality.check.measures.line.xml new file mode 100644 index 00000000..b0c603df --- /dev/null +++ b/quality_control/views/quality.check.measures.line.xml @@ -0,0 +1,21 @@ + + + + quality.check.measure.line.tree + quality.check.measure.line + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + @@ -327,6 +355,9 @@ + + +
From 8237d04f32a723a9ee08c015f81102b9e1ab4dba Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Tue, 11 Mar 2025 16:12:04 +0800 Subject: [PATCH 018/189] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=88=80=E5=85=B7?= =?UTF-8?q?=E7=BB=84=E8=A3=85=E6=89=AB=E7=A0=81=E9=AA=8C=E8=AF=81=E7=89=A9?= =?UTF-8?q?=E6=96=99=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_tool_management/models/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sf_tool_management/models/base.py b/sf_tool_management/models/base.py index 237d5b91..40100a93 100644 --- a/sf_tool_management/models/base.py +++ b/sf_tool_management/models/base.py @@ -983,19 +983,19 @@ class FunctionalToolAssembly(models.Model): # 物料必填校验 if not self.handle_code_id: - raise ValidationError('缺少【刀柄】物料信息!') + raise ValidationError('请扫【刀柄】RFID,录入【刀柄】物料信息!') if self.integral_lot_id: if not self.integral_verify: - raise ValidationError('【整体式刀具】未进行验证!') + raise ValidationError('请扫【整体式刀具】的货位编码进行验证!') elif self.blade_lot_id: if not self.blade_verify: - raise ValidationError('【刀片】未进行验证!') + raise ValidationError('请扫【刀片】的货位编码进行验证!') if self.bar_lot_id: if not self.bar_verify: - raise ValidationError('【刀杆】未进行验证!') + raise ValidationError('请扫【刀杆】的货位编码进行验证!') elif self.pad_lot_id: if not self.pad_verify: - raise ValidationError('【刀盘】未进行验证!') + raise ValidationError('请扫【刀盘】的货位编码进行验证!') # 组装参数必填校验 if self.after_assembly_max_lifetime_value == 0: raise ValidationError('组装参数信息【最大寿命值】不能为0!') From 78738ed8aa3c517cdf651d33afac2292eec92661 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Tue, 11 Mar 2025 16:19:00 +0800 Subject: [PATCH 019/189] =?UTF-8?q?=E6=A3=80=E6=9F=A5=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quality_control/__manifest__.py | 1 + quality_control/models/quality.py | 5 +- quality_control/security/ir.model.access.csv | 1 + .../src/binary/出厂检验报告上传模版.xlsx | Bin 0 -> 11607 bytes quality_control/views/quality_views.xml | 7 +- quality_control/wizard/__init__.py | 1 + .../wizard/import_complex_model.py | 418 ++++++++++++++++++ .../wizard/import_complex_model.xml | 33 ++ 8 files changed, 462 insertions(+), 4 deletions(-) create mode 100644 quality_control/static/src/binary/出厂检验报告上传模版.xlsx create mode 100644 quality_control/wizard/import_complex_model.py create mode 100644 quality_control/wizard/import_complex_model.xml diff --git a/quality_control/__manifest__.py b/quality_control/__manifest__.py index 3b080984..b1eecb9a 100644 --- a/quality_control/__manifest__.py +++ b/quality_control/__manifest__.py @@ -20,6 +20,7 @@ Quality Control """, 'data': [ 'data/quality_control_data.xml', + 'wizard/import_complex_model.xml', 'report/worksheet_custom_reports.xml', 'report/worksheet_custom_report_templates.xml', 'views/quality_views.xml', diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py index 9af5c24c..b1efad6c 100644 --- a/quality_control/models/quality.py +++ b/quality_control/models/quality.py @@ -151,8 +151,6 @@ class QualityCheck(models.Model): 新增测量值,如果测量值有5列了,则提示“最多只能有5列测量值” ''' self.ensure_one() - if len(self.measure_line_ids) >= 5: - raise UserError('最多只能有5列测量值') self.env['quality.check.measure.line'].create({ 'check_id': self.id, 'sequence': len(self.measure_line_ids) + 1, @@ -578,7 +576,8 @@ class QualityCheckMeasureLine(models.Model): _description = '质检测量明细' _order = 'sequence, id' - sequence = fields.Integer('序号', default=10) + sequence = fields.Integer('序号') + check_id = fields.Many2one('quality.check', string='质检单', required=True, ondelete='cascade') # 基本信息 diff --git a/quality_control/security/ir.model.access.csv b/quality_control/security/ir.model.access.csv index 9a6ff768..c0c409a6 100644 --- a/quality_control/security/ir.model.access.csv +++ b/quality_control/security/ir.model.access.csv @@ -1,3 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_quality_check_wizard,access.quality_check_wizard,model_quality_check_wizard,quality.group_quality_user,1,1,1,0 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 diff --git a/quality_control/static/src/binary/出厂检验报告上传模版.xlsx b/quality_control/static/src/binary/出厂检验报告上传模版.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..cd43894ac6440cb8e49dfb074a89117339b9f6bf GIT binary patch literal 11607 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 - + @@ -328,6 +328,11 @@

diff --git a/quality_control/wizard/__init__.py b/quality_control/wizard/__init__.py index 2b9d69c4..49415fd2 100644 --- a/quality_control/wizard/__init__.py +++ b/quality_control/wizard/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import quality_check_wizard +from . import import_complex_model diff --git a/quality_control/wizard/import_complex_model.py b/quality_control/wizard/import_complex_model.py new file mode 100644 index 00000000..e53e7bcd --- /dev/null +++ b/quality_control/wizard/import_complex_model.py @@ -0,0 +1,418 @@ +# -*- coding: utf-8 -*- +import base64 +import logging +import os +import traceback +from datetime import datetime +from io import BytesIO +from openpyxl import load_workbook +import pandas as pd +import xlrd + +from odoo import fields, models, api, Command, _ +# from odoo.exceptions import ValidationError +from odoo.exceptions import UserError +from datetime import datetime, timedelta + +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class ImportComplexModelWizard(models.TransientModel): + _name = 'quality.check.import.complex.model.wizard' + file_data = fields.Binary("数据文件") + model_name = fields.Char(string='Model Name') + field_basis = fields.Char(string='Field Basis') + + def get_model_column_name_labels(self, model): + fields_info = model.fields_get() + # 提取字段名称和展示名称 + field_labels = {field_info.get('string'): field_info for field_name, field_info in fields_info.items()} + + return field_labels + + def field_name_mapping(selfcolumn, column, field_data): + return {} + + @api.model + def page_to_field(self, field_data): + return {} + + def count_continuous_none(self, df): + # 用于存储间断的连续空值的区间 + none_intervals = [] + # if pd.isna(val): + # 遍历数据并查找连续的 None + start = 0 + num = 0 + for index, row in df.iterrows(): + if pd.isna(row[0]): + continue + else: + # 记录区间 + if num == 0: + start = index + num = 1 + else: + end = index + none_intervals.append({'start': start, 'end': index}) + start = end + # start = None # 重置 + + # 检查最后一个区间 + if len(df) - start >= 1: + none_intervals.append({'start': start, 'end': len(df)}) + + return none_intervals + + def find_repeated_ranges(self, data): + # 判断内联列表范围,有column列的为内联列表字段 + if not data: + return [] + + repeats = [] + start_index = 0 + current_value = data[0] + + for i in range(1, len(data)): + if data[i] == current_value: + continue + else: + if i - start_index > 1: # 有重复 + repeats.append({'start': start_index, 'end': i, 'column': data[i - 1]}) + # repeats.append((current_value, start_index, i - 1)) + current_value = data[i] + start_index = i + + # 检查最后一段 + if len(data) - start_index > 1: + repeats.append({'start': start_index, 'end': i, 'column': data[i - 1]}) + # repeats.append((current_value, start_index, len(data) - 1)) + + return repeats + + def import_data(self): + """导入Excel数据""" + if not self.file_data: + raise UserError(_('请先上传Excel文件')) + + # 解码文件数据 + file_content = base64.b64decode(self.file_data) + + try: + # 使用xlrd读取Excel文件 + workbook = xlrd.open_workbook(file_contents=file_content) + sheet = workbook.sheet_by_index(0) + + # 检查表头是否符合预期(忽略星号) + expected_headers = ['产品名称', '图号', '检测项目', '测量值1', '测量值2', '测量值3', '测量值4', '测量值5', '判定', '备注'] + actual_headers = sheet.row_values(0) + + # 处理合并单元格的情况 + # 获取所有合并单元格 + merged_cells = [] + for crange in sheet.merged_cells: + rlo, rhi, clo, chi = crange + if rlo == 0: # 只关注第一行(表头)的合并单元格 + merged_cells.append((clo, chi)) + + # 清理表头(移除星号和处理空值) + actual_headers_clean = [] + for i, header in enumerate(actual_headers): + # 检查当前列是否在合并单元格范围内但不是合并单元格的起始列 + is_merged_not_first = any(clo < i < chi for clo, chi in merged_cells) + + if is_merged_not_first: + # 如果是合并单元格的非起始列,跳过 + continue + + # 处理表头文本 + if isinstance(header, str): + header = header.replace('*', '').strip() + + if header: # 只添加非空表头 + actual_headers_clean.append(header) + + # 检查表头数量 + if len(actual_headers_clean) < len(expected_headers): + raise UserError(_('表头列数不足,请使用正确的模板文件')) + + # 检查表头顺序(忽略星号) + for i, header in enumerate(expected_headers): + if i >= len(actual_headers_clean) or header != actual_headers_clean[i]: + actual_header = actual_headers_clean[i] if i < len(actual_headers_clean) else "缺失" + raise UserError(_('表头顺序不正确,第%s列应为"%s",但实际为"%s"') % + (i + 1, header, actual_header)) + + # 获取当前活动的quality.check记录 + active_id = self.env.context.get('active_id') + quality_check = self.env['quality.check'].browse(active_id) + + if not quality_check: + raise UserError(_('未找到相关的质检记录')) + + # 记录是否有有效数据被导入 + valid_data_imported = False + + # 从第二行开始读取数据(跳过表头) + for row_index in range(1, sheet.nrows): + row = sheet.row_values(row_index) + + # 检查行是否有数据 + if not any(row): + continue + + # 创建quality.check.measure.line记录 + measure_line_vals = { + 'check_id': quality_check.id, + 'sequence': len(quality_check.measure_line_ids) + 1, + 'product_name': str(row[0]) if row[0] else '', # 产品名称列 + 'drawing_no': str(row[1]) if row[1] else '', # 图号列 + 'measure_item': row[2] or '', # 检测项目列 + 'measure_value1': str(row[4]) if row[4] else '', # 测量值1 + 'measure_value2': str(row[5]) if row[5] else '', # 测量值2 + 'measure_value3': str(row[6]) if len(row) > 6 and row[6] else '', # 测量值3 + 'measure_value4': str(row[7]) if len(row) > 7 and row[7] else '', # 测量值4 + 'measure_value5': str(row[8]) if len(row) > 8 and row[8] else '', # 测量值5 + 'measure_result': 'NG' if row[9] == 'NG' else 'OK', # 判定列 + 'remark': row[10] if len(row) > 10 and row[10] else '', # 备注列 + } + + self.env['quality.check.measure.line'].create(measure_line_vals) + valid_data_imported = True + + # 检查是否有有效数据被导入 + if not valid_data_imported: + raise UserError(_('表格中没有有效数据行可导入,请检查表格内容')) + + # 返回关闭弹窗的动作 + return { + 'type': 'ir.actions.act_window_close' + } + + except Exception as e: + _logger.error("导入Excel数据时出错: %s", str(e)) + _logger.error(traceback.format_exc()) + raise UserError(_('导入失败: %s') % str(e)) + + def process_first_line(self, df_columns, column_labels, field_data): + columns = [] + last_column_name = None + for index, column in enumerate(df_columns): + if not column_labels.get(column): + if 'Unnamed' in column: + columns.append(last_column_name) + else: + field_name_map = self.page_to_field(field_data) + field_name = field_name_map.get(column) + if field_name: + columns.append(field_name) + last_column_name = field_name + else: + columns.append(column) + last_column_name = column + else: + columns.append(column) + last_column_name = column + return columns + + def process_inline_list_column(self, columns, first_row, repeat_list): + for index, repeat_map in enumerate(repeat_list): + start = repeat_map.get('start') + end = int(repeat_map.get('end')) + if len(repeat_list) - 1 == index: + end = end + 1 + for i in range(start, end): + field_name = columns[i] + embedded_fields = first_row[i] + if pd.isna(embedded_fields): + columns[i] = field_name + else: + columns[i] = field_name + '?' + embedded_fields + + def process_data_list(self, model, data_list, column_labels, sheet): + try: + for index, data in enumerate(data_list): + # 转换每行数据到模型data = {dict: 11} {'刀具物料': '刀片', '刀尖特征': '刀尖倒角', '刀片形状': '五边形', '刀片物料参数': [{'刀片物料参数?内接圆直径IC/D(mm)': 23, '刀片物料参数?刀尖圆弧半径RE(mm)': 2, '刀片物料参数?刀片牙型': '石油管螺纹刀片', '刀片物料参数?切削刃长(mm)': 3, '刀片物料参数?厚度(mm)': 12, '刀片物料参数?后角(mm)': 4, '刀片物料参数?安装孔直径D1(mm)': 2, '刀片物料参数?有无断屑槽': '无', '刀片物料参数?物…视图 + self.import_model_record(model, data, column_labels, sheet) + except Exception as e: + traceback_error = traceback.format_exc() + logging.error('批量导入失败sheet %s' % sheet) + logging.error('批量导入失败 : %s' % traceback_error) + raise UserError(e) + + @api.model + def process_model_record_data(self, model, data, column_labels, sheet): + pass + + def import_model_record(self, model, data, column_labels, sheet): + self.process_model_record_data(model, data, column_labels, sheet) + model_data = self.convert_column_name(data, column_labels) + logging.info('批量导入模型{} 数据: {}'.format(model, model_data)) + new_model = model.create(model_data) + self.model_subsequent_processing(new_model, data) + + @api.model + def model_subsequent_processing(self, model, data): + pass + + def convert_column_name(self, data, column_labels): + tmp_map = {} + for key, value in data.items(): + if not column_labels.get(key): + continue + if key == "执行标准": + print('fqwioiqwfo ', value, column_labels) + field_info = column_labels.get(key) + tmp_map[field_info.get('name')] = self.process_field_data(value, field_info) + return tmp_map + + def process_field_data(self, value, field_info): + relation_field_types = ['many2one', 'one2many', 'many2many'] + field_type = field_info.get('type') + if field_type not in relation_field_types: + return value + if field_type == 'Boolean': + if value == '是' or value == '有': + return True + else: + return False + if field_type == 'many2one': + relation_info = self.env[field_info.get('relation')].sudo().search([('name', '=', value)], limit=1) + if relation_info: + return int(relation_info) + + if isinstance(value, list): + return self.process_basic_data_list(value, field_info) + else: + relation_info = self.env[field_info.get('relation')].sudo().search([('name', '=', value)], limit=1) + if relation_info: + return [Command.link(int(relation_info))] + + def process_basic_data_list(self, value, field_info): + if self.is_basic_data_list(value): + return [ + Command.link( + int(self.env[field_info.get('relation')].sudo().search([('name', '=', element)], limit=1))) + for element in value + ] + else: + association_column_labels = self.get_model_column_name_labels( + self.env[field_info.get('relation')].sudo()) + data_list = [ + {column_name.split('?')[1]: column_value + for column_name, column_value in association_column_data.items() + if + len(column_name.split('?')) == 2 and association_column_labels.get(column_name.split('?')[1])} + for association_column_data in value + ] + data_list = [self.convert_column_name(element, association_column_labels) for element in data_list] + return [ + Command.create( + column_map + ) for column_map in data_list + ] + + def get_remaining_ranges(self, ranges, full_list_length): + # 创建一个集合用于存储被覆盖的索引 + covered_indices = set() + + # 遍历范围集合,标记覆盖的索引 + for r in ranges: + start = r['start'] + end = r['end'] + # 将该范围内的索引加入集合 + covered_indices.update(range(start, end + 1)) + + # 计算未覆盖的范围 + remaining_ranges = [] + start = None + + for i in range(full_list_length): + if i not in covered_indices: + if start is None: + start = i # 开始新的未覆盖范围 + else: + if start is not None: + # 记录当前未覆盖范围 + remaining_ranges.append({'start': start, 'end': i - 1}) + start = None # 重置 + + # 处理最后一个范围 + if start is not None: + remaining_ranges.append({'start': start, 'end': full_list_length - 1}) + + return remaining_ranges + + def is_basic_data_list(self, lst): + basic_types = (int, float, str, bool) + lst_len = len(lst) + if lst_len < 1: + return False + if isinstance(lst[0], basic_types): + return True + return False + + def index_retrieve_data(self, df, range_map, i, data_map): + for remaining_map in range_map: + relation_column_data_map = {} + for j in range(remaining_map.get('start'), int(remaining_map.get('end')) + 1): + value = df.iat[i, j] + if pd.isna(value): + continue + if remaining_map.get('column'): + relation_column_data_map.update({df.columns[j]: value}) + else: + if not data_map.get(df.columns[j]): + data_map.update({df.columns[j]: value}) + elif isinstance(data_map.get(df.columns[j]), list): + data_map.get(df.columns[j]).append(value) + else: + lst = [data_map.get(df.columns[j]), value] + data_map.update({df.columns[j]: lst}) + if relation_column_data_map and len(relation_column_data_map) > 0: + data_map.setdefault(remaining_map.get('column'), []).append(relation_column_data_map) + + def parse_excel_data_matrix(self, df, repeat_list): + row_interval_list = self.count_continuous_none(df) + remaining_ranges = self.get_remaining_ranges(repeat_list, len(df.columns)) + data_list = [] + for row_interval_map in row_interval_list: + data_map = {} + for index in range(row_interval_map.get('start'), int(row_interval_map.get('end'))): + if index == 0: + self.index_retrieve_data(df, remaining_ranges, index, data_map) + else: + self.index_retrieve_data(df, remaining_ranges, index, data_map) + self.index_retrieve_data(df, repeat_list, index, data_map) + if len(data_map) > 0: + data_list.append(data_map) + return data_list + + def saadqw(self): + + excel_template = self.env['excel.template'].sudo().search([('model_id.model', '=', self.model_name)], limit=1) + file_content = base64.b64decode(excel_template.file_data) + return { + 'type': 'ir.actions.act_url', + 'url': 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,{file_content}', + 'target': 'self', + 'download': excel_template.file_name, + } + # return request.make_response( + # file_content, + # headers=[ + # ('Content-Disposition', f'attachment; filename="{excel_template.file_name}"'), + # ('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), + # ] + # ) + + def download_excel_template(self): + excel_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/quality_control/static/src/binary/出厂检验报告上传模版.xlsx' + value = dict( + type='ir.actions.act_url', + target='self', + url=excel_url, + ) + return value diff --git a/quality_control/wizard/import_complex_model.xml b/quality_control/wizard/import_complex_model.xml new file mode 100644 index 00000000..db7ea5d9 --- /dev/null +++ b/quality_control/wizard/import_complex_model.xml @@ -0,0 +1,33 @@ + + + + 请导入数据文件 + quality.check.import.complex.model.wizard + +
+ + + +
+
+
+
+
+ + 导入模型数据 + ir.actions.act_window + + quality.check.import.complex.model.wizard + form + + new + + + + + + +
\ No newline at end of file From de1bdbe18b11e3816d882deadea4201457b66b38 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Wed, 12 Mar 2025 09:08:41 +0800 Subject: [PATCH 020/189] =?UTF-8?q?=E5=87=86=E5=A4=87=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E7=94=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quality_control/models/quality.py | 32 +++++++++++-------- .../views/quality.check.measures.line.xml | 2 +- quality_control/views/quality_views.xml | 1 + 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py index b1efad6c..1af23aff 100644 --- a/quality_control/models/quality.py +++ b/quality_control/models/quality.py @@ -147,14 +147,22 @@ class QualityCheck(models.Model): measure_line_ids = fields.One2many('quality.check.measure.line', 'check_id', string='测量明细') def add_measure_line(self): - ''' + """ 新增测量值,如果测量值有5列了,则提示“最多只能有5列测量值” - ''' - self.ensure_one() - self.env['quality.check.measure.line'].create({ - 'check_id': self.id, - 'sequence': len(self.measure_line_ids) + 1, - }) + """ + pass + # self.ensure_one() + # self.env['quality.check.measure.line'].create({ + # 'check_id': self.id, + # 'sequence': len(self.measure_line_ids) + 1, + # }) + + def remove_measure_line(self): + """ + 删除测量值 + """ + pass + # self.ensure_one() @depends('product_id') def _compute_part_name_number(self): @@ -579,12 +587,12 @@ class QualityCheckMeasureLine(models.Model): sequence = fields.Integer('序号') check_id = fields.Many2one('quality.check', string='质检单', required=True, ondelete='cascade') - + # 基本信息 product_name = fields.Char('产品名称', related='check_id.product_id.name', readonly=True) drawing_no = fields.Char('图号') measure_item = fields.Char('检测项目') - + # 测量值 measure_value1 = fields.Char('测量值1') measure_value2 = fields.Char('测量值2') @@ -594,17 +602,15 @@ class QualityCheckMeasureLine(models.Model): # 展示列数 show_colomn_number = fields.Integer('展示列数', default=1) - + # 判定结果 measure_result = fields.Selection([ ('OK', 'OK'), ('NG', 'NG') ], string='判定', default='OK') - + remark = fields.Char('备注') def del_measure_value(self): self.ensure_one() self.sudo().unlink() - - \ No newline at end of file diff --git a/quality_control/views/quality.check.measures.line.xml b/quality_control/views/quality.check.measures.line.xml index b0c603df..c0e3be04 100644 --- a/quality_control/views/quality.check.measures.line.xml +++ b/quality_control/views/quality.check.measures.line.xml @@ -14,7 +14,7 @@ -
- + + diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index 970292b7..a0adf70f 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -26,6 +26,7 @@ class QualityCheck(models.Model): string='生产线') equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备') model_file = fields.Binary(related='workorder_id.glb_file', string='加工模型') + glb_url = fields.Char(related='workorder_id.glb_url', string='加工模型') detection_report = fields.Binary(related='workorder_id.detection_report', readonly=True, string='检测报告') test_results = fields.Selection([("合格", "合格"), ("返工", "返工")], string="检测结果", diff --git a/sf_quality/models/quality_cnc_test.py b/sf_quality/models/quality_cnc_test.py index 9810b0e0..1e89ef92 100644 --- a/sf_quality/models/quality_cnc_test.py +++ b/sf_quality/models/quality_cnc_test.py @@ -12,6 +12,7 @@ class SfQualityCncTest(models.Model): production_id = fields.Many2one(related='workorder_id.production_id', string='制造订单') product_id = fields.Many2one(related='workorder_id.product_id', string='产品') model_file = fields.Binary(related='workorder_id.glb_file', string='加工模型') + glb_url = fields.Char(related='workorder_id.glb_url', string='加工模型') processing_panel = fields.Char(related='workorder_id.processing_panel', string='加工面') equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备') production_line_id = fields.Many2one(related='workorder_id.production_line_id', diff --git a/sf_quality/views/quality_check_view.xml b/sf_quality/views/quality_check_view.xml index 83ffcbca..fa88b48c 100644 --- a/sf_quality/views/quality_check_view.xml +++ b/sf_quality/views/quality_check_view.xml @@ -13,6 +13,8 @@ + diff --git a/sf_quality/views/quality_cnc_test_view.xml b/sf_quality/views/quality_cnc_test_view.xml index d2d37686..188ff7be 100644 --- a/sf_quality/views/quality_cnc_test_view.xml +++ b/sf_quality/views/quality_cnc_test_view.xml @@ -87,7 +87,8 @@ - + + diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py index 259928bc..be61a3a7 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -248,6 +248,7 @@ class ResaleOrderLine(models.Model): # part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True) model_glb_file = fields.Binary('模型的glb文件', compute='_compute_model_glb_file', store=True) + glb_url = fields.Char('glb文件地址', compute='_compute_model_glb_file', store=True) # product_template_id = fields.Many2one( # string="产品", # comodel_name='product.template', @@ -265,7 +266,6 @@ class ResaleOrderLine(models.Model): embryo_redundancy_id = fields.Many2one('sf.embryo.redundancy', '坯料冗余') manual_quotation = fields.Boolean('人工编程', default=False) model_url = fields.Char('模型文件地址') - glb_url = fields.Char('glb文件地址') model_id = fields.Char('模型id') @api.depends('embryo_redundancy_id') @@ -279,6 +279,8 @@ class ResaleOrderLine(models.Model): if line.product_template_id: if not line.model_glb_file: line.model_glb_file = line.product_id.product_tmpl_id.model_file + if not line.glb_url: + line.glb_url = line.product_id.product_tmpl_id.glb_url if not line.price_unit: line.price_unit = line.product_id.product_tmpl_id.list_price diff --git a/sf_sale/views/quick_easy_order_view.xml b/sf_sale/views/quick_easy_order_view.xml index 89792a49..d8af409a 100644 --- a/sf_sale/views/quick_easy_order_view.xml +++ b/sf_sale/views/quick_easy_order_view.xml @@ -50,9 +50,13 @@ - + + +
- + diff --git a/sf_quality/__manifest__.py b/sf_quality/__manifest__.py index 9ca7128a..560963b7 100644 --- a/sf_quality/__manifest__.py +++ b/sf_quality/__manifest__.py @@ -17,10 +17,14 @@ 'data': [ 'security/ir.model.access.csv', 'data/check_standards.xml', + 'data/documents_data.xml', + 'data/insepection_report_template.xml', + 'data/report_actions.xml', 'views/view.xml', 'views/quality_cnc_test_view.xml', 'views/mrp_workorder.xml', - 'views/quality_check_view.xml' + 'views/quality_check_view.xml', + 'views/quality_company.xml' ], 'assets': { diff --git a/sf_quality/data/documents_data.xml b/sf_quality/data/documents_data.xml new file mode 100644 index 00000000..69fdbe8c --- /dev/null +++ b/sf_quality/data/documents_data.xml @@ -0,0 +1,11 @@ + + + + + + 出厂检验报告 + 存放出厂检验报告相关文件 + 11 + + + \ No newline at end of file diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml new file mode 100644 index 00000000..d8df704a --- /dev/null +++ b/sf_quality/data/insepection_report_template.xml @@ -0,0 +1,146 @@ + + + + + + + + + + \ No newline at end of file diff --git a/sf_quality/data/report_actions.xml b/sf_quality/data/report_actions.xml new file mode 100644 index 00000000..2d875d71 --- /dev/null +++ b/sf_quality/data/report_actions.xml @@ -0,0 +1,22 @@ + + + + 出厂检验报告 + quality.check + qweb-pdf + sf_quality.report_quality_inspection + sf_quality.report_quality_inspection + 'QC-' + object.name + '.pdf' + + report + + + + + 预览检验报告 + quality.inspection + qweb-html + sf_quality.report_quality_inspection + report + + \ No newline at end of file diff --git a/sf_quality/models/__init__.py b/sf_quality/models/__init__.py index 4fcdb16f..b547faa9 100644 --- a/sf_quality/models/__init__.py +++ b/sf_quality/models/__init__.py @@ -6,3 +6,4 @@ from . import quality from . import quality_cnc_test from . import mrp_workorder # from . import stock +from . import quality_company diff --git a/sf_quality/models/quality_company.py b/sf_quality/models/quality_company.py new file mode 100644 index 00000000..de80ad50 --- /dev/null +++ b/sf_quality/models/quality_company.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +# 为公司增加字段 +class Company(models.Model): + _inherit = 'res.company' + + factory_name = fields.Char('加工工厂') diff --git a/sf_quality/views/quality_check_view.xml b/sf_quality/views/quality_check_view.xml index eaee2a84..d288324e 100644 --- a/sf_quality/views/quality_check_view.xml +++ b/sf_quality/views/quality_check_view.xml @@ -70,6 +70,14 @@ +

- +
From f780d475621b7dd30cf4f842bf6fecb8adf67081 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Thu, 13 Mar 2025 16:12:48 +0800 Subject: [PATCH 041/189] =?UTF-8?q?=E5=8F=91=E5=B8=83=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quality_control/models/quality.py | 133 ++++++++++++++---- quality_control/security/ir.model.access.csv | 1 + .../views/quality.check.measures.line.xml | 4 +- quality_control/views/quality_views.xml | 12 +- sf_quality/data/documents_data.xml | 15 +- sf_quality/views/quality_check_view.xml | 7 +- 6 files changed, 140 insertions(+), 32 deletions(-) diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py index f67741a0..17acf218 100644 --- a/quality_control/models/quality.py +++ b/quality_control/models/quality.py @@ -128,7 +128,7 @@ class QualityPoint(models.Model): class QualityCheck(models.Model): _inherit = "quality.check" part_name = fields.Char('零件名称', related='product_id.part_name') - part_number = fields.Char('零件图号', related='product_id.part_number') + part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True) material_name = fields.Char('材料名称', compute='_compute_material_name') # # 总数量,值为调拨单_产品明细_数量 @@ -187,10 +187,21 @@ class QualityCheck(models.Model): ('OK', 'OK'), ('NG', 'NG') ], string='出厂检验报告结果', default='OK') - measure_operator = fields.Char('测量员', readonly=True) - quality_manager = fields.Char('质量管理人员', readonly=True) + measure_operator = fields.Many2one('res.users', string='操机员') + quality_manager = fields.Many2one('res.users', string='质检员') + # 流水号(从1开始,最大99) + serial_number = fields.Integer('流水号', default=1, readonly=True) + # 发布历史 + report_history_ids = fields.One2many('quality.check.report.history', 'check_id', string='发布历史') + + # 发布状态 + publish_status = fields.Selection([ + ('draft', '草稿'), + ('published', '已发布'), + ('canceled', '已撤销') + ], string='发布状态', default='draft') def add_measure_line(self): """ @@ -229,46 +240,91 @@ class QualityCheck(models.Model): res_ids=self.ids ) - # 获取默认的文档文件夹 - workspace = self.env['documents.folder'].search([('name', '=', '出厂检验报告')], limit=1) - + attachment = self.env['ir.attachment'].create({ + 'name': self.name, + 'type': 'binary', + 'datas': pdf_content, + 'res_model': self._name, + 'res_id': self.id, + 'mimetype': 'application/pdf', + }) + + # 获取已发布的文档文件夹 + workspace = self.env['documents.folder'].search([('parent_folder_id', '=', self.env.ref('sf_quality.documents_purchase_contracts_folder').id), ('name', '=', '已发布')], limit=1) + + if self.serial_number > 99: + raise UserError(_('流水号不能大于99')) + + str_serial_number = '0' + str(self.serial_number) if self.serial_number < 10 else str(self.serial_number) + str_part_number = self.part_number if self.part_number else '' # 3. 创建文档记录 doc_vals = { - 'name': f'FQC-{self.name}', - 'raw': pdf_content, - 'mimetype': 'application/pdf', + 'name': f'FQC{str_part_number}{str_serial_number}', + # 'raw': pdf_content, + 'attachment_id': attachment.id, + # 'mimetype': 'application/pdf', 'res_id': self.id, 'folder_id': workspace.id, 'res_model': self._name, } - # 如果已经有报告,则更新 - if self.report_number_id: - self.report_number_id.write(doc_vals) - else: - # 创建新的document记录 - doc = self.env['documents.document'].create(doc_vals) - # 关联到当前质检记录 - self.write({ - 'report_number_id': doc.id, - 'measure_operator': self.env.user.name, # 记录操作人 - 'quality_manager': self.env.user.name, # 记录质检人 - }) + doc = self.env['documents.document'].create(doc_vals) + # 关联到当前质检记录 + self.write({ + 'report_number_id': doc.id, + 'publish_status': 'published' + }) + # 记录发布历史 + self.env['quality.check.report.history'].create({ + 'check_id': self.id, + 'report_number_id': doc.id, + 'action': 'publish', + 'operator': self.env.user.name, + 'operation_time': datetime.now(), + 'document_status': 'published', + 'sequence': len(self.report_history_ids) + 1 + }) + + # 更新流水号 + self.serial_number += 1 + + # 返回成功消息 return True def do_cancel_publish(self): """ - 取消发布出厂检验报告 + 取消发布出厂检验报告(将当前质检单关联的出厂检验报告文档位置移动到废弃文件夹), 并记录发布历史 """ - pass + self.ensure_one() + # 1. 获取已发布的文档文件夹 + workspace = self.env['documents.folder'].search([('parent_folder_id', '=', self.env.ref('sf_quality.documents_purchase_contracts_folder').id), ('name', '=', '已发布')], limit=1) + # 2. 将当前质检单关联的出厂检验报告文档位置移动到废弃文件夹 + self.report_number_id.write({ + 'folder_id': self.env.ref('sf_quality.documents_purchase_contracts_folder_canceled').id, + }) + # 3. 更新发布状态 + self.publish_status = 'canceled' + # 3. 记录发布历史 + self.env['quality.check.report.history'].create({ + 'check_id': self.id, + 'report_number_id': self.report_number_id.id, + 'action': 'cancel_publish', + 'operator': self.env.user.name, + 'operation_time': datetime.now(), + 'document_status': 'canceled', + 'sequence': len(self.report_history_ids) + 1 + }) + return True + def do_re_publish(self): """ - 重新发布出厂检验报告 + 重新发布出厂检验报告,参考发布规则 """ - pass + self.do_publish() + def generate_qr_code(self): """生成二维码URL""" @@ -725,3 +781,30 @@ class QualityCheckMeasureLine(models.Model): def del_measure_value(self): self.ensure_one() self.sudo().unlink() + + +# 增加出厂检验报告发布历史 +class QualityCheckReportHistory(models.Model): + _name = 'quality.check.report.history' + _description = '出厂检验报告发布历史' + + check_id = fields.Many2one('quality.check', string='质检单', required=True, ondelete='cascade') + report_number_id = fields.Many2one('documents.document', string='报告编号', readonly=True) + + sequence = fields.Integer('序号') + # 操作(发布、撤销发布、重新发布) + action = fields.Selection([ + ('publish', '发布'), + ('cancel_publish', '撤销发布'), + ('re_publish', '重新发布') + ], string='操作') + # 操作人 + operator = fields.Char('操作人') + # 操作时间 + operation_time = fields.Datetime('操作时间') + # 文档状态(已发布、废弃) + document_status = fields.Selection([ + ('published', '已发布'), + ('canceled', '废弃') + ], string='操作后文档状态') + diff --git a/quality_control/security/ir.model.access.csv b/quality_control/security/ir.model.access.csv index c0c409a6..d06537b6 100644 --- a/quality_control/security/ir.model.access.csv +++ b/quality_control/security/ir.model.access.csv @@ -2,3 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_quality_check_wizard,access.quality_check_wizard,model_quality_check_wizard,quality.group_quality_user,1,1,1,0 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 \ No newline at end of file diff --git a/quality_control/views/quality.check.measures.line.xml b/quality_control/views/quality.check.measures.line.xml index c0e3be04..9000fbd3 100644 --- a/quality_control/views/quality.check.measures.line.xml +++ b/quality_control/views/quality.check.measures.line.xml @@ -4,7 +4,7 @@ quality.check.measure.line.tree quality.check.measure.line - + @@ -14,7 +14,7 @@ - +