GoodERP
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

616 lignes
26KB

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