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 f49ab660..9b6cc17f 100644 --- a/sf_demand_plan/models/sf_demand_plan.py +++ b/sf_demand_plan/models/sf_demand_plan.py @@ -3,6 +3,7 @@ from odoo import models, fields, api, _ from odoo.tools import float_compare from odoo.exceptions import ValidationError +import re class SfDemandPlan(models.Model): @@ -10,8 +11,7 @@ class SfDemandPlan(models.Model): _description = 'sf_demand_plan' state = fields.Selection([ - ('10', '需求确认'), - ('20', '待工艺设计'), + ('10', '待工艺设计'), ('30', '部分下达'), ('40', '已下达'), ('50', '取消'), @@ -85,6 +85,8 @@ class SfDemandPlan(models.Model): compute='_compute_readonly_custom_made_type', default=False ) + demand_plan_number = fields.Char('需求计划号', compute='_compute_demand_plan_number', readonly=True, store=True) + origin = fields.Char('来源', related='sale_order_id.name', readonly=True, store=True) @api.depends('product_id.part_number', 'product_id.model_name') def _compute_part_number(self): @@ -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,10 +190,26 @@ 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): @@ -218,3 +237,17 @@ class SfDemandPlan(models.Model): else: for demand_plan_line_id in line_ids: demand_plan_line_id.action_confirm() + #需求要求取值格式是来源+来源明细行ID,但是来源明细行ID取得就是product_id.name得最后一位,所以这里也直接截取product_id.name + @api.depends('product_id.name') + def _compute_demand_plan_number(self): + for line in self: + product_name = line.product_id.name or '' + plan_no = None + if line.product_id: + # 使用正则表达式匹配P-后面的所有字符 + match = re.search(r'P-(.*)', product_name) + if match: + plan_no = match.group(1) + line.demand_plan_number = plan_no + else: + line.demand_plan_number = None \ No newline at end of file diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index f6f33676..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,12 +583,13 @@ 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') @@ -706,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/wizard/sf_demand_plan_print_wizard.py b/sf_demand_plan/wizard/sf_demand_plan_print_wizard.py index 74c4b12f..8d3a27b4 100644 --- a/sf_demand_plan/wizard/sf_demand_plan_print_wizard.py +++ b/sf_demand_plan/wizard/sf_demand_plan_print_wizard.py @@ -9,11 +9,6 @@ class SfDemandPlanPrintWizard(models.TransientModel): _name = 'sf.demand.plan.print.wizard' _description = u'打印向导' - demand_plan_id = fields.Many2one('sf.production.demand.plan', string='需求计划ID') - product_id = fields.Many2one( - comodel_name='product.product', - related='demand_plan_id.product_id', - string='产品', store=True, index=True) model_id = fields.Char('模型ID') filename_url = fields.Char('文件名/URL') type = fields.Selection([ @@ -25,7 +20,7 @@ class SfDemandPlanPrintWizard(models.TransientModel): ('success', '成功'), ('fail', '失败'), ], string='状态', default='not_start') - machining_drawings = fields.Binary('2D加工图纸', related='product_id.machining_drawings', store=True) + machining_drawings = fields.Binary('2D加工图纸') cnc_worksheet = fields.Binary('程序单') @@ -44,14 +39,17 @@ class SfDemandPlanPrintWizard(models.TransientModel): # 执行打印 self.env['jikimo.printing'].sudo().print_pdf(pdf_data) record.status = 'success' - t_part, c_part = record.demand_plan_id.print_count.split('C') - t_num = int(t_part[1:]) - c_num = int(c_part) - if record.type == '1': - t_num += 1 - elif record.type == '2': - c_num += 1 - record.demand_plan_id.print_count = f"T{t_num}C{c_num}" + production_demand_plan_id = self.env['sf.production.demand.plan'].sudo().search( + [('model_id', '=', record.model_id)]) + for production_demand_plan in production_demand_plan_id: + t_part, c_part = production_demand_plan.print_count.split('C') + t_num = int(t_part[1:]) + c_num = int(c_part) + if record.type == '1': + t_num += 1 + elif record.type == '2': + c_num += 1 + production_demand_plan.print_count = f"T{t_num}C{c_num}" success_records.append({ 'filename_url': record.filename_url, }) @@ -78,18 +76,14 @@ class MrpWorkorder(models.Model): demand_plan_print = self.env['sf.demand.plan.print.wizard'].sudo().search( [('model_id', '=', record.model_id), ('type', '=', '2')]) if demand_plan_print: - self.env['sf.demand.plan.print.wizard'].sudo().write( + demand_plan_print.write( {'cnc_worksheet': record.cnc_worksheet, 'filename_url': record.cnc_worksheet_name}) else: - demand_plan = self.env['sf.production.demand.plan'].sudo().search( - [('product_id', '=', record.product_id.id)]) - if demand_plan: - wizard_vals = { - 'demand_plan_id': demand_plan.id, - 'model_id': demand_plan.model_id, - 'type': '2', - 'cnc_worksheet': record.cnc_worksheet, - 'filename_url': record.cnc_worksheet_name - } - self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) + wizard_vals = { + 'model_id': record.model_id, + 'type': '2', + 'cnc_worksheet': record.cnc_worksheet, + 'filename_url': record.cnc_worksheet_name + } + self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) return res diff --git a/sf_dlm/models/stock_rule_inherit.py b/sf_dlm/models/stock_rule_inherit.py index 4d715196..0051a42f 100644 --- a/sf_dlm/models/stock_rule_inherit.py +++ b/sf_dlm/models/stock_rule_inherit.py @@ -6,50 +6,50 @@ from odoo import models, fields, api, _ class StockRuleInherit(models.Model): _inherit = 'stock.rule' - @api.model - def _run_buy(self, procurements): - # 判断补货组的采购类型 - procurements_group = {'standard': [], 'outsourcing': []} - for procurement, rule in procurements: - is_outsourcing = False - product = procurement.product_id - # 获取主 BOM - bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) - - if bom: - # 遍历 BOM 中的组件(即坯料等) - for line in bom.bom_line_ids: - raw_material = line.product_id - # 检查路线 - for route in raw_material.route_ids: - # print('route.name:', route.name) - if route.name == '按订单补给外包商': - is_outsourcing = True - - if is_outsourcing: - procurements_group['outsourcing'].append((procurement, rule)) - else: - procurements_group['standard'].append((procurement, rule)) - - for key, value in procurements_group.items(): - super(StockRuleInherit, self)._run_buy(value) - - if key == 'outsourcing': - for procurement, rule in value: - supplier = procurement.values.get('supplier') - if supplier: - domain = rule._make_po_get_domain(procurement.company_id, procurement.values, - supplier.partner_id) - logging.info("domain=============: %s", domain) - po = self.env['purchase.order'].sudo().search([ - ('partner_id', '=', supplier.partner_id.id), - ('company_id', '=', procurement.company_id.id), # 保证公司一致 - ('origin', 'like', procurement.origin), # 根据来源匹配 - ('state', '=', 'draft') # 状态为草稿 - ], limit=1) - logging.info("po=: %s", po) - if po: - po.write({'purchase_type': 'outsourcing'}) + # @api.model + # def _run_buy(self, procurements): + # # 判断补货组的采购类型 + # procurements_group = {'standard': [], 'outsourcing': []} + # for procurement, rule in procurements: + # is_outsourcing = False + # product = procurement.product_id + # # 获取主 BOM + # bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) + # + # if bom: + # # 遍历 BOM 中的组件(即坯料等) + # for line in bom.bom_line_ids: + # raw_material = line.product_id + # # 检查路线 + # for route in raw_material.route_ids: + # # print('route.name:', route.name) + # if route.name == '按订单补给外包商': + # is_outsourcing = True + # + # if is_outsourcing: + # procurements_group['outsourcing'].append((procurement, rule)) + # else: + # procurements_group['standard'].append((procurement, rule)) + # + # for key, value in procurements_group.items(): + # super(StockRuleInherit, self)._run_buy(value) + # + # if key == 'outsourcing': + # for procurement, rule in value: + # supplier = procurement.values.get('supplier') + # if supplier: + # domain = rule._make_po_get_domain(procurement.company_id, procurement.values, + # supplier.partner_id) + # logging.info("domain=============: %s", domain) + # po = self.env['purchase.order'].sudo().search([ + # ('partner_id', '=', supplier.partner_id.id), + # ('company_id', '=', procurement.company_id.id), # 保证公司一致 + # ('origin', 'like', procurement.origin), # 根据来源匹配 + # ('state', '=', 'draft') # 状态为草稿 + # ], limit=1) + # logging.info("po=: %s", po) + # if po: + # po.write({'purchase_type': 'outsourcing'}) # # 首先调用父类的 _run_buy 方法,以保留原有逻辑 # super(StockRuleInherit, self)._run_buy(procurements) diff --git a/sf_warehouse/__manifest__.py b/sf_warehouse/__manifest__.py index 456d3b96..28f6434e 100644 --- a/sf_warehouse/__manifest__.py +++ b/sf_warehouse/__manifest__.py @@ -16,6 +16,7 @@ # 'security/sf_stock_security.xml', 'security/ir.model.access.csv', 'wizard/wizard_view.xml', + 'views/product.xml', 'views/view.xml', 'views/shelf_location.xml', 'views/change_stock_move_views.xml', diff --git a/sf_warehouse/models/__init__.py b/sf_warehouse/models/__init__.py index 72b9b610..9035056a 100644 --- a/sf_warehouse/models/__init__.py +++ b/sf_warehouse/models/__init__.py @@ -1,2 +1,3 @@ from . import model from . import sync_common +from . import product diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index 2cb35264..74daafb4 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -471,7 +471,7 @@ class ShelfLocation(models.Model): record.display_rfid = record.product_sn_id.rfid if record.product_sn_id else '' except Exception as e: record.display_rfid = '' - + @api.depends('product_id') def _compute_tool(self): """计算工具 RFID""" @@ -594,7 +594,7 @@ class ShelfLocation(models.Model): _layer_capacity = _cc_code % record.shelf_id.layer_capacity if _layer_capacity == 0: _layer_capacity = record.shelf_id.layer_capacity - else: + else: _layer_capacity = _layer_capacity _layer = _layer+1 _layer_capacity = f"{_layer_capacity:02d}" @@ -634,7 +634,7 @@ class SfShelfLocationLot(models.Model): for item in self: if item.qty_num > item.qty: raise ValidationError('变更数量不能比库存数量大!!!') - + class SfStockMoveLine(models.Model): @@ -899,7 +899,7 @@ class SfStockMoveLine(models.Model): def _compute_current_location_id(self): # 批量获取所有相关记录的picking pickings = self.mapped('picking_id') - + # 构建源picking的移库行与目标位置的映射 origin_location_map = {} for picking in pickings: @@ -907,11 +907,11 @@ class SfStockMoveLine(models.Model): origin_move = picking.move_ids[:1].move_orig_ids[:1] if not origin_move: continue - + origin_picking = origin_move.picking_id if not origin_picking: continue - + # 为每个picking构建lot_id到location的映射 origin_location_map[picking.id] = { move_line.lot_id.id: move_line.destination_location_id @@ -919,17 +919,17 @@ class SfStockMoveLine(models.Model): lambda ml: ml.destination_location_id and ml.lot_id ) } - + # 批量更新current_location_id for record in self: current_picking = record.picking_id if not current_picking: record.current_location_id = False continue - + # 获取当前picking对应的lot_location映射 lot_dest_map = origin_location_map.get(current_picking.id, {}) - + # 查找匹配的lot_id for move_line in current_picking.move_line_ids: if move_line.lot_id and move_line.lot_id.id in lot_dest_map: @@ -1082,6 +1082,10 @@ class SfStockPicking(models.Model): 重写验证方法,当验证时意味着调拨单已经完成,已经移动到了目标货位,所以需要将当前货位的状态改为空闲 """ res = super(SfStockPicking, self).button_validate() + if any(ml.state == 'done' for ml in self.move_line_ids): + # 验证产品库存为负库存问题 + self.move_ids.product_id.verify_product_repertory(self.location_id) + for line in self.move_line_ids: if line: if line.destination_location_id: diff --git a/sf_warehouse/models/product.py b/sf_warehouse/models/product.py new file mode 100644 index 00000000..a0839512 --- /dev/null +++ b/sf_warehouse/models/product.py @@ -0,0 +1,29 @@ +from odoo import models, fields +from odoo.exceptions import ValidationError + + +class SfProductCategory(models.Model): + _inherit = 'product.category' + + negative_inventory_allowed = fields.Boolean('可负库存', default=True) + + +class SfProductTemplate(models.Model): + _inherit = 'product.product' + + def verify_product_repertory(self, location_id): + """ + 验证产品 负库存 + """ + if not location_id: + raise ValidationError('当前位置为空!!') + elif len(location_id) != 1: + raise ValidationError(f'存在多个当前位置{[item.name for item in location_id]}') + elif location_id.usage == 'supplier': + return True + for pp in self: + if not pp.categ_id.negative_inventory_allowed: + sq = pp.stock_quant_ids.filtered(lambda sq: sq.quantity < 0 and sq.location_id == location_id) + if sq: + raise ValidationError( + f'产品{pp.name}的产品类型设置为不可负库存,当前操作会导致产品{pp.name}在库存{location_id.name}上的库存数量为负!!!') diff --git a/sf_warehouse/views/product.xml b/sf_warehouse/views/product.xml new file mode 100644 index 00000000..3844b94c --- /dev/null +++ b/sf_warehouse/views/product.xml @@ -0,0 +1,17 @@ + + + + product.category.property.form.warehouse + product.category + + + + + + + + + + + + \ No newline at end of file