From 962931bc9da24b2b87848be375061fdf7e61de52 Mon Sep 17 00:00:00 2001 From: liaodanlong Date: Thu, 17 Apr 2025 14:29:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=96=E5=8D=8F=E6=B5=81=E7=A8=8B=E6=9B=B4?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/__manifest__.py | 3 +- sf_manufacturing/models/__init__.py | 3 +- sf_manufacturing/models/mrp_production.py | 37 +++++++++++-- sf_manufacturing/models/mrp_workorder.py | 52 +++++++++++-------- sf_manufacturing/models/purchase_order.py | 31 +++++++++++ .../models/purchase_request_line.py | 27 ++++++++++ .../models/sf_production_common.py | 50 ++++++++++-------- sf_manufacturing/security/ir.model.access.csv | 4 +- .../views/mrp_routing_workcenter_view.xml | 2 +- sf_manufacturing/wizard/__init__.py | 1 + .../wizard/process_outsourcing.py | 42 +++++++++++++++ .../wizard/process_outsourcing.xml | 37 +++++++++++++ sf_sale/models/sale_order.py | 22 +++++--- sf_stock/__manifest__.py | 1 + sf_stock/data/stock_location_data.xml | 13 +++++ sf_stock/models/__init__.py | 3 +- sf_stock/models/stock_warehouse_orderpoint.py | 11 ++++ 17 files changed, 281 insertions(+), 58 deletions(-) create mode 100644 sf_manufacturing/models/purchase_request_line.py create mode 100644 sf_manufacturing/wizard/process_outsourcing.py create mode 100644 sf_manufacturing/wizard/process_outsourcing.xml create mode 100644 sf_stock/data/stock_location_data.xml create mode 100644 sf_stock/models/stock_warehouse_orderpoint.py diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index b0d77c53..817f75b9 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -11,7 +11,7 @@ 'category': 'sf', 'website': 'https://www.sf.jikimo.com', 'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse', 'jikimo_attachment_viewer', - 'jikimo_sale_multiple_supply_methods','product'], + 'jikimo_sale_multiple_supply_methods', 'product'], 'data': [ 'data/cron_data.xml', 'data/stock_data.xml', @@ -30,6 +30,7 @@ 'wizard/mrp_workorder_batch_replan_wizard_views.xml', 'wizard/sf_programming_reason_views.xml', 'wizard/sale_order_cancel_views.xml', + 'wizard/process_outsourcing.xml', 'views/mrp_views_menus.xml', 'views/agv_scheduling_views.xml', 'views/stock_lot_views.xml', diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index e6845317..93e2c39b 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -16,4 +16,5 @@ from . import sf_production_common from . import sale_order from . import quick_easy_order from . import purchase_order -from . import quality_check \ No newline at end of file +from . import quality_check +from . import purchase_request_line \ No newline at end of file diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index adddbd7d..34b1dd74 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -6,6 +6,7 @@ import json import os import re import traceback +from operator import itemgetter import requests from itertools import groupby @@ -889,11 +890,40 @@ class MrpProduction(models.Model): workorders_values.append( self.env[ 'mrp.workorder']._json_workorder_surface_process_str( - production, route, product_production_process.seller_ids[0].partner_id.id)) + production, route, product_production_process.seller_ids[0].partner_id.id if product_production_process.seller_ids else False)) production.workorder_ids = workorders_values for workorder in production.workorder_ids: workorder.duration_expected = workorder._get_duration_expected() + def _create_subcontract_purchase_request(self,purchase_request_line): + sorted_list = sorted(purchase_request_line, key=itemgetter('name')) + grouped_purchase_request_line = { + k: list(g) + for k, g in groupby(sorted_list, key=itemgetter('name')) + } + for name, request_line in grouped_purchase_request_line.items(): + request_line_sorted_list = sorted(request_line, key=itemgetter('product_id')) + grouped_purchase_request_line_sorted_list = { + k: list(g) + for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id')) + } + purchase_request_model = self.env["purchase.request"] + pr = purchase_request_model.create({ + "origin": name, + "company_id": self.company_id.id, + "picking_type_id": self.env.ref('stock.picking_type_in').id, + "group_id": request_line[0].get('group_id'), + "requested_by": self.env.context.get("uid", self.env.uid), + "assigned_to": False, + "bom_id":self[0].bom_id.id, + }) + for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items(): + cur_request_line = request_line_list[0] + cur_request_line['product_qty'] = len(request_line_list) + cur_request_line['request_id'] = pr.id + cur_request_line['origin'] = name + cur_request_line.pop('group_id', None) + self.env["purchase.request.line"].create(cur_request_line) # 外协出入库单处理 def get_subcontract_pick_purchase(self): production_all = self.sorted(lambda x: x.id) @@ -903,6 +933,7 @@ class MrpProduction(models.Model): for product_id, pd in grouped_product_ids.items(): product_id_to_production_names[product_id] = [p.name for p in pd] sorted_workorders = None + purchase_request_line = [] for production in production_all: proc_workorders = [] process_parameter_workorder = self.env['mrp.workorder'].search( @@ -917,11 +948,11 @@ class MrpProduction(models.Model): sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.sequence) if not sorted_workorders: return - purchase_request_line = [] for workorders in reversed(sorted_workorders): self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders) - self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names) + # self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names) purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(workorders, production) + self._create_subcontract_purchase_request(purchase_request_line) # 工单排序 def _reset_work_order_sequence1(self, k): diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index f187d506..648982b9 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -106,11 +106,11 @@ class ResMrpWorkOrder(models.Model): record.back_button_display = False # tag_type if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any( - detection_result.processing_panel == cur_workorder.processing_panel and - detection_result.routing_type == cur_workorder.routing_type and - cur_workorder.tag_type !='重新加工' and - detection_result.test_results != '合格' - for detection_result in cur_workorder.production_id.detection_result_ids + detection_result.processing_panel == cur_workorder.processing_panel and + detection_result.routing_type == cur_workorder.routing_type and + cur_workorder.tag_type != '重新加工' and + detection_result.test_results != '合格' + for detection_result in cur_workorder.production_id.detection_result_ids ): record.back_button_display = False else: @@ -122,11 +122,11 @@ class ResMrpWorkOrder(models.Model): else: record.back_button_display = False if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any( - detection_result.processing_panel == cur_workorder.processing_panel and - detection_result.routing_type == cur_workorder.routing_type and - cur_workorder.tag_type !='重新加工' and - detection_result.test_results != '合格' - for detection_result in cur_workorder.production_id.detection_result_ids + detection_result.processing_panel == cur_workorder.processing_panel and + detection_result.routing_type == cur_workorder.routing_type and + cur_workorder.tag_type != '重新加工' and + detection_result.test_results != '合格' + for detection_result in cur_workorder.production_id.detection_result_ids ): record.back_button_display = False @@ -429,9 +429,11 @@ class ResMrpWorkOrder(models.Model): def _compute_surface_technics_purchase_ids(self): for order in self: if order.routing_type == '表面工艺' and order.state not in ['cancel']: - domain = [('purchase_type', '=', 'consignment'), - ('origin', 'like', '%' + self.production_id.name + '%'), - ('state', '!=', 'cancel')] + domain = [('group_id', '=', self.production_id.procurement_group_id.id), + ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')] + # domain = [('purchase_type', '=', 'consignment'), + # ('origin', 'like', '%' + self.production_id.name + '%'), + # ('state', '!=', 'cancel')] purchase = self.env['purchase.order'].search(domain) order.surface_technics_purchase_count = 0 if not purchase: @@ -472,14 +474,15 @@ class ResMrpWorkOrder(models.Model): return result def _get_surface_technics_purchase_ids(self): - domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')] - purchase_orders = self.env['purchase.order'].search(domain) + # domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')] + domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')] + purchase_orders = self.env['purchase.order'].search(domain, order='id desc', # 按创建时间降序(最新的在前) + limit=1) purchase_orders_id = self.env['purchase.order'] for po in purchase_orders: for line in po.order_line: if line.product_id.server_product_process_parameters_id == self.surface_technics_parameters_id: - if line.product_qty == 1: - purchase_orders_id = line.order_id + purchase_orders_id = line.order_id return purchase_orders_id supplier_id = fields.Many2one('res.partner', string='外协供应商') @@ -1200,6 +1203,7 @@ class ResMrpWorkOrder(models.Model): 'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids, }] return workorders_values_str + def _process_compute_state(self): for workorder in self: # 如果工单的工序没有进行排序则跳出循环 @@ -1224,7 +1228,7 @@ class ResMrpWorkOrder(models.Model): and workorder.production_id.schedule_state == '已排' and len(workorder.production_id.picking_ids.filtered( lambda w: w.state not in ['done', 'cancel'])) == 0): - # and workorder.production_id.programming_state == '已编程' + # and workorder.production_id.programming_state == '已编程' if workorder.is_subcontract is True: if workorder.production_id.state == 'rework': workorder.state = 'waiting' @@ -1287,6 +1291,7 @@ class ResMrpWorkOrder(models.Model): mo.get_move_line(workorder.production_id, workorder)) else: workorder.state = 'waiting' + @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state', 'production_id.tool_state', 'production_id.schedule_state', 'sequence', 'production_id.programming_state') @@ -1301,6 +1306,7 @@ class ResMrpWorkOrder(models.Model): for check_id in workorder.check_ids: if not check_id.is_inspect: check_id.quality_state = 'none' + # 重写工单开始按钮方法 def button_start(self): # 判断工单状态是否为等待组件 @@ -1534,7 +1540,7 @@ class ResMrpWorkOrder(models.Model): # workorder.rfid_code_old = rfid_code # workorder.rfid_code = False logging.info('workorder.rfid_code:%s' % workorder.rfid_code) - + if is_production_id is True: logging.info('product_qty:%s' % record.production_id.product_qty) for move_raw_id in record.production_id.move_raw_ids: @@ -1552,7 +1558,8 @@ class ResMrpWorkOrder(models.Model): # 如果工单包含了外协工序,需要预留数量 if self.move_raw_ids.move_orig_ids.subcontract_workorder_id: location_id = self.move_raw_ids.location_id - quant = self.move_raw_ids.lot_ids.quant_ids.filtered(lambda q: q.location_id.id == location_id.id) + quant = self.move_raw_ids.lot_ids.quant_ids.filtered( + lambda q: q.location_id.id == location_id.id) if quant.reserved_quantity == 0: self.env['stock.quant']._update_reserved_quantity( self.move_raw_ids.product_id, @@ -1706,7 +1713,8 @@ class ResMrpWorkOrder(models.Model): 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) + individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids', + store=True) @api.depends('name') def _compute_routing_work_center_id(self): @@ -1727,6 +1735,7 @@ class ResMrpWorkOrder(models.Model): 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) @@ -2040,6 +2049,7 @@ class WorkPieceDelivery(models.Model): 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='类型') delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration') status = fields.Selection( diff --git a/sf_manufacturing/models/purchase_order.py b/sf_manufacturing/models/purchase_order.py index 044b68e1..a919b3be 100644 --- a/sf_manufacturing/models/purchase_order.py +++ b/sf_manufacturing/models/purchase_order.py @@ -66,6 +66,37 @@ class PurchaseOrder(models.Model): raise UserError('请对【产品】中的【数量】进行输入') if line.price_unit <= 0: raise UserError('请对【产品】中的【单价】进行输入') + if record.purchase_type == 'consignment': + bom_line_id = record.order_line[0].purchase_request_lines.request_id.bom_id.bom_line_ids + replenish = self.env['stock.warehouse.orderpoint'].search([ + ('product_id', '=', bom_line_id.product_id.id), + ( + 'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id), + # ('state', 'in', ['draft', 'confirmed']) + ], limit=1) + if not replenish: + replenish_model = self.env['stock.warehouse.orderpoint'] + replenish = replenish_model.create({ + 'product_id': bom_line_id.product_id.id, + 'location_id': self.env.ref( + 'sf_stock.stock_location_outsourcing_material_receiving_area').id, + 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, + 'group_id': record.group_id.id, + 'qty_to_order': 1, + 'origin': record.name, + }) + else: + replenish.write({ + 'product_id': bom_line_id.product_id.id, + 'location_id': self.env.ref( + 'sf_stock.stock_location_outsourcing_material_receiving_area').id, + 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, + 'group_id': record.group_id.id, + 'qty_to_order': 1 + replenish.qty_to_order, + 'origin': record.name + ',' + replenish.origin, + }) + replenish.action_replenish() + return super(PurchaseOrder, self).button_confirm() diff --git a/sf_manufacturing/models/purchase_request_line.py b/sf_manufacturing/models/purchase_request_line.py new file mode 100644 index 00000000..385594e4 --- /dev/null +++ b/sf_manufacturing/models/purchase_request_line.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +import base64 +import datetime +import logging +import json +import os +import re +import traceback +from operator import itemgetter + +import requests +from itertools import groupby +from collections import defaultdict, namedtuple + +from odoo import api, fields, models, SUPERUSER_ID, _ +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_compare, float_round, float_is_zero, format_datetime + + +class PurchaseRequestLine(models.Model): + _inherit = 'purchase.request.line' + is_subcontract = fields.Boolean(string='是否外协') + + +class PurchaseRequest(models.Model): + _inherit = 'purchase.request' + bom_id = fields.Many2one('mrp.bom') diff --git a/sf_manufacturing/models/sf_production_common.py b/sf_manufacturing/models/sf_production_common.py index 4cebbd27..708199bb 100644 --- a/sf_manufacturing/models/sf_production_common.py +++ b/sf_manufacturing/models/sf_production_common.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import logging from odoo import fields, models, api -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError from odoo.tools import str2bool @@ -16,6 +16,11 @@ class SfProductionProcessParameter(models.Model): is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False) routing_id = fields.Many2one('mrp.routing.workcenter', string="工序") + @api.constrains('outsourced_service_products') + def _validate_partner_limit(self): + for record in self: + if len(record.outsourced_service_products) > 1: + raise ValidationError("工艺参数不能与多个产品关联") @api.depends('outsourced_service_products') def _compute_is_product_button(self): for record in self: @@ -56,32 +61,31 @@ class SfProductionProcessParameter(models.Model): return super()._name_search(name, args, operator, limit, name_get_uid) def action_create_service_product(self): - # 获取产品分类(服务) - service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo() - default_values = { - 'detailed_type': 'service', - 'name': f"{self.process_id.name}{self.name}", - 'invoice_policy': 'delivery', - 'categ_id': service_categ.id, - 'description': f"基于{self.name}创建的服务产品", - 'sale_ok': True, # 可销售 - 'purchase_ok': True, # 可采购 - 'server_product_process_parameters_id': self.id, - } - # 创建服务产品 - # 返回打开新创建产品的表单视图 + if self.id: # 如果是已存在的记录 + self.write({}) # 空写入会触发保存 + else: # 如果是新记录 + self = self.create(self._convert_to_write(self.read()[0])) return { - 'name': '创建服务产品', 'type': 'ir.actions.act_window', - 'res_model': 'product.product', + 'name': '向导名称', + 'res_model': 'product.creation.wizard', 'view_mode': 'form', - 'view_id': self.env.ref('product.product_normal_form_view').id, - 'target': 'new', # 关键参数,使窗口以弹窗形式打开 - 'context': { - 'default_' + k: v for k, v in default_values.items() - }, + 'target': 'new', + 'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID } + # + # return { + # 'name': '创建服务产品', + # 'type': 'ir.actions.act_window', + # 'res_model': 'product.product', + # 'view_mode': 'form', + # 'view_id': self.env.ref('product.product_normal_form_view').id, + # 'target': 'new', # 关键参数,使窗口以弹窗形式打开 + # 'context': { + # 'default_' + k: v for k, v in default_values.items() + # }, + # } def action_hide_service_products(self): - self.outsourced_service_products.active = False + # self.outsourced_service_products.active = False self.active = False diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index 982506a5..852aa05f 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -193,4 +193,6 @@ access_sf_programming_record,sf_programming_record,model_sf_programming_record,b access_sf_work_individuation_page,sf_work_individuation_page,model_sf_work_individuation_page,sf_base.group_sf_mrp_user,1,1,1,0 access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page_group_plan_dispatch,model_sf_work_individuation_page,sf_base.group_plan_dispatch,1,1,0,0 access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0 -access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1 \ No newline at end of file +access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1 + +access_product_creation_wizard,product_creation_wizard,model_product_creation_wizard,base.group_user,1,1,1,0 \ No newline at end of file diff --git a/sf_manufacturing/views/mrp_routing_workcenter_view.xml b/sf_manufacturing/views/mrp_routing_workcenter_view.xml index 402c9ccb..d1eb0618 100644 --- a/sf_manufacturing/views/mrp_routing_workcenter_view.xml +++ b/sf_manufacturing/views/mrp_routing_workcenter_view.xml @@ -34,7 +34,7 @@