# -*- coding: utf-8 -*- from collections import defaultdict, namedtuple from odoo.addons.stock.models.stock_rule import ProcurementException from re import findall as regex_findall from re import split as regex_split from odoo import SUPERUSER_ID, _, api, models from odoo.tools import float_compare from odoo.exceptions import UserError class StockRule(models.Model): _inherit = 'stock.rule' @api.model def _run_pull(self, procurements): moves_values_by_company = defaultdict(list) mtso_products_by_locations = defaultdict(list) # To handle the `mts_else_mto` procure method, we do a preliminary loop to # isolate the products we would need to read the forecasted quantity, # in order to to batch the read. We also make a sanitary check on the # `location_src_id` field. # list1 = [] # for item in procurements: # num = int(item[0].product_qty) # if num > 1: # for no in range(1, num+1): # # Procurement = namedtuple('Procurement', ['product_id', 'product_qty', # 'product_uom', 'location_id', 'name', 'origin', # 'company_id', # 'values']) # s = Procurement(product_id=item[0].product_id,product_qty=1.0,product_uom=item[0].product_uom, # location_id=item[0].location_id, # name=item[0].name, # origin=item[0].origin, # company_id=item[0].company_id, # values=item[0].values, # ) # item1 = list(item) # item1[0]=s # # list1.append(tuple(item1)) # else: # list1.append(item) for procurement, rule in procurements: if not rule.location_src_id: msg = _('No source location defined on stock rule: %s!') % (rule.name,) raise ProcurementException([(procurement, msg)]) if rule.procure_method == 'mts_else_mto': mtso_products_by_locations[rule.location_src_id].append(procurement.product_id.id) # Get the forecasted quantity for the `mts_else_mto` procurement. forecasted_qties_by_loc = {} for location, product_ids in mtso_products_by_locations.items(): products = self.env['product.product'].browse(product_ids).with_context(location=location.id) forecasted_qties_by_loc[location] = {product.id: product.free_qty for product in products} # Prepare the move values, adapt the `procure_method` if needed. procurements = sorted(procurements, key=lambda proc: float_compare(proc[0].product_qty, 0.0, precision_rounding=proc[ 0].product_uom.rounding) > 0) list2 = [] for item in procurements: num = int(item[0].product_qty) product = self.env['product.product'].search( [("id", '=', item[0].product_id.id)]) product_tmpl = self.env['product.template'].search( ["&", ("id", '=', product.product_tmpl_id.id), ('single_manufacturing', "!=", False)]) if product_tmpl: if num > 1: for no in range(1, num + 1): Procurement = namedtuple('Procurement', ['product_id', 'product_qty', 'product_uom', 'location_id', 'name', 'origin', 'company_id', 'values']) s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom, location_id=item[0].location_id, name=item[0].name, origin=item[0].origin, company_id=item[0].company_id, values=item[0].values, ) item1 = list(item) item1[0] = s list2.append(tuple(item1)) else: list2.append(item) else: list2.append(item) for procurement, rule in list2: procure_method = rule.procure_method if rule.procure_method == 'mts_else_mto': qty_needed = procurement.product_uom._compute_quantity(procurement.product_qty, procurement.product_id.uom_id) if float_compare(qty_needed, 0, precision_rounding=procurement.product_id.uom_id.rounding) <= 0: procure_method = 'make_to_order' for move in procurement.values.get('group_id', self.env['procurement.group']).stock_move_ids: if move.rule_id == rule and float_compare(move.product_uom_qty, 0, precision_rounding=move.product_uom.rounding) > 0: procure_method = move.procure_method break forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed elif float_compare(qty_needed, forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id], precision_rounding=procurement.product_id.uom_id.rounding) > 0: procure_method = 'make_to_order' else: forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed procure_method = 'make_to_stock' move_values = rule._get_stock_move_values(*procurement) move_values['procure_method'] = procure_method moves_values_by_company[procurement.company_id.id].append(move_values) for company_id, moves_values in moves_values_by_company.items(): # create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example) moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(moves_values) # Since action_confirm launch following procurement_group we should activate it. moves._action_confirm() return True @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)) 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) self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) ''' 创建工单 ''' productions._create_workorder() 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: ''' 创建制造订单时生成序列号 ''' 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) return True class ProductionLot(models.Model): _inherit = 'stock.lot' @api.model def generate_lot_names1(self, display_name, first_lot, count): """Generate `lot_names` from a string.""" if first_lot.__contains__(display_name): first_lot = first_lot[(len(display_name) + 1):] # We look if the first lot contains at least one digit. caught_initial_number = regex_findall(r"\d+", first_lot) if not caught_initial_number: return self.generate_lot_names1(display_name, first_lot + "0", count) # We base the series on the last number found in the base lot. initial_number = caught_initial_number[-1] padding = len(initial_number) # We split the lot name to get the prefix and suffix. splitted = regex_split(initial_number, first_lot) # initial_number could appear several times, e.g. BAV023B00001S00001 prefix = initial_number.join(splitted[:-1]) suffix = splitted[-1] initial_number = int(initial_number) lot_names = [] for i in range(0, count): lot_names.append('%s-%s%s%s' % ( display_name, prefix, str(initial_number + i).zfill(padding), suffix )) return lot_names @api.model def _get_next_serial(self, company, product): """Return the next serial number to be attributed to the product.""" if product.tracking == "serial": last_serial = self.env['stock.lot'].search( [('company_id', '=', company.id), ('product_id', '=', product.id)], limit=1, order='id DESC') if last_serial: return self.env['stock.lot'].generate_lot_names1(product.name, last_serial.name, 2)[ 1] return "%s-%03d" % (product.name, 1) class ResPurchaseOrder(models.Model): _inherit = 'purchase.order' # def button_approve(self, force=False): # result = super().button_approve(force=force) # self._create_picking_RES() # return result # @api.depends('order_line.move_ids') # def _compute_subcontracting_resupply_picking_count(self): # for purchase in self: # purchase.subcontracting_resupply_picking_count = len(purchase._get_subcontracting_resupplies_new()) # # def _get_subcontracting_resupplies_new(self): # moves_subcontracted = self.order_line.move_ids.filtered(lambda m: m.is_subcontract) # # subcontracted_productions = moves_subcontracted.move_orig_ids.production_id # return moves_subcontracted.picking_id def _prepare_picking_RES(self): if not self.group_id: self.group_id = self.group_id.create({ 'name': self.name, 'partner_id': self.partner_id.id }) if not self.partner_id.property_stock_supplier.id: raise UserError(_("You must set a Vendor Location for this partner %s", self.partner_id.name)) picking_type_id_res = self.env['stock.picking.type'].search( [('sequence_code', '=', 'RES'), ('barcode', '=', 'WH-RESUPPLY')]) return { 'picking_type_id': picking_type_id_res.id if picking_type_id_res else self.picking_type_id.id, 'partner_id': self.partner_id.id, 'user_id': False, 'date': self.date_order, 'origin': self.name, 'location_dest_id': self._get_destination_location(), 'location_id': self.partner_id.property_stock_supplier.id, 'company_id': self.company_id.id, } def _create_picking(self): StockPicking = self.env['stock.picking'] for order in self.filtered(lambda po: po.state in ('purchase', 'done')): if any(product.type in ['product', 'consu'] for product in order.order_line.product_id): order = order.with_company(order.company_id) pickings = order.picking_ids.filtered(lambda x: x.state not in ('done', 'cancel')) if not pickings: if order.order_line.product_id.categ_type == '胚料': res = order._prepare_picking_RES() else: res = order._prepare_picking() picking = StockPicking.with_user(SUPERUSER_ID).create(res) pickings = picking else: picking = pickings[0] moves = order.order_line._create_stock_moves(picking) moves = moves.filtered(lambda x: x.state not in ('done', 'cancel'))._action_confirm() seq = 0 for move in sorted(moves, key=lambda move: move.date): seq += 5 move.sequence = seq moves._action_assign() # Get following pickings (created by push rules) to confirm them as well. forward_pickings = self.env['stock.picking']._get_impacted_pickings(moves) (pickings | forward_pickings).action_confirm() picking.message_post_with_view('mail.message_origin_link', values={'self': picking, 'origin': order}, subtype_id=self.env.ref('mail.mt_note').id) return True class ResPurchaseOrderLine(models.Model): _inherit = 'purchase.order.line' def _create_stock_moves(self, picking): values = [] for line in self.filtered(lambda l: not l.display_type): for val in line._prepare_stock_moves(picking): # val['production_id'] = 10 val['is_subcontract'] = True values.append(val) line.move_dest_ids.created_purchase_line_id = False return self.env['stock.move'].create(values)