# -*- coding: utf-8 -*- import logging from odoo import api, fields, models, _ from odoo.osv import expression from odoo.exceptions import UserError class SfLocation(models.Model): _inherit = 'stock.location' # 重写字段定义 name = fields.Char('Location Name', required=True, size=20) barcode = fields.Char('Barcode', copy=False, size=15) check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' # 仓库类别(selection:库区、库位、货位) # location_type = fields.Selection([ # ('库区', '库区'), # ('货架', '货架'), # ('货位', '货位') # ], string='存储类型') location_type = fields.Selection([ ('库区', '库区') ], string='存储类型') # 库区类型(selection:拣货区、存货区、收货区、退货区、次品区) area_type = fields.Selection([ ('拣货区', '拣货区'), ('存货区', '存货区'), ('收货区', '收货区'), ('退货区', '退货区'), ('次品区', '次品区') ], string='库区类型') # 当前位置 current_location_id = fields.Many2one('sf.shelf.location', string='当前位置') # 目的位置 destination_location_id = fields.Many2one('sf.shelf.location', string='目的位置') # 存储类型(selection:库区、货架) # storage_type = fields.Selection([ # ('库区', '库区'), # ('货架', '货架') # ], string='存储类型') # 产品类别 (关联:product.category) product_type = fields.Many2many('product.category', string='产品类别') # 货架独有字段:通道、方向、货架高度(m)、货架层数、层数容量 channel = fields.Char(string='通道') direction = fields.Selection([ ('R', 'R'), ('L', 'L') ], string='方向') shelf_height = fields.Float(string='货架高度(m)') shelf_layer = fields.Integer(string='货架层数') layer_capacity = fields.Integer(string='层数容量') # 货位独有字段:货位状态、产品(关联产品对象)、产品序列号(关联产品序列号对象) location_status = fields.Selection([ ('空闲', '空闲'), ('占用', '占用'), ('禁用', '禁用') ], string='货位状态', default='空闲') # product_id = fields.Many2one('product.template', string='产品') product_id = fields.Many2one('product.product', string='产品', compute='_compute_product_id', readonly=True) product_sn_id = fields.Many2one('stock.lot', string='产品序列号') # time_test = fields.Char(string='time') # 添加SQL约束 # _sql_constraints = [ # ('name_uniq', 'unique(name)', '位置名称必须唯一!'), # ] hide_location_type = fields.Boolean(compute='_compute_hide_what', string='隐藏仓库') hide_area = fields.Boolean(compute='_compute_hide_what', string='隐藏库区') hide_shelf = fields.Boolean(compute='_compute_hide_what', string='隐藏货架') hide_location = fields.Boolean(compute='_compute_hide_what', string='隐藏货位') # @api.model # def create(self, vals): # """ # 重写create方法,添加自定义的约束 # """ # print('create', vals) # if vals.get('location_id'): # location = self.env['stock.location'].browse(vals.get('location_id')) # if location.storage_type == '库区': # raise UserError('库区不能作为父级仓库') # return super().create(vals) # # @api.onchange('location_id') # def _onchange_location_id(self): # """ # 重写onchange方法,添加自定义的约束 # """ # if self.location_id: # if self.location_id.storage_type == '库区': # raise UserError('库区不能作为父级仓库') # @api.constrains('shelf_height') # def _check_shelf_height(self): # for record in self: # if not (0 <= record.shelf_height < 1000): # 限制字段值在0到999之间 # raise UserError('shelf_height的值必须在0到1000之间') # # @api.constrains('shelf_layer') # def _check_shelf_layer(self): # for record in self: # if not (0 < record.shelf_layer < 1000): # raise UserError('shelf_layer的值必须在0到999之间,且不能为0') # # @api.constrains('layer_capacity') # def _check_layer_capacity(self): # for record in self: # if not (0 <= record.layer_capacity < 1000): # raise UserError('layer_capacity的值必须在0到999之间,且不能为0') @api.depends('product_sn_id') def _compute_product_id(self): """ 根据产品序列号,获取产品 """ for record in self: if record.product_sn_id: record.product_id = record.product_sn_id.product_id record.location_status = '占用' else: record.product_id = False # record.location_status = '空闲' @api.depends('location_type') def _compute_hide_what(self): """ 根据仓库类别,隐藏不需要的字段 :return: """ for record in self: record.hide_location_type = False record.hide_area = False record.hide_shelf = False record.hide_location = False if record.location_type and record.location_type == '仓库': record.hide_location_type = True elif record.location_type and record.location_type == '库区': record.hide_area = True elif record.location_type and record.location_type == '货架': record.hide_shelf = True elif record.location_type and record.location_type == '货位': record.hide_location = True else: pass # # 添加Python约束 # @api.constrains('name', 'barcode') # def _check_len(self): # for rec in self: # if len(rec.name) > 20: # raise ValidationError("Location Name length must be less equal than 20!") # if len(rec.barcode) > 15: # raise ValidationError("Barcode length must be less equal than 15!") # @api.model # def default_get(self, fields): # print('fields:', fields) # res = super(SfLocation, self).default_get(fields) # print('res:', res) # if 'barcode' in fields and 'barcode' not in res: # # 这里是你生成barcode的代码 # pass # # res['barcode'] = self.generate_barcode() # 假设你有一个方法generate_barcode来生成barcode # return res # @api.model # def create(self, vals): # """ # 重写create方法,当仓库类型为货架时,自动生成其下面的货位,数量为货架层数*层数容量 # """ # res = super(SfLocation, self).create(vals) # if res.location_type == '货架': # for i in range(res.shelf_layer): # for j in range(res.layer_capacity): # self.create({ # 'name': res.name + '-' + str(i+1) + '-' + str(j+1), # 'location_id': res.id, # 'location_type': '货位', # 'barcode': self.generate_barcode(res, i, j), # 'location_status': '空闲' # }) # return res # 生成货位 def create_location(self): """ 当仓库类型为货架时,自动生成其下面的货位,数量为货架层数*层数容量 """ pass # if self.location_type == '货架': # for i in range(self.shelf_layer): # for j in range(self.layer_capacity): # self.create({ # 'name': self.name + '-' + str(i + 1) + '层' + '-' + str(j + 1) + '位置', # 'location_id': self.id, # 'location_type': '货位', # 'barcode': self.generate_barcode(i, j), # 'location_status': '空闲' # }) def generate_barcode(self, i, j): """ 生成货位条码 """ pass # # 这里是你生成barcode的代码 # # area_type_barcode = self.location_id.barcode # area_type_barcode = self.barcode # i_str = str(i + 1).zfill(3) # 确保是两位数,如果不足两位,左侧补0 # j_str = str(j + 1).zfill(3) # 确保是两位数,如果不足两位,左侧补0 # return area_type_barcode + self.channel + self.direction + '-' + self.barcode + '-' + i_str + '-' + j_str class ShelfLocation(models.Model): _name = 'sf.shelf.location' _description = '货架货位' _order = 'name' name = fields.Char('名称', required=True, size=20) barcode = fields.Char('编码', copy=False, size=15) check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' # 仓库类别(selection:库区、库位、货位) location_type = fields.Selection([ ('货架', '货架'), ('货位', '货位') ], string='存储类型') # 绑定库区 shelf_location_id = fields.Many2one('stock.location', string='所属库区', domain=[('location_type', '=', '库区')]) location_id = fields.Many2one('stock.location', string='所属库区', domain=[('location_type', '=', '库区')]) # 产品类别 (关联:product.category) # product_type = fields.Many2many('product.category', string='产品类别') # picking_product_type = fields.Many2many('stock.picking', string='调拨产品类别', related='location_dest_id.product_type') # 货架独有字段:通道、方向、货架高度(m)、货架层数、层数容量 channel = fields.Char(string='通道') direction = fields.Selection([ ('R', 'R'), ('L', 'L') ], string='方向') shelf_height = fields.Float(string='货架高度(m)') shelf_layer = fields.Integer(string='货架层数') layer_capacity = fields.Integer(string='层数容量') # 货位独有字段:货位状态、产品(关联产品对象)、产品序列号(关联产品序列号对象) location_status = fields.Selection([ ('空闲', '空闲'), ('占用', '占用'), ('禁用', '禁用') ], string='货位状态', default='空闲') # product_id = fields.Many2one('product.template', string='产品') product_id = fields.Many2one('product.product', string='产品', compute='_compute_product_id', readonly=True) product_sn_id = fields.Many2one('stock.lot', string='产品序列号') hide_shelf = fields.Boolean(compute='_compute_hide_what', string='隐藏货架') hide_location = fields.Boolean(compute='_compute_hide_what', string='隐藏货位') @api.onchange('shelf_location_id') def _onchange_shelf_location_id(self): """ 根据货架的所属库区修改货位的所属库区 """ if self.name: all_location = self.env['sf.shelf.location'].search([('name', 'ilike', self.name)]) for record in self: for location in all_location: location.location_id = record.shelf_location_id.id @api.depends('product_sn_id') def _compute_product_id(self): """ 根据产品序列号,获取产品 """ for record in self: if record.product_sn_id: record.sudo().product_id = record.product_sn_id.product_id record.sudo().location_status = '占用' else: record.sudo().product_id = False record.sudo().location_status = '空闲' @api.depends('location_type') def _compute_hide_what(self): """ 根据仓库类别,隐藏不需要的字段 :return: """ for record in self: record.sudo().hide_shelf = False record.sudo().hide_location = False if record.location_type and record.location_type == '货架': record.sudo().hide_shelf = True elif record.location_type and record.location_type == '货位': record.sudo().hide_location = True else: pass def create_location(self): """ 当仓库类型为货架时,自动生成其下面的货位,数量为货架层数*层数容量 """ if self.location_type == '货架': for i in range(self.shelf_layer): for j in range(self.layer_capacity): location_name = self.name + '-' + str(i + 1) + '层' + '-' + str(j + 1) + '位置' # 检查是否已经有同名的位置存在 existing_location = self.search([('name', '=', location_name)]) if not existing_location: self.create({ 'name': location_name, 'location_id': self.shelf_location_id.id, 'location_type': '货位', 'barcode': self.generate_barcode(i, j), 'location_status': '空闲', }) def generate_barcode(self, i, j): """ 生成货位条码 """ # 这里是你生成barcode的代码 # area_type_barcode = self.location_id.barcode area_type_barcode = self.barcode i_str = str(i + 1).zfill(3) # 确保是两位数,如果不足两位,左侧补0 j_str = str(j + 1).zfill(3) # 确保是两位数,如果不足两位,左侧补0 return area_type_barcode + self.channel + self.direction + '-' + self.barcode + '-' + i_str + '-' + j_str class Sf_stock_move_line(models.Model): _inherit = 'stock.move.line' current_location_id = fields.Many2one( 'sf.shelf.location', string='当前货位', compute='_compute_current_location_id', store=True) # location_dest_id = fields.Many2one('stock.location', string='目标库位') location_dest_id_product_type = fields.Many2many(related='location_dest_id.product_type') location_dest_id_value = fields.Integer(compute='_compute_location_dest_id_value', store=True) # def button_test(self): # print(self.picking_id.name) # stock_picking = self.env['stock.picking'].search([('name', '=', self.picking_id.name)], limit=1) # print(self.picking_id.name) # print(aa.move_line_ids.lot_id.name) # # 获取当前的stock.picking对象 # current_picking = self.env['stock.picking'].search([('name', '=', self.picking_id.name)], limit=1) # # # 获取当前picking的第一个stock.move对象 # current_move = current_picking.move_ids[0] if current_picking.move_ids else False # # # 如果存在相关的stock.move对象 # if current_move: # # 获取源stock.move对象 # origin_move = current_move.move_orig_ids[0] if current_move.move_orig_ids else False # # # 从源stock.move对象获取源stock.picking对象 # origin_picking = origin_move.picking_id if origin_move else False # # 现在,origin_picking就是current_picking的上一步 # # 获取目标stock.move对象 # dest_move = current_move.move_dest_ids[0] if current_move.move_dest_ids else False # # # 从目标stock.move对象获取目标stock.picking对象 # dest_picking = dest_move.picking_id if dest_move else False # # 现在,dest_picking就是current_picking的下一步 @api.depends('location_id') def _compute_current_location_id(self): for record in self: # 使用record代替self来引用当前遍历到的记录 logging.info('record.picking_id.name: %s' % record.picking_id.name) logging.info('record.env: %s' % record.env['stock.picking'].search([('name', '=', record.picking_id.name)])) # 获取当前的stock.picking对象 current_picking = record.env['stock.picking'].search([('name', '=', record.picking_id.name)], limit=1) # 获取当前picking的第一个stock.move对象 current_move = current_picking.move_ids[0] if current_picking.move_ids else False # 如果存在相关的stock.move对象 if current_move: # 获取源stock.move对象 origin_move = current_move.move_orig_ids[0] if current_move.move_orig_ids else False # 从源stock.move对象获取源stock.picking对象 origin_picking = origin_move.picking_id if origin_move else False # 如果前一个调拨单有目标货位 if origin_picking: for i in current_picking.move_line_ids: for j in origin_picking.move_line_ids: if j.destination_location_id and i.lot_id == j.lot_id: # 更新当前记录的current_location_id字段 record.current_location_id = j.destination_location_id # # 获取目标stock.move对象 # dest_move = current_move.move_dest_ids[0] if current_move.move_dest_ids else False # # # 从目标stock.move对象获取目标stock.picking对象 # dest_picking = dest_move.picking_id if dest_move else False # # 现在,dest_picking就是current_picking的下一步 # 是一张单据一张单据往下走的,所以这里的目标货位是上一张单据的当前货位,且这样去计算是可以的。 @api.depends('location_dest_id') def _compute_location_dest_id_value(self): for record in self: record.location_dest_id_value = record.location_dest_id.id if record.location_dest_id else False destination_location_id = fields.Many2one( 'sf.shelf.location', string='目标货位') @api.onchange('destination_location_id') def _compute_destination_location_id(self): for record in self: 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 # obj = self.env['sf.shelf.location'].search([('location_id', '=', # self.destination_location_id.id)]) obj = self.env['sf.shelf.location'].search([('name', '=', self.destination_location_id.name)]) if obj: obj.product_sn_id = record.lot_id.id else: pass else: obj = self.env['sf.shelf.location'].search([('name', '=', self.destination_location_id.name)]) if obj: obj.product_sn_id = record.lot_id.id class SfStockPicking(models.Model): _inherit = 'stock.picking' def button_validate(self): """ 重写验证方法,当验证时意味着调拨单已经完成,已经移动到了目标货位,所以需要将当前货位的状态改为空闲 """ res = super(SfStockPicking, self).button_validate() for line in self.move_line_ids: if line: if line.current_location_id: line.current_location_id.product_sn_id = False line.current_location_id = False return res class SfProcurementGroup(models.Model): _inherit = 'procurement.group' @api.model def _search_rule(self, route_ids, packaging_id, product_id, warehouse_id, domain): """ 修改路线多规则条件选取 """ if warehouse_id: domain = expression.AND( [['|', ('warehouse_id', '=', warehouse_id.id), ('warehouse_id', '=', False)], domain]) Rule = self.env['stock.rule'] res = self.env['stock.rule'] if route_ids: res_list = Rule.search(expression.AND([[('route_id', 'in', route_ids.ids)], domain]), order='route_sequence, sequence') for res1 in res_list: if product_id.categ_id in res1.location_dest_id.product_type or product_id.categ_id in \ res1.location_src_id.product_type: res = res1 if not res: res = Rule.search(expression.AND([[('route_id', 'in', route_ids.ids)], domain]), order='route_sequence, sequence', limit=1) if not res and packaging_id: packaging_routes = packaging_id.route_ids if packaging_routes: res_list = Rule.search(expression.AND([[('route_id', 'in', packaging_routes.ids)], domain]), order='route_sequence, sequence') for res1 in res_list: if product_id.categ_id in res1.location_dest_id.product_type or product_id.categ_id in \ res1.location_src_id.product_type: res = res1 if not res: res = Rule.search(expression.AND([[('route_id', 'in', packaging_routes.ids)], domain]), order='route_sequence, sequence', limit=1) if not res: product_routes = product_id.route_ids | product_id.categ_id.total_route_ids if product_routes: res_list = Rule.search(expression.AND([[('route_id', 'in', product_routes.ids)], domain]), order='route_sequence, sequence') for res1 in res_list: if product_id.categ_id in res1.location_dest_id.product_type or product_id.categ_id in \ res1.location_src_id.product_type: res = res1 if not res: res = Rule.search(expression.AND([[('route_id', 'in', product_routes.ids)], domain]), order='route_sequence, sequence', limit=1) if not res and warehouse_id: warehouse_routes = warehouse_id.route_ids if warehouse_routes: res_list = Rule.search(expression.AND([[('route_id', 'in', warehouse_routes.ids)], domain]), order='route_sequence, sequence') for res1 in res_list: if product_id.categ_id in res1.location_dest_id.product_type or product_id.categ_id in \ res1.location_src_id.product_type: res = res1 if not res: res = Rule.search(expression.AND([[('route_id', 'in', warehouse_routes.ids)], domain]), order='route_sequence, sequence', limit=1) return res class SfWarehouse(models.Model): _inherit = 'stock.warehouse' check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfRule(models.Model): _inherit = 'stock.rule' check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfRoute(models.Model): _inherit = 'stock.route' check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfPickingType(models.Model): _inherit = 'stock.picking.type' check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfBarcodeNomenclature(models.Model): _inherit = 'barcode.nomenclature' check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfPutawayRule(models.Model): _inherit = 'stock.putaway.rule' check_state = fields.Selection([ ('enable', '同意'), ('close', '不同意') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfWarehouseOrderpoint(models.Model): _inherit = 'stock.warehouse.orderpoint' check_state = fields.Selection([ ('enable', '同意'), ('close', '不同意') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfStockQuant(models.Model): _inherit = 'stock.quant' check_state = fields.Selection([ ('enable', '同意'), ('close', '不同意') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable' class SfStockScrap(models.Model): _inherit = 'stock.scrap' check_state = fields.Selection([ ('enable', '启用'), ('close', '关闭') ], string='审核状态', default='close') def action_check(self): self.check_state = 'enable'