|
-
- from odoo import models, fields, api
- from odoo.exceptions import UserError
-
-
- class WhMove(models.Model):
- _name = 'wh.move'
- _description = '移库单'
-
- MOVE_STATE = [
- ('draft', '草稿'),
- ('done', '已完成'),
- ('cancel', '已作废'),]
-
- @api.depends('line_out_ids', 'line_in_ids')
- def _compute_total_qty(self):
- for wm in self:
- goods_total = 0
- if wm.line_in_ids:
- # 入库商品总数
- goods_total = sum(line.goods_qty for line in wm.line_in_ids)
- elif wm.line_out_ids:
- # 出库商品总数
- goods_total = sum(line.goods_qty for line in wm.line_out_ids)
- wm.total_qty = goods_total
-
- @api.model
- def _get_default_warehouse_impl(self):
- if self.env.context.get('warehouse_type', 'stock'):
- return self.env['warehouse'].get_warehouse_by_type(
- self.env.context.get('warehouse_type', 'stock'))
-
- @api.model
- def _get_default_warehouse_dest_impl(self):
- if self.env.context.get('warehouse_dest_type', 'stock'):
- return self.env['warehouse'].get_warehouse_by_type(
- self.env.context.get('warehouse_dest_type', 'stock'))
-
- @api.model
- def _get_default_warehouse(self):
- '''获取调出仓库'''
- return self._get_default_warehouse_impl()
-
- @api.model
- def _get_default_warehouse_dest(self):
- '''获取调入仓库'''
- return self._get_default_warehouse_dest_impl()
-
- origin = fields.Char('移库类型', required=True,
- help='移库类型')
- name = fields.Char('单据编号', copy=False, default='/',
- help='单据编号,创建时会自动生成')
- ref = fields.Char('外部单号')
- state = fields.Selection(MOVE_STATE, '状态', copy=False, default='draft',
- index=True,
- tracking=True,
- help='移库单状态标识,新建时状态为草稿;确认后状态为已确认')
- partner_id = fields.Many2one('partner', '业务伙伴', ondelete='restrict',
- help='该单据对应的业务伙伴')
- date = fields.Date('单据日期', required=True, copy=False, default=fields.Date.context_today,
- help='单据创建日期,默认为当前天')
- warehouse_id = fields.Many2one('warehouse', '调出仓库',
- ondelete='restrict',
- required=True,
- domain="['|',('user_ids','=',False),('user_ids','in',uid)]",
- default=_get_default_warehouse,
- help='移库单的来源仓库')
- warehouse_dest_id = fields.Many2one('warehouse', '调入仓库',
- ondelete='restrict',
- required=True,
- domain="['|',('user_ids','=',False),('user_ids','in',uid)]",
- default=_get_default_warehouse_dest,
- help='移库单的目的仓库')
- approve_uid = fields.Many2one('res.users', '确认人',
- copy=False, ondelete='restrict',
- help='移库单的确认人')
- approve_date = fields.Datetime('确认日期', copy=False)
- line_ids = fields.One2many('wh.move.line', 'move_id', '出入库明细', copy=False)
- line_out_ids = fields.One2many('wh.move.line', 'move_id', '出库明细',
- domain=[
- ('type', 'in', ['out', 'internal'])],
- copy=True,
- help='出库类型的移库单对应的出库明细')
- line_in_ids = fields.One2many('wh.move.line', 'move_id', '入库明细',
- domain=[('type', '=', 'in')],
- context={'type': 'in'}, copy=True,
- help='入库类型的移库单对应的入库明细')
- in_goods_id = fields.Many2one('goods', string='入库商品',
- related='line_in_ids.goods_id')
- out_goods_id = fields.Many2one('goods', string='出库商品',
- related='line_out_ids.goods_id')
- auxiliary_id = fields.Many2one(
- 'auxiliary.financing', '辅助核算', help='辅助核算是对账务处理的一种补充,即实现更广泛的账务处理,\
- 以适应企业管理和决策的需要.辅助核算一般通过核算项目来实现', ondelete='restrict')
- note = fields.Text('备注',
- copy=False,
- help='可以为该单据添加一些需要的标识信息')
- total_qty = fields.Float('商品总数', compute=_compute_total_qty,
- digits='Quantity', store=True,
- help='该移库单的入/出库明细行包含的商品总数')
- user_id = fields.Many2one(
- 'res.users',
- '经办人',
- ondelete='restrict',
- tracking=True,
- states={'done': [('readonly', True)]},
- default=lambda self: self.env.user,
- help='单据经办人'
- )
- express_type = fields.Char(string='承运商')
- express_code = fields.Char('快递单号', copy=False)
- company_id = fields.Many2one(
- 'res.company',
- string='公司',
- change_default=True,
- default=lambda self: self.env.company)
- qc_result = fields.Binary('质检报告',
- help='点击上传质检报告')
- finance_category_id = fields.Many2one(
- 'core.category',
- string='收发类别',
- ondelete='restrict',
- states={'done': [('readonly', True)]},
- domain=[('type', '=', 'finance'), ('note', '!=', '由系统创建')],
- context={'type': 'finance'},
- help='生成凭证时从此字段上取商品科目的对方科目',
- )
-
- details = fields.Html('明细',compute='_compute_details')
-
- @api.depends('line_in_ids', 'line_out_ids')
- def _compute_details(self):
- for v in self:
- vl = {'col':[],'val':[]}
- vl['col'] = ['商品','数量']
- for l in v.line_in_ids:
- vl['val'].append([l.goods_id.name,l.goods_qty])
- for l in v.line_out_ids:
- vl['val'].append([l.goods_id.name,l.goods_qty])
- v.details = v.company_id._get_html_table(vl)
-
- def scan_barcode_move_line_operation(self, line, conversion):
- """
- 在原移库明细行中更新数量和辅助数量,不创建新行
- :return:
- """
- line.goods_qty += 1
- line.goods_uos_qty = line.goods_qty / conversion
- return True
-
- def scan_barcode_inventory_line_operation(self, line, conversion):
- '''盘点单明细行数量增加'''
- line.inventory_qty += 1
- line.inventory_uos_qty = line.inventory_qty / conversion
- line.difference_qty += 1
- line.difference_uos_qty = line.difference_qty / conversion
-
- return True
-
- def scan_barcode_move_in_out_operation(self, move, att, conversion, goods, val):
- """
- 对仓库各种移库单据上扫码的统一处理
- :return: 是否创建新的明细行
- """
- create_line = False
- loop_field = 'line_in_ids' if val['type'] == 'in' else 'line_out_ids'
- for line in move[loop_field]:
- line.cost_unit = (line.goods_id.price if val['type'] in ['out', 'internal']
- else line.goods_id.cost) # 其他出入库单 、内部调拨单
- line.price_taxed = (line.goods_id.price if val['type'] == 'out'
- else line.goods_id.cost) # 采购或销售单据
- # 如果商品属性或商品上存在条码,且明细行上已经存在该商品,则数量累加
- if (att and line.attribute_id == att) or (goods and line.goods_id == goods):
- create_line = self.scan_barcode_move_line_operation(
- line, conversion)
- return create_line
-
- def scan_barcode_inventory_operation(self, move, att, conversion, goods, val):
- '''盘点单扫码操作'''
- create_line = False
- for line in move.line_ids:
- # 如果商品属性上存在条码 或 商品上存在条码
- if (att and line.attribute_id == att) or (goods and line.goods_id == goods):
- create_line = self.scan_barcode_inventory_line_operation(
- line, conversion)
- return create_line
-
- def scan_barcode_each_model_operation(self, model_name, order_id, att, goods, conversion):
- val = {}
- create_line = False # 是否创建新的明细行
- order = self.env[model_name].browse(order_id)
- if model_name in ['wh.out', 'wh.in', 'wh.internal']:
- move = order.move_id
- # 在其他出库单上扫描条码
- if model_name == 'wh.out':
- val['type'] = 'out'
- # 在其他入库单上扫描条码
- if model_name == 'wh.in':
- val['type'] = 'in'
- # 销售出入库单的二维码
- if model_name == 'sell.delivery':
- move = order.sell_move_id
- val['type'] = order.is_return and 'in' or 'out'
- # 采购出入库单的二维码
- if model_name == 'buy.receipt':
- move = order.buy_move_id
- val['type'] = order.is_return and 'out' or 'in'
- # 调拔单的扫描条码
- if model_name == 'wh.internal':
- val['type'] = 'internal'
- if model_name != 'wh.inventory':
- create_line = self.scan_barcode_move_in_out_operation(
- move, att, conversion, goods, val)
-
- # 盘点单的扫码
- if model_name == 'wh.inventory':
- move = order
- create_line = self.scan_barcode_inventory_operation(
- move, att, conversion, goods, val)
-
- return move, create_line, val
-
-
- def check_goods_qty(self, goods, attribute, warehouse):
- '''SQL来取指定商品,属性,仓库,的当前剩余数量'''
-
- if attribute:
- change_conditions = "AND line.attribute_id = %s" % attribute.id
- elif goods:
- change_conditions = "AND line.goods_id = %s" % goods.id
- else:
- change_conditions = "AND 1 = 0"
- self.env.cr.execute('''
- SELECT sum(line.qty_remaining) as qty
- FROM wh_move_line line
-
- WHERE line.warehouse_dest_id = %s
- AND line.state = 'done'
- %s
- ''' % (warehouse.id, change_conditions,))
- return self.env.cr.fetchone()
-
- def prepare_move_line_data(self, att, val, goods, move):
- """
- 准备移库单明细数据
- :return: 字典
- """
- # 若传入的商品属性 att 上条码存在则取属性对应的商品,否则取传入的商品 goods
- goods = att and att.goods_id or goods
- goods_id = goods.id
- uos_id = goods.uos_id.id
- uom_id = goods.uom_id.id
- tax_rate = goods.tax_rate
- attribute_id = att and att.id or False
- conversion = goods.conversion
- # 采购入库取成本价,销售退货取销售价;采购退货取成本价,销售发货取销售价
- price_taxed = move._name == 'buy.receipt' and goods.cost or goods.price
- cost_unit = val.get('type') != 'in' and 0 or goods.cost
-
- val.update({
- 'goods_id': goods_id,
- 'attribute_id': attribute_id,
- 'warehouse_id': move.warehouse_id.id,
- 'uos_id': uos_id,
- 'uom_id': uom_id,
- })
- if move._name != 'wh.inventory':
- val.update({
- 'warehouse_dest_id': move.warehouse_dest_id.id,
- 'goods_uos_qty': 1.0 / conversion,
- 'goods_qty': 1,
- 'price_taxed': price_taxed,
- 'tax_rate': tax_rate,
- 'cost_unit': cost_unit,
- 'move_id': move.id})
- else:
- val.update({
- 'inventory_uos_qty': 1.0 / conversion,
- 'inventory_qty': 1,
- 'real_uos_qty': 0,
- 'real_qty': 0,
- 'difference_uos_qty': 1.0 / conversion,
- 'difference_qty': 1,
- 'inventory_id': move.id})
- return val
-
- @api.model
- def check_barcode(self, model_name, order_id, att, goods):
- pass
-
- @api.model
- def scan_barcode(self, model_name, barcode, order_id):
- """
- 扫描条码
- :param model_name: 模型名
- :param barcode: 条码
- :param order_id: 单据id
- :return:
- """
- att = self.env['attribute'].search([('ean', '=', barcode)])
- goods = self.env['goods'].search([('barcode', '=', barcode)])
- line_model = (model_name == 'wh.inventory' and 'wh.inventory.line'
- or 'wh.move.line')
-
- if not att and not goods:
- raise UserError('条码为 %s 的商品不存在' % (barcode))
- else:
- self.check_barcode(model_name, order_id, att, goods)
- conversion = att and att.goods_id.conversion or goods.conversion
- move, create_line, val = self.scan_barcode_each_model_operation(
- model_name, order_id, att, goods, conversion)
- if not create_line:
- self.env[line_model].create(
- self.prepare_move_line_data(att, val, goods, move))
-
- def check_qc_result(self):
- """
- 检验质检报告是否上传
- :return:
- """
- qc_rule = self.env['qc.rule'].search([
- ('move_type', '=', self.origin),
- ('warehouse_id', '=', self.warehouse_id.id),
- ('warehouse_dest_id', '=', self.warehouse_dest_id.id)])
- if qc_rule and not self.qc_result:
- raise UserError('请先上传质检报告')
-
- def prev_approve_order(self):
- """
- 确认单据之前所做的处理
- :return:
- """
- for order in self:
- if not order.line_out_ids and not order.line_in_ids:
- raise UserError('单据的明细行不可以为空')
- order.check_qc_result()
-
-
- def approve_order(self):
- """
- 确认单据
- :return:
- """
- for order in self:
- order.prev_approve_order()
- order.line_out_ids.action_done()
- order.line_in_ids.action_done()
-
- # 每次移库完成,清空库位上商品数量为0的商品和属性(不合逻辑的数据)
- for loc in self.env['location'].search([('save_qty', '=', 0),
- ('goods_id', '!=', False)
- ]):
- if not loc.current_qty:
- continue # pragma: no cover
- return self.write({
- 'approve_uid': self.env.uid,
- 'approve_date': fields.Datetime.now(self),
- })
-
- def prev_cancel_approved_order(self):
- pass
-
-
- def cancel_approved_order(self):
- """
- 撤销确认单据
- :return:
- """
- for order in self:
- order.prev_cancel_approved_order()
- order.line_out_ids.action_draft()
- order.line_in_ids.action_draft()
-
- return self.write({
- 'approve_uid': False,
- 'approve_date': False,
- })
-
-
- def write(self, vals):
- """
- 作废明细行
- """
- if vals.get('state', False) == 'cancel':
- for order in self:
- order.line_out_ids.action_cancel()
- order.line_in_ids.action_cancel()
-
- return super(WhMove, self).write(vals)
-
-
- def create_zero_wh_in(self, wh_in, model_name):
- """
- 创建一个缺货向导
- :param wh_in: 单据实例
- :param model_name: 单据模型
- :return:
- """
- all_line_message = ""
- today = fields.Datetime.now()
- line_in_ids = []
- goods_list = []
- for line in wh_in.line_out_ids:
- if line.goods_id.no_stock:
- continue
-
- result = self.check_goods_qty(
- line.goods_id, line.attribute_id, wh_in.warehouse_id)
- result = result[0] or 0
- if line.goods_qty > result and not line.lot_id and not self.env.context.get('wh_in_line_ids'):
- # 在销售出库时如果临时缺货,自动生成一张盘盈入库单
- if (line.goods_id.id, line.attribute_id.id) in goods_list:
- continue
- goods_list.append((line.goods_id.id, line.attribute_id.id))
- all_line_message += '商品 %s ' % line.goods_id.name
- if line.attribute_id:
- all_line_message += ' 型号%s' % line.attribute_id.name
- line_in_ids.append((0, 0, {
- 'goods_id': line.goods_id.id,
- 'attribute_id': line.attribute_id.id,
- 'goods_uos_qty': 0,
- 'uos_id': line.uos_id.id,
- 'goods_qty': 0,
- 'uom_id': line.uom_id.id,
- 'cost_unit': line.goods_id.cost,
- 'state': 'done',
- 'date': today}))
- all_line_message += u" 当前库存量不足,继续出售请点击确定,并及时盘点库存\n"
-
- if line.goods_qty <= 0 or line.price_taxed < 0:
- raise UserError('商品 %s 的数量和含税单价不能小于0。' % line.goods_id.name)
- if line_in_ids:
- vals = {
- 'type': 'inventory',
- 'warehouse_id': self.env.ref('warehouse.warehouse_inventory').id,
- 'warehouse_dest_id': wh_in.warehouse_id.id,
- 'state': 'done',
- 'date': today,
- 'line_in_ids': line_in_ids}
- return self.env[model_name].with_context(
- {'active_model':model_name}
- ).open_dialog('goods_inventory', {
- 'message': all_line_message,
- 'args': [vals],
- })
|