# -*- coding: utf-8 -*- from collections import Counter from xml import etree from odoo import models, fields, api, Command from odoo.exceptions import UserError, AccessError from odoo.http import request class jikimo_bom(models.Model): _name = 'jikimo.bom' _description = '功能刀具物料清单' tool_inventory_id = fields.Many2one('sf.tool.inventory', '功能刀具清单') tool_name = fields.Char(related="tool_inventory_id.name", string='功能刀具名称') functional_cutting_tool_model_id = fields.Many2one(related='tool_inventory_id.functional_cutting_tool_model_id', string='功能刀具类型') tool_groups_id = fields.Many2one(related='tool_inventory_id.tool_groups_id', string='刀具组') tool_length = fields.Float(related='tool_inventory_id.tool_length', string='刀具总长(mm)') diameter = fields.Float(related='tool_inventory_id.diameter', string='直径(mm)') angle = fields.Float(related='tool_inventory_id.angle', string='R角(mm)') extension = fields.Float(related='tool_inventory_id.extension', string='伸出长度(mm)') product_ids = fields.Many2many('product.product', string='产品') knife_handle_model = fields.Selection(related='tool_inventory_id.knife_handle_model', string='使用刀柄型号') options = fields.Char('产品清单') def name_get(self): result = [] for bom in self: result.append((bom.id, '功能刀具物料清单')) return result def check_types_in_list(self): """ 检查产品列表中的元素是否包含了所有指定的类型,并且每种类型至少出现一次。 :return: 如果条件满足返回True,否则返回False """ if not self.product_ids: return False try: # 统计每个类型的出现次数 type_counts = Counter(item.cutting_tool_material_id.name for item in self.product_ids) # 检查是否每种类型的出现次数都大于0,并且类型的数量与选项字符串中的数量相等 return all(count > 0 for count in type_counts.values()) and len(type_counts) == len(self.options.split('+')) except AttributeError: # 如果出现属性错误,说明产品列表中的元素可能缺少必要的属性 return False # type_counts = Counter(item.cutting_tool_material_id.name for item in self.product_ids) # return all(count > 0 for count in type_counts.values()) and len(type_counts) == self.options.split('+') def write(self, vals): # 在更新模型时记录旧的 Many2many ID 列表 if 'product_ids' in vals: old_product_counter = Counter(self.product_ids.ids) super(jikimo_bom, self).write(vals) new_product_counter = Counter(self.product_ids.ids) delete_product_counter = old_product_counter - new_product_counter if delete_product_counter: # 删除操作 if self.check_types_in_list(): return True else: raise UserError('每种物料最少要有一个') return True return super(jikimo_bom, self).write(vals) def bom_product_domains(self, assembly_options): """ 根据装配选项生成产品域列表 :param assembly_options: 装配选项字符串,各选项以'+'分隔 :return: 动态生成的产品搜索条件 """ self.options = assembly_options cutting_tool_materials = self.env['sf.cutting.tool.material'].search( [('name', 'in', assembly_options.split('+'))]) domains = [] for index, option in enumerate(cutting_tool_materials): domain = ['&', ('cutting_tool_material_id', '=', option.id), ("cutting_tool_type_id", "in", self.tool_inventory_id.functional_cutting_tool_model_id.cutting_tool_type_ids.ids)] if option.name == '刀柄': domain = ['&'] + domain + [ ("cutting_tool_taper_shank_model", "=", self.tool_inventory_id.knife_handle_model)] if option.name == '整体式刀具': domain = ['&'] + domain + [ '|', # 刀具直径 ('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter), # r角 ('cutting_tool_blade_tip_working_size', '=', self.tool_inventory_id.angle)] if option.name == '刀杆': domain = ['&'] + domain + [ ("cutting_tool_cutter_arbor_diameter", "=", self.tool_inventory_id.diameter)] if option.name == '刀片': domain = ['&'] + domain + [ ("cutting_tool_blade_tip_circular_arc_radius", "=", self.tool_inventory_id.angle)] if option.name == '刀盘': domain = ['&'] + domain + [ ("cutting_tool_cutter_head_diameter", "=", self.tool_inventory_id.diameter)] domains = domains + domain if index != 0: domains = ['|'] + domains domains = domains + [('stock_move_count', '>', 0)] return domains def generate_bill_materials(self, assembly_options): """ 生成物料清单 根据装配选项生成物料清单,首先获取产品领域,然后搜索相关产品,并设置产品ID。 :param assembly_options: 组装方式 :type assembly_options: 装配选项字符串,各选项以'+'分隔 """ domains = self.bom_product_domains(assembly_options) products = self.env['product.product'].search(domains) if products: self.product_ids = [Command.set(products.ids)] class jikimo_bom_line(models.Model): _name = 'jikimo.bom.line' _description = 'jikimo.bom.line' name = fields.Char() class ProductProduct(models.Model): _inherit = 'product.product' _order = 'cutting_tool_material_id, cutting_tool_type_id' stock_move_count = fields.Integer(string='stock_move count', compute='_compute_stock_move_count', store=True) @api.depends('stock_move_ids') def _compute_stock_move_count(self): for record in self: if record.stock_move_ids: record.stock_move_count = len(record.stock_move_ids) else: record.stock_move_count = 0 def search(self, args, offset=0, limit=None, order=None, count=False): # 你可以在这里修改 `args` 以调整搜索条件 # 例如,添加额外的搜索条件 if self.env.context.get('jikimo_bom_product'): bom_id = self.env['jikimo.bom'].browse(request.session.get('jikimo_bom_product').get('bom_id')) if not bom_id.options: raise UserError('请先选择组装方式') domains = bom_id.bom_product_domains(bom_id.options) args = args + domains return super(ProductProduct, self).search(args, offset=offset, limit=limit, order=order, count=count)