diff --git a/jikimo_purchase_request_tier_validation/models/stock_rule.py b/jikimo_purchase_request_tier_validation/models/stock_rule.py index 6bc521f2..366fb633 100644 --- a/jikimo_purchase_request_tier_validation/models/stock_rule.py +++ b/jikimo_purchase_request_tier_validation/models/stock_rule.py @@ -4,9 +4,10 @@ class StockRule(models.Model): _inherit = 'stock.rule' def _run_buy(self, procurements): - res = super(StockRule, self)._run_buy(procurements) # 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved origins = list(set([procurement[0].origin for procurement in procurements])) + res = super(StockRule, self)._run_buy(procurements) + # origins = list(set([procurement[0].origin for procurement in procurements])) for origin in origins: pr_ids = self.env["purchase.request"].sudo().search( [('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')]) diff --git a/sf_demand_plan/models/purchase_order.py b/sf_demand_plan/models/purchase_order.py index 5bc07313..b9802866 100644 --- a/sf_demand_plan/models/purchase_order.py +++ b/sf_demand_plan/models/purchase_order.py @@ -5,38 +5,42 @@ from odoo.tools import float_compare class PurchaseOrder(models.Model): _inherit = 'purchase.order' - demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan", - string="需求计划明细", readonly=True) - def button_confirm(self): - if self.demand_plan_line_id: + if self.order_line[0].demand_plan_line_id: self = self.with_context( - demand_plan_line_id=self.demand_plan_line_id.id + demand_plan_line_id=self.order_line[0].demand_plan_line_id.id ) res = super(PurchaseOrder, self).button_confirm() return res - @api.depends('origin', 'demand_plan_line_id') + @api.depends('origin') def _compute_purchase_type(self): for purchase in self: if purchase.order_line[0].product_id.categ_id.name == '坯料': if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协': purchase.purchase_type = 'outsourcing' else: - if purchase.demand_plan_line_id.supply_method == 'outsourcing': + if purchase.order_line[0].demand_plan_line_id.supply_method == 'outsourcing': purchase.purchase_type = 'outsourcing' - elif purchase.demand_plan_line_id.supply_method == 'purchase': + elif purchase.order_line[0].demand_plan_line_id.supply_method == 'purchase': purchase.purchase_type = 'outside' + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + + demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan", + string="需求计划明细", readonly=True) + @api.model def create(self, vals): - res = super(PurchaseOrder, self).create(vals) + res = super(PurchaseOrderLine, self).create(vals) if not res.demand_plan_line_id: - origin = [origin.replace(' ', '') for origin in res.origin.split(',')] + origin = [origin.replace(' ', '') for origin in res.order_id.origin.split(',')] if self.env.context.get('demand_plan_line_id'): res.demand_plan_line_id = self.env.context.get('demand_plan_line_id') - elif 'MO' in res.origin: + elif 'MO' in res.order_id.origin: # 原单据是制造订单 mp_ids = self.env['mrp.production'].sudo().search([('name', 'in', origin)]) if mp_ids: diff --git a/sf_demand_plan/models/purchase_request.py b/sf_demand_plan/models/purchase_request.py index 9cb15518..d1539976 100644 --- a/sf_demand_plan/models/purchase_request.py +++ b/sf_demand_plan/models/purchase_request.py @@ -28,83 +28,8 @@ class PurchaseRequestLine(models.Model): class PurchaseRequestLineMakePurchaseOrder(models.TransientModel): _inherit = "purchase.request.line.make.purchase.order" - def make_purchase_order(self): - res = [] - purchase_obj = self.env["purchase.order"] - po_line_obj = self.env["purchase.order.line"] - purchase = False - - if len(set([item_id.line_id.supply_method for item_id in self.item_ids])) > 1: - raise ValidationError('不同供货方式不可合并创建询价单!') - - for item in self.item_ids: - line = item.line_id - if item.product_qty <= 0.0: - raise UserError(_("Enter a positive quantity.")) - if self.purchase_order_id: - purchase = self.purchase_order_id - if not purchase: - po_data = self._prepare_purchase_order( - line.request_id.picking_type_id, - line.request_id.group_id, - line.company_id, - line.request_id.origin, - ) - po_data['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id - # po_data.update({'related_product':line.related_product.id}) - purchase = purchase_obj.create(po_data) - - # Look for any other PO line in the selected PO with same - # product and UoM to sum quantities instead of creating a new - # po line - domain = self._get_order_line_search_domain(purchase, item) - available_po_lines = po_line_obj.search(domain) - new_pr_line = True - # If Unit of Measure is not set, update from wizard. - if not line.product_uom_id: - line.product_uom_id = item.product_uom_id - # Allocation UoM has to be the same as PR line UoM - alloc_uom = line.product_uom_id - wizard_uom = item.product_uom_id - if available_po_lines and not item.keep_description: - new_pr_line = False - po_line = available_po_lines[0] - po_line.purchase_request_lines = [(4, line.id)] - po_line.move_dest_ids |= line.move_dest_ids - po_line_product_uom_qty = po_line.product_uom._compute_quantity( - po_line.product_uom_qty, alloc_uom - ) - wizard_product_uom_qty = wizard_uom._compute_quantity( - item.product_qty, alloc_uom - ) - all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty) - self.create_allocation(po_line, line, all_qty, alloc_uom) - else: - po_line_data = self._prepare_purchase_order_line(purchase, item) - if item.keep_description: - po_line_data["name"] = item.name - if line.related_product: - po_line_data.update({'related_product': line.related_product.id}) - po_line = po_line_obj.create(po_line_data) - po_line_product_uom_qty = po_line.product_uom._compute_quantity( - po_line.product_uom_qty, alloc_uom - ) - wizard_product_uom_qty = wizard_uom._compute_quantity( - item.product_qty, alloc_uom - ) - all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty) - self.create_allocation(po_line, line, all_qty, alloc_uom) - self._post_process_po_line(item, po_line, new_pr_line) - res.append(purchase.id) - - purchase_requests = self.item_ids.mapped("request_id") - purchase_requests.button_in_progress() - return { - "domain": [("id", "in", res)], - "name": _("RFQ"), - "view_mode": "tree,form", - "res_model": "purchase.order", - "view_id": False, - "context": False, - "type": "ir.actions.act_window", - } + @api.model + def _prepare_purchase_order_line(self, po, item): + ret = super(PurchaseRequestLineMakePurchaseOrder, self)._prepare_purchase_order_line(po, item) + ret['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id + return ret diff --git a/sf_demand_plan/models/sale_order.py b/sf_demand_plan/models/sale_order.py index fe8966a6..149fadfe 100644 --- a/sf_demand_plan/models/sale_order.py +++ b/sf_demand_plan/models/sale_order.py @@ -11,7 +11,7 @@ class ReSaleOrder(models.Model): groups='mrp.group_mrp_user', store=True) demand_plan_ids = fields.Many2many(comodel_name="sf.demand.plan", - string="需求计划", readonly=True) + string="需求计划", readonly=True) demand_plan_count = fields.Integer( string="需求计划生成计数", @@ -47,9 +47,9 @@ class ReSaleOrder(models.Model): if demand_plan.product_id.machining_drawings_name: filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] wizard_vals = { - 'demand_plan_id': demand_plan.id, 'model_id': demand_plan.model_id, 'filename_url': filename_url, + 'machining_drawings': product.machining_drawings, 'type': '1', } self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) diff --git a/sf_demand_plan/models/sf_demand_plan.py b/sf_demand_plan/models/sf_demand_plan.py index bd181937..9b6cc17f 100644 --- a/sf_demand_plan/models/sf_demand_plan.py +++ b/sf_demand_plan/models/sf_demand_plan.py @@ -72,6 +72,8 @@ class SfDemandPlan(models.Model): ('4', '低'), ], string='优先级', default='3') + overdelivery_allowed = fields.Boolean('可超量发货', default=False) + hide_button_release_plan = fields.Boolean( string='显示下达计划按钮', compute='_compute_hide_button_release_plan', @@ -148,9 +150,8 @@ class SfDemandPlan(models.Model): for line in self: sum_plan_uom_qty = sum(line.line_ids.mapped('plan_uom_qty')) pending_qty = line.product_uom_qty - sum_plan_uom_qty - rounding = line.product_id.uom_id.rounding or 0.01 if float_compare(pending_qty, 0, - precision_rounding=rounding) == -1: + precision_rounding=line.product_id.uom_id.rounding) == -1: line.pending_qty = 0 else: line.pending_qty = pending_qty @@ -170,7 +171,9 @@ class SfDemandPlan(models.Model): def _compute_state(self): for line in self: status_line = line.line_ids.filtered(lambda p: p.status == '60') - if line.sale_order_id.state == 'cancel': + if not line.line_ids: + line.state = '10' + elif line.sale_order_id.state == 'cancel': line.state = '50' line.line_ids.status = '100' elif len(line.line_ids) == len(status_line): @@ -187,16 +190,38 @@ class SfDemandPlan(models.Model): lambda p: p.status in ('50', '60') and p.new_supply_method == 'custom_made') line.readonly_custom_made_type = bool(production_demand_plan) + @api.constrains('line_ids') + def check_line_ids(self): + for item in self: + if not item.line_ids: + raise ValidationError('计划不能为空!') + + def write(self, vals): + res = super(SfDemandPlan, self).write(vals) + if 'line_ids' in vals: + for line in self.line_ids: + if not line.sale_order_id: + line.sale_order_id = self.sale_order_id + if not line.sale_order_line_id: + line.sale_order_line_id = self.sale_order_line_id + return res + def name_get(self): result = [] for plan in self: - result.append((plan.id, plan.sale_order_id.name)) + result.append((plan.id, plan.product_id.name)) return result def button_production_release_plan(self): line_ids = self.line_ids.filtered(lambda p: p.status == '30') sum_product_uom_qty = sum(line_ids.mapped('plan_uom_qty')) - if sum_product_uom_qty > self.product_uom_qty: + customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers') + if not self.overdelivery_allowed and line_ids.filtered(lambda p: p.location_id.id == customer_location_id): + if float_compare(sum_product_uom_qty, self.product_uom_qty, + precision_rounding=self.product_id.uom_id.rounding) == 1: + raise ValidationError(f"已禁止向合作伙伴/客户超量发货,请更换“补货原因”或将“可超量发货”设置为“是”。") + elif float_compare(sum_product_uom_qty, self.product_uom_qty, + precision_rounding=self.product_id.uom_id.rounding) == 1: return { 'name': _('需求计划'), 'type': 'ir.actions.act_window', diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index a0b5bb92..1d4b616a 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -28,10 +28,8 @@ class SfProductionDemandPlan(models.Model): ], string='状态', default='30', readonly=True) demand_plan_id = fields.Many2one(comodel_name="sf.demand.plan", string="物料需求", readonly=True) - sale_order_id = fields.Many2one(comodel_name="sale.order", related='demand_plan_id.sale_order_id', - string="销售订单", readonly=True) - sale_order_line_id = fields.Many2one(comodel_name="sale.order.line", related='demand_plan_id.sale_order_line_id', - string="销售订单明细", readonly=True) + sale_order_id = fields.Many2one(comodel_name="sale.order", string="销售订单", readonly=True) + sale_order_line_id = fields.Many2one(comodel_name="sale.order.line", string="销售订单明细", readonly=True) sale_order_line_number = fields.Char(string='销售订单行', compute='_compute_sale_order_line_number', store=True) company_id = fields.Many2one( related='sale_order_id.company_id', @@ -58,7 +56,7 @@ class SfProductionDemandPlan(models.Model): custom_made_type = fields.Selection([ ('automation', "自动化产线加工"), ('manual', "人工线下加工"), - ], string='自制类型', compute='_compute_custom_made_type', store=True) + ], string='产线类型', compute='_compute_custom_made_type', store=True) supply_method = fields.Selection([ ('automation', "自动化产线加工"), @@ -215,9 +213,8 @@ class SfProductionDemandPlan(models.Model): @api.depends('sale_order_line_id.qty_to_deliver') def _compute_qty_to_deliver(self): for line in self: - rounding = line.product_id.uom_id.rounding or 0.01 if float_compare(line.sale_order_line_id.qty_to_deliver, 0, - precision_rounding=rounding) == -1: + precision_rounding=line.product_id.uom_id.rounding) == -1: line.qty_to_deliver = 0 else: line.qty_to_deliver = line.sale_order_line_id.qty_to_deliver @@ -285,11 +282,15 @@ class SfProductionDemandPlan(models.Model): def _compute_material_check(self): for record in self: if record.mrp_production_ids and record.mrp_production_ids.move_raw_ids: + # 获取完成的制造订单 + done_manufacturing = record.mrp_production_ids.filtered(lambda mo: mo.state == 'done') + product_qty = sum(done_manufacturing.mapped('product_qty')) + # 需求数量-完成数量 + product_uom_qty = record.plan_uom_qty - product_qty total_reserved_availability = sum( record.mrp_production_ids.mapped('move_raw_ids.reserved_availability')) - rounding = record.product_id.uom_id.rounding or 0.01 - if float_compare(total_reserved_availability, record.plan_uom_qty, - precision_rounding=rounding) >= 0: + if float_compare(total_reserved_availability, product_uom_qty, + precision_rounding=record.product_id.uom_id.rounding) >= 0: record.material_check = '1' # 已齐套 else: record.material_check = '0' # 未齐套 @@ -364,11 +365,12 @@ class SfProductionDemandPlan(models.Model): return action def button_action_print(self): + model_id = self.mapped('model_id') return { 'res_model': 'sf.demand.plan.print.wizard', 'type': 'ir.actions.act_window', 'name': _("打印"), - 'domain': [('demand_plan_id', 'in', self.ids)], + 'domain': [('model_id', 'in', model_id)], 'views': [[self.env.ref('sf_demand_plan.action_plan_print_tree').id, 'list']], 'target': 'new', } @@ -581,17 +583,23 @@ class SfProductionDemandPlan(models.Model): # programming_no = list(set(programming_mrp_production_ids)) # numbers_str = "、".join(programming_no) # raise ValidationError(f"编程单号:{numbers_str},请去云平台处理") - - def button_delete(self): - self.ensure_one() - if len(self.demand_plan_id.line_ids) == 1: - raise ValidationError(f"最后一条计划,不能删除!") - self.unlink() + @api.model + def unlink(self): + for item in self: + if item.status not in ('10', '20', '30'): + raise ValidationError(u'只能删除状态为【草稿,待确认,需求确认】的需求计划。') + else: + super(SfProductionDemandPlan, item).unlink() def button_batch_release_plan(self): filtered_plan = self.filtered(lambda mo: mo.status == '30') if not filtered_plan: raise UserError(_("没有需要下达的计划!")) + check_overdelivery_allowed = False + if not self.demand_plan_id.overdelivery_allowed: + customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers') + if self.location_id.id == customer_location_id: + check_overdelivery_allowed = True # 按产品分组并计算总数 product_data = {} for plan in filtered_plan: @@ -607,12 +615,15 @@ class SfProductionDemandPlan(models.Model): # 检查需求超过计划数量的产品 warning_messages = [] for product, data in product_data.items(): - if data['plan_uom_qty'] > data['product_uom_qty']: + if float_compare(data['plan_uom_qty'], data['product_uom_qty'], + precision_rounding=product.uom_id.rounding) == 1: warning_messages.append( _("您正在下达的产品 %s,计划量%s,需求数量为%s,已超过需求数量") % (product.display_name, data['plan_uom_qty'], data['product_uom_qty']) ) - if warning_messages: + if warning_messages and check_overdelivery_allowed: + raise ValidationError(f"已禁止向合作伙伴/客户超量发货,请更换“补货原因”或将“可超量发货”设置为“是”。") + elif warning_messages: warning_message = "\n".join(warning_messages) return { 'name': _('需求计划'), @@ -631,7 +642,15 @@ class SfProductionDemandPlan(models.Model): self.ensure_one() if not self.new_supply_method: raise ValidationError(f"供货方式不能为空!") - if self.plan_uom_qty > self.product_uom_qty: + check_overdelivery_allowed = False + if not self.demand_plan_id.overdelivery_allowed: + customer_location_id = self.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers') + if self.location_id.id == customer_location_id: + check_overdelivery_allowed = True + if check_overdelivery_allowed: + if float_compare(self.plan_uom_qty, self.product_uom_qty,precision_rounding=self.product_id.uom_id.rounding) == 1: + raise ValidationError(f"已禁止向合作伙伴/客户超量发货,请更换“补货原因”或将“可超量发货”设置为“是”。") + elif float_compare(self.plan_uom_qty, self.product_uom_qty,precision_rounding=self.product_id.uom_id.rounding) == 1: return { 'name': _('需求计划'), 'type': 'ir.actions.act_window', @@ -690,9 +709,9 @@ class SfProductionDemandPlan(models.Model): # 复制成品模板上的属性 self.product_id.product_tmpl_id.copy_template(product_template_id) - + future_time = datetime.now() + timedelta(hours=8) # 生成BOM单据编码 - code = f"{self.product_id.default_code}-{bom_type}-{datetime.now().strftime('%Y%m%d%H%M%S')}" + code = f"{self.product_id.default_code}-{bom_type}-{future_time.strftime('%Y%m%d%H%M%S')}" order_id = self.sale_order_id product = self.product_id diff --git a/sf_demand_plan/views/demand_plan_info.xml b/sf_demand_plan/views/demand_plan_info.xml index 80344940..414e60d1 100644 --- a/sf_demand_plan/views/demand_plan_info.xml +++ b/sf_demand_plan/views/demand_plan_info.xml @@ -36,13 +36,14 @@ + - - + + @@ -53,6 +54,7 @@ + @@ -71,10 +73,39 @@ class="btn-primary" attrs="{'invisible': [('hide_release_production_order', '=', False)]}" /> -