GoodERP
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

848 líneas
37KB

  1. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. from odoo import fields, models, api, _
  4. from odoo.exceptions import UserError
  5. from datetime import datetime
  6. from odoo.tools import float_compare, float_is_zero
  7. from odoo.exceptions import ValidationError
  8. # 采购订单确认状态可选值
  9. BUY_ORDER_STATES = [
  10. ('draft', '草稿'),
  11. ('done', '已确认'),
  12. ('cancel', '已作废')]
  13. # 字段只读状态
  14. READONLY_STATES = {
  15. 'done': [('readonly', True)],
  16. 'cancel': [('readonly', True)],
  17. }
  18. class BuyOrder(models.Model):
  19. _name = "buy.order"
  20. _inherit = ['mail.thread', 'mail.activity.mixin']
  21. _description = "采购订单"
  22. _order = 'date desc, id desc'
  23. @api.depends('line_ids.subtotal', 'discount_amount')
  24. def _compute_amount(selfs):
  25. '''当订单行和优惠金额改变时,改变成交金额'''
  26. for self in selfs:
  27. total = sum(line.subtotal for line in self.line_ids)
  28. self.amount = total - self.discount_amount
  29. self.untax_amount = sum(line.amount for line in self.line_ids)
  30. self.tax_amount = sum(line.tax_amount for line in self.line_ids)
  31. @api.depends('line_ids.quantity')
  32. def _compute_qty(selfs):
  33. '''当订单行数量改变时,更新总数量'''
  34. for self in selfs:
  35. self.total_qty = sum(line.quantity for line in self.line_ids)
  36. @api.depends('receipt_ids.state')
  37. def _get_buy_goods_state(selfs):
  38. '''返回收货状态'''
  39. for self in selfs:
  40. if all(line.quantity_in == 0 for line in self.line_ids):
  41. if any(r.state == 'draft' for r in self.receipt_ids) or self.state=='draft':
  42. self.goods_state = '未入库'
  43. else:
  44. self.goods_state = '全部作废'
  45. elif any(line.quantity > line.quantity_in for line in self.line_ids):
  46. if any(r.state == 'draft' for r in self.receipt_ids):
  47. self.goods_state = '部分入库'
  48. else:
  49. self.goods_state = '部分入库剩余作废'
  50. else:
  51. self.goods_state = '全部入库'
  52. @api.model
  53. def _default_warehouse_dest_impl(self):
  54. if self.env.context.get('warehouse_dest_type'):
  55. return self.env['warehouse'].get_warehouse_by_type(
  56. self.env.context.get('warehouse_dest_type'), False)
  57. @api.model
  58. def _default_warehouse_dest(self):
  59. '''获取默认调入仓库'''
  60. return self._default_warehouse_dest_impl()
  61. def _get_paid_amount(selfs):
  62. '''计算采购订单付款/退款状态'''
  63. for self in selfs:
  64. if not self.invoice_by_receipt: # 分期付款时
  65. money_invoices = self.env['money.invoice'].search([
  66. ('name', '=', self.name),
  67. ('state', '=', 'done')])
  68. self.paid_amount = sum([invoice.reconciled for invoice in money_invoices])
  69. else:
  70. receipts = self.env['buy.receipt'].search([('order_id', '=', self.id)])
  71. # 采购订单上输入预付款时
  72. money_order_rows = self.env['money.order'].search([('buy_id', '=', self.id),
  73. ('partner_id', '=', self.partner_id.id),
  74. ('state', '=', 'done')])
  75. self.paid_amount = sum([receipt.invoice_id.reconciled for receipt in receipts]) +\
  76. sum([order_row.to_reconcile for order_row in money_order_rows])
  77. @api.depends('receipt_ids')
  78. def _compute_receipt(self):
  79. for order in self:
  80. order.receipt_count = len([receipt for receipt in order.receipt_ids if not receipt.is_return])
  81. order.return_count = len([receipt for receipt in order.receipt_ids if receipt.is_return])
  82. @api.depends('receipt_ids')
  83. def _compute_invoice(self):
  84. for order in self:
  85. money_invoices = self.env['money.invoice'].search([
  86. ('name', '=', order.name)])
  87. order.invoice_ids = not money_invoices and order.receipt_ids.mapped('invoice_id') or money_invoices + order.receipt_ids.mapped('invoice_id')
  88. order.invoice_count = len(order.invoice_ids.ids)
  89. partner_id = fields.Many2one('partner', '供应商',
  90. ondelete='restrict',
  91. help='供应商')
  92. contact = fields.Char('联系人')
  93. address_id = fields.Many2one('partner.address', '地址',
  94. domain="[('partner_id', '=', partner_id)]",
  95. help='联系地址')
  96. date = fields.Date('单据日期',
  97. default=lambda self: fields.Date.context_today(self),
  98. index=True,
  99. copy=False,
  100. help="默认是订单创建日期")
  101. planned_date = fields.Date(
  102. '要求交货日期',
  103. default=lambda self: fields.Date.context_today(
  104. self),
  105. index=True,
  106. copy=False,
  107. help="订单的要求交货日期")
  108. name = fields.Char('单据编号',
  109. index=True,
  110. copy=False,
  111. help="采购订单的唯一编号,当创建时它会自动生成下一个编号。")
  112. type = fields.Selection([('buy', '采购'),
  113. ('return', '退货')],
  114. '类型',
  115. default='buy',
  116. help='采购订单的类型,分为采购或退货')
  117. ref = fields.Char('供应商订单号')
  118. warehouse_dest_id = fields.Many2one('warehouse',
  119. '调入仓库',
  120. required=True,
  121. default=_default_warehouse_dest,
  122. ondelete='restrict',
  123. help='将商品调入到该仓库')
  124. invoice_by_receipt = fields.Boolean(string="按收货结算",
  125. default=True,
  126. help='如未勾选此项,可在资金行里输入付款金额,订单保存后,采购人员可以单击资金行上的【确认】按钮。')
  127. line_ids = fields.One2many('buy.order.line',
  128. 'order_id',
  129. '采购订单行',
  130. copy=True,
  131. help='采购订单的明细行,不能为空')
  132. pay_method = fields.Many2one('pay.method',
  133. string='付款方式',
  134. ondelete='restrict')
  135. note = fields.Text('备注',
  136. help='单据备注')
  137. discount_rate = fields.Float('优惠率(%)',
  138. digits='Amount',
  139. help='整单优惠率')
  140. discount_amount = fields.Float('抹零',
  141. digits='Amount',
  142. help='整单优惠金额,可由优惠率自动计算出来,也可手动输入')
  143. amount = fields.Float('成交金额',
  144. store=True,
  145. compute='_compute_amount',
  146. digits='Amount',
  147. help='总金额减去优惠金额')
  148. untax_amount = fields.Float('不含税合计',
  149. store=True,
  150. compute='_compute_amount',
  151. digits='Amount')
  152. tax_amount = fields.Float('税金合计',
  153. store=True,
  154. compute='_compute_amount',
  155. digits='Amount')
  156. total_qty = fields.Float(string='数量合计', store=True, readonly=True,
  157. compute='_compute_qty',
  158. digits='Quantity',
  159. help='数量总计')
  160. prepayment = fields.Float('预付款',
  161. digits='Amount',
  162. help='输入预付款确认采购订单,会产生一张付款单')
  163. bank_account_id = fields.Many2one('bank.account',
  164. '结算账户',
  165. ondelete='restrict',
  166. help='用来核算和监督企业与其他单位或个人之间的债权债务的结算情况')
  167. approve_uid = fields.Many2one('res.users',
  168. '确认人',
  169. copy=False,
  170. ondelete='restrict',
  171. help='确认单据的人')
  172. state = fields.Selection(BUY_ORDER_STATES,
  173. '确认状态',
  174. readonly=True,
  175. help="采购订单的确认状态",
  176. index=True,
  177. copy=False,
  178. default='draft')
  179. goods_state = fields.Char('收货状态',
  180. compute=_get_buy_goods_state,
  181. default='未入库',
  182. store=True,
  183. help="采购订单的收货状态",
  184. index=True,
  185. copy=False)
  186. cancelled = fields.Boolean('已终止',
  187. help='该单据是否已终止')
  188. pay_ids = fields.One2many("payment.plan",
  189. "buy_id",
  190. string="付款计划",
  191. help='分批付款时使用付款计划')
  192. goods_id = fields.Many2one(
  193. 'goods', related='line_ids.goods_id', string='商品')
  194. receipt_ids = fields.One2many(
  195. 'buy.receipt', 'order_id', string='入库单', copy=False)
  196. receipt_count = fields.Integer(
  197. compute='_compute_receipt', string='入库单数量', default=0)
  198. return_count = fields.Integer(
  199. compute='_compute_receipt', string='退货单数量', default=0)
  200. invoice_ids = fields.One2many(
  201. 'money.invoice', compute='_compute_invoice', string='Invoices')
  202. invoice_count = fields.Integer(
  203. compute='_compute_invoice', string='Invoices Count', default=0)
  204. currency_id = fields.Many2one('res.currency',
  205. '外币币别',
  206. store=True,
  207. related='partner_id.s_category_id.account_id.currency_id',
  208. help='外币币别')
  209. is_multi_currency = fields.Boolean('多币种', related='company_id.is_multi_currency')
  210. express_type = fields.Char(string='承运商',)
  211. term_id = fields.Many2one('core.value', "贸易条款",
  212. domain=[('type', '=', 'price_term')],
  213. context={'type': 'price_term'})
  214. user_id = fields.Many2one(
  215. 'res.users',
  216. '经办人',
  217. ondelete='restrict',
  218. default=lambda self: self.env.user,
  219. help='单据经办人',
  220. )
  221. company_id = fields.Many2one(
  222. 'res.company',
  223. string='公司',
  224. change_default=True,
  225. default=lambda self: self.env.company)
  226. paid_amount = fields.Float(
  227. '已付金额', compute=_get_paid_amount, readonly=True)
  228. paid_no_goods = fields.Boolean('已付款未到货',compute="_compute_paid_no_goods",store=True)
  229. money_order_id = fields.Many2one(
  230. 'money.order',
  231. '预付款单',
  232. readonly=True,
  233. copy=False,
  234. help='输入预付款确认时产生的预付款单')
  235. details = fields.Html('明细',compute='_compute_details')
  236. project_id = fields.Many2one('project', string='项目')
  237. @api.depends('money_order_id.state','goods_state')
  238. def _compute_paid_no_goods(self):
  239. for o in self:
  240. o.paid_no_goods = False
  241. if o.state == 'done' and o.goods_state == '未入库' and o.paid_amount:
  242. if not all(line.goods_id.no_stock for line in self.line_ids):
  243. o.paid_no_goods = True
  244. @api.depends('line_ids')
  245. def _compute_details(self):
  246. for v in self:
  247. vl = {'col':[],'val':[]}
  248. vl['col'] = [
  249. _('商品'),
  250. _('数量'),
  251. _('单价'),
  252. _('已收')
  253. ]
  254. for l in v.line_ids:
  255. vl['val'].append([l.goods_id.name, l.quantity, l.price, l.quantity_in])
  256. v.details = v.company_id._get_html_table(vl)
  257. @api.onchange('discount_rate', 'line_ids')
  258. def onchange_discount_rate(self):
  259. '''当优惠率或采购订单行发生变化时,单据优惠金额发生变化'''
  260. total = sum(line.subtotal for line in self.line_ids)
  261. self.discount_amount = total * self.discount_rate * 0.01
  262. @api.onchange('partner_id')
  263. def onchange_partner_id(self):
  264. if self.partner_id:
  265. for line in self.line_ids:
  266. line.tax_rate = line.goods_id.get_tax_rate(line.goods_id, self.partner_id, 'buy')
  267. self.contact = self.partner_id.main_contact
  268. self.pay_method = self.partner_id.pay_method
  269. @api.onchange('address_id')
  270. def onchange_address_id(self):
  271. if self.address_id:
  272. self.contact = self.address_id.contact
  273. def _get_vals(self):
  274. '''返回创建 money_order 时所需数据'''
  275. flag = (self.type == 'buy' and 1 or -1) # 用来标志入库或退货
  276. amount = flag * self.amount
  277. this_reconcile = flag * self.prepayment
  278. money_lines = [{
  279. 'bank_id': self.bank_account_id.id,
  280. 'amount': this_reconcile,
  281. }]
  282. return {
  283. 'partner_id': self.partner_id.id,
  284. 'bank_name': self.partner_id.bank_name,
  285. 'bank_num': self.partner_id.bank_num,
  286. 'date': fields.Date.context_today(self),
  287. 'line_ids':
  288. [(0, 0, line) for line in money_lines],
  289. 'amount': amount,
  290. 'reconciled': this_reconcile,
  291. 'to_reconcile': amount,
  292. 'state': 'draft',
  293. 'origin_name': self.name,
  294. 'buy_id': self.id,
  295. }
  296. def generate_payment_order(self):
  297. '''由采购订单生成付款单'''
  298. # 入库单/退货单
  299. if self.prepayment:
  300. money_order = self.with_context(type='pay').env['money.order'].create(
  301. self._get_vals()
  302. )
  303. return money_order
  304. def buy_order_done(self):
  305. '''确认采购订单'''
  306. self.ensure_one()
  307. if self.state == 'done':
  308. raise UserError('请不要重复确认')
  309. if not self.line_ids:
  310. raise UserError('请输入商品明细行')
  311. for line in self.line_ids:
  312. # 检查属性是否填充,防止无权限人员不填就可以保存
  313. if line.using_attribute and not line.attribute_id:
  314. raise UserError('请输入商品:%s 的属性' % line.goods_id.name)
  315. if line.quantity <= 0 or line.price_taxed < 0:
  316. raise UserError('商品 %s 的数量和含税单价不能小于0' % line.goods_id.name)
  317. if line.tax_amount > 0 and self.currency_id:
  318. raise UserError('外贸免税')
  319. if not self.bank_account_id and self.prepayment:
  320. raise UserError('预付款不为空时,请选择结算账户')
  321. if not self.amount and self.env.context.get('func', '') != 'amount_not_value':
  322. vals = {}
  323. return self.env[self.sudo()._name].with_context(
  324. {'active_model': self.sudo()._name}
  325. ).open_dialog('amount_not_value', {
  326. 'message': '当前采购单价格为0,是否继续进行?',
  327. 'args': [vals],
  328. })
  329. # 采购预付款生成付款单
  330. money_order = self.generate_payment_order()
  331. self.buy_generate_receipt()
  332. self.approve_uid = self._uid
  333. self.write({
  334. 'money_order_id': money_order and money_order.id,
  335. 'state': 'done', # 为保证审批流程顺畅,否则,未审批就可审核
  336. })
  337. def amount_not_value(self, vals):
  338. for line in self.line_ids:
  339. line.onchange_price()
  340. self.buy_order_done()
  341. def buy_order_draft(self):
  342. '''撤销确认采购订单'''
  343. self.ensure_one()
  344. if self.state == 'draft':
  345. raise UserError('请不要重复撤销%s' % self._description)
  346. if any(r.state == 'done' for r in self.receipt_ids):
  347. raise UserError('该采购订单已经收货,不能撤销确认!')
  348. # 查找产生的发票并删除
  349. for inv in self.invoice_ids:
  350. if inv.state == 'done':
  351. raise UserError('该采购订单已经收票,不能撤销确认!')
  352. else:
  353. inv.unlink()
  354. for plan in self.pay_ids:
  355. plan.date_application = ''
  356. # 查找产生的入库单并删除
  357. self.receipt_ids.unlink()
  358. # 查找产生的付款单并撤销确认,删除
  359. for money_order_id in self.env['money.order'].search([('buy_id','=',self.id)]):
  360. if money_order_id.state == 'done':
  361. raise UserError('该采购订单已经付款,不能撤销确认!')
  362. money_order_id.unlink()
  363. self.approve_uid = False
  364. self.state = 'draft'
  365. def get_receipt_line(self, line, single=False):
  366. '''返回采购入库/退货单行'''
  367. self.ensure_one()
  368. qty = 0
  369. discount_amount = 0
  370. if single:
  371. qty = 1
  372. discount_amount = (line.discount_amount /
  373. ((line.quantity - line.quantity_in) or 1))
  374. else:
  375. qty = line.quantity - line.quantity_in
  376. discount_amount = line.discount_amount
  377. return {
  378. 'type': self.type == 'buy' and 'in' or 'out',
  379. 'buy_line_id': line.id,
  380. 'goods_id': line.goods_id.id,
  381. 'attribute_id': line.attribute_id.id,
  382. 'uos_id': line.goods_id.uos_id.id,
  383. 'goods_qty': qty,
  384. 'uom_id': line.uom_id.id,
  385. 'cost_unit': line.price,
  386. 'price': line.price,
  387. 'price_taxed': line.price_taxed,
  388. 'discount_rate': line.discount_rate,
  389. 'discount_amount': discount_amount,
  390. 'tax_rate': line.tax_rate,
  391. 'plan_date':self.planned_date,
  392. }
  393. def _generate_receipt(self, receipt_line):
  394. '''根据明细行生成入库单或退货单'''
  395. # 如果退货,warehouse_dest_id,warehouse_id要调换
  396. warehouse = (self.type == 'buy'
  397. and self.env.ref("warehouse.warehouse_supplier")
  398. or self.warehouse_dest_id)
  399. warehouse_dest = (self.type == 'buy'
  400. and self.warehouse_dest_id
  401. or self.env.ref("warehouse.warehouse_supplier"))
  402. rec = (self.type == 'buy' and self.with_context(is_return=False)
  403. or self.with_context(is_return=True))
  404. receipt_id = rec.env['buy.receipt'].create({
  405. 'partner_id': self.partner_id.id,
  406. 'warehouse_id': warehouse.id,
  407. 'warehouse_dest_id': warehouse_dest.id,
  408. 'date': self.planned_date,
  409. 'date_due': self.planned_date,
  410. 'order_id': self.id,
  411. 'ref': self.ref,
  412. 'origin': 'buy.receipt',
  413. 'discount_rate': self.discount_rate,
  414. 'discount_amount': self.discount_amount,
  415. 'invoice_by_receipt': self.invoice_by_receipt,
  416. 'currency_id': self.currency_id.id,
  417. 'currency_rate': self.env['res.currency'].get_rate_silent(
  418. self.date, self.currency_id.id) or 0,
  419. 'project_id': self.project_id.id,
  420. })
  421. if self.type == 'buy':
  422. receipt_id.write({'line_in_ids': [
  423. (0, 0, line) for line in receipt_line]})
  424. else:
  425. receipt_id.write({'line_out_ids': [
  426. (0, 0, line) for line in receipt_line]})
  427. return receipt_id
  428. def buy_generate_receipt(self):
  429. '''由采购订单生成采购入库/退货单'''
  430. self.ensure_one()
  431. receipt_line = [] # 采购入库/退货单行
  432. for line in self.line_ids:
  433. # 如果订单部分入库,则点击此按钮时生成剩余数量的入库单
  434. to_in = line.quantity - line.quantity_in
  435. if to_in <= 0:
  436. continue
  437. if line.goods_id.force_batch_one:
  438. i = 0
  439. while i < to_in:
  440. i += 1
  441. receipt_line.append(
  442. self.get_receipt_line(line, single=True))
  443. else:
  444. receipt_line.append(self.get_receipt_line(line, single=False))
  445. if not receipt_line:
  446. return {}
  447. self._generate_receipt(receipt_line)
  448. return {}
  449. def action_view_receipt(self):
  450. '''
  451. This function returns an action that display existing picking orders of given purchase order ids.
  452. When only one found, show the picking immediately.
  453. '''
  454. self.ensure_one()
  455. action = {
  456. 'name': '采购入库单',
  457. 'type': 'ir.actions.act_window',
  458. 'view_mode': 'form',
  459. 'res_model': 'buy.receipt',
  460. 'view_id': False,
  461. 'target': 'current',
  462. }
  463. #receipt_ids = sum([order.receipt_ids.ids for order in self], [])
  464. receipt_ids = [receipt.id for receipt in self.receipt_ids if not receipt.is_return]
  465. # choose the view_mode accordingly
  466. if len(receipt_ids) > 1:
  467. action['domain'] = "[('id','in',[" + \
  468. ','.join(map(str, receipt_ids)) + "])]"
  469. action['view_mode'] = 'list,form'
  470. elif len(receipt_ids) == 1:
  471. view_id = self.env.ref('buy.buy_receipt_form').id
  472. action['views'] = [(view_id, 'form')]
  473. action['res_id'] = receipt_ids and receipt_ids[0] or False
  474. return action
  475. def action_view_return(self):
  476. '''
  477. 该采购订单对应的退货单
  478. '''
  479. self.ensure_one()
  480. action = {
  481. 'name': '采购退货单',
  482. 'type': 'ir.actions.act_window',
  483. 'view_mode': 'form',
  484. 'res_model': 'buy.receipt',
  485. 'view_id': False,
  486. 'target': 'current',
  487. }
  488. receipt_ids = [receipt.id for receipt in self.receipt_ids if receipt.is_return]
  489. if len(receipt_ids) > 1:
  490. action['domain'] = "[('id','in',[" + \
  491. ','.join(map(str, receipt_ids)) + "])]"
  492. action['view_mode'] = 'list,form'
  493. elif len(receipt_ids) == 1:
  494. view_id = self.env.ref('buy.buy_return_form').id
  495. action['views'] = [(view_id, 'form')]
  496. action['res_id'] = receipt_ids and receipt_ids[0] or False
  497. return action
  498. def action_view_invoice(self):
  499. '''
  500. This function returns an action that display existing invoices of given purchase order ids( linked/computed via buy.receipt).
  501. When only one found, show the invoice immediately.
  502. '''
  503. self.ensure_one()
  504. if self.invoice_count == 0:
  505. return False
  506. action = {
  507. 'name': '结算单(供应商发票)',
  508. 'type': 'ir.actions.act_window',
  509. 'view_mode': 'form',
  510. 'res_model': 'money.invoice',
  511. 'view_id': False,
  512. 'target': 'current',
  513. }
  514. invoice_ids = self.invoice_ids.ids
  515. action['domain'] = "[('id','in',[" + \
  516. ','.join(map(str, invoice_ids)) + "])]"
  517. action['view_mode'] = 'list'
  518. return action
  519. class BuyOrderLine(models.Model):
  520. _name = 'buy.order.line'
  521. _description = '采购订单明细'
  522. # 根据采购商品的主单位数量,计算该商品的辅助单位数量
  523. @api.depends('quantity', 'goods_id')
  524. def _get_goods_uos_qty(self):
  525. for line in self:
  526. if line.goods_id and line.quantity:
  527. line.goods_uos_qty = line.quantity / line.goods_id.conversion
  528. else:
  529. line.goods_uos_qty = 0
  530. # 根据商品的辅助单位数量,反算出商品的主单位数量
  531. @api.onchange('goods_uos_qty', 'goods_id')
  532. def _inverse_quantity(self):
  533. for line in self:
  534. line.quantity = line.goods_uos_qty * line.goods_id.conversion
  535. @api.depends('goods_id')
  536. def _compute_using_attribute(selfs):
  537. '''返回订单行中商品是否使用属性'''
  538. for self in selfs:
  539. self.using_attribute = self.goods_id.attribute_ids and True or False
  540. @api.depends('quantity', 'price_taxed', 'discount_amount', 'tax_rate')
  541. def _compute_all_amount(selfs):
  542. for self in selfs:
  543. '''当订单行的数量、含税单价、折扣额、税率改变时,改变采购金额、税额、价税合计'''
  544. self.subtotal = self.price_taxed * self.quantity - self.discount_amount # 价税合计
  545. self.tax_amount = self.subtotal / (100 + self.tax_rate) * self.tax_rate # 税额
  546. self.amount = self.subtotal - self.tax_amount # 金额
  547. @api.onchange('price', 'tax_rate')
  548. def onchange_price(self):
  549. '''当订单行的不含税单价改变时,改变含税单价'''
  550. price = self.price_taxed / (1 + self.tax_rate * 0.01) # 不含税单价
  551. decimal = self.env.ref('core.decimal_price')
  552. if float_compare(price, self.price, precision_digits=decimal.digits) != 0:
  553. self.price_taxed = self.price * (1 + self.tax_rate * 0.01)
  554. order_id = fields.Many2one('buy.order',
  555. '订单编号',
  556. index=True,
  557. required=True,
  558. ondelete='cascade',
  559. help='关联订单的编号')
  560. partner_id = fields.Many2one(
  561. 'partner',
  562. string="供应商",
  563. related='order_id.partner_id',
  564. store=True)
  565. goods_id = fields.Many2one('goods',
  566. '商品',
  567. ondelete='restrict',
  568. help='商品')
  569. using_attribute = fields.Boolean('使用属性',
  570. compute=_compute_using_attribute,
  571. help='商品是否使用属性')
  572. attribute_id = fields.Many2one('attribute',
  573. '属性',
  574. ondelete='restrict',
  575. domain="[('goods_id', '=', goods_id)]",
  576. help='商品的属性,当商品有属性时,该字段必输')
  577. goods_uos_qty = fields.Float('辅助数量', digits='Quantity', compute='_get_goods_uos_qty',
  578. inverse='_inverse_quantity', store=True,
  579. help='商品的辅助数量')
  580. uos_id = fields.Many2one('uom', string='辅助单位', ondelete='restrict', readonly=True, help='商品的辅助单位')
  581. uom_id = fields.Many2one('uom',
  582. '单位',
  583. ondelete='restrict',
  584. help='商品计量单位')
  585. quantity = fields.Float('数量',
  586. default=1,
  587. required=True,
  588. digits='Quantity',
  589. help='下单数量')
  590. quantity_in = fields.Float('已执行数量',
  591. copy=False,
  592. digits='Quantity',
  593. help='采购订单产生的入库单/退货单已执行数量')
  594. price = fields.Float('采购单价',
  595. store=True,
  596. digits='Price',
  597. help='不含税单价,由含税单价计算得出')
  598. price_taxed = fields.Float('含税单价',
  599. digits='Price',
  600. help='含税单价,取自商品成本或对应供应商的采购价')
  601. discount_rate = fields.Float('折扣率%',
  602. help='折扣率')
  603. discount_amount = fields.Float('折扣额',
  604. digits='Amount',
  605. help='输入折扣率后自动计算得出,也可手动输入折扣额')
  606. amount = fields.Float('金额',
  607. compute=_compute_all_amount,
  608. store=True,
  609. digits='Amount',
  610. help='金额 = 价税合计 - 税额')
  611. tax_rate = fields.Float('税率(%)',
  612. default=lambda self: self.env.user.company_id.import_tax_rate,
  613. help='默认值取公司进项税率')
  614. tax_amount = fields.Float('税额',
  615. compute=_compute_all_amount,
  616. store=True,
  617. digits='Amount',
  618. help='由税率计算得出')
  619. subtotal = fields.Float('价税合计',
  620. compute=_compute_all_amount,
  621. store=True,
  622. digits='Amount',
  623. help='含税单价 乘以 数量')
  624. procure_date = fields.Date('计划交期', help='供应商计划交货日期。采购订单(要求交货日期)+ 料品(供应商备货周期)。')
  625. note = fields.Char('备注',
  626. help='本行备注')
  627. company_id = fields.Many2one(
  628. 'res.company',
  629. string='公司',
  630. change_default=True,
  631. default=lambda self: self.env.company)
  632. quantity_todo = fields.Float(
  633. '未执行数量', compute="_compute_quantity_todo",
  634. store=True, digits='Quantity')
  635. @api.depends('quantity', 'quantity_in')
  636. def _compute_quantity_todo(self):
  637. for s in self:
  638. s.quantity_todo = s.quantity - s.quantity_in
  639. @api.onchange('goods_id', 'quantity','order_id')
  640. def onchange_goods_id(self):
  641. '''当订单行的商品变化时,带出商品上的单位、成本价。
  642. 在采购订单上选择供应商,自动带出供货价格,没有设置供货价的取成本价格。'''
  643. if not self.order_id.partner_id:
  644. raise UserError('请先选择一个供应商!')
  645. if self.goods_id:
  646. self.uom_id = self.goods_id.uom_id
  647. self.uos_id = self.goods_id.uos_id
  648. if self.price == 0:
  649. self.price = self.goods_id.cost
  650. # 使用搜索使模型排序生效
  651. vendor_ids = self.env['vendor.goods'].search([
  652. ('goods_id', '=', self.goods_id.id )])
  653. for line in vendor_ids:
  654. if line.date and line.date > self.order_id.date:
  655. continue
  656. if line.vendor_id == self.order_id.partner_id \
  657. and self.quantity >= line.min_qty:
  658. if self.env.company.vendor_price_taxed:
  659. self.price_taxed = line.price
  660. else:
  661. self.price = line.price
  662. break
  663. self.tax_rate = self.goods_id.get_tax_rate(self.goods_id, self.order_id.partner_id, 'buy')
  664. @api.onchange('quantity', 'price_taxed', 'discount_rate')
  665. def onchange_discount_rate(self):
  666. '''当数量、单价或优惠率发生变化时,优惠金额发生变化'''
  667. price = self.price_taxed / (1 + self.tax_rate * 0.01)
  668. decimal = self.env.ref('core.decimal_price')
  669. if float_compare(price, self.price, precision_digits=decimal.digits) != 0:
  670. self.price = price
  671. self.discount_amount = (self.quantity * price *
  672. self.discount_rate * 0.01)
  673. @api.constrains('tax_rate')
  674. def _check_tax_rate(self):
  675. for record in self:
  676. if record.tax_rate > 100:
  677. raise UserError('税率不能输入超过100的数')
  678. if record.tax_rate < 0:
  679. raise UserError('税率不能输入负数')
  680. class Payment(models.Model):
  681. _name = "payment.plan"
  682. _description = '付款计划'
  683. name = fields.Char(string="付款阶段名称", required=True,
  684. help='付款计划名称')
  685. amount_money = fields.Float(string="金额", required=True,
  686. help='付款金额')
  687. date_application = fields.Date(string="申请日期", readonly=True,
  688. help='付款申请日期')
  689. buy_id = fields.Many2one("buy.order",
  690. help='关联的采购订单',
  691. ondelete='cascade'
  692. )
  693. money_invoice_id = fields.Many2one('money.invoice', string='结算单', ondelete='restrict',)
  694. @api.constrains('amount_money', 'buy_id')
  695. def _check_payment_amount(self):
  696. for plan in self:
  697. if not plan.buy_id:
  698. continue
  699. # buy.order 模型中有一个名为 'amount' 的字段表示订单总金额。
  700. order_amount = plan.buy_id.amount
  701. # 计算同一个采购订单下,除了当前记录之外的其他付款计划总和
  702. domain = [('buy_id', '=', plan.buy_id.id)]
  703. # 如果当前记录已存在(是更新操作),需要排除它,以免把旧金额重复计算
  704. if plan.id:
  705. domain.append(('id', '!=', plan.id))
  706. other_plans = self.search(domain)
  707. other_amount_sum = sum(other_plans.mapped('amount_money'))
  708. # 当前总金额 = 其他计划金额 + 当前计划正在保存的金额
  709. current_total = other_amount_sum + plan.amount_money
  710. if current_total > order_amount:
  711. raise ValidationError(
  712. _(
  713. "付款计划金额总计 (%.2f) 超过了关联采购订单 '%s' 的总金额 (%.2f)。\n"
  714. "无法保存。"
  715. ) % (current_total, plan.buy_id.name, order_amount)
  716. )
  717. def unlink(self):
  718. # 1. 获取所有需要删除的记录所关联的结算单
  719. invoices = self.mapped('money_invoice_id')
  720. # 2. 解除关联
  721. # 因为设置了 ondelete='restrict',如果不先解除关系,直接删除 invoices 会报错。
  722. # write 操作可能会触发其他逻辑,但这是清除关联的标准方法。
  723. self.write({'money_invoice_id': False})
  724. # 3. 删除关联的结算单
  725. if invoices:
  726. invoices.unlink()
  727. # 4. 执行父类的删除方法,删除当前的付款计划记录
  728. return super().unlink()
  729. def request_payment(self):
  730. self.ensure_one()
  731. if not self.buy_id.company_id.bank_account_id.id:
  732. raise UserError('%s未设置开户行,不能申请付款,\n请联系系统管理员进行设置。' % self.buy_id.company_id.name)
  733. categ = self.env.ref('money.core_category_purchase')
  734. tax_rate = self.buy_id.line_ids[0].tax_rate
  735. tax_amount = self.amount_money * tax_rate / (100 + tax_rate)
  736. if not float_is_zero(self.amount_money, 2):
  737. source_id = self.env['money.invoice'].create({
  738. 'name': self.buy_id.name,
  739. 'partner_id': self.buy_id.partner_id.id,
  740. 'category_id': categ.id,
  741. 'date': fields.Date.context_today(self),
  742. 'amount': self.amount_money,
  743. 'tax_amount': tax_amount,
  744. 'reconciled': 0,
  745. 'to_reconcile': self.amount_money,
  746. 'date_due': fields.Date.context_today(self),
  747. 'state': 'draft',
  748. })
  749. # 避免付款单去核销一张未确认的结算单(公司按发票确认应收应付的场景下出现)
  750. if source_id.state == 'draft':
  751. source_id.money_invoice_done()
  752. self.with_context(type='pay').env["money.order"].create({
  753. 'partner_id': self.buy_id.partner_id.id,
  754. 'bank_name': self.buy_id.partner_id.bank_name,
  755. 'bank_num': self.buy_id.partner_id.bank_num,
  756. 'date': fields.Date.context_today(self),
  757. 'source_ids':
  758. [(0, 0, {'name': source_id.id,
  759. 'category_id': categ.id,
  760. 'date': source_id.date,
  761. 'amount': self.amount_money,
  762. 'reconciled': 0.0,
  763. 'to_reconcile': self.amount_money,
  764. 'this_reconcile': self.amount_money})],
  765. 'line_ids':
  766. [(0, 0, {'bank_id': self.buy_id.company_id.bank_account_id.id,
  767. 'amount': self.amount_money})],
  768. 'type': 'pay',
  769. 'amount': self.amount_money,
  770. 'reconciled': 0,
  771. 'to_reconcile': self.amount_money,
  772. 'state': 'draft',
  773. 'buy_id': self.buy_id.id,
  774. })
  775. self.money_invoice_id = source_id.id
  776. self.date_application = datetime.now()
上海开阖软件有限公司 沪ICP备12045867号-1