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.

358 linhas
17KB

  1. # -*- coding: utf-8 -*-
  2. from odoo import fields, models, api
  3. from odoo import _
  4. from odoo.exceptions import UserError
  5. from odoo.tools import float_compare
  6. # 订单确认状态可选值
  7. SELL_ORDER_STATES = [
  8. ('draft', '草稿'),
  9. ('done', '已确认'),
  10. ('cancel', '已作废')]
  11. class SellAdjust(models.Model):
  12. _name = "sell.adjust"
  13. _inherit = ['mail.thread']
  14. _description = "销售变更单"
  15. _order = 'date desc, id desc'
  16. name = fields.Char('单据编号', copy=False,
  17. help='变更单编号,保存时可自动生成')
  18. order_id = fields.Many2one('sell.order', '原始单据',
  19. copy=False, ondelete='restrict',
  20. help='要调整的原始销售订单,只能调整已确认且没有全部出库的销售订单')
  21. date = fields.Date('单据日期',
  22. default=lambda self: fields.Date.context_today(self),
  23. index=True, copy=False,
  24. help='变更单创建日期,默认是当前日期')
  25. change_type = fields.Selection([('price', '价格'), ('qty', '数量')], '调整类型', default='qty')
  26. line_ids = fields.One2many('sell.adjust.line', 'order_id', '变更单行',
  27. copy=True,
  28. help='变更单明细行,不允许为空')
  29. approve_uid = fields.Many2one('res.users', '确认人',
  30. copy=False, ondelete='restrict',
  31. help='确认变更单的人')
  32. state = fields.Selection(SELL_ORDER_STATES, '确认状态',
  33. index=True, copy=False,
  34. default='draft',
  35. tracking=True,
  36. help='变更单确认状态')
  37. note = fields.Text('备注',
  38. help='单据备注')
  39. user_id = fields.Many2one(
  40. 'res.users',
  41. '经办人',
  42. ondelete='restrict',
  43. default=lambda self: self.env.user,
  44. help='单据经办人',
  45. )
  46. company_id = fields.Many2one(
  47. 'res.company',
  48. string='公司',
  49. change_default=True,
  50. default=lambda self: self.env.company)
  51. @api.onchange('order_id')
  52. def onchange_sell_order(self):
  53. if not self.order_id:
  54. return {}
  55. self.line_ids = False
  56. change_lines = []
  57. for line in self.order_id.line_ids:
  58. vals = {
  59. 'quantity': 0,
  60. 'goods_id': line.goods_id.id,
  61. 'using_attribute': line.using_attribute,
  62. 'attribute_id': line.attribute_id.id,
  63. 'uom_id': line.uom_id.id,
  64. 'price': line.price,
  65. 'price_taxed': line.price_taxed,
  66. 'discount_rate': line.discount_rate,
  67. 'discount_amount': line.discount_amount,
  68. 'amount': line.amount,
  69. 'tax_rate': line.tax_rate,
  70. 'note': line.note,
  71. 'company_id': line.company_id.id,
  72. }
  73. change_lines.append((0, 0, vals))
  74. self.line_ids = change_lines
  75. def check_order(self):
  76. self.ensure_one()
  77. if self.state == 'done':
  78. raise UserError(_('请不要重复确认!'))
  79. if not self.line_ids:
  80. raise UserError(_('请输入商品明细行!'))
  81. for line in self.line_ids:
  82. # 调价格不能有数量变更
  83. if self.change_type == 'price' and line.quantity:
  84. raise UserError(_('调整价格时不能调整数量!'))
  85. # 调数量不能有价格变更
  86. if self.change_type == 'qty':
  87. origin_line = self.env['sell.order.line'].search(
  88. [('goods_id', '=', line.goods_id.id),
  89. ('attribute_id', '=', line.attribute_id.id),
  90. ('order_id', '=', self.order_id.id)])
  91. if len(origin_line) > 1:
  92. raise UserError(_('要调整的商品 %s 在原始单据中不唯一') % line.goods_id.name)
  93. if round(origin_line.price - line.price, 6):
  94. raise UserError(_('调整数量时不能调整单价!'))
  95. def sell_adjust_done(self):
  96. #二种调整不能同时进行,人算不过来
  97. self.check_order()
  98. if self.change_type == 'qty':
  99. self.change_qty()
  100. if self.change_type == 'price':
  101. self.change_price()
  102. self.state = 'done'
  103. self.approve_uid = self._uid
  104. def change_price(self):
  105. '''
  106. 确认销售数量单价变更单:
  107. 当结算单有付款或发票号,报错。
  108. 其他都可以改。
  109. '''
  110. for line in self.line_ids:
  111. origin_line = self.env['sell.order.line'].search(
  112. [('goods_id', '=', line.goods_id.id),
  113. ('attribute_id', '=', line.attribute_id.id),
  114. ('order_id', '=', self.order_id.id)])
  115. if not origin_line:
  116. raise UserError(_('调整价格时不能新增行!'))
  117. #变更销售单
  118. origin_line_amount = round(line.price * origin_line.quantity,2)
  119. origin_line_subtotal = round(line.price_taxed * origin_line.quantity,2)
  120. note = origin_line.note or ''
  121. note += '变更单:%s单价%s变更%s。\n' % (self.name, origin_line.price, line.price)
  122. origin_line.write({
  123. 'price': line.price,
  124. 'price_taxed': line.price_taxed,
  125. 'amount': origin_line_amount,
  126. 'tax_amount': origin_line_subtotal - origin_line_amount,
  127. 'subtotal': origin_line_subtotal,
  128. 'note': note,
  129. })
  130. #变更送货单
  131. move_line = self.env['wh.move.line'].search(
  132. [('sell_line_id', '=', origin_line.id)])
  133. move_line_amount = round(line.price * move_line.goods_qty, 2)
  134. move_line_subtotal = round(line.price_taxed * move_line.goods_qty, 2)
  135. move_line.write({
  136. 'price': line.price,
  137. 'price_taxed': line.price_taxed,
  138. 'amount': move_line_amount,
  139. 'tax_amount': move_line_subtotal - move_line_amount,
  140. 'subtotal': move_line_subtotal,
  141. 'note': note,
  142. })
  143. # 删除结算单
  144. invoice_id = self.env['money.invoice'].search([('move_id', '=', move_line.move_id.id)])
  145. if invoice_id and (invoice_id.reconciled or invoice_id.bill_number):
  146. raise UserError(_('已付款或有发票号码的订单不能变更价格!'))
  147. if invoice_id:
  148. invoice_id.unlink()
  149. #如果送货单无对账单,重新生成
  150. delivery_ids = self.env['sell.delivery'].search(
  151. [('order_id', '=', self.order_id.id),
  152. ('state', '=', 'done')])
  153. for delivery_id in delivery_ids:
  154. if not delivery_id.invoice_id:
  155. delivery_id._delivery_make_invoice()
  156. def change_qty(self):
  157. '''确认销售数量变更单:
  158. 当调整后数量 < 原单据中已出库数量,则报错;
  159. 当调整后数量 > 原单据中已出库数量,则更新原单据及发货单分单的数量;
  160. 当调整后数量 = 原单据中已出库数量,则更新原单据数量,删除发货单分单;
  161. 当新增商品时,则更新原单据及发货单分单明细行。
  162. '''
  163. delivery = self.env['sell.delivery'].search(
  164. [('order_id', '=', self.order_id.id),
  165. ('state', '=', 'draft')])
  166. if not delivery:
  167. raise UserError(_('销售发货单已全部出库,不能调整'))
  168. for line in self.line_ids:
  169. # 检查属性是否填充,防止无权限人员不填就可以保存
  170. if line.using_attribute and not line.attribute_id:
  171. raise UserError(_('请输入商品:%s 的属性') % line.goods_id.name)
  172. origin_line = self.env['sell.order.line'].search(
  173. [('goods_id', '=', line.goods_id.id),
  174. ('attribute_id', '=', line.attribute_id.id),
  175. ('order_id', '=', self.order_id.id)])
  176. if len(origin_line) > 1:
  177. raise UserError(_('要调整的商品 %s 在原始单据中不唯一') % line.goods_id.name)
  178. if origin_line:
  179. origin_line.quantity += line.quantity # 调整后数量
  180. new_note = '变更单:%s %s。\n' % (self.name, line.note)
  181. origin_line.note = (origin_line.note and
  182. origin_line.note + new_note or new_note)
  183. if origin_line.quantity < origin_line.quantity_out:
  184. raise UserError(_(' %s 调整后数量不能小于原订单已出库数量') %
  185. line.goods_id.name)
  186. elif origin_line.quantity > origin_line.quantity_out:
  187. # 查找出原销售订单产生的草稿状态的发货单明细行,并更新它
  188. move_line = self.env['wh.move.line'].search(
  189. [('sell_line_id', '=', origin_line.id),
  190. ('state', '=', 'draft')])
  191. if move_line:
  192. move_line.goods_qty += line.quantity
  193. else:
  194. raise UserError(_('商品 %s 已全部入库,建议新建采购订单') %
  195. line.goods_id.name)
  196. # 调整后数量与已出库数量相等时,删除产生的发货单分单
  197. else:
  198. # 先删除对应的发货单行
  199. move_line = self.env['wh.move.line'].search(
  200. [('sell_line_id', '=', origin_line.id), ('state', '=',
  201. 'draft')])
  202. if move_line:
  203. move_line.unlink()
  204. # 如果发货单明细没有了,则删除发货单
  205. if len(delivery.sell_move_id.line_out_ids) == 0:
  206. delivery.unlink()
  207. else:
  208. vals = {
  209. 'order_id': self.order_id.id,
  210. 'goods_id': line.goods_id.id,
  211. 'attribute_id': line.attribute_id.id,
  212. 'quantity': line.quantity,
  213. 'uom_id': line.uom_id.id,
  214. 'price_taxed': line.price_taxed,
  215. 'discount_rate': line.discount_rate,
  216. 'discount_amount': line.discount_amount,
  217. 'tax_rate': line.tax_rate,
  218. 'note': line.note or '',
  219. }
  220. new_line = self.env['sell.order.line'].create(vals)
  221. delivery_line = []
  222. if line.goods_id.force_batch_one:
  223. i = 0
  224. while i < line.quantity:
  225. i += 1
  226. delivery_line.append(
  227. self.order_id.get_delivery_line(new_line, single=True))
  228. else:
  229. delivery_line.append(
  230. self.order_id.get_delivery_line(new_line, single=False))
  231. delivery.write(
  232. {'line_out_ids': [(0, 0, li) for li in delivery_line]})
  233. class SellAdjustLine(models.Model):
  234. _name = 'sell.adjust.line'
  235. _description = '销售变更单明细'
  236. @api.depends('goods_id')
  237. def _compute_using_attribute(self):
  238. '''返回订单行中商品是否使用属性'''
  239. for l in self:
  240. l.using_attribute = l.goods_id.attribute_ids and True or False
  241. @api.depends('quantity', 'price_taxed', 'discount_amount', 'tax_rate')
  242. def _compute_all_amount(selfs):
  243. '''当订单行的数量、单价、折扣额、税率改变时,改变采购金额、税额、价税合计'''
  244. for self in selfs:
  245. self.subtotal = self.price_taxed * self.quantity - self.discount_amount # 价税合计
  246. self.tax_amount = self.subtotal / \
  247. (100 + self.tax_rate) * self.tax_rate # 税额
  248. self.amount = self.subtotal - self.tax_amount # 金额
  249. @api.onchange('price', 'tax_rate')
  250. def onchange_price(self):
  251. '''当订单行的不含税单价改变时,改变含税单价'''
  252. price = self.price_taxed / (1 + self.tax_rate * 0.01) # 不含税单价
  253. decimal = self.env.ref('core.decimal_price')
  254. if float_compare(price, self.price, precision_digits=decimal.digits) != 0:
  255. self.price_taxed = self.price * (1 + self.tax_rate * 0.01)
  256. order_id = fields.Many2one('sell.adjust', '订单编号', index=True,
  257. required=True, ondelete='cascade',
  258. help='关联的变更单编号')
  259. goods_id = fields.Many2one('goods', '商品', ondelete='restrict',
  260. help='商品')
  261. using_attribute = fields.Boolean('使用属性', compute=_compute_using_attribute,
  262. help='商品是否使用属性')
  263. attribute_id = fields.Many2one('attribute', '属性',
  264. ondelete='restrict',
  265. domain="[('goods_id', '=', goods_id)]",
  266. help='商品的属性,当商品有属性时,该字段必输')
  267. uom_id = fields.Many2one('uom', '单位', ondelete='restrict',
  268. help='商品计量单位')
  269. quantity = fields.Float('调整数量',
  270. default=1,
  271. required=True,
  272. digits='Quantity',
  273. help='相对于原单据对应明细行的调整数量,可正可负')
  274. price = fields.Float('销售单价',
  275. store=True,
  276. digits='Price',
  277. help='不含税单价,由含税单价计算得出')
  278. price_taxed = fields.Float('含税单价',
  279. digits='Price',
  280. help='含税单价,取自商品零售价')
  281. discount_rate = fields.Float('折扣率%',
  282. help='折扣率')
  283. discount_amount = fields.Float('折扣额',
  284. digits='Amount',
  285. help='输入折扣率后自动计算得出,也可手动输入折扣额')
  286. amount = fields.Float('金额',
  287. compute=_compute_all_amount,
  288. store=True,
  289. digits='Amount',
  290. help='金额 = 价税合计 - 税额')
  291. tax_rate = fields.Float('税率(%)', default=lambda self: self.env.user.company_id.import_tax_rate,
  292. help='默认值取公司销项税率')
  293. tax_amount = fields.Float('税额',
  294. compute=_compute_all_amount,
  295. store=True,
  296. digits='Amount',
  297. help='由税率计算得出')
  298. subtotal = fields.Float('价税合计',
  299. compute=_compute_all_amount,
  300. store=True,
  301. digits='Amount',
  302. help='含税单价 乘以 数量')
  303. note = fields.Char('备注',
  304. help='本行备注')
  305. company_id = fields.Many2one(
  306. 'res.company',
  307. string='公司',
  308. change_default=True,
  309. default=lambda self: self.env.company)
  310. @api.onchange('goods_id')
  311. def onchange_goods_id(self):
  312. '''当订单行的商品变化时,带出商品上的单位、默认仓库、价格'''
  313. if self.goods_id:
  314. self.uom_id = self.goods_id.uom_id
  315. # 修正 单价 及含税单价问题
  316. self.price_taxed = self.goods_id.price * (1 + self.goods_id.tax_rate * 0.01) if self.goods_id.tax_rate else self.goods_id.price
  317. self.tax_rate = self.goods_id.get_tax_rate(self.goods_id, self.order_id.order_id.partner_id, 'sell')
  318. @api.onchange('quantity', 'price_taxed', 'discount_rate')
  319. def onchange_discount_rate(self):
  320. '''当数量、含税单价或优惠率发生变化时,优惠金额发生变化'''
  321. self.price = self.price_taxed / (1 + self.tax_rate * 0.01)
  322. self.discount_amount = (self.quantity * self.price *
  323. self.discount_rate * 0.01)
  324. @api.constrains('tax_rate')
  325. def _check_(self):
  326. for record in self:
  327. if record.tax_rate > 100:
  328. raise UserError(_('税率不能输入超过100的数'))
  329. if record.tax_rate < 0:
  330. raise UserError(_('税率不能输入负数'))
上海开阖软件有限公司 沪ICP备12045867号-1