GoodERP
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

785 lines
34KB

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