From 5236f019502e4e6f33f75a40bc85bc5f70fbaf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Tue, 30 Jun 2026 11:09:43 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=87=E8=B4=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/inherit_product_template.py | 98 ++++++++++++++++++- .../views/inherit_product_template.xml | 11 +++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/yuthon_sps_approval/models/inherit_product_template.py b/yuthon_sps_approval/models/inherit_product_template.py index 22e98163a..51f692c00 100644 --- a/yuthon_sps_approval/models/inherit_product_template.py +++ b/yuthon_sps_approval/models/inherit_product_template.py @@ -6,6 +6,7 @@ class ProductTemplate(models.Model): _inherit = 'product.template' is_fixed_assets = fields.Boolean(string='是否固定资产', default=True) + journal_item_count = fields.Integer(string='会计凭证行数量', compute='_compute_journal_item_count') @api.onchange('is_fixed_assets') def _onchange_fixed_assets(self): @@ -21,4 +22,99 @@ class ProductTemplate(models.Model): template_ids = self.env["product.template"].browse(self._context.get('active_ids', self._context.get('active_id'))) company_id = self.env['res.company'].search([('name', '=', '集团本部')]) for template in template_ids: - template.write({'company_id': company_id.id}) \ No newline at end of file + template.write({'company_id': company_id.id}) + + # 常见关联模型映射(表名 → 中文显示名) + _DELETE_CHECK_MODELS = [ + # (模型名, 关联字段, 中文名, 描述模板) + ('account.move.line', 'product_id', '会计凭证行(Journal Item)', '该产品已被 {count} 条会计凭证行引用'), + ('sale.order.line', 'product_template_id', '销售订单行', '该产品已被 {count} 条销售订单行引用'), + ('purchase.order.line', 'product_template_id', '采购订单行', '该产品已被 {count} 条采购订单行引用'), + ('stock.move', 'product_tmpl_id', '库存移动单', '该产品已被 {count} 条库存移动单引用'), + ('stock.move.line', 'product_tmpl_id', '库存移动明细', '该产品已被 {count} 条库存移动明细引用'), + ('stock.quant', 'product_tmpl_id', '库存数量记录', '该产品存在 {count} 条库存数量记录'), + ('maintenance.equipment', 'product_tmpl_id', '固定资产设备', '该产品已关联 {count} 个设备'), + ('mrp.production', 'product_tmpl_id', '生产工单', '该产品已关联 {count} 张生产工单'), + ('mrp.bom', 'product_tmpl_id', '物料清单(BOM)', '该产品已关联 {count} 个物料清单'), + ('pos.order.line', 'product_id', 'POS订单行', '该产品已被 {count} 条POS订单行引用'), + ] + + def unlink(self): + """重写删除方法,删除前检查关联数据并给出友好提示""" + # 逐个检查每个产品的关联情况 + blocked_products = [] + for record in self: + references = self._check_product_references(record) + if references: + blocked_products.append((record.display_name, references)) + + # 如果有被阻塞的产品,汇总提示 + if blocked_products: + detail_lines = [] + for prod_name, refs in blocked_products: + for ref_msg in refs: + detail_lines.append(f'• 【{prod_name}】{ref_msg}') + raise UserError( + '以下产品无法删除,因为它们已被其他业务数据引用:\n\n' + + '\n'.join(detail_lines) + + '\n\n建议操作:请使用「存档」功能替代「删除」,' + '存档后产品不会在列表中展示且不影响已有数据。' + ) + return super().unlink() + + def _check_product_references(self, record): + """检查单个产品的关联引用,返回提示信息列表""" + results = [] + # 获取该产品所有变体ID + variant_ids = record.product_variant_ids.ids + + for model_name, field_name, cn_name, msg_template in self._DELETE_CHECK_MODELS: + try: + # 检查模型是否存在 + if model_name not in self.env: + continue + + domain = [] + # 根据关联字段是 product_id 还是 product_tmpl_id 构建搜索条件 + if field_name == 'product_id': + domain = [('product_id', 'in', variant_ids)] + elif field_name == 'product_template_id' or field_name == 'product_tmpl_id': + domain = [(field_name, '=', record.id)] + + count = self.env[model_name].search_count(domain) + if count > 0: + results.append(msg_template.format(count=count)) + except Exception: + # 某些模型可能查询异常,跳过不影响主流程 + continue + + return results + + def _compute_journal_item_count(self): + """计算该产品关联的会计凭证行数量""" + for record in self: + # 获取所有变体ID + variant_ids = record.product_variant_ids.ids + if 'account.move.line' in record.env: + # 按变体product_id统计凭证行数量 + count = self.env['account.move.line'].search_count([ + ('product_id', 'in', variant_ids) + ]) + record.journal_item_count = count + else: + record.journal_item_count = 0 + + def action_view_journal_items(self): + """打开关联的会计凭证行列表""" + self.ensure_one() + variant_ids = self.product_variant_ids.ids + # 构建搜索域:按产品的变体ID查找凭证行 + domain = [('product_id', 'in', variant_ids)] + return { + 'name': f'{self.display_name} - 会计凭证行', + 'type': 'ir.actions.act_window', + 'res_model': 'account.move.line', + 'view_mode': 'tree,form', + 'domain': domain, + 'context': {'default_product_id': variant_ids[0] if variant_ids else False}, + } \ No newline at end of file diff --git a/yuthon_sps_approval/views/inherit_product_template.xml b/yuthon_sps_approval/views/inherit_product_template.xml index de4a4b445..d7d15446b 100644 --- a/yuthon_sps_approval/views/inherit_product_template.xml +++ b/yuthon_sps_approval/views/inherit_product_template.xml @@ -26,6 +26,17 @@