GoodERP
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

355 Zeilen
17KB

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