From 1541326dfc9d8c86853793e14e9989f2fefc103e Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 10 Mar 2025 14:56:42 +0800 Subject: [PATCH 01/46] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8E=92=E7=A8=8B?= =?UTF-8?q?=E5=8D=95=E5=8F=96=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/wizard/sale_order_cancel.py | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index 516f8804..c14f7620 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -94,6 +94,12 @@ class SFSaleOrderCancelWizard(models.TransientModel): if mo_quality_checks: mo_quality_checks.write({'quality_state': 'cancel'}) + # 取消制造订单的排程单 + mo_plan_orders = self.env['sf.production.plan'].search([ + ('origin', '=', order.name)]) + if mo_plan_orders: + mo_plan_orders.write({'state': 'cancel'}) + # 取消制造订单的子制造订单 child_mo_ids = self.env['mrp.production'].search([ ('origin', '=', mo.name) @@ -182,6 +188,12 @@ class SFSaleOrderCancelLine(models.TransientModel): 'progress': '加工中', 'assigned': '就绪' } + plan_map_dict = { + 'draft': '待排程', + 'done': '已排程', + 'processing': '加工中', + 'finished': '已完成', + 'cancel': '已取消'} module_name_dict = { 'purchase': '采购', @@ -288,6 +300,31 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) + # 检查所有的排程单 + sf_plan_orders = self.env['sf.production.plan'].search([ + ('origin', '=', order.name)]) + if sf_plan_orders: + p1 = 0 + for plan_order in sf_plan_orders: + if not plan_order.product_id.default_code: + continue + p1 += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '排程', + 'doc_name': '排程单', + 'operation_type': '', + 'doc_number': plan_order.name, + 'line_number': p1, + 'product_name': f'[{plan_order.product_id.default_code}] {plan_order.product_id.name}', + 'quantity': 1, + 'doc_state': plan_map_dict.get(plan_order.state, plan_order.state), + 'cancel_reason': '已有异动' if plan_order.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + # 检查组件的制造单 # component_mos = self.env['mrp.production'].search([ # ('origin', '=', mo.name)]) 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 02/46] =?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 03/46] =?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 + + + + + + + + + + + +