GoodERP
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

503 linhas
19KB

  1. # -*- coding: utf-8 -*-
  2. from .utils import create_name, safe_division
  3. from odoo.exceptions import UserError
  4. from odoo import models
  5. from odoo import _
  6. from odoo import fields
  7. from odoo import api
  8. from odoo.tools import float_compare, float_is_zero
  9. class WhInventory(models.Model):
  10. _name = 'wh.inventory'
  11. _description = '盘点单'
  12. _inherit = ['mail.thread']
  13. _order = 'date DESC, id DESC'
  14. INVENTORY_STATE = [
  15. ('draft', '草稿'),
  16. ('query', '查询中'),
  17. ('confirmed', '待确认盘盈盘亏'),
  18. ('done', '完成'),
  19. ]
  20. @api.model
  21. def _get_default_warehouse_impl(self):
  22. if self.env.context.get('warehouse_type', 'stock'):
  23. return self.env['warehouse'].get_warehouse_by_type(
  24. self.env.context.get('warehouse_type', 'stock'))
  25. @api.model
  26. def _get_default_warehouse(self):
  27. '''获取盘点仓库'''
  28. return self._get_default_warehouse_impl()
  29. date = fields.Date('日期', default=fields.Date.context_today,
  30. help='盘点单创建日期,默认为当前天')
  31. name = fields.Char('名称', copy=False, default='/',
  32. help='单据编号,创建时会自动生成')
  33. warehouse_id = fields.Many2one('warehouse', '仓库', required=True, default=_get_default_warehouse,
  34. help='盘点单盘点的仓库')
  35. goods = fields.Many2many('goods', string='商品',
  36. help='盘点单盘点的商品')
  37. out_id = fields.Many2one('wh.out', '盘亏单据', copy=False,
  38. help='盘亏生成的其他出库单单据')
  39. in_id = fields.Many2one('wh.in', '盘盈单据', copy=False,
  40. help='盘盈生成的其他入库单单据')
  41. state = fields.Selection(
  42. INVENTORY_STATE, '状态', copy=False, default='draft',
  43. index=True,
  44. tracking=True,
  45. help='盘点单状态,新建时状态为草稿;'
  46. '点击查询后为确认后状态为查询中;'
  47. '有盘亏盘盈时生成的其他出入库单没有确认时状态为待确认盘盈盘亏;'
  48. '盘亏盘盈生成的其他出入库单确认后状态为完成')
  49. line_ids = fields.One2many(
  50. 'wh.inventory.line', 'inventory_id', '明细', copy=False,
  51. help='盘点单的明细行')
  52. note = fields.Text('备注',
  53. help='可以为该单据添加一些需要的标识信息')
  54. company_id = fields.Many2one(
  55. 'res.company',
  56. string='公司',
  57. change_default=True,
  58. default=lambda self: self.env.company)
  59. def requery_inventory(self):
  60. self.delete_confirmed_wh()
  61. self.state = 'query'
  62. @api.model_create_multi
  63. @create_name
  64. def create(self, vals_list):
  65. return super(WhInventory, self).create(vals_list)
  66. def unlink(self):
  67. for inventory in self:
  68. inventory.delete_confirmed_wh()
  69. return super(WhInventory, self).unlink()
  70. def delete_confirmed_wh(self):
  71. for inventory in self:
  72. if inventory.state == 'confirmed':
  73. if (inventory.out_id and inventory.out_id.state == 'done') \
  74. or (inventory.in_id and inventory.in_id.state == 'done'):
  75. raise UserError(_('请先撤销掉相关的盘盈盘亏单据'))
  76. else:
  77. inventory.out_id.unlink()
  78. inventory.in_id.unlink()
  79. return True
  80. def check_done(self):
  81. for inventory in self:
  82. if inventory.state == 'confirmed' and \
  83. (not inventory.out_id or inventory.out_id.state == 'done') and \
  84. (not inventory.in_id or inventory.in_id.state == 'done'):
  85. self.state = 'done'
  86. return True
  87. if inventory.state == 'done' and \
  88. (not inventory.out_id or inventory.out_id.state != 'done') and \
  89. (not inventory.in_id or inventory.in_id.state != 'done'):
  90. self.state = 'confirmed'
  91. return True
  92. return False
  93. def open_out(self):
  94. for inventory in self:
  95. return {
  96. 'type': 'ir.actions.act_window',
  97. 'res_model': 'wh.out',
  98. 'view_mode': 'form',
  99. 'res_id': inventory.out_id.id,
  100. }
  101. def open_in(self):
  102. for inventory in self:
  103. return {
  104. 'type': 'ir.actions.act_window',
  105. 'res_model': 'wh.in',
  106. 'view_mode': 'form',
  107. 'res_id': inventory.in_id.id,
  108. }
  109. def delete_line(self):
  110. self.line_ids.unlink()
  111. def create_losses_out(self, inventory, out_line):
  112. inventory_warehouse = self.env.ref('warehouse.warehouse_inventory')
  113. out_vals = {
  114. 'warehouse_dest_id': inventory_warehouse.id,
  115. 'warehouse_id': inventory.warehouse_id.id,
  116. 'type': 'inventory',
  117. 'line_out_ids': [],
  118. }
  119. for line in out_line:
  120. out_vals['line_out_ids'].append(
  121. [0, False, line.get_move_line(wh_type='out')])
  122. out_id = self.env['wh.out'].create(out_vals)
  123. inventory.out_id = out_id
  124. def create_overage_in(self, inventory, in_line):
  125. inventory_warehouse = self.env.ref('warehouse.warehouse_inventory')
  126. in_vals = {
  127. 'warehouse_dest_id': inventory.warehouse_id.id,
  128. 'warehouse_id': inventory_warehouse.id,
  129. 'type': 'inventory',
  130. 'line_in_ids': [],
  131. }
  132. for line in in_line:
  133. in_vals['line_in_ids'].append(
  134. [0, False, line.get_move_line(wh_type='in')])
  135. in_id = self.env['wh.in'].create(in_vals)
  136. inventory.in_id = in_id
  137. def generate_inventory(self):
  138. for inventory in self:
  139. if self.state in ['done', 'confirmed']:
  140. raise UserError(_('请不要重复点击生成盘点单据按钮'))
  141. out_line, in_line = [], []
  142. for line in inventory.line_ids:
  143. if line.difference_qty < 0:
  144. out_line.append(line)
  145. elif line.difference_qty > 0:
  146. in_line.append(line)
  147. if out_line:
  148. self.create_losses_out(inventory, out_line)
  149. if in_line:
  150. self.create_overage_in(inventory, in_line)
  151. if len(out_line) + len(in_line) == 0:
  152. inventory.state = 'done'
  153. if out_line or in_line:
  154. inventory.state = 'confirmed'
  155. return True
  156. def get_line_detail(self):
  157. for inventory in self:
  158. sql_text = '''
  159. SELECT wh.id as warehouse_id,
  160. goods.id as goods_id,
  161. line.attribute_id as attribute_id,
  162. line.lot as lot,
  163. uom.id as uom_id,
  164. uos.id as uos_id,
  165. sum(line.qty_remaining) as qty,
  166. sum(line.uos_qty_remaining) as uos_qty
  167. FROM wh_move_line line
  168. LEFT JOIN goods goods ON line.goods_id = goods.id
  169. LEFT JOIN uom uom ON goods.uom_id = uom.id
  170. LEFT JOIN uom uos ON goods.uos_id = uos.id
  171. LEFT JOIN warehouse wh ON line.warehouse_dest_id = wh.id
  172. WHERE line.qty_remaining != 0
  173. AND wh.type = 'stock'
  174. AND line.state = 'done'
  175. AND (goods.no_stock IS NULL OR goods.no_stock = FALSE)
  176. %s
  177. GROUP BY
  178. wh.id, line.lot, line.attribute_id, goods.id, uom.id, uos.id
  179. ORDER BY
  180. goods.id, line.lot
  181. '''
  182. extra_text = ' AND wh.id = %s' % inventory.warehouse_id.id
  183. if inventory.goods:
  184. goods_ids = inventory.goods.ids
  185. goods_ids.append(0)
  186. extra_text += " AND goods.id IN {ids}".format(
  187. ids=tuple(set(goods_ids)))
  188. inventory.env.cr.execute(sql_text % extra_text)
  189. res = inventory.env.cr.dictfetchall()
  190. for line in res:
  191. # 盘点单查询的盘点数量不应该包含移库在途的 #1358
  192. for int_line in self.env['wh.move.line'].search(
  193. [('goods_id', '=', line['goods_id']),
  194. ('attribute_id', '=', line['attribute_id']),
  195. ('lot_id', '=', line['lot']),
  196. ('warehouse_id', '=', line['warehouse_id']),
  197. ('type', '=', 'internal'),
  198. ('state', '=', 'draft')]):
  199. line['qty'] -= int_line.goods_qty
  200. line['uos_qty'] -= int_line.goods_uos_qty
  201. if not line['qty']:
  202. res.remove(line)
  203. return res
  204. def query_inventory(self):
  205. line_obj = self.env['wh.inventory.line']
  206. for inventory in self:
  207. inventory.delete_line()
  208. line_ids = inventory.get_line_detail()
  209. for line in line_ids:
  210. line_obj.create_wh_inventory_line_by_data(inventory.id, line)
  211. if line_ids:
  212. inventory.state = 'query'
  213. return True
  214. class WhInventoryLine(models.Model):
  215. _name = 'wh.inventory.line'
  216. _description = '盘点单明细'
  217. LOT_TYPE = [
  218. ('out', '出库'),
  219. ('in', '入库'),
  220. ('nothing', '不做处理'),
  221. ]
  222. @api.depends('inventory_qty', 'real_qty', 'inventory_uos_qty', 'real_uos_qty')
  223. def _get_difference_qty(self):
  224. for line in self:
  225. line.difference_qty = line.inventory_qty - line.real_qty
  226. line.difference_uos_qty = line.inventory_uos_qty - line.real_uos_qty
  227. if float_is_zero(line.difference_qty, 2) and not float_is_zero(line.difference_uos_qty, 2):
  228. line.difference_qty = line.difference_uos_qty * line.goods_id.conversion
  229. if not float_is_zero(line.difference_qty, 2) and line.difference_uos_qty == 0:
  230. line.difference_uos_qty = line.difference_qty / line.goods_id.conversion
  231. @api.depends('inventory_uos_qty', 'real_uos_qty')
  232. def _get_difference_uos_qty(self):
  233. for line in self:
  234. line.difference_uos_qty = line.inventory_uos_qty - line.real_uos_qty
  235. inventory_id = fields.Many2one('wh.inventory', '盘点', ondelete='cascade',
  236. help='盘点单行对应的盘点单')
  237. warehouse_id = fields.Many2one('warehouse', '仓库', required=True, ondelete='restrict',
  238. help='盘点单行对应的仓库')
  239. goods_id = fields.Many2one('goods', '商品', required=True, ondelete='restrict',
  240. help='盘点单行对应的商品')
  241. attribute_id = fields.Many2one('attribute', '属性', ondelete='restrict',
  242. help='盘点单行对应的商品的属性')
  243. using_batch = fields.Boolean(
  244. related='goods_id.using_batch', string='批号管理',
  245. help='盘点单行对应的商品是否使用批号管理,是True否则False')
  246. force_batch_one = fields.Boolean(
  247. related='goods_id.force_batch_one', string='每批号数量为1',
  248. help='盘点单行对应的商品是否使用每批号数量为1,是True否则False')
  249. lot = fields.Char('批号',
  250. help='盘点单行对应的商品批号')
  251. new_lot = fields.Char('盘盈批号',
  252. help='盘点单行对应的商品盘盈批号')
  253. new_lot_id = fields.Many2one('wh.move.line', '盘亏批号',
  254. ondelete='restrict',
  255. help='盘点单行对应的商品盘亏批号')
  256. lot_type = fields.Selection(LOT_TYPE, '批号类型', default='nothing',
  257. help='批号类型: 出库、入库、不做处理')
  258. uom_id = fields.Many2one('uom', '单位', ondelete='restrict',
  259. help='盘点单行对应的商品的计量单位')
  260. uos_id = fields.Many2one('uom', '辅助单位', ondelete='restrict',
  261. help='盘点单行对应的商品的辅助单位')
  262. real_qty = fields.Float(
  263. '账面数量', digits='Quantity',
  264. help='盘点单行对应的商品的账面数量')
  265. real_uos_qty = fields.Float(
  266. '账面辅助数量', digits='Quantity',
  267. help='盘点单行对应的商品的账面辅助数量')
  268. inventory_qty = fields.Float(
  269. '实际数量', digits='Quantity',
  270. required=True,
  271. help='盘点单行对应的商品的实际数量')
  272. inventory_uos_qty = fields.Float(
  273. '实际辅助数量', digits='Quantity',
  274. required=True,
  275. help='盘点单行对应的商品的实际辅助数量')
  276. difference_qty = fields.Float(
  277. '差异数量', digits='Quantity',
  278. compute='_get_difference_qty',
  279. help='盘点单行对应的商品的差异数量')
  280. difference_uos_qty = fields.Float(
  281. '差异辅助数量', digits='Quantity',
  282. compute='_get_difference_uos_qty',
  283. help='盘点单行对应的商品的差异辅助数量')
  284. company_id = fields.Many2one(
  285. 'res.company',
  286. string='公司',
  287. change_default=True,
  288. default=lambda self: self.env.company)
  289. def check_difference_identical(self):
  290. if self.difference_qty * self.difference_uos_qty < 0:
  291. self.inventory_qty = self.real_qty
  292. self.inventory_uos_qty = self.real_uos_qty
  293. return {'warning': {
  294. 'title': _('错误'),
  295. 'message': _('盘盈盘亏数量应该与辅助单位的盘盈盘亏数量盈亏方向一致'),
  296. }}
  297. def create_wh_inventory_line_by_data(self, inventory_id, line_data):
  298. self.create({
  299. 'inventory_id': inventory_id,
  300. 'warehouse_id': line_data.get('warehouse_id'),
  301. 'goods_id': line_data.get('goods_id'),
  302. 'attribute_id': line_data.get('attribute_id'),
  303. 'lot': line_data.get('lot'),
  304. 'uom_id': line_data.get('uom_id'),
  305. 'uos_id': line_data.get('uos_id'),
  306. 'real_qty': line_data.get('qty'),
  307. 'real_uos_qty': line_data.get('uos_qty'),
  308. 'inventory_qty': line_data.get('qty'),
  309. 'inventory_uos_qty': line_data.get('uos_qty'),
  310. })
  311. def line_role_back(self):
  312. self.inventory_qty = self.real_qty
  313. self.inventory_uos_qty = self.real_uos_qty
  314. self.difference_qty = 0
  315. self.difference_uos_qty = 0
  316. self.new_lot = ''
  317. self.new_lot_id = False
  318. self.lot_type = 'nothing'
  319. @api.onchange('inventory_qty')
  320. def onchange_qty(self):
  321. self.ensure_one()
  322. if self.goods_id and self.goods_id.using_batch:
  323. if self.goods_id.force_batch_one and self.difference_qty:
  324. flag = self.difference_qty > 0 and 1 or -1
  325. if abs(self.difference_qty) != 1:
  326. self.line_role_back()
  327. return {'warning': {
  328. 'title': _('警告'),
  329. 'message': _('商品上设置了序号为1,此时一次只能盘亏或盘盈一个商品数量'),
  330. }}
  331. if self.difference_qty > 0:
  332. self.lot_type = 'in'
  333. self.new_lot = self.new_lot or self.lot
  334. self.new_lot_id = False
  335. elif self.difference_qty < 0:
  336. self.lot_type = 'out'
  337. self.new_lot = ''
  338. else:
  339. self.lot_type = 'nothing'
  340. self.new_lot_id = False
  341. self.new_lot = ''
  342. return self.check_difference_identical()
  343. @api.onchange('inventory_uos_qty')
  344. def onchange_uos_qty(self):
  345. self.inventory_qty = self.goods_id.conversion_unit(
  346. self.inventory_uos_qty)
  347. def get_move_line(self, wh_type='in', context=None):
  348. inventory_warehouse = self.env['warehouse'] \
  349. .get_warehouse_by_type('inventory')
  350. for inventory in self:
  351. cost, cost_unit = inventory.goods_id. \
  352. get_suggested_cost_by_warehouse(
  353. inventory.warehouse_id, abs(inventory.difference_qty),
  354. lot_id=inventory.new_lot_id,
  355. attribute=inventory.attribute_id)
  356. if wh_type == 'out':
  357. lot_id = self.env['wh.move.line'].search([('warehouse_dest_id', '=', inventory.warehouse_id.id),
  358. ('goods_id', '=', inventory.goods_id.id),
  359. ('lot', '=', inventory.lot),
  360. ('state', '=', 'done')
  361. ], limit=1)
  362. res = {
  363. 'type': wh_type,
  364. 'lot': inventory.lot,
  365. 'lot_id': lot_id.id,
  366. 'goods_id': inventory.goods_id.id,
  367. 'attribute_id': inventory.attribute_id.id,
  368. 'uom_id': inventory.uom_id.id,
  369. 'uos_id': inventory.uos_id.id,
  370. 'cost_unit': cost_unit,
  371. 'cost': cost,
  372. }
  373. elif wh_type == 'in':
  374. res = {
  375. 'type': wh_type,
  376. 'lot': inventory.new_lot,
  377. 'lot_id': inventory.new_lot_id.id,
  378. 'goods_id': inventory.goods_id.id,
  379. 'attribute_id': inventory.attribute_id.id,
  380. 'uom_id': inventory.uom_id.id,
  381. 'uos_id': inventory.uos_id.id,
  382. 'cost_unit': cost_unit,
  383. 'cost': cost,
  384. }
  385. difference_qty, difference_uos_qty = abs(
  386. inventory.difference_qty), abs(inventory.difference_uos_qty)
  387. res.update({'goods_qty': difference_qty})
  388. return res
  389. class WhOut(models.Model):
  390. _inherit = 'wh.out'
  391. inventory_ids = fields.One2many('wh.inventory', 'out_id', '盘点单',
  392. help='其他出库单行对应的盘点单行,盘亏的情况')
  393. def approve_order(self):
  394. res = super(WhOut, self).approve_order()
  395. for order in self:
  396. order.inventory_ids.check_done()
  397. return res
  398. def cancel_approved_order(self):
  399. res = super(WhOut, self).cancel_approved_order()
  400. for order in self:
  401. order.inventory_ids.check_done()
  402. return res
  403. class WhIn(models.Model):
  404. _inherit = 'wh.in'
  405. inventory_ids = fields.One2many('wh.inventory', 'in_id', '盘点单',
  406. help='其他入库单行对应的盘点单行,盘盈的情况')
  407. def approve_order(self):
  408. res = super(WhIn, self).approve_order()
  409. for order in self:
  410. order.inventory_ids.check_done()
  411. return res
  412. def cancel_approved_order(self):
  413. res = super(WhIn, self).cancel_approved_order()
  414. for order in self:
  415. order.inventory_ids.check_done()
  416. return res
上海开阖软件有限公司 沪ICP备12045867号-1