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

635 行
27KB

  1. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. from odoo.exceptions import UserError, ValidationError
  4. from odoo import fields, models, api
  5. from odoo.tools import float_compare, float_is_zero
  6. class MoneyOrder(models.Model):
  7. _name = 'money.order'
  8. _description = "收付款单"
  9. _inherit = ['mail.thread', 'mail.activity.mixin']
  10. _order = 'id desc'
  11. TYPE_SELECTION = [
  12. ('pay', '付款'),
  13. ('get', '收款'),
  14. ]
  15. @api.model
  16. def create(self, values):
  17. # 创建单据时,根据订单类型的不同,生成不同的单据编号
  18. if self.env.context.get('type') == 'pay':
  19. values.update(
  20. {'name': self.env['ir.sequence'].next_by_code('pay.order')})
  21. else:
  22. values.update(
  23. {'name': self.env['ir.sequence'].next_by_code('get.order')})
  24. # 创建时查找该业务伙伴是否存在 未审核 状态下的收付款单
  25. orders = self.env['money.order'].search([
  26. ('partner_id', '=', values.get('partner_id')),
  27. ('state', '=', 'draft'),
  28. ('source_ids', '!=', False),
  29. ('id', '!=', self.id)])
  30. if values.get('source_ids') and orders:
  31. raise UserError('该业务伙伴存在未确认的收/付款单,请先确认')
  32. return super(MoneyOrder, self).create(values)
  33. def write(self, values):
  34. # 修改时查找该业务伙伴是否存在 未审核 状态下的收付款单
  35. if values.get('partner_id'):
  36. orders = self.env['money.order'].search([
  37. ('partner_id', '=', values.get('partner_id')),
  38. ('state', '=', 'draft'),
  39. ('id', '!=', self.id)])
  40. if orders:
  41. raise UserError('业务伙伴(%s)存在未审核的收/付款单,请先审核' %
  42. orders.partner_id.name)
  43. return super(MoneyOrder, self).write(values)
  44. @api.depends('discount_amount',
  45. 'line_ids.amount',
  46. 'source_ids.this_reconcile')
  47. def _compute_advance_payment(self):
  48. """
  49. 计算字段advance_payment(本次预收)
  50. """
  51. for mo in self:
  52. amount, this_reconcile = 0.0, 0.0
  53. for line in mo.line_ids:
  54. amount += line.amount
  55. for line in mo.source_ids:
  56. this_reconcile += line.this_reconcile
  57. if mo.type == 'get':
  58. mo.advance_payment = \
  59. amount - this_reconcile + mo.discount_amount
  60. else:
  61. mo.advance_payment = \
  62. amount - this_reconcile - mo.discount_amount
  63. mo.amount = amount
  64. @api.depends('partner_id', 'type')
  65. def _compute_currency_id(self):
  66. """
  67. 取出币别
  68. :return:
  69. """
  70. for mo in self:
  71. partner_currency_id = (mo.type == 'get') \
  72. and mo.partner_id.c_category_id.account_id.currency_id.id \
  73. or mo.partner_id.s_category_id.account_id.currency_id.id
  74. mo.currency_id = \
  75. partner_currency_id or mo.env.user.company_id.currency_id.id
  76. state = fields.Selection([
  77. ('draft', '草稿'),
  78. ('done', '已完成'),
  79. ('cancel', '已作废'),
  80. ], string='状态', readonly=True, default='draft', copy=False, index=True,
  81. help='收/付款单状态标识,新建时状态为草稿;确认后状态为已完成')
  82. partner_id = fields.Many2one('partner', string='往来单位', required=True,
  83. ondelete='restrict',
  84. readonly="state!='draft'",
  85. help='该单据对应的业务伙伴,单据确认时会影响他的应收应付余额')
  86. date = fields.Date(string='单据日期',
  87. default=lambda self: fields.Date.context_today(self),
  88. readonly="state!='draft'",
  89. help='单据创建日期')
  90. name = fields.Char(string='单据编号', copy=False, readonly=True,
  91. help='单据编号,创建时会根据类型自动生成')
  92. note = fields.Text(string='备注', help='可以为该单据添加一些需要的标识信息')
  93. currency_id = fields.Many2one(
  94. 'res.currency',
  95. '币别',
  96. compute='_compute_currency_id',
  97. store=True,
  98. readonly=True,
  99. tracking=True,
  100. help='业务伙伴的类别科目上对应的外币币别')
  101. discount_amount = fields.Float(string='我方承担费用',
  102. readonly="state!='draft'",
  103. digits='Amount',
  104. help='收/付款时发生的银行手续费或给业务伙伴的现金折扣。')
  105. discount_account_id = fields.Many2one(
  106. 'finance.account', '费用科目',
  107. domain="[('account_type','=','normal')]",
  108. readonly="state!='draft'",
  109. help='收/付款单确认生成凭证时,手续费或折扣对应的科目')
  110. line_ids = fields.One2many('money.order.line', 'money_id',
  111. string='收/付款单行',
  112. readonly="state!='draft'",
  113. help='收/付款单明细行')
  114. source_ids = fields.One2many('source.order.line', 'money_id',
  115. string='待核销行',
  116. readonly="state!='draft'",
  117. help='收/付款单待核销行')
  118. type = fields.Selection(TYPE_SELECTION, string='类型',
  119. default=lambda self: self.env.context.get('type'),
  120. help='类型:收款单 或者 付款单')
  121. amount = fields.Float(string='总金额', compute='_compute_advance_payment',
  122. digits='Amount',
  123. store=True, readonly=True,
  124. help='收/付款单行金额总和')
  125. advance_payment = fields.Float(
  126. string='本次预付',
  127. compute='_compute_advance_payment',
  128. digits='Amount',
  129. store=True, readonly=True,
  130. help='根据收/付款单行金额总和,原始单据行金额总和及折扣额计算得来的预收/预付款,值>=0')
  131. to_reconcile = fields.Float(string='未核销金额',
  132. digits='Amount',
  133. help='未核销的预收/预付款金额')
  134. reconciled = fields.Float(string='已核销金额',
  135. digits='Amount',
  136. help='已核销的预收/预付款金额')
  137. origin_name = fields.Char('原始单据编号',
  138. help='原始单据编号')
  139. bank_name = fields.Char('开户行',
  140. readonly="state!='draft'",
  141. help='开户行取自业务伙伴,可修改')
  142. bank_num = fields.Char('银行账号',
  143. readonly="state!='draft'",
  144. help='银行账号取自业务伙伴,可修改')
  145. approve_uid = fields.Many2one('res.users', '确认人',
  146. copy=False, ondelete='restrict')
  147. approve_date = fields.Datetime('确认日期', copy=False)
  148. company_id = fields.Many2one(
  149. 'res.company',
  150. string='公司',
  151. change_default=True,
  152. default=lambda self: self.env.company)
  153. voucher_id = fields.Many2one('voucher',
  154. '对应凭证',
  155. readonly=True,
  156. ondelete='restrict',
  157. copy=False,
  158. help='收/付款单确认时生成的对应凭证')
  159. def create_reconcile(self):
  160. self.ensure_one()
  161. if self.env['money.invoice'].search([
  162. ('partner_id', '=', self.partner_id.id),
  163. ('state', '=', 'done'),
  164. ('to_reconcile', '!=', 0),
  165. ], limit=1):
  166. if self.type == 'get':
  167. business_type = 'adv_pay_to_get'
  168. else:
  169. business_type = 'adv_get_to_pay'
  170. recon = self.env['reconcile.order'].create({
  171. 'partner_id': self.partner_id.id,
  172. 'business_type': business_type})
  173. recon.onchange_partner_id()
  174. action = {
  175. 'name': '核销单',
  176. 'type': 'ir.actions.act_window',
  177. 'view_mode': 'form',
  178. 'res_model': 'reconcile.order',
  179. 'res_id': recon.id,
  180. }
  181. return action
  182. else:
  183. raise UserError('没有未核销结算单')
  184. def write_off_reset(self):
  185. """
  186. 单据审核前重置计算单行上的本次核销金额
  187. :return:
  188. """
  189. self.ensure_one()
  190. if self.state != 'draft':
  191. raise ValueError('已确认的单据不能执行这个操作')
  192. for source in self.source_ids:
  193. source.this_reconcile = 0
  194. return True
  195. @api.onchange('date')
  196. def onchange_date(self):
  197. """
  198. 当修改日期时,则根据context中的money的type对客户添加过滤,过滤出是供应商还是客户。
  199. (因为date有默认值所以这个过滤是默认触发的) 其实和date是否变化没有关系,页面加载就触发下面的逻辑
  200. :return:
  201. """
  202. if self.env.context.get('type') == 'get':
  203. return {'domain': {'partner_id': [('c_category_id', '!=', False)]}}
  204. else:
  205. return {'domain': {'partner_id': [('s_category_id', '!=', False)]}}
  206. def _get_source_line(self, invoice):
  207. """
  208. 根据传入的invoice的对象取出对应的值 构造出 source_line的一个dict 包含source line的主要参数
  209. :param invoice: money_invoice对象
  210. :return: dict
  211. """
  212. return {
  213. 'name': invoice.id,
  214. 'category_id': invoice.category_id.id,
  215. 'amount': invoice.amount,
  216. 'date': invoice.date,
  217. 'reconciled': invoice.reconciled,
  218. 'to_reconcile': invoice.to_reconcile,
  219. 'this_reconcile': invoice.to_reconcile,
  220. 'date_due': invoice.date_due,
  221. }
  222. def _get_invoice_search_list(self):
  223. """
  224. 构造出 invoice 搜索的domain
  225. :return:
  226. """
  227. invoice_search_list = [('partner_id', '=', self.partner_id.id),
  228. ('to_reconcile', '!=', 0),
  229. ('state', '=', 'done')]
  230. if self.env.context.get('type') == 'get':
  231. invoice_search_list.append(('category_id.type', '=', 'income'))
  232. else: # type = 'pay':
  233. invoice_search_list.append(('category_id.type', '=', 'expense'))
  234. return invoice_search_list
  235. @api.onchange('partner_id')
  236. def onchange_partner_id(self):
  237. """
  238. 对partner修改的监控当 partner 修改时,
  239. 就对 页面相对应的字段进行修改(bank_name,bank_num,source_ids)
  240. """
  241. if not self.partner_id:
  242. return {}
  243. self.source_ids = False
  244. source_lines = []
  245. self.bank_name = self.partner_id.bank_name
  246. self.bank_num = self.partner_id.bank_num
  247. for invoice in self.env['money.invoice'].search(
  248. self._get_invoice_search_list()):
  249. source_lines.append((0, 0, self._get_source_line(invoice)))
  250. self.source_ids = source_lines
  251. def money_order_done(self):
  252. '''对收付款单的审核按钮'''
  253. for order in self:
  254. if order.state == 'done':
  255. raise UserError('请不要重复确认')
  256. if order.type == 'pay' and \
  257. not order.partner_id.s_category_id.account_id:
  258. raise UserError('请输入供应商类别(%s)上的科目' %
  259. order.partner_id.s_category_id.name)
  260. if order.type == 'get' and \
  261. not order.partner_id.c_category_id.account_id:
  262. raise UserError('请输入客户类别(%s)上的科目' %
  263. order.partner_id.c_category_id.name)
  264. if order.advance_payment < 0 and order.source_ids:
  265. raise UserError('本次核销金额不能大于付款金额。\n差额: %s' %
  266. (order.advance_payment))
  267. total = 0
  268. for line in order.line_ids:
  269. rate_silent = self.env['res.currency'].get_rate_silent(
  270. order.date, line.currency_id.id)
  271. if order.type == 'pay': # 付款账号余额减少, 退款账号余额增加
  272. decimal_amount = self.env.ref('core.decimal_amount')
  273. balance = (
  274. line.currency_id
  275. != self.env.user.company_id.currency_id
  276. and line.bank_id.currency_amount
  277. or line.bank_id.balance)
  278. if float_compare(
  279. balance, line.amount,
  280. precision_digits=decimal_amount.digits) == -1:
  281. raise UserError('账户余额不足。\n账户余额:%s 付款行金额:%s' %
  282. (balance, line.amount))
  283. if (line.currency_id
  284. != self.env.user.company_id.currency_id): # 外币
  285. line.bank_id.currency_amount -= line.amount
  286. line.bank_id.balance -= line.amount * rate_silent
  287. else:
  288. line.bank_id.balance -= line.amount
  289. else: # 收款账号余额增加, 退款账号余额减少
  290. if (line.currency_id
  291. != self.env.user.company_id.currency_id): # 外币
  292. line.bank_id.currency_amount += line.amount
  293. line.bank_id.balance += line.amount * rate_silent
  294. else:
  295. line.bank_id.balance += line.amount
  296. total += line.amount
  297. if order.type == 'pay':
  298. order.partner_id.payable -= total - order.discount_amount
  299. else:
  300. order.partner_id.receivable -= total + order.discount_amount
  301. # 更新结算单的未核销金额、已核销金额
  302. for source in order.source_ids:
  303. decimal_amount = self.env.ref('core.decimal_amount')
  304. if float_compare(
  305. source.this_reconcile,
  306. abs(source.to_reconcile),
  307. precision_digits=decimal_amount.digits) == 1:
  308. raise UserError(
  309. '本次核销金额不能大于未核销金额。\n 核销金额:%s 未核销金额:%s'
  310. % (abs(source.to_reconcile), source.this_reconcile))
  311. source.name.to_reconcile -= source.this_reconcile
  312. source.name.reconciled += source.this_reconcile
  313. if source.this_reconcile == 0: # 如果核销行的本次付款金额为0,删除
  314. source.unlink()
  315. # 生成凭证并审核
  316. if order.type == 'get':
  317. voucher = order.create_money_order_get_voucher(
  318. order.line_ids, order.source_ids, order.partner_id,
  319. order.name, order.note or '')
  320. else:
  321. voucher = order.create_money_order_pay_voucher(
  322. order.line_ids, order.source_ids, order.partner_id,
  323. order.name, order.note or '')
  324. voucher.voucher_done()
  325. return order.write({
  326. 'to_reconcile': order.advance_payment,
  327. 'reconciled': order.amount - order.advance_payment,
  328. 'voucher_id': voucher.id,
  329. 'approve_uid': self.env.uid,
  330. 'approve_date': fields.Datetime.now(self),
  331. 'state': 'done',
  332. })
  333. def money_order_draft(self):
  334. """
  335. 收付款单反审核方法
  336. """
  337. for order in self:
  338. if order.state == 'draft':
  339. raise UserError('请不要重复撤销 %s' % self._description)
  340. # 收/付款单 存在已审核金额不为0的核销单
  341. total_current_reconciled = order.amount - order.advance_payment
  342. decimal_amount = self.env.ref('core.decimal_amount')
  343. if float_compare(order.reconciled,
  344. total_current_reconciled,
  345. precision_digits=decimal_amount.digits) != 0:
  346. raise UserError('单据已核销金额不为0,不能反审核!请检查核销单!')
  347. total = 0
  348. for line in order.line_ids:
  349. rate_silent = self.env['res.currency'].get_rate_silent(
  350. order.date, line.currency_id.id)
  351. if order.type == 'pay': # 反审核:付款账号余额增加
  352. if (line.currency_id
  353. != self.env.user.company_id.currency_id): # 外币
  354. line.bank_id.currency_amount += line.amount
  355. line.bank_id.balance += line.amount * rate_silent
  356. else:
  357. line.bank_id.balance += line.amount
  358. else: # 反审核:收款账号余额减少
  359. balance = (
  360. line.currency_id
  361. != self.env.user.company_id.currency_id
  362. and line.bank_id.currency_amount
  363. or line.bank_id.balance)
  364. decimal_amount = self.env.ref('core.decimal_amount')
  365. if float_compare(
  366. balance,
  367. line.amount,
  368. precision_digits=decimal_amount.digits) == -1:
  369. raise UserError('账户余额不足。\n 账户余额:%s 收款行金额:%s' %
  370. (balance, line.amount))
  371. if (line.currency_id
  372. != self.env.user.company_id.currency_id): # 外币
  373. line.bank_id.currency_amount -= line.amount
  374. line.bank_id.balance -= line.amount * rate_silent
  375. else:
  376. line.bank_id.balance -= line.amount
  377. total += line.amount
  378. if order.type == 'pay':
  379. order.partner_id.payable += total - order.discount_amount
  380. else:
  381. order.partner_id.receivable += total + order.discount_amount
  382. for source in order.source_ids:
  383. source.name.to_reconcile += source.this_reconcile
  384. source.name.reconciled -= source.this_reconcile
  385. voucher = order.voucher_id
  386. order.write({
  387. 'to_reconcile': 0,
  388. 'reconciled': 0,
  389. 'voucher_id': False,
  390. 'approve_uid': False,
  391. 'approve_date': False,
  392. 'state': 'draft',
  393. })
  394. # 反审核凭证并删除
  395. if voucher.state == 'done':
  396. voucher.voucher_draft()
  397. voucher.unlink()
  398. return True
  399. def _prepare_vouch_line_data(self, line, name, account_id, debit, credit,
  400. voucher_id, partner_id, currency_id):
  401. rate_silent = currency_amount = 0
  402. if currency_id:
  403. rate_silent = self.env['res.currency'].get_rate_silent(
  404. self.date, currency_id)
  405. currency_amount = debit or credit
  406. debit = debit * (rate_silent or 1)
  407. credit = credit * (rate_silent or 1)
  408. return {
  409. 'name': name,
  410. 'account_id': account_id,
  411. 'debit': debit,
  412. 'credit': credit,
  413. 'voucher_id': voucher_id,
  414. 'partner_id': partner_id,
  415. 'currency_id': currency_id,
  416. 'currency_amount': currency_amount,
  417. 'rate_silent': rate_silent or ''
  418. }
  419. def _create_voucher_line(self, line, name, account_id, debit, credit,
  420. voucher_id, partner_id, currency_id):
  421. line_data = self._prepare_vouch_line_data(
  422. line, name, account_id, debit, credit,
  423. voucher_id, partner_id, currency_id)
  424. voucher_line = self.env['voucher.line'].create(line_data)
  425. return voucher_line
  426. def create_money_order_get_voucher(self, line_ids, source_ids,
  427. partner, name, note):
  428. """
  429. 为收款单创建凭证
  430. :param line_ids: 收款单明细
  431. :param source_ids: 没用到
  432. :param partner: 客户
  433. :param name: 收款单名称
  434. :return: 创建的凭证
  435. """
  436. vouch_obj = self.env['voucher'].create(
  437. {'date': self.date, 'ref': '%s,%s' % (self._name, self.id)})
  438. # self.write({'voucher_id': vouch_obj.id})
  439. amount_all = 0.0
  440. line_data = False
  441. for line in line_ids:
  442. line_data = line
  443. if not line.bank_id.account_id:
  444. raise UserError('请配置%s的会计科目' % (line.bank_id.name))
  445. # 生成借方明细行
  446. if line.amount: # 可能输入金额为0的收款单用于核销尾差
  447. self._create_voucher_line(line,
  448. "%s %s" % (name, note),
  449. line.bank_id.account_id.id,
  450. line.amount,
  451. 0,
  452. vouch_obj.id,
  453. '',
  454. line.currency_id.id
  455. )
  456. amount_all += line.amount
  457. if self.discount_amount != 0:
  458. # 生成借方明细行
  459. self._create_voucher_line(
  460. False,
  461. "%s 现金折扣 %s" % (name, note),
  462. self.discount_account_id.id,
  463. self.discount_amount,
  464. 0,
  465. vouch_obj.id,
  466. self.partner_id.id,
  467. line_data and line_data.currency_id.id or self.currency_id.id
  468. )
  469. if partner.c_category_id:
  470. partner_account_id = partner.c_category_id.account_id.id
  471. # 生成贷方明细行
  472. if amount_all + self.discount_amount:
  473. self._create_voucher_line(
  474. '',
  475. "%s %s" % (name, note),
  476. partner_account_id,
  477. 0,
  478. amount_all + self.discount_amount,
  479. vouch_obj.id,
  480. self.partner_id.id,
  481. line_data and line.currency_id.id or self.currency_id.id
  482. )
  483. return vouch_obj
  484. def create_money_order_pay_voucher(self, line_ids, source_ids,
  485. partner, name, note):
  486. """
  487. 为付款单创建凭证
  488. :param line_ids: 付款单明细
  489. :param source_ids: 没用到
  490. :param partner: 供应商
  491. :param name: 付款单名称
  492. :return: 创建的凭证
  493. """
  494. vouch_obj = self.env['voucher'].create(
  495. {'date': self.date, 'ref': '%s,%s' % (self._name, self.id)})
  496. # self.write({'voucher_id': vouch_obj.id})
  497. amount_all = 0.0
  498. line_data = False
  499. for line in line_ids:
  500. line_data = line
  501. if not line.bank_id.account_id:
  502. raise UserError('请配置%s的会计科目' % (line.bank_id.name))
  503. # 生成贷方明细行 credit
  504. if line.amount: # 支持金额为0的付款用于核销尾差
  505. self._create_voucher_line(line,
  506. "%s %s" % (name, note),
  507. line.bank_id.account_id.id,
  508. 0,
  509. line.amount,
  510. vouch_obj.id,
  511. '',
  512. line.currency_id.id
  513. )
  514. amount_all += line.amount
  515. partner_account_id = partner.s_category_id.account_id.id
  516. # 生成借方明细行 debit
  517. if amount_all - self.discount_amount:
  518. self._create_voucher_line(
  519. '',
  520. "%s %s" % (name, note),
  521. partner_account_id,
  522. amount_all - self.discount_amount,
  523. 0,
  524. vouch_obj.id,
  525. self.partner_id.id,
  526. line_data and line.currency_id.id or self.currency_id.id
  527. )
  528. if self.discount_amount != 0:
  529. # 生成借方明细行 debit
  530. self._create_voucher_line(
  531. line_data and line_data or False,
  532. "%s 手续费 %s" % (name, note),
  533. self.discount_account_id.id,
  534. self.discount_amount,
  535. 0,
  536. vouch_obj.id,
  537. self.partner_id.id,
  538. line_data and line.currency_id.id or self.currency_id.id
  539. )
  540. return vouch_obj
  541. class MoneyOrderLine(models.Model):
  542. _name = 'money.order.line'
  543. _description = '收付款单明细'
  544. @api.depends('bank_id')
  545. def _compute_currency_id(self):
  546. """
  547. 获取币别
  548. """
  549. for mol in self:
  550. mol.currency_id = mol.bank_id.account_id.currency_id.id \
  551. or mol.env.user.company_id.currency_id.id
  552. if mol.bank_id and mol.currency_id != mol.money_id.currency_id:
  553. raise ValidationError(
  554. '结算帐户与业务伙伴币别不一致。\n 结算账户币别:%s 业务伙伴币别:%s'
  555. % (mol.currency_id.name, mol.money_id.currency_id.name))
  556. money_id = fields.Many2one('money.order', string='收付款单',
  557. ondelete='cascade',
  558. help='订单行对应的收付款单')
  559. bank_id = fields.Many2one('bank.account', string='结算账户',
  560. required=True, ondelete='restrict',
  561. help='本次收款/付款所用的计算账户,确认收付款单会修改对应账户的金额')
  562. amount = fields.Float(string='金额',
  563. digits='Amount',
  564. help='本次结算金额')
  565. mode_id = fields.Many2one('settle.mode', string='结算方式',
  566. ondelete='restrict',
  567. help='结算方式:支票、信用卡等')
  568. currency_id = fields.Many2one(
  569. 'res.currency', '币别',
  570. compute='_compute_currency_id',
  571. store=True, readonly=True,
  572. help='结算账户对应的外币币别')
  573. number = fields.Char(string='结算号',
  574. help='本次结算号')
  575. note = fields.Char(string='备注',
  576. help='可以为本次结算添加一些需要的标识信息')
  577. company_id = fields.Many2one(
  578. 'res.company',
  579. string='公司',
  580. change_default=True,
  581. default=lambda self: self.env.company)
上海开阖软件有限公司 沪ICP备12045867号-1