diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml index 8842f8bf..a62adf34 100644 --- a/sf_dlm_management/views/product_template_management_view.xml +++ b/sf_dlm_management/views/product_template_management_view.xml @@ -31,9 +31,11 @@ options="{'no_create': True}" attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}" placeholder="请选择"/> + @@ -47,10 +49,10 @@ - - @@ -95,6 +97,16 @@ + + {'readonly': ['|',('id','!=',False),('categ_type', '=', + '刀具')], 'required': True} + + + + + + + @@ -455,7 +467,8 @@ - + diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index 7d9bbac5..b500a6e8 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -50,8 +50,8 @@ class ResProductMo(models.Model): cutting_tool_material_id = fields.Many2one('sf.cutting.tool.material', string='刀具物料') cutting_tool_type = fields.Char(string="刀具物料类型", related='cutting_tool_material_id.name', store=True) - cutting_tool_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='型号') - specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='规格') + cutting_tool_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='型号名称') + specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='物料号') cutting_tool_type_id = fields.Many2one('sf.cutting.tool.type', string='类型', domain="[('cutting_tool_material_id.name', '=', cutting_tool_type)]") @@ -104,6 +104,15 @@ class ResProductMo(models.Model): compaction_way_id = fields.Many2one('maintenance.equipment.image', '压紧方式', domain=[('type', '=', '压紧方式')]) + name = fields.Char('产品名称', compute='_compute_tool_name', store=True, required=False) + + @api.depends('cutting_tool_model_id', 'specification_id') + def _compute_tool_name(self): + for item in self: + if item.cutting_tool_model_id and item.specification_id: + name = '%s%s' % (item.cutting_tool_model_id.name, item.specification_id.name) + item.name = name + @api.onchange('cutting_tool_model_id') def _onchange_cutting_tool_model_id(self): for item in self: @@ -721,6 +730,10 @@ class ResProductMo(models.Model): logging.info('create-model_file:%s' % len(vals['model_file'])) self._sanitize_vals(vals) templates = super(ResProductMo, self).create(vals_list) + # 产品名称唯一性校验 + for item in templates: + if len(self.search([('name', '=', item.name)])) > 1: + raise ValidationError('产品名称【%s】已存在' % item.name) if "create_product_product" not in self._context: templates._create_variant_ids() diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 6874dd22..f0961947 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -668,12 +668,14 @@ class ReStockMove(models.Model): else: view = self.env.ref('stock.view_stock_move_nosuggest_operations') - if self.product_id.tracking == "serial" and self.state == "assigned": - print(self.origin) - if self.product_id.categ_id.name == '刀具': - self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) - else: - self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) + if self.state == "assigned": + if self.product_id.tracking == "serial": + if self.product_id.categ_id.name == '刀具': + self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) + else: + self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) + elif self.product_id.tracking == "lot": + self._put_tool_lot(self.company_id, self.product_id, self.origin) return { 'name': _('Detailed Operations'), @@ -698,6 +700,16 @@ class ReStockMove(models.Model): ), } + def _put_tool_lot(self, company, product, origin): + if product.tracking == "lot" and self.product_id.categ_id.name == '刀具': + if not self.move_line_nosuggest_ids: + lot_names = self.env['stock.lot'].generate_lot_names( + '%s-%s-%s' % ('%s-T-DJWL-%s' % ( + product.cutting_tool_model_id.code.split('-')[0], product.cutting_tool_material_id.code), + datetime.now().strftime("%Y%m%d"), origin), 1) + move_lines_commands = self._generate_serial_move_line_commands_tool_lot(lot_names) + self.write({'move_line_nosuggest_ids': move_lines_commands}) + def _get_tool_next_serial(self, company, product, origin): """Return the next serial number to be attributed to the product.""" if product.tracking == "serial": @@ -711,6 +723,67 @@ class ReStockMove(models.Model): else: return "%s-T-%s-%s-%03d" % (split_codes[0], origin, product.specification_id.name, 1) + def _generate_serial_move_line_commands_tool_lot(self, lot_names, origin_move_line=None): + """Return a list of commands to update the move lines (write on + existing ones or create new ones). + Called when user want to create and assign multiple serial numbers in + one time (using the button/wizard or copy-paste a list in the field). + + :param lot_names: A list containing all serial number to assign. + :type lot_names: list + :param origin_move_line: A move line to duplicate the value from, default to None + :type origin_move_line: record of :class:`stock.move.line` + :return: A list of commands to create/update :class:`stock.move.line` + :rtype: list + """ + self.ensure_one() + + # Select the right move lines depending of the picking type configuration. + move_lines = self.env['stock.move.line'] + if self.picking_type_id.show_reserved: + move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name) + else: + move_lines = self.move_line_nosuggest_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name) + + loc_dest = origin_move_line and origin_move_line.location_dest_id + move_line_vals = { + 'picking_id': self.picking_id.id, + 'location_id': self.location_id.id, + 'product_id': self.product_id.id, + 'product_uom_id': self.product_id.uom_id.id, + 'qty_done': self.product_uom_qty, + } + if origin_move_line: + # `owner_id` and `package_id` are taken only in the case we create + # new move lines from an existing move line. Also, updates the + # `qty_done` because it could be usefull for products tracked by lot. + move_line_vals.update({ + 'owner_id': origin_move_line.owner_id.id, + 'package_id': origin_move_line.package_id.id, + 'qty_done': origin_move_line.qty_done or 1, + }) + + move_lines_commands = [] + qty_by_location = defaultdict(float) + for lot_name in lot_names: + # We write the lot name on an existing move line (if we have still one)... + if move_lines: + move_lines_commands.append((1, move_lines[0].id, { + 'lot_name': lot_name, + 'qty_done': 1, + })) + qty_by_location[move_lines[0].location_dest_id.id] += 1 + move_lines = move_lines[1:] + # ... or create a new move line with the serial name. + else: + loc = loc_dest or self.location_dest_id._get_putaway_strategy(self.product_id, quantity=1, + packaging=self.product_packaging_id, + additional_qty=qty_by_location) + move_line_cmd = dict(move_line_vals, lot_name=lot_name, location_dest_id=loc.id) + move_lines_commands.append((0, 0, move_line_cmd)) + qty_by_location[loc.id] += 1 + return move_lines_commands + class ReStockQuant(models.Model): _inherit = 'stock.quant' diff --git a/sf_tool_management/controllers/controllers.py b/sf_tool_management/controllers/controllers.py index 4b68e3f8..baba93fc 100644 --- a/sf_tool_management/controllers/controllers.py +++ b/sf_tool_management/controllers/controllers.py @@ -126,4 +126,4 @@ class Manufacturing_Connect(http.Controller): except Exception as e: res = {'Succeed': False, 'ErrorCode': 202, 'Error': e} logging.info('put_tool_preset_parameter_port error:%s' % e) - return json.JSONEncoder().encode(res) \ No newline at end of file + return json.JSONEncoder().encode(res) diff --git a/sf_tool_management/models/base.py b/sf_tool_management/models/base.py index 11ea4ac4..cfb68b7f 100644 --- a/sf_tool_management/models/base.py +++ b/sf_tool_management/models/base.py @@ -413,8 +413,9 @@ class FunctionalToolAssembly(models.Model): return categories.browse(functional_tool_type_ids) # 刀具物料信息 - # ==============整体式刀具型号============ - integral_freight_barcode = fields.Char('整体式刀具货位') + # ==============整体式刀具型号============= + integral_freight_barcode_id = fields.Many2one('sf.shelf.location', string='整体式刀具货位') + integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次') integral_product_id = fields.Many2one('product.product', string='整体式刀具名称', compute='_compute_integral_product_id', store=True) cutting_tool_integral_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='整体式刀具型号', @@ -424,19 +425,15 @@ class FunctionalToolAssembly(models.Model): sf_tool_brand_id_1 = fields.Many2one('sf.machine.brand', string='整体式刀具品牌', related='integral_product_id.brand_id') - @api.depends('integral_freight_barcode') + @api.depends('integral_freight_barcode_id') def _compute_integral_product_id(self): for item in self: - if item.integral_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search( - [('barcode', '=', item.integral_freight_barcode)]) - if location: - item.integral_product_id = location.product_id.id - else: - item.integral_product_id = False + if item.integral_freight_barcode_id: + item.integral_product_id = item.integral_freight_barcode_id.product_id.id # =================刀片型号============= - blade_freight_barcode = fields.Char('刀片货位') + blade_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀片货位') + blade_lot_id = fields.Many2one('stock.lot', string='刀片批次') blade_product_id = fields.Many2one('product.product', string='刀片名称', compute='_compute_blade_product_id', store=True) cutting_tool_blade_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀片型号', @@ -445,18 +442,15 @@ class FunctionalToolAssembly(models.Model): related='blade_product_id.specification_id') sf_tool_brand_id_2 = fields.Many2one('sf.machine.brand', '刀片品牌', related='blade_product_id.brand_id') - @api.depends('blade_freight_barcode') + @api.depends('blade_freight_barcode_id') def _compute_blade_product_id(self): for item in self: - if item.blade_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.blade_freight_barcode)]) - if location: - item.blade_product_id = location.product_id.id - else: - item.blade_product_id = False + if item.blade_freight_barcode_id: + item.blade_product_id = item.blade_freight_barcode_id.product_id.id # ==============刀杆型号================ - bar_freight_barcode = fields.Char('刀杆货位') + bar_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀杆货位') + bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次') bar_product_id = fields.Many2one('product.product', string='刀杆名称', compute='_compute_bar_product_id', store=True) cutting_tool_cutterbar_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀杆型号', @@ -465,18 +459,15 @@ class FunctionalToolAssembly(models.Model): related='bar_product_id.specification_id') sf_tool_brand_id_3 = fields.Many2one('sf.machine.brand', '刀杆品牌', related='bar_product_id.brand_id') - @api.depends('bar_freight_barcode') + @api.depends('bar_freight_barcode_id') def _compute_bar_product_id(self): for item in self: - if item.bar_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.bar_freight_barcode)]) - if location: - item.bar_product_id = location.product_id.id - else: - item.bar_product_id = False + if item.bar_freight_barcode_id: + item.bar_product_id = item.bar_freight_barcode_id.product_id.id # =============刀盘型号================ - pad_freight_barcode = fields.Char('刀盘货位') + pad_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀盘货位') + pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次') pad_product_id = fields.Many2one('product.product', string='刀盘名称', compute='_compute_pad_product_id', store=True) cutting_tool_cutterpad_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀盘型号', @@ -485,15 +476,11 @@ class FunctionalToolAssembly(models.Model): related='pad_product_id.specification_id') sf_tool_brand_id_4 = fields.Many2one('sf.machine.brand', '刀盘品牌', related='pad_product_id.brand_id') - @api.depends('pad_freight_barcode') + @api.depends('pad_freight_barcode_id') def _compute_pad_product_id(self): for item in self: - if item.pad_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.pad_freight_barcode)]) - if location: - item.pad_product_id = location.product_id.id - else: - item.pad_product_id = False + if item.pad_freight_barcode_id: + item.pad_product_id = item.pad_freight_barcode_id.product_id.id # ==============刀柄型号============== handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_handle_product_id', store=True) @@ -517,7 +504,8 @@ class FunctionalToolAssembly(models.Model): item.handle_freight_rfid = False # ==============夹头型号============== - chuck_freight_barcode = fields.Char('夹头货位') + chuck_freight_barcode_id = fields.Many2one('sf.shelf.location', string='夹头货位') + chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次') chuck_product_id = fields.Many2one('product.product', string='夹头名称', compute='_compute_chuck_product_id', store=True) cutting_tool_cutterhead_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='夹头型号', @@ -526,17 +514,18 @@ class FunctionalToolAssembly(models.Model): related='chuck_product_id.specification_id') sf_tool_brand_id_6 = fields.Many2one('sf.machine.brand', '夹头品牌', related='chuck_product_id.brand_id') - @api.depends('chuck_freight_barcode') + @api.depends('chuck_freight_barcode_id') def _compute_chuck_product_id(self): for item in self: - if item.chuck_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.chuck_freight_barcode)]) - if location: - item.chuck_product_id = location.product_id.id - else: - item.chuck_product_id = False + if item.chuck_freight_barcode_id: + item.chuck_product_id = item.chuck_freight_barcode_id.product_id.id # ==================待删除字段================== + integral_freight_barcode = fields.Char('整体式刀具货位') + blade_freight_barcode = fields.Char('刀片货位') + bar_freight_barcode = fields.Char('刀杆货位') + pad_freight_barcode = fields.Char('刀盘货位') + chuck_freight_barcode = fields.Char('夹头货位') blade_name = fields.Char('') integral_name = fields.Char('') blade_code_id = fields.Many2one('stock.lot', '刀片序列号') @@ -740,6 +729,22 @@ class FunctionalToolDismantle(models.Model): raise ValidationError('该功能刀具因为%s拆解,无需录入库位' % self.dismantle_cause) name = fields.Char('名称', related='functional_tool_id.name') + code = fields.Char('拆解单号', default=lambda self: self._get_code(), readonly=True) + + def _get_code(self): + """ + 自动生成拆解单编码 + """ + new_time = str(fields.Date.today()) + datetime = new_time[2:4] + new_time[5:7] + new_time[-2:] + functional_tool_dismantle = self.env['sf.functional.tool.dismantle'].sudo().search( + [('code', 'ilike', datetime)], limit=1, order="id desc") + if not functional_tool_dismantle: + num = "%03d" % 1 + else: + m = int(functional_tool_dismantle.code[-3:]) + 1 + num = "%03d" % m + return 'GNDJ-CJD-%s-%s' % (datetime, num) functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, domain=[('functional_tool_status', '!=', '已拆除')]) @@ -771,6 +776,8 @@ class FunctionalToolDismantle(models.Model): related='handle_product_id.cutting_tool_model_id') handle_brand_id = fields.Many2one('sf.machine.brand', string='刀柄品牌', related='handle_product_id.brand_id') handle_rfid = fields.Char(string='刀柄Rfid', compute='_compute_functional_tool_num', store=True) + handle_lot_id = fields.Many2one('stock.lot', string='刀柄序列号', compute='_compute_functional_tool_num', + store=True) scrap_boolean = fields.Boolean(string='刀柄是否报废', default=False) # 整体式 @@ -780,6 +787,8 @@ class FunctionalToolDismantle(models.Model): related='integral_product_id.cutting_tool_model_id') integral_brand_id = fields.Many2one('sf.machine.brand', string='整体式刀具品牌', related='integral_product_id.brand_id') + integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次', compute='_compute_functional_tool_num', + store=True) integral_freight_id = fields.Many2one('sf.shelf.location', '整体式刀具目标货位', domain="[('product_id', 'in', (integral_product_id, False))]") @@ -789,6 +798,7 @@ class FunctionalToolDismantle(models.Model): blade_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀片型号', related='blade_product_id.cutting_tool_model_id') blade_brand_id = fields.Many2one('sf.machine.brand', string='刀片品牌', related='blade_product_id.brand_id') + blade_lot_id = fields.Many2one('stock.lot', string='刀片批次', compute='_compute_functional_tool_num', store=True) blade_freight_id = fields.Many2one('sf.shelf.location', '刀片目标货位', domain="[('product_id', 'in', (blade_product_id, False))]") @@ -798,6 +808,7 @@ class FunctionalToolDismantle(models.Model): bar_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀杆型号', related='bar_product_id.cutting_tool_model_id') bar_brand_id = fields.Many2one('sf.machine.brand', string='刀杆品牌', related='bar_product_id.brand_id') + bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次', compute='_compute_functional_tool_num', store=True) bar_freight_id = fields.Many2one('sf.shelf.location', '刀杆目标货位', domain="[('product_id', 'in', (bar_product_id, False))]") @@ -807,6 +818,7 @@ class FunctionalToolDismantle(models.Model): pad_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀盘型号', related='pad_product_id.cutting_tool_model_id') pad_brand_id = fields.Many2one('sf.machine.brand', string='刀盘品牌', related='pad_product_id.brand_id') + pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次', compute='_compute_functional_tool_num', store=True) pad_freight_id = fields.Many2one('sf.shelf.location', '刀盘目标货位', domain="[('product_id', 'in', (pad_product_id, False))]") @@ -816,6 +828,7 @@ class FunctionalToolDismantle(models.Model): chuck_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='夹头型号', related='chuck_product_id.cutting_tool_model_id') chuck_brand_id = fields.Many2one('sf.machine.brand', string='夹头品牌', related='chuck_product_id.brand_id') + chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次', compute='_compute_functional_tool_num', store=True) chuck_freight_id = fields.Many2one('sf.shelf.location', '夹头目标货位', domain="[('product_id', 'in', (chuck_product_id, False))]") @@ -839,12 +852,20 @@ class FunctionalToolDismantle(models.Model): item.rfid = item.functional_tool_id.rfid item.handle_rfid = item.functional_tool_id.rfid + # 产品 item.handle_product_id = item.functional_tool_id.functional_tool_name_id.handle_product_id.id item.integral_product_id = item.functional_tool_id.functional_tool_name_id.integral_product_id.id item.blade_product_id = item.functional_tool_id.functional_tool_name_id.blade_product_id.id item.bar_product_id = item.functional_tool_id.functional_tool_name_id.bar_product_id.id item.pad_product_id = item.functional_tool_id.functional_tool_name_id.pad_product_id.id item.chuck_product_id = item.functional_tool_id.functional_tool_name_id.chuck_product_id.id + # 批次/序列号 + item.handle_lot_id = item.functional_tool_id.functional_tool_name_id.handle_code_id.id + item.integral_lot_id = item.functional_tool_id.functional_tool_name_id.integral_lot_id.id + item.blade_lot_id = item.functional_tool_id.functional_tool_name_id.blade_lot_id.id + item.bar_lot_id = item.functional_tool_id.functional_tool_name_id.bar_lot_id.id + item.pad_lot_id = item.functional_tool_id.functional_tool_name_id.pad_lot_id.id + item.chuck_lot_id = item.functional_tool_id.functional_tool_name_id.chuck_lot_id.id else: item.tool_groups_id = False item.tool_type_id = False @@ -860,8 +881,16 @@ class FunctionalToolDismantle(models.Model): item.pad_product_id = False item.chuck_product_id = False + item.handle_lot_id = False + item.integral_lot_id = False + item.blade_lot_id = False + item.bar_lot_id = False + item.pad_lot_id = False + item.chuck_lot_id = False + def confirmation_disassembly(self): logging.info('%s刀具确认开始拆解' % self.dismantle_cause) + code = self.code if self.functional_tool_id.functional_tool_status == '已拆除': raise ValidationError('Rfid为【%s】的功能刀具已经拆解,请勿重复操作!' % self.functional_tool_id.rfid_dismantle) # 对拆解的功能刀具进行校验,只有在刀具房的功能刀具才能拆解 @@ -889,43 +918,52 @@ class FunctionalToolDismantle(models.Model): if self.dismantle_cause in ['寿命到期报废', '崩刀报废']: # 除刀柄外物料报废 入库到Scrap if self.integral_product_id: - self.integral_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.integral_product_id.dismantle_stock_moves(False, self.integral_lot_id, location, + location_dest_scrap, code) elif self.blade_product_id: - self.blade_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.blade_product_id.dismantle_stock_moves(False, self.blade_lot_id, location, location_dest_scrap, + code) if self.bar_product_id: - self.bar_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.bar_product_id.dismantle_stock_moves(False, self.bar_lot_id, location, location_dest_scrap, + code) elif self.pad_product_id: - self.pad_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.pad_product_id.dismantle_stock_moves(False, self.pad_lot_id, location, location_dest_scrap, + code) if self.chuck_product_id: - self.chuck_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.chuck_product_id.dismantle_stock_moves(False, self.chuck_lot_id, location, location_dest_scrap, + code) # ===========功能刀具[磨削]拆解============== - elif self.dismantle_cause in ['刀具需磨削']: - location_dest = self.env['stock.location'].search([('name', '=', '磨削房')]) - # 除刀柄外物料拆解 入库到具体库位 - if self.integral_product_id: - self.integral_product_id.dismantle_stock_moves(False, location, location_dest) - elif self.blade_product_id: - self.blade_product_id.dismantle_stock_moves(False, location, location_dest) - if self.bar_product_id: - self.bar_product_id.dismantle_stock_moves(False, location, location_dest) - elif self.pad_product_id: - self.pad_product_id.dismantle_stock_moves(False, location, location_dest) - if self.chuck_product_id: - self.chuck_product_id.dismantle_stock_moves(False, location, location_dest) - # ==============功能刀具[更换]拆解============== - elif self.dismantle_cause in ['更换为其他刀具']: - # 除刀柄外物料拆解 入库到具体库位 + # elif self.dismantle_cause in ['刀具需磨削']: + # location_dest = self.env['stock.location'].search([('name', '=', '磨削房')]) + # # 除刀柄外物料拆解 入库到具体库位 + # if self.integral_product_id: + # self.integral_product_id.dismantle_stock_moves(False, location, location_dest) + # elif self.blade_product_id: + # self.blade_product_id.dismantle_stock_moves(False, location, location_dest) + # if self.bar_product_id: + # self.bar_product_id.dismantle_stock_moves(False, location, location_dest) + # elif self.pad_product_id: + # self.pad_product_id.dismantle_stock_moves(False, location, location_dest) + # if self.chuck_product_id: + # self.chuck_product_id.dismantle_stock_moves(False, location, location_dest) + # ==============功能刀具[更换,磨削]拆解============== + elif self.dismantle_cause in ['更换为其他刀具', '刀具需磨削']: + # 除刀柄外物料拆解 入库到具体货位 if self.integral_freight_id: - self.integral_product_id.dismantle_stock_moves(self.integral_freight_id.barcode, location, - location_dest) + self.integral_product_id.dismantle_stock_moves(self.integral_freight_id, self.integral_lot_id, location, + location_dest, code) elif self.blade_freight_id: - self.blade_product_id.dismantle_stock_moves(self.blade_freight_id.barcode, location, location_dest) + self.blade_product_id.dismantle_stock_moves(self.blade_freight_id, self.blade_lot_id, location, + location_dest, code) if self.bar_freight_id: - self.bar_product_id.dismantle_stock_moves(self.bar_freight_id.barcode, location, location_dest) + self.bar_product_id.dismantle_stock_moves(self.bar_freight_id, self.bar_lot_id, location, + location_dest, code) elif self.pad_freight_id: - self.pad_product_id.dismantle_stock_moves(self.pad_freight_id.barcode, location, location_dest) + self.pad_product_id.dismantle_stock_moves(self.pad_freight_id, self.pad_lot_id, location, + location_dest, code) if self.chuck_freight_id: - self.chuck_product_id.dismantle_stock_moves(self.chuck_freight_id.barcode, location, location_dest) + self.chuck_product_id.dismantle_stock_moves(self.chuck_freight_id, self.chuck_lot_id, location, + location_dest, code) # ===============删除功能刀具的Rfid字段的值, 赋值给Rfid(已拆解)字段===== self.functional_tool_id.write({ 'rfid_dismantle': self.functional_tool_id.rfid, @@ -946,26 +984,22 @@ class FunctionalToolDismantle(models.Model): class ProductProduct(models.Model): _inherit = 'product.product' - def dismantle_stock_moves(self, shelf_location_barcode, location_id, location_dest_id): + def dismantle_stock_moves(self, shelf_location_id, lot_id, location_id, location_dest_id, code): # 创建功能刀具拆解单产品库存移动记录 stock_move_id = self.env['stock.move'].sudo().create({ - 'name': '功能刀具拆解', + 'name': code, 'product_id': self.id, 'location_id': location_id.id, 'location_dest_id': location_dest_id.id, 'product_uom_qty': 1.00, 'state': 'done' }) - if shelf_location_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', shelf_location_barcode)]) - location.product_num = location.product_num + 1 - else: - location = self.env['sf.shelf.location'] # 创建移动历史记录 stock_move_line_id = self.env['stock.move.line'].sudo().create({ 'product_id': self.id, + 'lot_id': lot_id.id, 'move_id': stock_move_id.id, - 'current_location_id': location.id, + 'destination_location_id': shelf_location_id.id, 'install_tool_time': fields.Datetime.now(), 'qty_done': 1.0, 'state': 'done', diff --git a/sf_tool_management/views/functional_tool_views.xml b/sf_tool_management/views/functional_tool_views.xml index 76607a16..100ee297 100644 --- a/sf_tool_management/views/functional_tool_views.xml +++ b/sf_tool_management/views/functional_tool_views.xml @@ -115,7 +115,7 @@ options="{'no_create': True, 'no_quick_create': True}"/> - + diff --git a/sf_tool_management/views/tool_base_views.xml b/sf_tool_management/views/tool_base_views.xml index b3e93238..7a8eed80 100644 --- a/sf_tool_management/views/tool_base_views.xml +++ b/sf_tool_management/views/tool_base_views.xml @@ -551,13 +551,14 @@ - +
- + + @@ -569,24 +570,27 @@ + attrs="{'invisible': [('integral_freight_barcode_id', '=', False)]}">
- + +
- +
- + + @@ -595,13 +599,14 @@
- +
- + + @@ -609,13 +614,14 @@
- +
- + + @@ -722,6 +728,7 @@ sf.functional.tool.dismantle + @@ -749,13 +756,14 @@

- +

+ @@ -792,6 +800,7 @@ + @@ -804,10 +813,12 @@ - + attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具', '刀具需磨削'])], 'readonly': [('state', '=', '已拆解')], + 'required': [('chuck_lot_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具', '刀具需磨削'])]}"/> +
@@ -819,11 +830,12 @@
- - + attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具', '刀具需磨削'])], 'readonly': [('state', '=', '已拆解')], + 'required': [('integral_lot_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具', '刀具需磨削'])]}"/> +
@@ -835,10 +847,12 @@
- + attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具', '刀具需磨削'])], 'readonly': [('state', '=', '已拆解')], + 'required': [('blade_lot_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具', '刀具需磨削'])]}"/> +
@@ -848,10 +862,12 @@ - + attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具', '刀具需磨削'])], 'readonly': [('state', '=', '已拆解')], + 'required': [('bar_lot_id', '!=', False),('dismantle_cause', 'in', ['更换为其他刀具', '刀具需磨削'])]}"/> +
@@ -861,10 +877,12 @@ - + attrs="{'invisible': [('dismantle_cause', 'not in', ['更换为其他刀具', '刀具需磨削'])], 'readonly': [('state', '=', '已拆解')], + 'required': [('pad_lot_id', '!=', False), ('dismantle_cause', 'in', ['更换为其他刀具', '刀具需磨削'])]}"/> +
@@ -880,6 +898,7 @@ + diff --git a/sf_tool_management/wizard/wizard.py b/sf_tool_management/wizard/wizard.py index 3ff8b5db..a859f780 100644 --- a/sf_tool_management/wizard/wizard.py +++ b/sf_tool_management/wizard/wizard.py @@ -233,7 +233,10 @@ class FunctionalToolAssemblyOrder(models.TransientModel): # 功能刀具组装信息 # ===============整体式刀具型号================= - integral_freight_barcode = fields.Char('整体式刀具货位') + integral_freight_barcode_id = fields.Many2one('sf.shelf.location', string='整体式刀具货位', + domain="[('product_id.cutting_tool_material_id.name', '=', '整体式刀具'),('product_num', '>', 0)]") + integral_freight_lot_id = fields.Many2one('sf.shelf.location.lot', string='整体式刀具批次', + domain="[('shelf_location_id', '=', integral_freight_barcode_id)]") integral_product_id = fields.Many2one('product.product', string='整体式刀具名称', compute='_compute_integral_product_id', store=True) cutting_tool_integral_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='整体式刀具型号', @@ -243,28 +246,18 @@ class FunctionalToolAssemblyOrder(models.TransientModel): sf_tool_brand_id_1 = fields.Many2one('sf.machine.brand', string='整体式刀具品牌', related='integral_product_id.brand_id') - @api.depends('integral_freight_barcode') + @api.depends('integral_freight_lot_id') def _compute_integral_product_id(self): - if self.integral_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.integral_freight_barcode)]) - if location: - if not location.product_id: - raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode) - else: - material_name_id = location.product_id.cutting_tool_material_id - if material_name_id and material_name_id.name == '整体式刀具': - if location.product_num == 0: - raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode) - self.integral_product_id = location.product_id.id - else: - raise ValidationError( - '编码为【%s】的货位存放的产品为【%s】,不是整体式刀具,请重新选择!' % ( - location.barcode, location.product_id.name)) - else: - raise ValidationError('编码为【%s】的货位不存在!' % self.integral_freight_barcode) + if self.integral_freight_lot_id: + self.integral_product_id = self.integral_freight_lot_id.lot_id.product_id.id + else: + self.integral_product_id = False # ===============刀片型号==================== - blade_freight_barcode = fields.Char('刀片货位') + blade_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀片货位', + domain="[('product_id.cutting_tool_material_id.name', '=', '刀片'),('product_num', '>', 0)]") + blade_freight_lot_id = fields.Many2one('sf.shelf.location.lot', string='刀片批次', + domain="[('shelf_location_id', '=', blade_freight_barcode_id)]") blade_product_id = fields.Many2one('product.product', string='刀片名称', compute='_compute_blade_product_id', store=True) cutting_tool_blade_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀片型号', @@ -273,28 +266,18 @@ class FunctionalToolAssemblyOrder(models.TransientModel): related='blade_product_id.specification_id') sf_tool_brand_id_2 = fields.Many2one('sf.machine.brand', '刀片品牌', related='blade_product_id.brand_id') - @api.depends('blade_freight_barcode') + @api.depends('blade_freight_lot_id') def _compute_blade_product_id(self): - if self.blade_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.blade_freight_barcode)]) - if location: - if not location.product_id: - raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode) - else: - material_name_id = location.product_id.cutting_tool_material_id - if material_name_id and material_name_id.name == '刀片': - if location.product_num == 0: - raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode) - self.blade_product_id = location.product_id.id - else: - raise ValidationError( - '编码为【%s】的货位存放的产品为【%s】,不是刀片,请重新选择!' % ( - location.barcode, location.product_id.name)) - else: - raise ValidationError('编码为【%s】的货位不存在!' % self.blade_freight_barcode) + if self.blade_freight_lot_id: + self.blade_product_id = self.blade_freight_lot_id.lot_id.product_id.id + else: + self.blade_product_id = False # ====================刀杆型号================== - bar_freight_barcode = fields.Char('刀杆货位') + bar_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀杆货位', + domain="[('product_id.cutting_tool_material_id.name', '=', '刀杆'),('product_num', '>', 0)]") + bar_freight_lot_id = fields.Many2one('sf.shelf.location.lot', string='刀杆批次', + domain="[('shelf_location_id', '=', bar_freight_barcode_id)]") bar_product_id = fields.Many2one('product.product', string='刀杆名称', compute='_compute_bar_product_id', store=True) cutting_tool_cutterbar_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀杆型号', @@ -303,28 +286,18 @@ class FunctionalToolAssemblyOrder(models.TransientModel): related='bar_product_id.specification_id') sf_tool_brand_id_3 = fields.Many2one('sf.machine.brand', '刀杆品牌', related='bar_product_id.brand_id') - @api.depends('bar_freight_barcode') + @api.depends('bar_freight_lot_id') def _compute_bar_product_id(self): - if self.bar_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.bar_freight_barcode)]) - if location: - if not location.product_id: - raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode) - else: - material_name_id = location.product_id.cutting_tool_material_id - if material_name_id and material_name_id.name == '刀杆': - if location.product_num == 0: - raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode) - self.bar_product_id = location.product_id.id - else: - raise ValidationError( - '编码为【%s】的货位存放的产品为【%s】,不是刀杆,请重新选择!' % ( - location.barcode, location.product_id.name)) - else: - raise ValidationError('编码为【%s】的货位不存在!' % self.bar_freight_barcode) + if self.bar_freight_lot_id: + self.bar_product_id = self.bar_freight_lot_id.lot_id.product_id.id + else: + self.bar_product_id = False # ===============刀盘型号=================== - pad_freight_barcode = fields.Char('刀盘货位') + pad_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀盘货位', + domain="[('product_id.cutting_tool_material_id.name', '=', '刀盘'),('product_num', '>', 0)]") + pad_freight_lot_id = fields.Many2one('sf.shelf.location.lot', string='刀盘批次', + domain="[('shelf_location_id', '=', pad_freight_barcode_id)]") pad_product_id = fields.Many2one('product.product', string='刀盘名称', compute='_compute_pad_product_id', store=True) cutting_tool_cutterpad_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀盘型号', @@ -333,25 +306,12 @@ class FunctionalToolAssemblyOrder(models.TransientModel): related='pad_product_id.specification_id') sf_tool_brand_id_4 = fields.Many2one('sf.machine.brand', '刀盘品牌', related='pad_product_id.brand_id') - @api.depends('pad_freight_barcode') + @api.depends('pad_freight_lot_id') def _compute_pad_product_id(self): - if self.pad_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.pad_freight_barcode)]) - if location: - if not location.product_id: - raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode) - else: - material_name_id = location.product_id.cutting_tool_material_id - if material_name_id and material_name_id.name == '刀盘': - if location.product_num == 0: - raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode) - self.pad_product_id = location.product_id.id - else: - raise ValidationError( - '编码为【%s】的货位存放的产品为【%s】,不是刀盘,请重新选择!' % ( - location.barcode, location.product_id.name)) - else: - raise ValidationError('编码为【%s】的货位不存在!' % self.pad_freight_barcode) + if self.pad_freight_lot_id: + self.pad_product_id = self.pad_freight_lot_id.lot_id.product_id.id + else: + self.pad_product_id = False # ================刀柄型号=============== handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_rfid') @@ -374,7 +334,10 @@ class FunctionalToolAssemblyOrder(models.TransientModel): self.pad_product_id = False # =================夹头型号============== - chuck_freight_barcode = fields.Char('夹头货位') + chuck_freight_barcode_id = fields.Many2one('sf.shelf.location', string='夹头货位', + domain="[('product_id.cutting_tool_material_id.name', '=', '夹头'),('product_num', '>', 0)]") + chuck_freight_lot_id = fields.Many2one('sf.shelf.location.lot', string='夹头批次', + domain="[('shelf_location_id', '=', chuck_freight_barcode_id)]") chuck_product_id = fields.Many2one('product.product', string='夹头名称', compute='_compute_chuck_product_id', store=True) cutting_tool_cutterhead_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='夹头型号', @@ -383,25 +346,12 @@ class FunctionalToolAssemblyOrder(models.TransientModel): related='chuck_product_id.specification_id') sf_tool_brand_id_6 = fields.Many2one('sf.machine.brand', '夹头品牌', related='chuck_product_id.brand_id') - @api.depends('chuck_freight_barcode') + @api.depends('chuck_freight_lot_id') def _compute_chuck_product_id(self): - if self.chuck_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.chuck_freight_barcode)]) - if location: - if not location.product_id: - raise ValidationError('编码为【%s】的货位为空货位!' % location.barcode) - else: - material_name_id = location.product_id.cutting_tool_material_id - if material_name_id and material_name_id.name == '夹头': - if location.product_num == 0: - raise ValidationError('编码为【%s】的货位的产品库存数量为0,请重新选择!' % location.barcode) - self.chuck_product_id = location.product_id.id - else: - raise ValidationError( - '编码为【%s】的货位存放的产品为【%s】,不是夹头,请重新选择!' % ( - location.barcode, location.product_id.name)) - else: - raise ValidationError('编码为【%s】的货位不存在!' % self.chuck_freight_barcode) + if self.chuck_freight_lot_id: + self.chuck_product_id = self.chuck_freight_lot_id.lot_id.product_id.id + else: + self.chuck_product_id = False # ======================================== def on_barcode_scanned(self, barcode): @@ -423,23 +373,23 @@ class FunctionalToolAssemblyOrder(models.TransientModel): if location: material_name = location.product_id.cutting_tool_material_id.name if material_name == '夹头': - record.chuck_freight_barcode = barcode + record.chuck_freight_barcode_id = location.id elif material_name == '整体式刀具': - record.integral_freight_barcode = barcode - record.blade_freight_barcode = '' - record.bar_freight_barcode = '' - record.pad_freight_barcode = '' + record.integral_freight_barcode_id = location.id + record.blade_freight_barcode_id = False + record.bar_freight_barcode_id = False + record.pad_freight_barcode_id = False elif material_name == '刀片': - record.blade_freight_barcode = barcode - record.integral_freight_barcode = '' + record.blade_freight_barcode_id = location.id + record.integral_freight_barcode_id = False elif material_name == '刀杆': - record.bar_freight_barcode = barcode - record.integral_freight_barcode = '' - record.pad_freight_barcode = '' + record.bar_freight_barcode_id = location.id + record.integral_freight_barcode_id = False + record.pad_freight_barcode_id = False elif material_name == '刀盘': - record.pad_freight_barcode = barcode - record.integral_freight_barcode = '' - record.bar_freight_barcode = '' + record.pad_freight_barcode_id = location.id + record.integral_freight_barcode_id = False + record.bar_freight_barcode_id = False else: raise ValidationError('扫描的刀具物料不存在,请重新扫描!') else: @@ -601,7 +551,7 @@ class FunctionalToolAssemblyOrder(models.TransientModel): else: obj.after_assembly_functional_tool_name = '' - @api.onchange('integral_freight_barcode') + @api.onchange('integral_freight_barcode_id') def _onchange_after_assembly_functional_tool_diameter(self): for obj in self: if obj.integral_product_id: @@ -609,7 +559,7 @@ class FunctionalToolAssemblyOrder(models.TransientModel): else: obj.after_assembly_functional_tool_diameter = 0 - @api.onchange('blade_freight_barcode') + @api.onchange('blade_freight_barcode_id') def _onchange_after_assembly_knife_tip_r_angle(self): for obj in self: if obj.blade_product_id: @@ -650,15 +600,20 @@ class FunctionalToolAssemblyOrder(models.TransientModel): if self.handle_code_id: product_id.tool_material_stock_moves(self.handle_code_id, self.assembly_order_code) if self.integral_product_id: - self.integral_product_id.material_stock_moves(self.integral_freight_barcode, self.assembly_order_code) + self.integral_product_id.material_stock_moves(self.integral_freight_barcode_id, + self.integral_freight_lot_id, self.assembly_order_code) if self.blade_product_id: - self.blade_product_id.material_stock_moves(self.blade_freight_barcode, self.assembly_order_code) + self.blade_product_id.material_stock_moves(self.blade_freight_barcode_id, + self.blade_freight_lot_id, self.assembly_order_code) if self.bar_product_id: - self.bar_product_id.material_stock_moves(self.bar_freight_barcode, self.assembly_order_code) + self.bar_product_id.material_stock_moves(self.bar_freight_barcode_id, + self.bar_freight_lot_id, self.assembly_order_code) if self.pad_product_id: - self.pad_product_id.material_stock_moves(self.pad_freight_barcode, self.assembly_order_code) + self.pad_product_id.material_stock_moves(self.pad_freight_barcode_id, + self.pad_freight_lot_id, self.assembly_order_code) if self.chuck_product_id: - self.chuck_product_id.material_stock_moves(self.chuck_freight_barcode, self.assembly_order_code) + self.chuck_product_id.material_stock_moves(self.chuck_freight_barcode_id, + self.chuck_freight_lot_id, self.assembly_order_code) # ============================创建功能刀具列表、安全库存记录=============================== # 封装功能刀具数据 @@ -729,12 +684,17 @@ class FunctionalToolAssemblyOrder(models.TransientModel): 'code': self.code, 'rfid': self.rfid, 'tool_groups_id': self.after_tool_groups_id.id, - 'integral_freight_barcode': self.integral_freight_barcode, - 'blade_freight_barcode': self.blade_freight_barcode, - 'bar_freight_barcode': self.bar_freight_barcode, - 'pad_freight_barcode': self.pad_freight_barcode, 'handle_code_id': self.handle_code_id.id, - 'chuck_freight_barcode': self.chuck_freight_barcode, + 'integral_freight_barcode_id': self.integral_freight_barcode_id.id, + 'integral_lot_id': self.integral_freight_lot_id.lot_id.id, + 'blade_freight_barcode_id': self.blade_freight_barcode_id.id, + 'blade_lot_id': self.blade_freight_lot_id.lot_id.id, + 'bar_freight_barcode_id': self.bar_freight_barcode_id.id, + 'bar_lot_id': self.bar_freight_lot_id.lot_id.id, + 'pad_freight_barcode_id': self.pad_freight_barcode_id.id, + 'pad_lot_id': self.pad_freight_lot_id.lot_id.id, + 'chuck_freight_barcode_id': self.chuck_freight_barcode_id.id, + 'chuck_lot_id': self.chuck_freight_lot_id.lot_id.id, 'after_assembly_functional_tool_name': self.after_assembly_functional_tool_name, 'after_assembly_functional_tool_type_id': self.after_assembly_functional_tool_type_id.id, @@ -909,7 +869,7 @@ class ProductProduct(models.Model): tool_material.create_stock_quant(location_inventory_id, stock_location_id, None, assembly_order_code, False, False) - def material_stock_moves(self, shelf_location_barcode, assembly_order_code): + def material_stock_moves(self, shelf_location_barcode_id, lot_id, assembly_order_code): # 创建库存移动记录 stock_move_id = self.env['stock.move'].sudo().create({ 'name': assembly_order_code, @@ -920,21 +880,16 @@ class ProductProduct(models.Model): 'state': 'done' }) - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', shelf_location_barcode)]) # 创建移动历史记录 stock_move_line_id = self.env['stock.move.line'].sudo().create({ 'product_id': self.id, 'move_id': stock_move_id.id, - 'current_location_id': location.id, + 'lot_id': lot_id.lot_id.id, + 'current_location_id': shelf_location_barcode_id.id, 'install_tool_time': fields.Datetime.now(), 'qty_done': 1.0, 'state': 'done', }) - if location.product_num > 0: - location.product_num = location.product_num - 1 - else: - raise ValidationError( - '【%s】货位的【%s】产品库存数量为零,请采购入库后再重新组装!' % (location.barcode, location.product_id.name)) return stock_move_id, stock_move_line_id diff --git a/sf_tool_management/wizard/wizard_view.xml b/sf_tool_management/wizard/wizard_view.xml index 955bb998..ad8de498 100644 --- a/sf_tool_management/wizard/wizard_view.xml +++ b/sf_tool_management/wizard/wizard_view.xml @@ -229,13 +229,16 @@
+ attrs="{'invisible': ['|','|',('blade_freight_barcode_id', '!=', False),('bar_freight_barcode_id', '!=', False),('pad_freight_barcode_id', '!=', False)]}">
- + + + + @@ -249,13 +252,16 @@
- +
- + + + + @@ -270,13 +276,16 @@
+ attrs="{'invisible': ['|',('integral_freight_barcode_id', '!=', False),('pad_freight_barcode_id', '!=', False)]}">
- + + + + @@ -291,13 +300,16 @@
+ attrs="{'invisible': ['|',('integral_freight_barcode_id', '!=', False),('bar_freight_barcode_id', '!=', False)]}">
- + + + + @@ -317,7 +329,10 @@ - + + + + diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index 1a7ba1e0..155d5924 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -452,8 +452,9 @@ class ShelfLocation(models.Model): # product_id = fields.Many2one('product.template', string='产品') product_id = fields.Many2one('product.product', string='产品', compute='_compute_product_id', store=True) product_sn_id = fields.Many2one('stock.lot', string='产品序列号') + product_sn_ids = fields.One2many('sf.shelf.location.lot', 'shelf_location_id', string='产品批次号') # 产品数量 - product_num = fields.Integer('数量') + product_num = fields.Integer('总数量', compute='_compute_number', store=True) @api.depends('product_num') def _compute_product_num(self): @@ -463,6 +464,15 @@ class ShelfLocation(models.Model): elif record.product_num == 0: record.location_status = '空闲' + @api.depends('product_sn_ids.qty') + def _compute_number(self): + for item in self: + if item.product_sn_ids: + qty = 0 + for product_sn_id in item.product_sn_ids: + qty += product_sn_id.qty + item.product_num = qty + # 修改货位状态为禁用 def action_location_status_disable(self): self.location_status = '禁用' @@ -471,7 +481,7 @@ class ShelfLocation(models.Model): def action_location_status_enable(self): self.location_status = '空闲' - @api.depends('product_sn_id') + @api.depends('product_sn_id', 'product_sn_ids') def _compute_product_id(self): """ 根据产品序列号,获取产品 @@ -484,7 +494,8 @@ class ShelfLocation(models.Model): record.sudo().product_num = 1 except Exception as e: print('eeeeeee占用', e) - + elif record.product_sn_ids: + return True else: try: record.sudo().product_id = False @@ -525,7 +536,24 @@ class ShelfLocation(models.Model): return records -class Sf_stock_move_line(models.Model): +class SfShelfLocationLot(models.Model): + _name = 'sf.shelf.location.lot' + _description = '批次数量' + + name = fields.Char('名称', related='lot_id.name') + shelf_location_id = fields.Many2one('sf.shelf.location', string="货位") + lot_id = fields.Many2one('stock.lot', string='批次号') + qty = fields.Integer('数量') + qty_num = fields.Integer('变更数量') + + @api.onchange('qty_num') + def _onchange_qty_num(self): + for item in self: + if item.qty_num > item.qty: + raise ValidationError('变更数量不能比库存数量大!!!') + + +class SfStockMoveLine(models.Model): _name = 'stock.move.line' _inherit = ['stock.move.line', 'printing.utils'] @@ -825,15 +853,20 @@ class Sf_stock_move_line(models.Model): obj = self.env['sf.shelf.location'].search([('name', '=', self.destination_location_id.name)]) if record.lot_id: - shelf_location_obj = self.env['sf.shelf.location'].search( - [('product_sn_id', '=', record.lot_id.id)]) - if shelf_location_obj: - shelf_location_obj.product_sn_id = False - if obj: - obj.product_sn_id = record.lot_id.id - else: - if obj: - obj.product_sn_id = record.lot_id.id + if record.product_id.tracking == 'serial': + shelf_location_obj = self.env['sf.shelf.location'].search( + [('product_sn_id', '=', record.lot_id.id)]) + if shelf_location_obj: + shelf_location_obj.product_sn_id = False + if obj: + obj.product_sn_id = record.lot_id.id + else: + if obj: + obj.product_sn_id = record.lot_id.id + elif record.product_id.tracking == 'lot': + self.put_shelf_location(record) + if not obj.product_id: + obj.product_id = record.product_id.id else: if obj: obj.product_id = record.product_id.id @@ -853,6 +886,47 @@ class Sf_stock_move_line(models.Model): raise ValidationError( '【%s】货位已经被占用,请重新选择!!!' % item.destination_location_id.barcode) + def put_shelf_location(self, vals): + """ + 对货位的批量数据进行数量计算 + """ + for record in vals: + if record.lot_id and record.product_id.tracking == 'lot': + if record.current_location_id: + location_lot = self.env['sf.shelf.location.lot'].sudo().search( + [('shelf_location_id', '=', record.current_location_id.id), ('lot_id', '=', record.lot_id.id)]) + if location_lot: + location_lot.qty -= record.qty_done + if location_lot.qty == 0: + location_lot.unlink() + elif location_lot.qty < 0: + raise ValidationError('【%s】货位【%s】批次的【%s】产品数量不足!' % ( + record.current_location_id.barcode, record.lot_id.name, record.product_id.name)) + else: + raise ValidationError('【%s】货位不存在【%s】批次的【%s】产品' % ( + record.current_location_id.barcode, record.lot_id.name, record.product_id.name)) + if record.destination_location_id: + location_lot = self.env['sf.shelf.location.lot'].sudo().search( + [('shelf_location_id', '=', record.destination_location_id.id), + ('lot_id', '=', record.lot_id.id)]) + if location_lot: + location_lot.qty += record.qty_done + else: + self.env['sf.shelf.location.lot'].sudo().create({ + 'shelf_location_id': record.destination_location_id.id, + 'lot_id': record.lot_id.id, + 'qty': record.qty_done + }) + if not record.destination_location_id.product_id: + record.destination_location_id.product_id = record.product_id.id + + @api.model_create_multi + def create(self, vals_list): + + records = super(SfStockMoveLine, self).create(vals_list) + self.put_shelf_location(records) + return records + class SfStockPicking(models.Model): _inherit = 'stock.picking' diff --git a/sf_warehouse/security/ir.model.access.csv b/sf_warehouse/security/ir.model.access.csv index f981d477..88514fd3 100644 --- a/sf_warehouse/security/ir.model.access.csv +++ b/sf_warehouse/security/ir.model.access.csv @@ -1,7 +1,9 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_sf_shelf_location_group_sf_stock_user_group_sf_stock_user,sf.shelf.location,model_sf_shelf_location,sf_base.group_sf_stock_user,1,0,0,0 +access_sf_shelf_location_lot_group_sf_stock_user_group_sf_stock_user,sf.shelf.location.lot,model_sf_shelf_location_lot,sf_base.group_sf_stock_user,1,0,0,0 access_sf_shelf_location_group_sf_stock_manager,sf.shelf.location,model_sf_shelf_location,sf_base.group_sf_stock_manager,1,1,1,0 +access_sf_shelf_location_lot_group_sf_stock_manager,sf.shelf.location.lot,model_sf_shelf_location_lot,sf_base.group_sf_stock_manager,1,1,1,0 access_sf_shelf_group_sf_stock_user_group_sf_stock_user,sf.shelf.group.sf.stock.user,model_sf_shelf,sf_base.group_sf_stock_user,1,0,0,0 access_sf_shelf_group_sf_stock_manager,sf.shelf.group.sf.stock.manager,model_sf_shelf,sf_base.group_sf_stock_manager,1,1,1,0 @@ -101,6 +103,7 @@ access_stock_replenish_option_group_sf_stock_user,stock.replenishment.option,sto access_mrp_production_group_sf_stock_user,mrp.production,mrp.model_mrp_production,sf_base.group_sf_stock_user,1,1,1,0 access_sf_shelf_location_group_plan_dispatch,sf.shelf.location,model_sf_shelf_location,sf_base.group_plan_dispatch,1,0,0,0 +access_sf_shelf_location_lot_group_plan_dispatch,sf.shelf.location.lot,model_sf_shelf_location_lot,sf_base.group_plan_dispatch,1,0,0,0 access_stock_move,stock.move,stock.model_stock_move,sf_base.group_plan_dispatch,1,1,1,0 access_stock_picking_group_plan_dispatch,stock.picking,stock.model_stock_picking,sf_base.group_plan_dispatch,1,0,0,0 access_stock_lot_group_plan_dispatch,stock.lot,stock.model_stock_lot,sf_base.group_plan_dispatch,1,0,0,0 @@ -142,6 +145,9 @@ access_sf_shelf_location_wizard_group_sf_stock_manager,sf_shelf_location_wizard_ access_sf_shelf_location_group_sf_tool_user,sf.shelf.location.group_sf_tool_user,model_sf_shelf_location,sf_base.group_sf_tool_user,1,1,0,0 access_sf_shelf_group_user,sf.shelf.location.group_user,model_sf_shelf_location,base.group_user,1,1,0,0 +access_sf_shelf_location_lot_group_sf_tool_user,sf.shelf.location.lot.group_sf_tool_user,model_sf_shelf_location_lot,sf_base.group_sf_tool_user,1,1,0,0 +access_sf_shelf_lot_group_user,sf.shelf.location.lot.group_user,model_sf_shelf_location_lot,base.group_user,1,1,0,0 + access_ir_model_group_sf_stock_user,ir_model_group_sf_stock_user,base.model_ir_model,sf_base.group_sf_stock_user,1,1,0,0 access_mrp_workorder_group_sf_stock_user,mrp_workorder_group_sf_stock_user,mrp.model_mrp_workorder,sf_base.group_sf_stock_user,1,0,0,0 diff --git a/sf_warehouse/views/shelf_location.xml b/sf_warehouse/views/shelf_location.xml index a284f288..418f7d5c 100644 --- a/sf_warehouse/views/shelf_location.xml +++ b/sf_warehouse/views/shelf_location.xml @@ -133,9 +133,11 @@ type="action" context="{'default_name':name, 'default_current_name':name, + 'default_current_product_sn_ids':product_sn_ids, + 'default_lot_id':product_sn_id, 'default_current_shelf_id':shelf_id, 'default_current_location_id':location_id, - 'default_current_barcode':barcode, + 'default_current_barcode_id':id, 'default_current_product_id':product_id, }" class="btn-primary" attrs="{'invisible':[('location_status','!=','占用')]}"/> @@ -169,8 +171,16 @@ - + + + + + + + diff --git a/sf_warehouse/wizard/wizard.py b/sf_warehouse/wizard/wizard.py index db64fe07..62ed5771 100644 --- a/sf_warehouse/wizard/wizard.py +++ b/sf_warehouse/wizard/wizard.py @@ -8,17 +8,23 @@ class ShelfLocationWizard(models.TransientModel): name = fields.Char('') - current_location_id = fields.Many2one('stock.location', string='所属库区', readonly=True) + lot_id = fields.Many2one('stock.lot', string="序列号", readonly=True) + current_location_id = fields.Many2one('stock.location', string='所属库区', readonly=True) current_shelf_id = fields.Many2one('sf.shelf', string='当前货架', readonly=True) - current_barcode = fields.Char('当前货位编码', readonly=True) + current_barcode_id = fields.Many2one('sf.shelf.location', string='当前货位编码', readonly=True) current_name = fields.Char('当前货位名称', readonly=True) current_product_id = fields.Many2one('product.product', string='产品', readonly=True) + current_product_sn_ids = fields.Many2many('sf.shelf.location.lot', 'shelf_location_wizard', string='产品批次号', + readonly=True) + destination_location_id = fields.Many2one('stock.location', string='目标库区', compute='_compute_destination_name') destination_shelf_id = fields.Many2one('sf.shelf', string='目标货架', compute='_compute_destination_name') destination_barcode_id = fields.Many2one('sf.shelf.location', string='目标货位编码', required=True, - domain="") + domain="[('product_id', 'in', (False, current_product_id))]") destination_name = fields.Char('目标货位名称', compute='_compute_destination_name') + destination_product_sn_ids = fields.Many2many('sf.shelf.location.lot', 'shelf_location_wizard', string='批次号', + domain="[('shelf_location_id', '=', current_barcode_id)]") def return_domain(self): val = [('location_status', '=', '空闲')] @@ -32,9 +38,11 @@ class ShelfLocationWizard(models.TransientModel): def _compute_destination_name(self): if self.destination_barcode_id: self.destination_name = self.destination_barcode_id.name + self.destination_location_id = self.destination_barcode_id.location_id.id self.destination_shelf_id = self.destination_barcode_id.shelf_id.id else: self.destination_name = '' + self.destination_location_id = False self.destination_shelf_id = False # @@ -43,22 +51,46 @@ class ShelfLocationWizard(models.TransientModel): # if self.destination_barcode_id: # self.destination_shelf_id = self.destination_barcode_id.shelf_id.id - def confirm_the_change(self): - shelf_location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', self.current_barcode)]) - # 变更货位 - if self.destination_barcode_id and shelf_location: - if self.destination_barcode_id.product_id and self.destination_barcode_id.product_id == shelf_location.product_id and not self.destination_barcode_id.product_sn_id: - self.destination_barcode_id.product_num += shelf_location.product_num - else: - self.destination_barcode_id.product_sn_id = shelf_location.product_sn_id.id - self.destination_barcode_id.product_id = shelf_location.product_id.id - self.destination_barcode_id.product_num = shelf_location.product_num + def create_stock_moves(self, lot_id, num): + # 创建产品货位变更的库存移动记录 + stock_move_id = self.env['stock.move'].sudo().create({ + 'name': 'HWBG/%s' % self.id, + 'product_id': self.current_product_id.id, + 'location_id': self.current_location_id.id, + 'location_dest_id': self.destination_location_id.id, + 'product_uom_qty': num, + 'state': 'done' + }) + # 创建移动历史记录 + stock_move_line_id = self.env['stock.move.line'].sudo().create({ + 'product_id': self.current_product_id.id, + 'lot_id': lot_id.id, + 'move_id': stock_move_id.id, + 'current_location_id': self.current_barcode_id.id, + 'destination_location_id': self.destination_barcode_id.id, + 'install_tool_time': fields.Datetime.now(), + 'qty_done': num, + 'state': 'done' + }) - shelf_location.product_sn_id = False - shelf_location.product_id = False - shelf_location.product_num = 0 + return stock_move_id, stock_move_line_id + + def confirm_the_change(self): + if self.destination_barcode_id: + if self.lot_id: + self.current_barcode_id.product_sn_id = False + self.destination_barcode_id.product_sn_id = self.lot_id.id + self.create_stock_moves(self.lot_id, 1) + elif self.current_product_sn_ids: + for current_product_sn_id in self.current_product_sn_ids: + self.create_stock_moves(current_product_sn_id.lot_id, current_product_sn_id.qty_num) + current_product_sn_id.write({ + 'qty_num': 0 + }) + else: + raise ValidationError('没有需要变更的批次/序列号!') else: - raise ValidationError('目标货位出错,请联系管理员!') + raise ValidationError('请选择目标货位编码!') # 关闭弹出窗口 return {'type': 'ir.actions.act_window_close'} diff --git a/sf_warehouse/wizard/wizard_view.xml b/sf_warehouse/wizard/wizard_view.xml index 1bb95e15..d77a813c 100644 --- a/sf_warehouse/wizard/wizard_view.xml +++ b/sf_warehouse/wizard/wizard_view.xml @@ -6,27 +6,45 @@
- + - + + + + + + + + + + - - - + + + + + + + + + + + +