diff --git a/jikimo_purchase_request/__manifest__.py b/jikimo_purchase_request/__manifest__.py index 2ae6ce67..c6e58a41 100644 --- a/jikimo_purchase_request/__manifest__.py +++ b/jikimo_purchase_request/__manifest__.py @@ -10,6 +10,7 @@ 'data': [ 'security/ir.model.access.csv', 'views/sale_order_view.xml', + 'views/purchase_order.xml', 'views/mrp_production.xml', 'views/purchase_request_view.xml', 'wizard/purchase_request_line_make_purchase_order_view.xml', diff --git a/jikimo_purchase_request/models/purchase_order.py b/jikimo_purchase_request/models/purchase_order.py index 95f81e9f..29e6c646 100644 --- a/jikimo_purchase_request/models/purchase_order.py +++ b/jikimo_purchase_request/models/purchase_order.py @@ -16,6 +16,69 @@ class PurchaseOrder(models.Model): ('rejected', '已驳回') ], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True) + # 成品采购订单对应的坯料采购申请单和采购订单数量 + purchase_request_count = fields.Integer('子·采购申请数量', compute='_compute_purchase_request') + purchase_order_count = fields.Integer('子·采购订单数量', compute='_compute_purchase_request') + + @api.depends('state') + def _compute_purchase_request(self): + for record in self: + purchase_request_ids, purchase_order_ids = record.get_purchase_request_order() + record.purchase_request_count = len(purchase_request_ids) + record.purchase_order_count = len(purchase_order_ids) + + def action_view_preform_body_purchase_request(self): + self.ensure_one() + name_list = self._get_pinking_name() + purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)]) + + action = { + 'res_model': 'purchase.request', + 'type': 'ir.actions.act_window', + } + if len(purchase_request_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': purchase_request_ids[0].id, + }) + else: + action.update({ + 'name': _("子·采购申请"), + 'domain': [('id', 'in', purchase_request_ids.ids)], + 'view_mode': 'tree,form', + }) + return action + + def action_view_preform_body_purchase_order(self): + self.ensure_one() + name_list = self._get_pinking_name() + purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)]) + + action = { + 'res_model': 'purchase.order', + 'type': 'ir.actions.act_window', + } + if len(purchase_order_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': purchase_order_ids[0].id, + }) + else: + action.update({ + 'name': _("子·采购订单"), + 'domain': [('id', 'in', purchase_order_ids.ids)], + 'view_mode': 'tree,form', + }) + return action + + def get_purchase_request_order(self): + name_list = self._get_pinking_name() + purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)]) + purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)]) + return purchase_request_ids, purchase_order_ids + + def _get_pinking_name(self): + return [picking_id.name for picking_id in self.picking_ids if picking_id.name] def button_confirm(self): res = super(PurchaseOrder, self).button_confirm() diff --git a/jikimo_purchase_request/models/purchase_request.py b/jikimo_purchase_request/models/purchase_request.py index 7287cb6a..3820b1ac 100644 --- a/jikimo_purchase_request/models/purchase_request.py +++ b/jikimo_purchase_request/models/purchase_request.py @@ -16,6 +16,7 @@ class PurchaseRequest(models.Model): ) rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True) + rule_purchase_to_request = fields.Boolean('采购单根据规则创建坯料采购申请', default=False) @api.depends('state') def _compute_state(self): diff --git a/jikimo_purchase_request/models/stock_picking.py b/jikimo_purchase_request/models/stock_picking.py index 97b294f3..037484c7 100644 --- a/jikimo_purchase_request/models/stock_picking.py +++ b/jikimo_purchase_request/models/stock_picking.py @@ -44,4 +44,15 @@ class StockPicking(models.Model): purchase_request_lines.move_dest_ids = [ (4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id') ] - return res \ No newline at end of file + return res + + def _subcontracted_produce(self, subcontract_details): + super()._subcontracted_produce(subcontract_details) + + # 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved + if self: + pr_ids = self.env["purchase.request"].sudo().search( + [('origin', 'like', self.name), ('rule_purchase_to_request', '=', True), ('state', '=', 'draft')]) + if pr_ids: + pr_ids.write({'need_validation': False}) + pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False}) diff --git a/jikimo_purchase_request/models/stock_rule.py b/jikimo_purchase_request/models/stock_rule.py index 4a99e804..a6d3d893 100644 --- a/jikimo_purchase_request/models/stock_rule.py +++ b/jikimo_purchase_request/models/stock_rule.py @@ -26,7 +26,7 @@ class StockRule(models.Model): request_data = rule._prepare_purchase_request( procurement.origin, procurement.values ) - request_data.update({'rule_new_add': True}) + request_data = self._update_request_data(procurement, request_data) pr = purchase_request_model.create(request_data) cache[domain] = pr elif ( @@ -44,6 +44,18 @@ class StockRule(models.Model): request_line_data.update({'origin': procurement.origin}) purchase_request_line_model.create(request_line_data) + def _update_request_data(self, procurement, request_data): + sp = self.env['stock.picking'].sudo().search([('name', '=', procurement.origin)]) + if len(sp) == 1: + po = self.env['purchase.order'].sudo().search( + [('name', '=', sp.origin), ('purchase_type', '=', 'outsourcing')]) + if po: + request_data.update({'rule_purchase_to_request': True}) + else: + request_data.update({'rule_new_add': True}) + return request_data + + def _run_buy(self, procurements): # 如果补货组相同,并且产品相同,则合并 procurements_dict = defaultdict() diff --git a/jikimo_purchase_request/views/purchase_order.xml b/jikimo_purchase_request/views/purchase_order.xml new file mode 100644 index 00000000..78f5b6a8 --- /dev/null +++ b/jikimo_purchase_request/views/purchase_order.xml @@ -0,0 +1,28 @@ + + + + purchase.order.inherited.form.jikimo.purchase.request + purchase.order + + + + + + + + + \ No newline at end of file diff --git a/jikimo_purchase_request/views/purchase_request_view.xml b/jikimo_purchase_request/views/purchase_request_view.xml index 151152e8..f793eaaa 100644 --- a/jikimo_purchase_request/views/purchase_request_view.xml +++ b/jikimo_purchase_request/views/purchase_request_view.xml @@ -67,6 +67,16 @@ + +
+
+
diff --git a/sf_demand_plan/__manifest__.py b/sf_demand_plan/__manifest__.py index 40323c5f..4de23ac4 100644 --- a/sf_demand_plan/__manifest__.py +++ b/sf_demand_plan/__manifest__.py @@ -14,9 +14,13 @@ 'data': [ 'security/ir.model.access.csv', 'data/stock_route_group.xml', +<<<<<<< HEAD 'views/sale_order_views.xml', 'views/demand_plan.xml', 'views/demand_main_plan.xml', +======= + 'views/demand_plan.xml', +>>>>>>> develop 'views/stock_route.xml', 'wizard/sf_demand_plan_print_wizard_view.xml', 'wizard/sf_release_plan_wizard_views.xml', diff --git a/sf_demand_plan/models/__init__.py b/sf_demand_plan/models/__init__.py index 0f8324dc..29e60c3c 100644 --- a/sf_demand_plan/models/__init__.py +++ b/sf_demand_plan/models/__init__.py @@ -7,4 +7,5 @@ from . import sale_order from . import stock_route from . import stock_rule from . import purchase_request -from . import purchase_order \ No newline at end of file +from . import purchase_order + diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index d772f41f..d292a180 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -94,11 +94,17 @@ class SfProductionDemandPlan(models.Model): model_long = fields.Char('尺寸(mm)', compute='_compute_model_long') blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类', related='product_id.blank_type') +<<<<<<< HEAD #blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型', related='product_id.blank_precision') #materials_id = fields.Char('材料', compute='_compute_materials_id', store=True) embryo_long = fields.Char('坯料尺寸(mm)', related='demand_plan_id.embryo_long') materials_id = fields.Char('材料', related='demand_plan_id.materials_id') +======= + blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型', related='product_id.blank_precision') + embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long') + materials_id = fields.Char('材料', compute='_compute_materials_id', store=True) +>>>>>>> develop model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度', related='product_id.model_machining_precision') #model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', @@ -112,6 +118,7 @@ class SfProductionDemandPlan(models.Model): product_remark = fields.Char("产品备注", related='product_id.model_remark') order_code = fields.Char('E-SHOP订单号', related='sale_order_id.order_code') +<<<<<<< HEAD order_state = fields.Selection( string='订单状态',related='sale_order_line_id.state') #route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True) @@ -119,6 +126,15 @@ class SfProductionDemandPlan(models.Model): domain=[('demand_plan_selectable', '=', True)], compute='_compute_route_ids', store=True) +======= + order_state = fields.Selection( + string='订单状态', + related='sale_order_line_id.state') + route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True) + route_ids = fields.Many2many('stock.route', 'stock_route_demand_plan', 'demand_plan_id', 'route_id', '路线', + domain=[('demand_plan_selectable', '=', True)], compute='_compute_route_ids', + store=True) +>>>>>>> develop contract_date = fields.Date('合同日期', related='sale_order_id.contract_date') date_order = fields.Datetime('下单日期', related='sale_order_id.date_order') contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True) @@ -188,6 +204,73 @@ class SfProductionDemandPlan(models.Model): continue pdp.route_ids = None +<<<<<<< HEAD +======= + hide_action_stock_picking = fields.Boolean( + string='显示调拨单按钮', + compute='_compute_hide_action_stock_picking', + default=False + ) + + hide_action_outsourcing_stock_picking = fields.Boolean( + string='委外显示调拨单按钮', + compute='_compute_hide_action_stock_picking', + default=False + ) + + hide_action_view_programming = fields.Boolean( + string='显示编程单按钮', + compute='_compute_hid_button', + default=False + ) + + outsourcing_purchase_request = fields.Char('委外采购申请单') + + @api.depends('supply_method') + def _compute_route_ids(self): + for pdp in self: + if pdp.supply_method: + group_id = self.env['stock.route.group'].sudo().search([('code', '=', pdp.supply_method)]) + route_ids = self.env['stock.route'].sudo().search( + [('demand_plan_selectable', '=', True), ('stock_route_group_ids', '=', group_id.id)]) + if route_ids: + pdp.route_ids = route_ids.ids + break + pdp.route_ids = None + + @api.depends('sale_order_id.state', 'sale_order_id.mrp_production_ids.schedule_state', 'sale_order_id.order_line', + 'sale_order_id.mrp_production_ids.state') + def _compute_status(self): + for record in self: + if record.sale_order_id: + sale_order_state = record.sale_order_id.state + if sale_order_state in ('draft', 'sent', 'supply method'): + record.status = '20' # 待确认 + if record.supply_method in ('purchase', 'outsourcing') and sale_order_state in ( + 'sale', 'processing', 'physical_distribution', 'delivered', + 'done') and sale_order_state != 'cancel': + record.status = '60' # 已下达 + if record.supply_method in ('automation', 'manual'): + if sale_order_state in ( + 'sale', 'processing', 'physical_distribution', 'delivered', + 'done') and sale_order_state != 'cancel': + record.status = '30' # 需求确认 + # 检查所有制造订单的排程单状态,有一个为待排程状态,就为待下达生产 + pending_productions = record.sale_order_id.mrp_production_ids.filtered( + lambda p: p.state == 'confirmed' and p.product_id.id == record.product_id.id + ) + if pending_productions: + record.status = '50' # 待下达生产 + # 检查所有制造订单的排程单状态 + if record.sale_order_id.mrp_production_ids: + product_productions = record.sale_order_id.mrp_production_ids.filtered( + lambda p: p.product_id.id == record.product_id.id + ) + if product_productions and all(order.schedule_state != '未排' for order in product_productions): + record.status = '60' # 已下达 + if sale_order_state == 'cancel' or not record.sale_order_line_id: + record.status = '100' # 取消 +>>>>>>> develop @api.depends('sale_order_line_id.product_id.name') def _compute_sale_order_line_number(self): @@ -205,6 +288,57 @@ class SfProductionDemandPlan(models.Model): else: line.model_long = None +<<<<<<< HEAD +======= + @api.depends('product_id.model_long', 'product_id.model_width', 'product_id.model_height', 'product_id.blank_type') + def _compute_embryo_long(self): + for line in self: + if line.product_id: + if line.product_id.blank_type == '圆料': + line.embryo_long = f"Ø{round(line.product_id.model_width, 3)}*{round(line.product_id.model_long, 3)}" + else: + line.embryo_long = f"{round(line.product_id.model_long, 3)}*{round(line.product_id.model_width, 3)}*{round(line.product_id.model_height, 3)}" + else: + line.embryo_long = None + + @api.depends('product_id.materials_id') + def _compute_materials_id(self): + for line in self: + if line.product_id: + line.materials_id = f"{line.product_id.materials_id.name}/{line.product_id.materials_type_id.name}" + else: + line.materials_id = None + + @api.depends('product_id.model_process_parameters_ids') + def _compute_model_process_parameters_ids(self): + for line in self: + if line.product_id and line.product_id.model_process_parameters_ids: + line.model_process_parameters_ids = [(6, 0, line.product_id.model_process_parameters_ids.ids)] + else: + line.model_process_parameters_ids = [(5, 0, 0)] + + def _compute_inventory_quantity_auto_apply(self): + location_id = self.env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id + product_ids = self.mapped('product_id').ids + if product_ids: + quant_data = self.env['stock.quant'].read_group( + domain=[ + ('product_id', 'in', product_ids), + ('location_id', '=', location_id) + ], + fields=['product_id', 'inventory_quantity_auto_apply'], + groupby=['product_id'] + ) + quantity_map = {item['product_id'][0]: item['inventory_quantity_auto_apply'] for item in quant_data} + else: + quantity_map = {} + for line in self: + if line.product_id: + line.inventory_quantity_auto_apply = quantity_map.get(line.product_id.id, 0.0) + else: + line.inventory_quantity_auto_apply = 0.0 + +>>>>>>> develop @api.depends('sale_order_id.mrp_production_ids.workorder_ids.date_start') def _compute_actual_start_date(self): for record in self: @@ -312,6 +446,7 @@ class SfProductionDemandPlan(models.Model): for pro_plan in pro_plan_list: pro_plan.do_production_schedule() +<<<<<<< HEAD def update_sale_order_state(self): demand_plan = self.env['sf.demand.main.plan'].sudo().search([('sale_order_id', '=', self.sale_order_id.id)]) demand_plan_state = demand_plan.filtered(lambda line: line.state != '40') @@ -319,6 +454,8 @@ class SfProductionDemandPlan(models.Model): # 修改销售订单为加工中 self.sale_order_id.state = 'processing' +======= +>>>>>>> develop def button_action_print(self): return { 'res_model': 'sf.demand.plan.print.wizard', diff --git a/sf_demand_plan/models/stock_route.py b/sf_demand_plan/models/stock_route.py index 7719d7b1..2fa69958 100644 --- a/sf_demand_plan/models/stock_route.py +++ b/sf_demand_plan/models/stock_route.py @@ -19,7 +19,11 @@ class SfStockRoute(models.Model): [('supply_method', 'in', stock_route_group)]) if demand_plan_ids: sr.demand_plan_ids = demand_plan_ids.ids +<<<<<<< HEAD continue +======= + break +>>>>>>> develop sr.demand_plan_ids = None # def name_get(self): diff --git a/sf_demand_plan/security/ir.model.access.csv b/sf_demand_plan/security/ir.model.access.csv index 5bb41d0d..5509f856 100644 --- a/sf_demand_plan/security/ir.model.access.csv +++ b/sf_demand_plan/security/ir.model.access.csv @@ -5,6 +5,7 @@ access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for disp access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0 access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0 +<<<<<<< HEAD access_sf_demand_main_plan,sf.demand.main.plan,model_sf_demand_main_plan,base.group_user,1,0,0,0 access_sf_demand_main_plan_for_dispatch,sf.demand.main.plan for dispatch,model_sf_demand_main_plan,sf_base.group_plan_dispatch,1,1,0,0 @@ -12,4 +13,8 @@ access_stock_route_group,stock.route.group,model_stock_route_group,base.group_us access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0 access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0 -access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1 \ No newline at end of file +access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1 +======= +access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0 +access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0 +>>>>>>> develop diff --git a/sf_demand_plan/static/src/scss/style.css b/sf_demand_plan/static/src/scss/style.css index 9662a10a..08d382c1 100644 --- a/sf_demand_plan/static/src/scss/style.css +++ b/sf_demand_plan/static/src/scss/style.css @@ -81,4 +81,9 @@ input,label { cursor: pointer; } -} \ No newline at end of file +} + +th[data-name=processing_time] + th::before{ + content: '待执行单据'; + line-height: 38px; +} diff --git a/sf_demand_plan/views/demand_plan.xml b/sf_demand_plan/views/demand_plan.xml index c602031b..a18dc596 100644 --- a/sf_demand_plan/views/demand_plan.xml +++ b/sf_demand_plan/views/demand_plan.xml @@ -28,18 +28,13 @@ - + - - - - @@ -66,6 +61,7 @@ + @@ -98,7 +94,8 @@ - + =', time_48_hours_ago.strftime('%Y-%m-%d %H:%M:%S')), - ('tracking_value_ids.field_desc', '=', '状态'), - ('tracking_value_ids.new_value_char', 'in', ['就绪', '生产中']) - ]) + # # 计划量,目前只能从mail.message中筛选出 + # plan_order_messages = request.env['mail.message'].sudo().search([ + # ('model', '=', 'mrp.workorder'), + # ('create_date', '>=', time_48_hours_ago.strftime('%Y-%m-%d %H:%M:%S')), + # ('tracking_value_ids.field_desc', '=', '状态'), + # ('tracking_value_ids.new_value_char', 'in', ['就绪', '生产中']) + # ]) for line in line_list: + not_done_data = [] + done_data = [] + not_done_index = 1 + done_index = 1 if line == '业绩总览': work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] @@ -904,17 +910,12 @@ class Sf_Dashboard_Connect(http.Controller): # [('production_line_id.name', '=', line), ('state', 'not in', ['finished']), # ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True) # ]) - # not_done_orders = work_order_obj.search(work_order_domain + - # [('state', 'in', ['ready', 'progress'])], order='id asc' - # ) - not_done_orders = request.env['mrp.workorder'].sudo().browse(plan_order_messages.mapped('res_id')) - if line == '业绩总览': - not_done_orders = not_done_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工']) - elif line == '人工线下加工中心': - not_done_orders = not_done_orders.filtered(lambda o: o.routing_type == '人工线下加工') - else: - not_done_orders = not_done_orders.filtered(lambda o: o.routing_type == 'CNC加工' and o.production_line_id.name == line) - + not_done_orders = work_order_obj.search(work_order_domain + [ + ('state', 'in', ['ready', 'progress']), + ('date_planned_start', '>=', time_48_hours_ago), + ('date_planned_start', '<=', current_time) + ], order='id asc' + ) # 完成订单 # 获取当前时间,并计算24小时前的时间 @@ -922,11 +923,11 @@ class Sf_Dashboard_Connect(http.Controller): # time_24_hours_ago = current_time - timedelta(hours=24) finish_orders = work_order_obj.search(work_order_domain + [ - ('state', 'in', ['finished']), + ('state', 'in', ['done']), ('production_id.state', 'not in', ['cancel']), ('date_finished', '>=', time_48_hours_ago) ], order='id asc') - # print(finish_orders) + # logging.info('完成订单: %s' % finish_orders) # 获取所有未完成订单的ID列表 order_ids = [order.id for order in not_done_orders] @@ -962,14 +963,6 @@ class Sf_Dashboard_Connect(http.Controller): material_match = re.search(material_pattern, blank_name) material = material_match.group(1) if material_match else 'No match found' - state_dict = { - 'draft': '待排程', - 'done': '已排程', - 'processing': '生产中', - 'finished': '已完成', - 'ready': '待加工', - 'progress': '生产中', - } line_dict = { 'sequence': not_done_index, @@ -985,8 +978,6 @@ class Sf_Dashboard_Connect(http.Controller): not_done_index += 1 for finish_order in finish_orders: - if not finish_order.actual_end_time: - continue blank_name = '' try: blank_name = finish_order.production_id.move_raw_ids[0].product_id.name @@ -1002,13 +993,13 @@ class Sf_Dashboard_Connect(http.Controller): line_dict = { 'sequence': done_index, - 'workorder_name': finish_order.name, + 'workorder_name': finish_order.production_id.name, 'blank_name': blank_name, 'material': material, 'dimensions': dimensions, - 'order_qty': order.qty_produced, - 'finish_time': finish_order.actual_end_time.strftime( - '%Y-%m-%d %H:%M:%S') if finish_order.actual_end_time else ' ' + 'order_qty': finish_order.qty_produced, + 'finish_time': finish_order.date_finished.strftime( + '%Y-%m-%d %H:%M:%S') if finish_order.date_finished else ' ' } done_data.append(line_dict) diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 2d0545af..cf196ad6 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -1710,76 +1710,7 @@ class MrpProduction(models.Model): else: vals['procurement_group_id'] = is_custemer_group_id[key] - productions = super(MrpProduction, self).create(vals_list) - - # 查询成品工序排序(自动化产线加工),供后续使用 - product_model_type_routing_sorts = self.env['sf.product.model.type.routing.sort'].search([], order='sequence asc') - # 查询成品工序排序(人工线下加工),供后续使用 - manual_product_model_type_routing_sorts = self.env['sf.manual.product.model.type.routing.sort'].search([], order='sequence asc') - # 查询坯料工序排序,供后续使用 - embryo_model_type_routing_sorts = self.env['sf.embryo.model.type.routing.sort'].search([], order='sequence asc') - - for production in productions: - # 生成序列号 - production.action_generate_serial() - # 创建工序模板 - technology_design_values = [] - i = 0 - if production.product_id.categ_id.type == '成品': - # 根据加工面板的面数及成品工序模板生成工序设计 - if production.production_type == '自动化产线加工': - product_routing_workcenter = product_model_type_routing_sorts.filtered( - lambda s: s.product_model_type_id.id == production.product_id.product_model_type_id.id - ) - else: - product_routing_workcenter = manual_product_model_type_routing_sorts.filtered( - lambda s: s.manual_product_model_type_id.id == production.product_id.product_model_type_id.id - ) - if production.production_type == '自动化产线加工': - for k in (production.product_id.model_processing_panel.split(',')): - for route in product_routing_workcenter: - i += 1 - technology_design_values.append( - self.env['sf.technology.design'].json_technology_design_str(k, route, i, False) - ) - elif production.production_type == '人工线下加工': - for route in product_routing_workcenter: - i += 1 - technology_design_values.append( - self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False) - ) - else: - for route in product_routing_workcenter: - i += 1 - technology_design_values.append( - self.env['sf.technology.design'].json_technology_design_str(False, route, i, False) - ) - elif production.product_id.categ_id.type == '坯料': - embryo_routing_workcenter = embryo_model_type_routing_sorts.filtered( - lambda s: s.embryo_model_type_id.id == production.product_id.embryo_model_type_id.id - ) - for route_embryo in embryo_routing_workcenter: - i += 1 - technology_design_values.append( - self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, False) - ) - # 处理表面工艺 - for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: - if item.route_workcenter_id.surface_technics_id.id: - for process_param in production.product_id.model_process_parameters_ids: - if item.route_workcenter_id.surface_technics_id == process_param.process_id: - technology_design_values.append( - self.env['sf.technology.design'].json_technology_design_str( - False, - item.route_workcenter_id, - i, - process_param - ) - ) - production.technology_design_ids = technology_design_values - # 设置制造订单状态为待工艺确认 - productions.write({'state': 'technology_to_confirmed'}) - return productions + return super(MrpProduction, self).create(vals_list) @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', 'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id') diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index 4062814e..584399dc 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -27,13 +27,15 @@ class ResProductMo(models.Model): categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True) model_name = fields.Char('模型名称') blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类') + blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型') model_long = fields.Float('模型长(mm)', digits=(16, 3)) model_width = fields.Float('模型宽(mm)', digits=(16, 3)) model_height = fields.Float('模型高(mm)', digits=(16, 3)) + unit_number = fields.Float('单件用量', digits=(16, 3), default=1) model_volume = fields.Float('模型体积(m³)') model_area = fields.Float('模型表面积(m²)') model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度') - model_processing_panel = fields.Char('模型加工面板') + model_processing_panel = fields.Char('模型加工面板', default='') model_remark = fields.Char('模型备注说明') length = fields.Float('长(mm)', digits=(16, 3)) width = fields.Float('宽(mm)', digits=(16, 3)) @@ -901,15 +903,17 @@ class ResProductMo(models.Model): vals = { 'name': product_name, 'blank_type': item.get('blank_type'), + 'blank_precision': item.get('blank_precision'), 'model_long': item.get('blank_length') if blank_bool else self.format_float(item['model_long'] + embryo_redundancy_id.long), 'model_width': item.get('blank_width') if blank_bool else self.format_float(item['model_width'] + embryo_redundancy_id.width), 'model_height': item.get('blank_height') if blank_bool else self.format_float(item['model_height'] + embryo_redundancy_id.height), + 'unit_number': item.get('unit_number'), 'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) * (item['model_width'] + embryo_redundancy_id.width) * (item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else ( item.get('blank_length') * item.get('blank_width') * item.get('blank_height')), 'product_model_type_id': model_type.id, - 'model_processing_panel': item['processing_panel_detail'], + 'model_processing_panel': item['processing_panel_detail'] if item['processing_panel_detail'] else '', 'model_machining_precision': item['model_machining_precision'], 'model_code': item['barcode'], 'length': item['model_long'], @@ -957,7 +961,7 @@ class ResProductMo(models.Model): self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id, 'quality_standard', item['quality_standard_mimetype']) return copy_product_id - + def format_float(self, value): # 将浮点数转换为字符串 value_str = str(value) diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 4e8dc438..ba6e9d20 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -21,6 +21,7 @@ from odoo.addons.sf_base.commons.common import Common from odoo.exceptions import UserError from io import BytesIO from odoo.exceptions import ValidationError +from dateutil.relativedelta import relativedelta class stockWarehouse(models.Model): @@ -163,203 +164,203 @@ class StockRule(models.Model): [('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1) attachment_info.write({'name': name}) - # @api.model - # def _run_manufacture(self, procurements): - # productions_values_by_company = defaultdict(list) - # errors = [] - # for procurement, rule in procurements: - # if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0: - # # If procurement contains negative quantity, don't create a MO that would be for a negative value. - # continue - # bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values) + @api.model + def _run_manufacture(self, procurements): + productions_values_by_company = defaultdict(list) + errors = [] + for procurement, rule in procurements: + if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0: + # If procurement contains negative quantity, don't create a MO that would be for a negative value. + continue + bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values) - # productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom)) + productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom)) - # if errors: - # raise ProcurementException(errors) + if errors: + raise ProcurementException(errors) - # for company_id, productions_values in productions_values_by_company.items(): - # # create the MO as SUPERUSER because the current user may not have the rights to do it - # # (mto product launched by a sale for example) - # '''创建制造订单''' - # productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( - # productions_values) + for company_id, productions_values in productions_values_by_company.items(): + # create the MO as SUPERUSER because the current user may not have the rights to do it + # (mto product launched by a sale for example) + '''创建制造订单''' + productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( + productions_values) - # ''' - # 创建工单 - # ''' - # # productions._create_workorder() - # # - # # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) - # productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \ - # ( - # p.move_dest_ids.procure_method != 'make_to_order' and not - # p.move_raw_ids and not p.workorder_ids)).action_confirm() - # # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区 - # for production in productions: - # if production.picking_ids: - # product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id - # if product_type_id.name == '坯料': - # location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) - # if not location_id: - # logging.info(f'没有搜索到【坯料存货区】: {location_id}') - # break - # for picking_id in production.picking_ids: - # if picking_id.picking_type_id.name == '内部调拨': - # if picking_id.location_dest_id.product_type != product_type_id: - # picking_id.location_dest_id = location_id.id - # elif picking_id.picking_type_id.name == '生产发料': - # if picking_id.location_id.product_type != product_type_id: - # picking_id.location_id = location_id.id + ''' + 创建工单 + ''' + # productions._create_workorder() + # + # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) + productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \ + ( + p.move_dest_ids.procure_method != 'make_to_order' and not + p.move_raw_ids and not p.workorder_ids)).action_confirm() + # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区 + for production in productions: + if production.picking_ids: + product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id + if product_type_id.name == '坯料': + location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) + if not location_id: + logging.info(f'没有搜索到【坯料存货区】: {location_id}') + break + for picking_id in production.picking_ids: + if picking_id.picking_type_id.name == '内部调拨': + if picking_id.location_dest_id.product_type != product_type_id: + picking_id.location_dest_id = location_id.id + elif picking_id.picking_type_id.name == '生产发料': + if picking_id.location_id.product_type != product_type_id: + picking_id.location_id = location_id.id - # for production in productions: - # ''' - # 创建制造订单时生成序列号 - # ''' - # # production.action_generate_serial() - # origin_production = production.move_dest_ids and production.move_dest_ids[ - # 0].raw_material_production_id or False - # orderpoint = production.orderpoint_id - # if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual': - # production.message_post( - # body=_('This production order has been created from Replenishment Report.'), - # message_type='comment', - # subtype_xmlid='mail.mt_note') - # elif orderpoint: - # production.message_post_with_view( - # 'mail.message_origin_link', - # values={'self': production, 'origin': orderpoint}, - # subtype_id=self.env.ref('mail.mt_note').id) - # elif origin_production: - # production.message_post_with_view( - # 'mail.message_origin_link', - # values={'self': production, 'origin': origin_production}, - # subtype_id=self.env.ref('mail.mt_note').id) + for production in productions: + ''' + 创建制造订单时生成序列号 + ''' + production.action_generate_serial() + origin_production = production.move_dest_ids and production.move_dest_ids[ + 0].raw_material_production_id or False + orderpoint = production.orderpoint_id + if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual': + production.message_post( + body=_('This production order has been created from Replenishment Report.'), + message_type='comment', + subtype_xmlid='mail.mt_note') + elif orderpoint: + production.message_post_with_view( + 'mail.message_origin_link', + values={'self': production, 'origin': orderpoint}, + subtype_id=self.env.ref('mail.mt_note').id) + elif origin_production: + production.message_post_with_view( + 'mail.message_origin_link', + values={'self': production, 'origin': origin_production}, + subtype_id=self.env.ref('mail.mt_note').id) - # ''' - # 创建生产计划 - # ''' - # # 工单耗时 - # # workorder_duration = 0 - # # for workorder in production.workorder_ids: - # # workorder_duration += workorder.duration_expected + ''' + 创建生产计划 + ''' + # 工单耗时 + workorder_duration = 0 + for workorder in production.workorder_ids: + workorder_duration += workorder.duration_expected - # # sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) - # # # 如果订单为空,则获取来源制造订单的销售单 - # # if not sale_order: - # # mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)], - # # limit=1) - # # if mrp_production: - # # sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)]) - # # else: - # # mrp_production = production - # # # if sale_order: - # # # sale_order.write({'schedule_status': 'to schedule'}) - # # self.env['sf.production.plan'].sudo().with_company(company_id).create({ - # # 'name': production.name, - # # 'order_deadline': sale_order.deadline_of_delivery, - # # 'production_id': production.id, - # # 'date_planned_start': production.date_planned_start, - # # 'origin': mrp_production.origin, - # # 'product_qty': production.product_qty, - # # 'product_id': production.product_id.id, - # # 'state': 'draft', - # # }) - # all_production = productions - # grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)} - # # 初始化一个字典来存储每个product_id对应的生产订单名称列表 - # product_id_to_production_names = {} - # # 对于每个product_id,获取其所有生产订单的名称 - # for product_id, all_production in grouped_product_ids.items(): - # # 为同一个product_id创建一个生产订单名称列表 - # product_id_to_production_names[product_id] = [production.name for production in all_production] - # for production_item in productions: - # technology_design_values = [] - # # production_programming = self.env['mrp.production'].search( - # # [('product_id.id', '=', production_item.product_id.id), - # # ('origin', '=', production_item.origin)], - # # limit=1, order='id asc') - # # if production_item.product_id.id in product_id_to_production_names: - # # # 同一个产品多个制造订单对应一个编程单和模型库 - # # # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 - # # if not production_item.programming_no and production_item.production_type in ['自动化产线加工', - # # '人工线下加工']: - # # if not production_programming.programming_no: - # # production_item.fetchCNC( - # # ', '.join(product_id_to_production_names[production_item.product_id.id])) - # # else: - # # production_item.write({'programming_no': production_programming.programming_no, - # # 'programming_state': '编程中'}) - # i = 0 - # if production_item.product_id.categ_id.type == '成品': - # # 根据加工面板的面数及成品工序模板生成工序设计 - # if production_item.production_type == '自动化产线加工': - # model = 'sf.product.model.type.routing.sort' - # domain = [ - # ('product_model_type_id', '=', production_item.product_id.product_model_type_id.id)] - # else: - # model = 'sf.manual.product.model.type.routing.sort' - # domain = [('manual_product_model_type_id', '=', - # production_item.product_id.product_model_type_id.id)] - # product_routing_workcenter = self.env[model].search(domain, order='sequence asc') - # if production_item.production_type == '自动化产线加工': - # for k in (production_item.product_id.model_processing_panel.split(',')): - # for route in product_routing_workcenter: - # i += 1 - # technology_design_values.append( - # self.env['sf.technology.design'].json_technology_design_str(k, route, i, False)) - # elif production_item.production_type == '人工线下加工': - # for route in product_routing_workcenter: - # i += 1 - # technology_design_values.append( - # self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False)) - # else: - # for route in product_routing_workcenter: - # i += 1 - # technology_design_values.append( - # self.env['sf.technology.design'].json_technology_design_str(False, route, i, False)) - # elif production_item.product_id.categ_id.type == '坯料': - # embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search( - # [('embryo_model_type_id', '=', production_item.product_id.embryo_model_type_id.id)], - # order='sequence asc' - # ) - # for route_embryo in embryo_routing_workcenter: - # i += 1 - # technology_design_values.append( - # self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, - # False)) - # surface_technics_arr = [] - # route_workcenter_arr = [] - # for item in production_item.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: - # if item.route_workcenter_id.surface_technics_id.id: - # for process_param in production_item.product_id.model_process_parameters_ids: - # if item.route_workcenter_id.surface_technics_id == process_param.process_id: - # surface_technics_arr.append( - # item.route_workcenter_id.surface_technics_id.id) - # route_workcenter_arr.append(item.route_workcenter_id.id) - # if surface_technics_arr: - # production_process = self.env['sf.production.process'].search( - # [('id', 'in', surface_technics_arr)], - # order='sequence asc' - # ) - # for p in production_process: - # logging.info('production_process:%s' % p.name) - # process_parameters = production_item.product_id.model_process_parameters_ids.filtered( - # lambda pm: pm.process_id.id == p.id) - # for process_parameter in process_parameters: - # i += 1 - # route_production_process = self.env[ - # 'mrp.routing.workcenter'].search( - # [('surface_technics_id', '=', p.id), - # ('id', 'in', route_workcenter_arr)]) - # technology_design_values.append( - # self.env['sf.technology.design'].json_technology_design_str(False, - # route_production_process, - # i, - # process_parameter)) - # production_item.technology_design_ids = technology_design_values - # productions.write({'state': 'technology_to_confirmed'}) - # return True + sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) + # 如果订单为空,则获取来源制造订单的销售单 + if not sale_order: + mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)], + limit=1) + if mrp_production: + sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)]) + else: + mrp_production = production + # if sale_order: + # sale_order.write({'schedule_status': 'to schedule'}) + self.env['sf.production.plan'].sudo().with_company(company_id).create({ + 'name': production.name, + 'order_deadline': sale_order.deadline_of_delivery, + 'production_id': production.id, + 'date_planned_start': production.date_planned_start, + 'origin': mrp_production.origin, + 'product_qty': production.product_qty, + 'product_id': production.product_id.id, + 'state': 'draft', + }) + all_production = productions + grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)} + # 初始化一个字典来存储每个product_id对应的生产订单名称列表 + product_id_to_production_names = {} + # 对于每个product_id,获取其所有生产订单的名称 + for product_id, all_production in grouped_product_ids.items(): + # 为同一个product_id创建一个生产订单名称列表 + product_id_to_production_names[product_id] = [production.name for production in all_production] + for production_item in productions: + technology_design_values = [] + production_programming = self.env['mrp.production'].search( + [('product_id.id', '=', production_item.product_id.id), + ('origin', '=', production_item.origin)], + limit=1, order='id asc') + if production_item.product_id.id in product_id_to_production_names: + # 同一个产品多个制造订单对应一个编程单和模型库 + # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 + if not production_item.programming_no and production_item.production_type in ['自动化产线加工', + '人工线下加工']: + if not production_programming.programming_no: + production_item.fetchCNC( + ', '.join(product_id_to_production_names[production_item.product_id.id])) + else: + production_item.write({'programming_no': production_programming.programming_no, + 'programming_state': '编程中'}) + i = 0 + if production_item.product_id.categ_id.type == '成品': + # 根据加工面板的面数及成品工序模板生成工序设计 + if production_item.production_type == '自动化产线加工': + model = 'sf.product.model.type.routing.sort' + domain = [ + ('product_model_type_id', '=', production_item.product_id.product_model_type_id.id)] + else: + model = 'sf.manual.product.model.type.routing.sort' + domain = [('manual_product_model_type_id', '=', + production_item.product_id.product_model_type_id.id)] + product_routing_workcenter = self.env[model].search(domain, order='sequence asc') + if production_item.production_type == '自动化产线加工': + for k in (production_item.product_id.model_processing_panel.split(',')): + for route in product_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(k, route, i, False)) + elif production_item.production_type == '人工线下加工': + for route in product_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False)) + else: + for route in product_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(False, route, i, False)) + elif production_item.product_id.categ_id.type == '坯料': + embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search( + [('embryo_model_type_id', '=', production_item.product_id.embryo_model_type_id.id)], + order='sequence asc' + ) + for route_embryo in embryo_routing_workcenter: + i += 1 + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, + False)) + surface_technics_arr = [] + route_workcenter_arr = [] + for item in production_item.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: + if item.route_workcenter_id.surface_technics_id.id: + for process_param in production_item.product_id.model_process_parameters_ids: + if item.route_workcenter_id.surface_technics_id == process_param.process_id: + surface_technics_arr.append( + item.route_workcenter_id.surface_technics_id.id) + route_workcenter_arr.append(item.route_workcenter_id.id) + if surface_technics_arr: + production_process = self.env['sf.production.process'].search( + [('id', 'in', surface_technics_arr)], + order='sequence asc' + ) + for p in production_process: + logging.info('production_process:%s' % p.name) + process_parameters = production_item.product_id.model_process_parameters_ids.filtered( + lambda pm: pm.process_id.id == p.id) + for process_parameter in process_parameters: + i += 1 + route_production_process = self.env[ + 'mrp.routing.workcenter'].search( + [('surface_technics_id', '=', p.id), + ('id', 'in', route_workcenter_arr)]) + technology_design_values.append( + self.env['sf.technology.design'].json_technology_design_str(False, + route_production_process, + i, + process_parameter)) + production_item.technology_design_ids = technology_design_values + productions.write({'state': 'technology_to_confirmed'}) + return True class ProductionLot(models.Model): @@ -727,6 +728,33 @@ class StockPicking(models.Model): production.workorder_ids.write({'back_button_display': False}) return res + def _prepare_subcontract_mo_vals(self, subcontract_move, bom): + subcontract_move.ensure_one() + group = self.env['procurement.group'].sudo().search([('name', '=', self.name)]) + if not group: + group = self.env['procurement.group'].create({ + 'name': self.name, + 'partner_id': self.partner_id.id, + }) + product = subcontract_move.product_id + warehouse = self._get_warehouse(subcontract_move) + vals = { + 'company_id': subcontract_move.company_id.id, + 'procurement_group_id': group.id, + 'subcontractor_id': subcontract_move.picking_id.partner_id.commercial_partner_id.id, + 'picking_ids': [subcontract_move.picking_id.id], + 'product_id': product.id, + 'product_uom_id': subcontract_move.product_uom.id, + 'bom_id': bom.id, + 'location_src_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id, + 'location_dest_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id, + 'product_qty': subcontract_move.product_uom_qty, + 'picking_type_id': warehouse.subcontracting_type_id.id, + 'date_planned_start': subcontract_move.date - relativedelta(days=product.produce_delay) + } + return vals + + # 创建 外协出库入单 def create_outcontract_picking(self, workorders, item, sorted_workorders): production = workorders[0].production_id diff --git a/sf_mrs_connect/models/__init__.py b/sf_mrs_connect/models/__init__.py index 6450f26f..01498b21 100644 --- a/sf_mrs_connect/models/__init__.py +++ b/sf_mrs_connect/models/__init__.py @@ -2,4 +2,3 @@ from . import ftp_operate from . import res_config_setting from . import sync_common from . import order_price -from . import mrp_production diff --git a/sf_mrs_connect/models/mrp_production.py b/sf_mrs_connect/models/mrp_production.py deleted file mode 100644 index ac3f6640..00000000 --- a/sf_mrs_connect/models/mrp_production.py +++ /dev/null @@ -1,44 +0,0 @@ -from itertools import groupby -from odoo import models, api -from odoo.tools.misc import OrderedSet - - -class MrpProduction(models.Model): - _inherit = 'mrp.production' - - - @api.model_create_multi - def create(self, vals_list): - """ - 生成编程单 - """ - productions = super().create(vals_list) - # 定义变量存储编程单 - grouped_product_programming_no = {} - # 定义产品拼接成的制造订单名称 - grouped_product_production_name = {} - # 查出所有的制造订单,为了适配通过补货生成的制造订单 - all_productions = self.env['mrp.production'].search([('origin', '=', productions[0].origin)]) - # 将不同产品的制造订单进行分组 - grouped_product_productions = {k: list(g) for k, g in groupby(all_productions, key=lambda x: x.product_id.id)} - - for product_id, grouped_productions in grouped_product_productions.items(): - # 产品对应的编程单号 - if product_id not in grouped_product_programming_no: - # 使用列表推导式获取非空的programming_no - programming_nos = [p.programming_no for p in grouped_productions if p.programming_no if p.programming_no is not False] - if programming_nos: - grouped_product_programming_no[product_id] = programming_nos[0] - grouped_product_production_name[product_id] = ','.join(list(map(lambda p:p.name, grouped_productions))) - # 同一个产品的制造订单只请求一次CNC编程 - for production in productions: - if not production.programming_no and production.production_type in ['自动化产线加工','人工线下加工']: - if production.product_id.id not in grouped_product_programming_no: - production.fetchCNC(grouped_product_production_name[production.product_id.id]) - grouped_product_programming_no[production.product_id.id] = production.programming_no - else: - production.write({ - 'programming_no': grouped_product_programming_no[production.product_id.id], - 'programming_state': '编程中' - }) - return productions \ No newline at end of file diff --git a/sf_plan/models/__init__.py b/sf_plan/models/__init__.py index 0eba6ec9..57dea5da 100644 --- a/sf_plan/models/__init__.py +++ b/sf_plan/models/__init__.py @@ -3,4 +3,3 @@ from . import custom_plan from . import change_manufactuing -from . import mrp_production diff --git a/sf_plan/models/mrp_production.py b/sf_plan/models/mrp_production.py deleted file mode 100644 index 961d5f35..00000000 --- a/sf_plan/models/mrp_production.py +++ /dev/null @@ -1,41 +0,0 @@ -from odoo import models, api - - -class MrpProduction(models.Model): - _inherit = 'mrp.production' - - @api.model_create_multi - def create(self, vals_list): - """ - 创建生产计划 - """ - productions = super().create(vals_list) - - for production in productions: - # 工单耗时 - workorder_duration = 0 - for workorder in production.workorder_ids: - workorder_duration += workorder.duration_expected - - sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) - # 如果订单为空,则获取来源制造订单的销售单 - if not sale_order: - mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)], - limit=1) - if mrp_production: - sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)]) - else: - mrp_production = production - # if sale_order: - # sale_order.write({'schedule_status': 'to schedule'}) - self.env['sf.production.plan'].sudo().with_company(production.company_id).create({ - 'name': production.name, - 'order_deadline': sale_order.deadline_of_delivery, - 'production_id': production.id, - 'date_planned_start': production.date_planned_start, - 'origin': mrp_production.origin, - 'product_qty': production.product_qty, - 'product_id': production.product_id.id, - 'state': 'draft', - }) - return productions \ No newline at end of file diff --git a/sf_quality/models/stock.py b/sf_quality/models/stock.py index add80f8a..7b70b77a 100644 --- a/sf_quality/models/stock.py +++ b/sf_quality/models/stock.py @@ -1,59 +1,180 @@ +import logging + from odoo import api, models +from odoo.exceptions import ValidationError, UserError class StockPicking(models.Model): _inherit = 'stock.picking' + def _compute_check(self): + super()._compute_check() + for picking in self: + picking_to_quality = picking.get_picking_to_quality() + if not picking_to_quality: + picking.quality_check_todo = False + break + else: + need_quality_line = picking.get_need_quality_line(picking_to_quality) + if not need_quality_line or all(not line.get('need_done_check_ids') for line in need_quality_line): + picking.quality_check_todo = False + + def check_quality(self): + self.ensure_one() + # checkable_products = self.mapped('move_line_ids').mapped('product_id') + # checks = self.check_ids.filtered(lambda check: check.quality_state == 'none' and ( + # check.product_id in checkable_products or check.measure_on == 'operation')) + checks = self.env['quality.check'] + picking_to_quality = self._get_picking_to_quality() + need_quality_line = self.get_need_quality_line(picking_to_quality) + if need_quality_line and any(line.get('need_done_check_ids') for line in need_quality_line): + for item in need_quality_line: + checks += item.get('need_done_check_ids') + if checks: + return checks.action_open_quality_check_wizard() + return False + def button_validate(self): - """ + """= 出厂检验报告上传 """ out_quality_checks = self.env['quality.check'].search( - [('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告')]) + [('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告'), + ('quality_state', '=', 'pass')]) # out_quality_checks 可能存在多个 if out_quality_checks: for out_quality_check in out_quality_checks: if not out_quality_check.is_factory_report_uploaded: if out_quality_check and self.state == 'assigned': out_quality_check.upload_factory_report() + quality_action = self.pinking_checkout_quality() + if quality_action: + return quality_action + res = super(StockPicking, self).button_validate() + return res + def pinking_checkout_quality(self): """ 调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示: “警告:存在不合格产品XXXX n 件、YYYYY m件,继续调拨请点“确认”,否则请取消?” """ - 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) - if all(check_id.measure_on == 'move_line' for check_id in check_ids): - number = sum(check_ids.mapped('qty_line')) - else: - number = sum(self.move_ids_without_package.filtered( - lambda ml: ml.product_id == product_id).mapped('quantity_done')) - if number == 0: - number = sum(self.move_ids_without_package.filtered( - lambda ml: ml.product_id == product_id).mapped('reserved_availability')) - if number == 0: - number = sum(self.move_ids_without_package.filtered( - lambda ml: ml.product_id == product_id).mapped('product_uom_qty')) - fail_check_text = (f'{fail_check_text}、{product_id.display_name} {number}件' - if fail_check_text != '' else f'{product_id.display_name} {number}件') - return { - 'type': 'ir.actions.act_window', - 'res_model': 'picking.validate.check.wizard', - 'name': '质检不合格提示', - 'view_mode': 'form', - 'target': 'new', - 'context': { - 'default_picking_id': self.id, - 'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?', - 'again_validate': True} - } - res = super(StockPicking, self).button_validate() + try: + self.ensure_one() + context = self.env.context + if not context.get('pinking_checkout_quality'): + picking_to_quality = self._get_picking_to_quality() + if not picking_to_quality: return False + need_quality_val = self.get_need_quality_line(picking_to_quality) + if any(line.get('fail_check_ids') for line in need_quality_val): + # 回滚事务,为二次确认/取消做准备 + self.env.cr.rollback() + # 获取存在失败的 质检单 调拨单明细行 + check_list = [item for item in need_quality_val if item.get('fail_check_ids')] + fail_check_text = '' + for item in check_list: + move_id, pre_done_qty = item.get('move_id'), item.get('pre_done_qty') + fail_check_text = (f'{fail_check_text}、{move_id.product_id.display_name} {pre_done_qty}件' + if fail_check_text != '' else f'{move_id.product_id.display_name} {pre_done_qty}件') + return { + 'type': 'ir.actions.act_window', + 'res_model': 'picking.validate.check.wizard', + 'name': '质检不合格提示', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_picking_id': self.id, + 'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?', + 'pinking_checkout_quality': True} + } + else: + return False + except Exception as e: + logging.info('pinking_checkout_quality()方法报错:%s' % e) + raise ValidationError('调拨单验证质检单是否合格时报错,请联系管理员处理!!') + + def get_need_quality_line(self, picking_to_quality): + """ + # 对需要进行质检,还没有质检完成的明细行进行统计 + # 1、当【质量标准_控制方式】=“产品、作业”,仅校验“预完成数量”>0的产品行对应的质检单必须处理。 + 2、当【质量标准_控制方式】=“数量”时,仅校验“预完成数量”>0的产品行对应的质检单必须处理: + 1)每一类的“总单数”=【调拨单_需求】时,则已处理的质检单“单数”≥“预完成数量”时,可执行调拨单验证; + 2)每一类的“总单数”<【调拨单_需求】时,则已处理的质检单“单数”≥0时,可执行调拨单验证; + """ + res = [] + for item in picking_to_quality: + need_done_check_ids = self.env['quality.check'] + fail_check_ids = self.env['quality.check'] + move_id, pre_done_qty, check_ids = item.values() + check_ids_1 = check_ids.filtered(lambda qc: qc.measure_on in ('operation', 'product')) + if check_ids_1: + check_ids_1_done = check_ids_1.filtered(lambda qc: qc.quality_state in ('pass', 'fail')) + check_ids_1_fail = check_ids_1.filtered(lambda qc: qc.quality_state == 'fail') + check_ids_1_none = check_ids_1.filtered(lambda qc: qc.quality_state == 'none') + if check_ids_1 and not check_ids_1_done: + need_done_check_ids += check_ids_1_none + if check_ids_1_fail: + fail_check_ids += check_ids_1_fail + + check_ids_2 = check_ids.filtered(lambda qc: qc.measure_on == 'move_line') + if check_ids_2: + check_ids_2_done = check_ids_2.filtered(lambda qc: qc.quality_state in ('pass', 'fail')) + check_ids_2_fail = check_ids_2.filtered(lambda qc: qc.quality_state == 'fail') + check_ids_2_none = check_ids_2.filtered(lambda qc: qc.quality_state == 'none') + # 每一类的“总单数”=【调拨单_需求】时,则已处理的质检单“单数”≥“预完成数量”时,可执行调拨单验证; + if len(check_ids_2) >= move_id.product_uom_qty and len(check_ids_2_done) < pre_done_qty: + need_done_check_ids += check_ids_2_none + # 每一类的“总单数”<【调拨单_需求】时,则已处理的质检单“单数”≥0时,可执行调拨单验证 + elif len(check_ids_2) < move_id.product_uom_qty and len(check_ids_2_done) == 0: + need_done_check_ids += check_ids_2_none + if check_ids_2_fail: + fail_check_ids += check_ids_2_fail + + if need_done_check_ids or fail_check_ids: + res.append({'move_id': move_id, + 'pre_done_qty': pre_done_qty, + 'check_ids': check_ids, + 'fail_check_ids': fail_check_ids, + 'need_done_check_ids': need_done_check_ids}) return res + + def get_picking_to_quality(self): + self.ensure_one() + return self._get_picking_to_quality() + + def _get_picking_to_quality(self): + """ + 对需要质检的明细行进行统计(针对“预完成数量”>0的行) + """ + quality_piking_line_list = [] + pre_done_qty_lines = self._get_pinking_pre_done_qty() + for line in pre_done_qty_lines: + move_id, pre_done_qty = line.values() + if pre_done_qty == 0: + continue + product_id = move_id.product_id + check_ids = self.check_ids.filtered(lambda c: c.product_id == product_id) + quality_piking_line_list.append({'move_id': move_id, 'pre_done_qty': pre_done_qty, 'check_ids': check_ids}) + return quality_piking_line_list + + def _get_pinking_pre_done_qty(self): + """ + return: 明细行 及 预完成数量 + 1、若调拨单所有明细行的【完成】=0,且任意行的【预留】<【需求】,则验证时将会话是否需创建欠单。 + ---->此时“预完成数量”=【预留】 + 2、若调拨单任意行的0<【完成】<【需求】,且则验证时将会话是否需创建欠单 + ---->此时“预完成数量”=【完成】 + """ + # if all(move_id.quantity_done == 0 for move_id in self.move_ids_without_package): + # pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.reserved_availability} for move_id in + # self.move_ids_without_package] + # else: + # pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.quantity_done} for move_id in + # self.move_ids_without_package] + pre_done_qty = [] + for move_id in self.move_ids_without_package: + if move_id.quantity_done > 0: + pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.quantity_done}) + else: + pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.reserved_availability}) + return pre_done_qty