GoodERP
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

597 行
28KB

  1. from logging import Logger
  2. import logging
  3. from .utils import safe_division
  4. from jinja2 import Environment, PackageLoader
  5. from odoo import models, fields, api
  6. from odoo.exceptions import UserError
  7. from odoo.tools import float_compare
  8. from decimal import Decimal
  9. import logging
  10. _logger = logging.getLogger(__name__)
  11. env = Environment(loader=PackageLoader(
  12. 'odoo.addons.warehouse', 'html'), autoescape=True)
  13. class WhMoveLine(models.Model):
  14. _name = 'wh.move.line'
  15. _description = '移库单明细'
  16. _order = 'lot'
  17. _rec_name = 'note'
  18. MOVE_LINE_TYPE = [
  19. ('out', '出库'),
  20. ('in', '入库'),
  21. ('internal', '内部调拨'),
  22. ]
  23. MOVE_LINE_STATE = [
  24. ('draft', '草稿'),
  25. ('done', '已完成'),
  26. ('cancel', '已作废'),
  27. ]
  28. ORIGIN_EXPLAIN = {
  29. ('wh.assembly', 'out'): '组装单子件',
  30. ('wh.assembly', 'in'): '组装单组合件',
  31. ('wh.disassembly', 'out'): '拆卸单组合件',
  32. ('wh.disassembly', 'in'): '拆卸单子件',
  33. ('wh.internal', True): '调拨出库',
  34. ('wh.internal', False): '调拨入库',
  35. 'wh.out.inventory': '盘亏',
  36. 'wh.out.others': '其他出库',
  37. 'wh.in.inventory': '盘盈',
  38. 'wh.in.others': '其他入库',
  39. 'buy.receipt.buy': '采购入库',
  40. 'buy.receipt.return': '采购退货',
  41. 'sell.delivery.sell': '销售出库',
  42. 'sell.delivery.return': '销售退货',
  43. }
  44. @api.depends('goods_qty', 'price_taxed', 'discount_amount', 'tax_rate', 'currency_id')
  45. def _compute_all_amount(self):
  46. """当订单行的数量、含税单价、折扣额、税率改变时,改变金额、税额、价税合计"""
  47. for wml in self:
  48. if wml.tax_rate > 100:
  49. raise UserError('税率不能输入超过100的数')
  50. if wml.tax_rate < 0:
  51. raise UserError('税率不能输入负数')
  52. wml.onchange_price()
  53. wml.currency_rate = wml.env['res.currency'].get_rate_silent(wml.date, wml.currency_id.id) or 1
  54. wml.subtotal = wml.price_taxed * wml.goods_qty - wml.discount_amount # 价税合计
  55. wml.standard_subtotal = wml.subtotal * wml.currency_rate # 本位币价税合计
  56. wml.tax_amount = wml.subtotal / \
  57. (100 + wml.tax_rate) * wml.tax_rate # 税额
  58. wml.amount = wml.subtotal - wml.tax_amount # 金额
  59. wml.standard_amount = wml.amount * wml.currency_rate # 本位币金额
  60. @api.onchange('price', 'tax_rate')
  61. def onchange_price(self):
  62. if not self.goods_id:
  63. return
  64. """当订单行的不含税单价改变时,改变含税单价"""
  65. price = self.price_taxed / (1 + self.tax_rate * 0.01) # 不含税单价
  66. decimal = self.env.ref('core.decimal_price')
  67. if float_compare(price, self.price, precision_digits=decimal.digits) != 0:
  68. self.price_taxed = self.price * (1 + self.tax_rate * 0.01)
  69. @api.depends('goods_id')
  70. def _compute_using_attribute(self):
  71. for wml in self:
  72. wml.using_attribute = wml.goods_id.attribute_ids and True or False
  73. @api.depends('move_id.warehouse_id')
  74. def _get_line_warehouse(self):
  75. for wml in self:
  76. wml.warehouse_id = wml.move_id.warehouse_id.id # 关联单据头调出仓库
  77. if (wml.move_id.origin in ('wh.assembly', 'wh.disassembly', 'outsource')) and wml.type == 'in':
  78. wml.warehouse_id = self.env.ref(
  79. 'warehouse.warehouse_production').id
  80. @api.depends('move_id.warehouse_dest_id')
  81. def _get_line_warehouse_dest(self):
  82. for wml in self:
  83. wml.warehouse_dest_id = wml.move_id.warehouse_dest_id.id # 关联单据头调入仓库
  84. if (wml.move_id.origin in ('wh.assembly', 'wh.disassembly', 'outsource')) and wml.type == 'out':
  85. wml.warehouse_dest_id = self.env.ref(
  86. 'warehouse.warehouse_production').id
  87. @api.depends('goods_id')
  88. def _compute_uom_uos(self):
  89. for wml in self:
  90. if wml.goods_id:
  91. wml.uom_id = wml.goods_id.uom_id
  92. wml.uos_id = wml.goods_id.uos_id
  93. @api.depends('goods_qty', 'goods_id')
  94. def _get_goods_uos_qty(self):
  95. for wml in self:
  96. if wml.goods_id and wml.goods_qty:
  97. wml.goods_uos_qty = wml.goods_qty / wml.goods_id.conversion
  98. else:
  99. wml.goods_uos_qty = 0
  100. def _inverse_goods_qty(self):
  101. for wml in self:
  102. wml.goods_qty = wml.goods_uos_qty * wml.goods_id.conversion
  103. @api.depends('goods_id', 'goods_qty')
  104. def compute_line_net_weight(self):
  105. for move_line in self:
  106. move_line.line_net_weight = move_line.goods_id.net_weight * move_line.goods_qty
  107. move_id = fields.Many2one('wh.move', string='移库单', ondelete='cascade',
  108. help='出库/入库/移库单行对应的移库单')
  109. partner_id = fields.Many2one('partner',string='业务伙伴',related='move_id.partner_id',store=True)
  110. plan_date = fields.Date('计划日期', default=fields.Date.context_today)
  111. date = fields.Date('完成日期', copy=False,
  112. help='单据完成日期')
  113. cost_time = fields.Datetime('确认时间', copy=False,
  114. help='单据确认时间')
  115. type = fields.Selection(MOVE_LINE_TYPE,
  116. '类型',
  117. required=True,
  118. default=lambda self: self.env.context.get('type'),
  119. help='类型:出库、入库 或者 内部调拨')
  120. state = fields.Selection(MOVE_LINE_STATE, '状态', copy=False, default='draft',
  121. index=True,
  122. help='状态标识,新建时状态为草稿;确认后状态为已完成')
  123. goods_id = fields.Many2one('goods', string='商品', required=True,
  124. index=True, ondelete='restrict',
  125. help='该单据行对应的商品')
  126. goods_class = fields.Char('商品类别', related='goods_id.goods_class_id.name')
  127. using_attribute = fields.Boolean(compute='_compute_using_attribute', string='使用属性',
  128. help='该单据行对应的商品是否存在属性,存在True否则False')
  129. attribute_id = fields.Many2one('attribute', '属性', ondelete='restrict', index=True,
  130. help='该单据行对应的商品的属性')
  131. using_batch = fields.Boolean(related='goods_id.using_batch', string='批号管理',
  132. readonly=True,
  133. help='该单据行对应的商品是否使用批号管理')
  134. force_batch_one = fields.Boolean(related='goods_id.force_batch_one', string='每批号数量为1',
  135. readonly=True,
  136. help='该单据行对应的商品是否每批号数量为1,是True否则False')
  137. lot = fields.Char('入库批号',
  138. help='该单据行对应的商品的批号,一般是入库单行')
  139. lot_id = fields.Many2one('wh.move.line', '批号',
  140. domain="[('goods_id', '=', goods_id), ('state', '=', 'done'), ('lot', '!=', False), "
  141. "('qty_remaining', '>', 0), ('warehouse_dest_id', '=', warehouse_id)]",
  142. help='该单据行对应的商品的批号,一般是出库单行')
  143. lot_qty = fields.Float(related='lot_id.qty_remaining', string='批号数量',
  144. digits='Quantity',
  145. help='该单据行对应的商品批号的商品剩余数量')
  146. lot_uos_qty = fields.Float('批号辅助数量',
  147. digits='Quantity',
  148. help='该单据行对应的商品的批号辅助数量')
  149. location_id = fields.Many2one('location', ondelete='restrict', string='库位', index=True)
  150. production_date = fields.Date('生产日期', default=fields.Date.context_today,
  151. help='商品的生产日期')
  152. shelf_life = fields.Integer('保质期(天)',
  153. help='商品的保质期(天)')
  154. uom_id = fields.Many2one('uom', string='单位', ondelete='restrict', compute=_compute_uom_uos,
  155. help='商品的计量单位', store=True)
  156. uos_id = fields.Many2one('uom', string='辅助单位', ondelete='restrict', compute=_compute_uom_uos,
  157. readonly=True, help='商品的辅助单位', store=True)
  158. warehouse_id = fields.Many2one('warehouse', '调出仓库',
  159. ondelete='restrict',
  160. store=True,
  161. index=True,
  162. compute=_get_line_warehouse,
  163. help='单据的来源仓库')
  164. warehouse_dest_id = fields.Many2one('warehouse', '调入仓库',
  165. ondelete='restrict',
  166. store=True,
  167. index=True,
  168. compute=_get_line_warehouse_dest,
  169. help='单据的目的仓库')
  170. goods_qty = fields.Float('数量',
  171. digits='Quantity',
  172. default=1,
  173. required=True,
  174. help='商品的数量')
  175. all_lack = fields.Float('缺货数量', digits='Quantity', compute="_get_lack")
  176. wh_lack = fields.Float('本仓缺货', digits='Quantity', compute="_get_lack")
  177. goods_uos_qty = fields.Float('辅助数量', digits='Quantity',
  178. compute=_get_goods_uos_qty, inverse=_inverse_goods_qty, store=True,
  179. help='商品的辅助数量')
  180. currency_id = fields.Many2one('res.currency',
  181. '外币币别',
  182. help='外币币别')
  183. is_multi_currency = fields.Boolean('多币种', related='company_id.is_multi_currency')
  184. currency_rate = fields.Float('汇率', digits='Price')
  185. price = fields.Float('单价',
  186. store=True,
  187. digits='Price',
  188. help='商品的单价')
  189. price_taxed = fields.Float('含税单价',
  190. digits='Price',
  191. help='商品的含税单价')
  192. discount_rate = fields.Float('折扣率%',
  193. help='单据的折扣率%')
  194. discount_amount = fields.Float('折扣额',
  195. digits='Amount',
  196. help='单据的折扣额')
  197. amount = fields.Float('金额', compute=_compute_all_amount, store=True,
  198. digits='Amount',
  199. help='单据的金额,计算得来')
  200. standard_amount = fields.Float('本位币金额', compute=_compute_all_amount,
  201. store=True, readonly=True,
  202. digits='总数',
  203. help='本位币的成交金额')
  204. tax_rate = fields.Float('税率(%)',
  205. help='单据的税率(%)')
  206. tax_amount = fields.Float('税额', compute=_compute_all_amount, store=True,
  207. digits='Amount',
  208. help='单据的税额,有单价×数量×税率计算得来')
  209. subtotal = fields.Float('价税合计', compute=_compute_all_amount, store=True,
  210. digits='Amount',
  211. help='价税合计,有不含税金额+税额计算得来')
  212. standard_subtotal = fields.Float('本位币价税合计', compute=_compute_all_amount, store=True,
  213. digits='总数',
  214. help='本位币价税合计,有不含税金额+税额计算得来')
  215. note = fields.Text('备注',
  216. help='可以为该单据添加一些需要的标识信息')
  217. cost_unit = fields.Float('单位成本', digits='Price',
  218. help='入库/出库单位成本')
  219. cost = fields.Float('成本', compute='_compute_cost', inverse='_inverse_cost',
  220. digits='Amount', store=True,
  221. help='入库/出库成本')
  222. line_net_weight = fields.Float(
  223. string='净重小计', digits='Weight', compute=compute_line_net_weight, store=True)
  224. expiration_date = fields.Date('过保日',
  225. help='商品保质期截止日期')
  226. company_id = fields.Many2one(
  227. 'res.company',
  228. string='公司',
  229. change_default=True,
  230. default=lambda self: self.env.company)
  231. scrap = fields.Boolean('报废')
  232. share_cost = fields.Float('采购费用',
  233. digits='Amount',
  234. help='点击分摊按钮或确认时将采购费用进行分摊得出的费用')
  235. bill_date = fields.Date('单据日期', related='move_id.date', help='单据创建日期,默认为当前天')
  236. bill_finance_category_id = fields.Many2one(
  237. 'core.category',
  238. string='收发类别',
  239. ondelete='restrict', related='move_id.finance_category_id',
  240. help='生成凭证时从此字段上取商品科目的对方科目',
  241. )
  242. @api.model_create_multi
  243. def create(self, vals_list):
  244. new_ids = super(WhMoveLine, self).create(vals_list)
  245. for new_id in new_ids:
  246. # 只针对入库单行
  247. if new_id.type != 'out' and not new_id.location_id and new_id.warehouse_dest_id:
  248. # 有库存的产品
  249. qty_now = self.move_id.check_goods_qty(
  250. new_id.goods_id, new_id.attribute_id, new_id.warehouse_dest_id)[0]
  251. if qty_now:
  252. # 建议将产品上架到现有库位上
  253. new_id.location_id = new_id.env['location'].search([('goods_id', '=', new_id.goods_id.id),
  254. ('attribute_id', '=',
  255. new_id.attribute_id and new_id.attribute_id.id or False),
  256. ('warehouse_id', '=', new_id.warehouse_dest_id.id)],
  257. limit=1)
  258. return new_ids
  259. @api.depends('cost_unit', 'price', 'goods_qty', 'discount_amount', 'share_cost', 'currency_id')
  260. def _compute_cost(self):
  261. for wml in self:
  262. wml.cost = 0
  263. if (wml.env.context.get('type') == 'in' or wml.type == 'in') and wml.goods_id:
  264. if wml.price: # 按采购价记成本
  265. if wml.currency_id: # 如果是外币,则以本位币计成本
  266. wml.cost = (wml.price * wml.goods_qty - wml.discount_amount) * wml.currency_rate + wml.share_cost
  267. else:
  268. wml.cost = wml.price * wml.goods_qty - wml.discount_amount + wml.share_cost
  269. elif wml.cost_unit: # 按出库成本退货
  270. wml.cost = wml.cost_unit * wml.goods_qty - wml.discount_amount + wml.share_cost
  271. elif wml.cost_unit:
  272. wml.cost = wml.cost_unit * wml.goods_qty
  273. def _inverse_cost(self):
  274. for wml in self:
  275. wml.cost_unit = safe_division(wml.cost, wml.goods_qty)
  276. def get_origin_explain(self):
  277. self.ensure_one()
  278. if self.move_id.origin in ('wh.assembly', 'wh.disassembly'):
  279. return self.ORIGIN_EXPLAIN.get((self.move_id.origin, self.type))
  280. elif self.move_id.origin == 'wh.internal':
  281. return self.ORIGIN_EXPLAIN.get((self.move_id.origin, self.env.context.get('internal_out', False)))
  282. elif self.move_id.origin in self.ORIGIN_EXPLAIN.keys():
  283. return self.ORIGIN_EXPLAIN.get(self.move_id.origin)
  284. return ''
  285. @api.model
  286. def default_get(self, fields):
  287. res = super(WhMoveLine, self).default_get(fields)
  288. if self.env.context.get('goods_id') and self.env.context.get('warehouse_id'):
  289. res.update({
  290. 'goods_id': self.env.context.get('goods_id'),
  291. 'warehouse_id': self.env.context.get('warehouse_id')
  292. })
  293. return res
  294. def get_real_cost_unit(self):
  295. self.ensure_one()
  296. return safe_division(self.cost, self.goods_qty)
  297. def name_get(self):
  298. res = []
  299. for line in self:
  300. if self.env.context.get('match'):
  301. res.append((line.id, '%s-%s->%s(%s, %s%s)' %
  302. (line.move_id.name, line.warehouse_id.name, line.warehouse_dest_id.name,
  303. line.goods_id.name, str(line.goods_qty), line.uom_id.name)))
  304. else:
  305. res.append((line.id, line.lot))
  306. return res
  307. @api.model
  308. def name_search(self, name='', args=None, operator='ilike', limit=100):
  309. """ 批号下拉的时候显示批次和剩余数量 """
  310. result = []
  311. domain = []
  312. if args:
  313. domain = args
  314. if name:
  315. domain.append(('lot', operator, name))
  316. records = self.search(domain, limit=limit)
  317. for line in records:
  318. # 增加了Decimal函数,强制批次余额数显示为6位小数 author: zou.jason@qq.com 邹霍梁
  319. # 原因:原 line.qty_remaining 取数 (0.000001),会导致由于6位小数太小时,显示为 (1e-06) 错误
  320. if line.expiration_date:
  321. result.append((line.id, '%s %s 余 %s 过保日 %s' % (
  322. line.lot, line.warehouse_dest_id.name,
  323. Decimal(line.qty_remaining).quantize(Decimal("0.000000")), line.expiration_date)))
  324. else:
  325. result.append((line.id, '%s %s 余 %s' % (
  326. line.lot, line.warehouse_dest_id.name,
  327. Decimal(line.qty_remaining).quantize(Decimal("0.000000")))))
  328. return result
  329. def check_availability(self):
  330. if self.warehouse_dest_id == self.warehouse_id:
  331. # 如果是 商品库位转移生成的内部移库,则不用约束调入仓和调出仓是否相同;否则需要约束
  332. if not (self.move_id.origin == 'wh.internal' and not self.location_id == False):
  333. raise UserError('调出仓库不可以和调入仓库一样')
  334. # 检查属性或批号是否填充,防止无权限人员不填就可以保存
  335. if self.using_attribute and not self.attribute_id:
  336. raise UserError('请输入商品:%s 的属性' % self.goods_id.name)
  337. if self.using_batch:
  338. if self.type == 'in' and not self.lot:
  339. raise UserError('请输入商品:%s 的批号' % self.goods_id.name)
  340. if self.type in ['out', 'internal'] and not self.lot_id:
  341. raise UserError('请选择商品:%s 的批号' % self.goods_id.name)
  342. def prev_action_done(self):
  343. pass
  344. def action_done(self):
  345. for line in self:
  346. _logger.info('正在确认ID为%s的移库行' % line.id)
  347. line.check_availability()
  348. line.prev_action_done()
  349. line.write({
  350. 'state': 'done',
  351. 'date': line.move_id.date,
  352. 'cost_time': fields.Datetime.now(self),
  353. })
  354. if line.type in ('in', 'internal'):
  355. locations = self.env['location'].search([('warehouse_id', '=', line.warehouse_dest_id.id)])
  356. if locations and not line.location_id:
  357. raise UserError('调入仓库 %s 进行了库位管理,请在明细行输入库位' % line.warehouse_dest_id.name)
  358. if line.location_id:
  359. line.location_id.write(
  360. {'attribute_id': line.attribute_id.id, 'goods_id': line.goods_id.id})
  361. if line.type == 'in' and line.scrap:
  362. if not self.env.user.company_id.wh_scrap_id:
  363. raise UserError('请在公司上输入废品库')
  364. dic = {
  365. 'type': 'internal',
  366. 'goods_id': line.goods_id.id,
  367. 'uom_id': line.uom_id.id,
  368. 'attribute_id': line.attribute_id.id,
  369. 'goods_qty': line.goods_qty,
  370. 'warehouse_id': line.warehouse_dest_id.id,
  371. 'warehouse_dest_id': self.env.user.company_id.wh_scrap_id.id
  372. }
  373. if line.lot:
  374. dic.update({'lot_id': line.id})
  375. wh_internal = self.env['wh.internal'].search([('ref', '=', line.move_id.name)])
  376. if not wh_internal:
  377. value = {
  378. 'ref': line.move_id.name,
  379. 'date': fields.Datetime.now(self),
  380. 'warehouse_id': line.warehouse_dest_id.id,
  381. 'warehouse_dest_id': self.env.user.company_id.wh_scrap_id.id,
  382. 'line_out_ids': [(0, 0, dic)],
  383. }
  384. self.env['wh.internal'].create(value)
  385. else:
  386. dic['move_id'] = wh_internal.move_id.id
  387. self.env['wh.move.line'].create(dic)
  388. def check_cancel(self):
  389. pass
  390. def prev_action_draft(self):
  391. pass
  392. def action_draft(self):
  393. for line in self:
  394. line.check_cancel()
  395. line.prev_action_draft()
  396. line.write({
  397. 'state': 'draft',
  398. 'date': False,
  399. })
  400. def compute_lot_compatible(self):
  401. for wml in self:
  402. if wml.warehouse_id and wml.lot_id and wml.lot_id.warehouse_dest_id != wml.warehouse_id:
  403. wml.lot_id = False
  404. if wml.goods_id and wml.lot_id and wml.lot_id.goods_id != wml.goods_id:
  405. wml.lot_id = False
  406. def compute_lot_domain(self):
  407. warehouse_id = self.env.context.get('default_warehouse_id')
  408. lot_domain = [('goods_id', '=', self.goods_id.id), ('state', '=', 'done'),
  409. ('lot', '!=', False), ('qty_remaining', '>', 0),
  410. ('warehouse_dest_id.type', '=', 'stock')]
  411. if warehouse_id:
  412. lot_domain.append(('warehouse_dest_id', '=', warehouse_id))
  413. if self.attribute_id:
  414. lot_domain.append(('attribute_id', '=', self.attribute_id.id))
  415. return lot_domain
  416. def compute_suggested_cost(self):
  417. for wml in self:
  418. if wml.env.context.get('type') == 'out' and wml.goods_id and wml.warehouse_id and wml.goods_qty:
  419. _, cost_unit = wml.goods_id.get_suggested_cost_by_warehouse(
  420. wml.warehouse_id, wml.goods_qty, wml.lot_id, wml.attribute_id)
  421. wml.cost_unit = cost_unit
  422. if wml.env.context.get('type') == 'in' and wml.goods_id:
  423. wml.cost_unit = wml.goods_id.cost
  424. @api.onchange('goods_id')
  425. def onchange_goods_id(self):
  426. if self.goods_id:
  427. self.uom_id = self.goods_id.uom_id
  428. self.uos_id = self.goods_id.uos_id
  429. self.attribute_id = False
  430. partner_id = self.env.context.get('default_partner')
  431. partner = self.env['partner'].browse(partner_id)
  432. if self.type == 'in':
  433. self.tax_rate = self.goods_id.get_tax_rate(self.goods_id, partner, 'buy')
  434. if self.type == 'out':
  435. self.tax_rate = self.goods_id.get_tax_rate(self.goods_id, partner, 'sell')
  436. if self.goods_id.using_batch and self.goods_id.force_batch_one:
  437. self.goods_qty = 1
  438. self.goods_uos_qty = self.goods_id.anti_conversion_unit(
  439. self.goods_qty)
  440. else:
  441. self.goods_qty = self.goods_id.conversion_unit(
  442. self.goods_uos_qty or 1)
  443. else:
  444. return
  445. self.compute_suggested_cost()
  446. self.compute_lot_compatible()
  447. return {'domain': {'lot_id': self.compute_lot_domain()}}
  448. @api.onchange('warehouse_id')
  449. def onchange_warehouse_id(self):
  450. if not self.warehouse_id:
  451. return
  452. self.compute_suggested_cost()
  453. self.compute_lot_domain()
  454. self.compute_lot_compatible()
  455. return {'domain': {'lot_id': self.compute_lot_domain()}}
  456. @api.onchange('attribute_id')
  457. def onchange_attribute_id(self):
  458. if not self.attribute_id:
  459. return
  460. self.compute_suggested_cost()
  461. return {'domain': {'lot_id': self.compute_lot_domain()}}
  462. @api.onchange('goods_qty')
  463. def onchange_goods_qty(self):
  464. if not self.goods_id:
  465. return
  466. self.compute_suggested_cost()
  467. @api.onchange('goods_uos_qty')
  468. def onchange_goods_uos_qty(self):
  469. if self.goods_id:
  470. self.goods_qty = self.goods_id.conversion_unit(self.goods_uos_qty)
  471. self.compute_suggested_cost()
  472. @api.onchange('lot_id')
  473. def onchange_lot_id(self):
  474. if self.lot_id:
  475. if self.lot_id.qty_remaining < self.goods_qty:
  476. self.goods_qty = self.lot_id.qty_remaining
  477. self.lot_qty = self.lot_id.qty_remaining
  478. self.lot_uos_qty = self.goods_id.anti_conversion_unit(self.lot_qty)
  479. if self.env.context.get('type') in ['internal', 'out']:
  480. self.lot = self.lot_id.lot
  481. @api.onchange('goods_qty', 'price_taxed', 'discount_rate')
  482. def onchange_discount_rate(self):
  483. if not self.goods_id:
  484. return
  485. """当数量、单价或优惠率发生变化时,优惠金额发生变化"""
  486. price = self.price_taxed / (1 + self.tax_rate * 0.01)
  487. decimal = self.env.ref('core.decimal_price')
  488. if float_compare(price, self.price, precision_digits=decimal.digits) != 0:
  489. self.price = price
  490. self.discount_amount = self.goods_qty * self.price * self.discount_rate * 0.01
  491. @api.onchange('discount_amount')
  492. def onchange_discount_amount(self):
  493. if not self.goods_id:
  494. return
  495. """当优惠金额发生变化时,重新取默认的单位成本,以便计算实际的单位成本"""
  496. self.compute_suggested_cost()
  497. @api.constrains('goods_qty')
  498. def check_goods_qty(self):
  499. """序列号管理的商品数量必须为1"""
  500. for wml in self:
  501. if wml.force_batch_one and wml.goods_qty > 1:
  502. raise UserError('商品 %s 进行了序列号管理,数量必须为1' % wml.goods_id.name)
  503. def get_lot_id(self):
  504. if self.type == 'out' and self.goods_id.using_batch:
  505. domain = [
  506. ('qty_remaining', '>=', self.goods_qty),
  507. ('state', '=', 'done'),
  508. ('warehouse_dest_id', '=', self.warehouse_id.id),
  509. ('goods_id', '=', self.goods_id.id)
  510. ]
  511. line = self.env['wh.move.line'].search(
  512. domain, order='location_id, expiration_date, cost_time, id',limit=1)
  513. if line:
  514. self.lot_id = line.id
  515. self.lot = line.lot
  516. else:
  517. print('not lot for %s in %s' % (self.goods_id.name, self.move_id.name))
  518. @api.depends('goods_id', 'goods_qty', 'warehouse_id', 'state')
  519. def _get_lack(self):
  520. for s in self:
  521. s.all_lack = 0
  522. s.wh_lack = 0
  523. if s.type != 'in' and s.state == 'draft' and s.goods_id:
  524. s.all_lack = s.goods_qty
  525. s.wh_lack = s.goods_qty
  526. qty = s.goods_id.get_stock_qty()
  527. for i in qty:
  528. s.all_lack -= i['qty']
  529. if i['warehouse'] == s.warehouse_id.name:
  530. s.wh_lack -= i['qty']
  531. @api.depends('lot')
  532. def _compute_display_name(self):
  533. for rec in self:
  534. rec.display_name = rec.lot
上海开阖软件有限公司 沪ICP备12045867号-1