GoodERP
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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