GoodERP
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

453 linhas
21KB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. from odoo import _
  5. from odoo.exceptions import UserError
  6. from odoo import fields, models, api
  7. from odoo.tools import float_compare, float_is_zero
  8. class ReconcileOrder(models.Model):
  9. _name = 'reconcile.order'
  10. _description = '核销单'
  11. _inherit = ['mail.thread']
  12. TYPE_SELECTION = [
  13. ('adv_pay_to_get', '预收冲应收'),
  14. ('adv_get_to_pay', '预付冲应付'),
  15. ('get_to_pay', '应收冲应付'),
  16. ('get_to_get', '应收转应收'),
  17. ('pay_to_pay', '应付转应付'),
  18. ]
  19. state = fields.Selection([
  20. ('draft', '草稿'),
  21. ('done', '已确认'),
  22. ('cancel', '已作废'),
  23. ], string='状态', readonly=True,
  24. default='draft', copy=False, index=True,
  25. tracking=True,
  26. help='核销单状态标识,新建时状态为草稿;确认后状态为已确认')
  27. partner_id = fields.Many2one('partner', string='往来单位', required=True,
  28. ondelete='restrict',
  29. help='该单据对应的业务伙伴,与业务类型一起带出待核销的明细行')
  30. to_partner_id = fields.Many2one('partner', string='转入往来单位',
  31. ondelete='restrict',
  32. help='应收转应收、应付转应付时对应的转入业务伙伴,'
  33. '订单确认时会影响该业务伙伴的应收/应付')
  34. advance_payment_ids = fields.One2many(
  35. 'advance.payment', 'pay_reconcile_id',
  36. string='预收/付款单行',
  37. help='业务伙伴有预收/付款单,自动带出,用来与应收/应付款单核销')
  38. receivable_source_ids = fields.One2many(
  39. 'source.order.line', 'receivable_reconcile_id',
  40. string='应收结算单行',
  41. help='业务伙伴有应收结算单,自动带出,待与预收款单核销')
  42. payable_source_ids = fields.One2many(
  43. 'source.order.line', 'payable_reconcile_id',
  44. string='应付结算单行',
  45. help='业务伙伴有应付结算单,自动带出,待与预付款单核销')
  46. business_type = fields.Selection(TYPE_SELECTION, string='业务类型',
  47. help='类型:预收冲应收,预付冲应付,应收冲应付,应收转应收,应付转应付'
  48. )
  49. name = fields.Char(string='单据编号', copy=False, readonly=True,
  50. help='单据编号,创建时会自动生成')
  51. date = fields.Date(string='单据日期',
  52. default=lambda self: fields.Date.context_today(self),
  53. help='单据创建日期')
  54. note = fields.Text(string='备注',
  55. help='可以为该单据添加一些需要的标识信息')
  56. company_id = fields.Many2one(
  57. 'res.company',
  58. string='公司',
  59. change_default=True,
  60. default=lambda self: self.env.company)
  61. @api.model_create_multi
  62. def create(self, vals_list):
  63. # 创建时查找该业务伙伴是否存在 未审核 状态下的核销单
  64. for values in vals_list:
  65. if values.get('partner_id'):
  66. orders = self.env['reconcile.order'].search([
  67. ('partner_id', '=', values.get('partner_id')),
  68. ('state', '=', 'draft'),
  69. ('id', '!=', self.id),
  70. ('business_type', '=', values.get('business_type'))])
  71. if orders:
  72. raise UserError(
  73. _('业务伙伴(%s)、业务类型(%s)存在未审核的核销单,请先审核') % (
  74. orders.partner_id.name,
  75. dict(self.fields_get(
  76. allfields=['business_type']
  77. )['business_type']['selection']
  78. )[orders.business_type]))
  79. return super(ReconcileOrder, self).create(vals_list)
  80. def write(self, values):
  81. # 写入时查找该业务伙伴是否存在 未审核 状态下的核销单
  82. orders = self.env['reconcile.order'].search([
  83. ('partner_id', '=',
  84. (values.get('partner_id') or self.partner_id.id)),
  85. ('state', '=', 'draft'),
  86. ('id', '!=', self.id),
  87. ('business_type', '=',
  88. (values.get('business_type') or self.business_type))])
  89. if orders:
  90. raise UserError(
  91. _('业务伙伴(%s)、业务类型(%s)存在未审核的核销单,请先审核') % (
  92. orders.partner_id.name,
  93. dict(self.fields_get(
  94. allfields=['business_type'])['business_type']
  95. ['selection'])[orders.business_type]))
  96. return super(ReconcileOrder, self).write(values)
  97. def _get_money_order(self, way='get'):
  98. """
  99. 搜索到满足条件的预收/付款单,为one2many字段赋值构造列表
  100. :param way: 收/付款单的type
  101. :return: list
  102. """
  103. money_orders = self.env['money.order'].search(
  104. [('partner_id', '=', self.partner_id.id),
  105. ('type', '=', way),
  106. ('state', '=', 'done'),
  107. ('to_reconcile', '!=', 0)])
  108. result = []
  109. for order in money_orders:
  110. result.append((0, 0, {
  111. 'name': order.id,
  112. 'amount': order.amount,
  113. 'date': order.date,
  114. 'reconciled': order.reconciled,
  115. 'to_reconcile': order.to_reconcile,
  116. 'this_reconcile': order.to_reconcile,
  117. }))
  118. return result
  119. def _get_money_invoice(self, way='income'):
  120. """
  121. 搜索到满足条件的money.invoice记录并且取出invoice对象 构造出one2many的
  122. :param way: money.invoice 中的category_id 的type
  123. :return:
  124. """
  125. MoneyInvoice = self.env['money.invoice'].search([
  126. ('category_id.type', '=', way),
  127. ('partner_id', '=', self.partner_id.id),
  128. ('state', '=', 'done'),
  129. ('to_reconcile', '!=', 0)])
  130. result = []
  131. for invoice in MoneyInvoice:
  132. result.append((0, 0, {
  133. 'name': invoice.id,
  134. 'category_id': invoice.category_id.id,
  135. 'amount': invoice.amount,
  136. 'date': invoice.date,
  137. 'reconciled': invoice.reconciled,
  138. 'to_reconcile': invoice.to_reconcile,
  139. 'date_due': invoice.date_due,
  140. 'this_reconcile': invoice.to_reconcile,
  141. }))
  142. return result
  143. @api.onchange('partner_id', 'to_partner_id', 'business_type')
  144. def onchange_partner_id(self):
  145. """
  146. onchange 类型字段 当改变 客户或者转入往来单位 业务类型 自动生成 对应的
  147. 核销单各种明细。
  148. :return:
  149. """
  150. if not self.partner_id or not self.business_type:
  151. return {}
  152. # 先清空之前填充的数据
  153. self.advance_payment_ids = None
  154. self.receivable_source_ids = None
  155. self.payable_source_ids = None
  156. if self.business_type == 'adv_pay_to_get': # 预收冲应收
  157. self.advance_payment_ids = self._get_money_order('get')
  158. self.receivable_source_ids = self._get_money_invoice('income')
  159. if self.business_type == 'adv_get_to_pay': # 预付冲应付
  160. self.advance_payment_ids = self._get_money_order('pay')
  161. self.payable_source_ids = self._get_money_invoice('expense')
  162. if self.business_type == 'get_to_pay': # 应收冲应付
  163. self.receivable_source_ids = self._get_money_invoice('income')
  164. self.payable_source_ids = self._get_money_invoice('expense')
  165. if self.business_type == 'get_to_get': # 应收转应收
  166. self.receivable_source_ids = self._get_money_invoice('income')
  167. return {'domain':
  168. {'to_partner_id': [('c_category_id', '!=', False)]}}
  169. if self.business_type == 'pay_to_pay': # 应付转应付
  170. self.payable_source_ids = self._get_money_invoice('expense')
  171. return {'domain':
  172. {'to_partner_id': [('s_category_id', '!=', False)]}}
  173. def _get_or_pay(self, line, business_type,
  174. partner_id, to_partner_id, name):
  175. """
  176. 核销单 核销时 对具体核销单行进行的操作
  177. :param line:
  178. :param business_type:
  179. :param partner_id:
  180. :param to_partner_id:
  181. :param name:
  182. :return:
  183. """
  184. decimal_amount = self.env.ref('core.decimal_amount')
  185. if float_compare(
  186. line.this_reconcile,
  187. line.to_reconcile,
  188. precision_digits=decimal_amount.digits) == 1:
  189. raise UserError(_('核销金额不能大于未核销金额。\n核销金额:%s 未核销金额:%s') %
  190. (line.this_reconcile, line.to_reconcile))
  191. # 更新每一行的已核销余额、未核销余额
  192. line.name.to_reconcile -= line.this_reconcile
  193. line.name.reconciled += line.this_reconcile
  194. # 应收转应收、应付转应付
  195. if business_type in ['get_to_get', 'pay_to_pay']:
  196. if not float_is_zero(line.this_reconcile, 2):
  197. # 转入业务伙伴往来增加
  198. self.env['money.invoice'].create({
  199. 'name': name,
  200. 'category_id': line.category_id.id,
  201. 'amount': line.this_reconcile,
  202. 'date': self.date,
  203. 'reconciled': 0, # 已核销金额
  204. 'to_reconcile': line.this_reconcile, # 未核销金额
  205. 'date_due': line.date_due,
  206. 'partner_id': to_partner_id.id,
  207. })
  208. # 转出业务伙伴往来减少
  209. to_invoice_id = self.env['money.invoice'].create({
  210. 'name': name,
  211. 'category_id': line.category_id.id,
  212. 'amount': -line.this_reconcile,
  213. 'date': self.date,
  214. 'date_due': line.date_due,
  215. 'partner_id': partner_id.id,
  216. })
  217. # 核销 转出业务伙伴 的转出金额
  218. to_invoice_id.to_reconcile = 0
  219. to_invoice_id.reconciled = -line.this_reconcile
  220. # 应收冲应付,应收行、应付行分别生成负的结算单,并且核销
  221. if business_type in ['get_to_pay']:
  222. if not float_is_zero(line.this_reconcile, 2):
  223. invoice_id = self.env['money.invoice'].create({
  224. 'name': name,
  225. 'category_id': line.category_id.id,
  226. 'amount': -line.this_reconcile,
  227. 'date': self.date,
  228. 'date_due': line.date_due,
  229. 'partner_id': partner_id.id,
  230. })
  231. # 核销 业务伙伴 的本次核销金额
  232. invoice_id.to_reconcile = 0
  233. invoice_id.reconciled = -line.this_reconcile
  234. return True
  235. def reconcile_order_done(self):
  236. '''核销单的审核按钮'''
  237. # 核销金额不能大于未核销金额
  238. for order in self:
  239. if order.state == 'done':
  240. raise UserError(_('核销单%s已确认,不能再次确认。') % order.name)
  241. order_reconcile, invoice_reconcile = 0, 0
  242. if order.business_type in ['get_to_get', 'pay_to_pay'] \
  243. and order.partner_id == order.to_partner_id:
  244. raise UserError(
  245. _('业务伙伴和转入往来单位不能相同。\n业务伙伴:%s 转入往来单位:%s')
  246. % (order.partner_id.name, order.to_partner_id.name))
  247. # 核销预收预付
  248. for line in order.advance_payment_ids:
  249. order_reconcile += line.this_reconcile
  250. decimal_amount = self.env.ref('core.decimal_amount')
  251. if float_compare(
  252. line.this_reconcile,
  253. line.to_reconcile,
  254. precision_digits=decimal_amount.digits) == 1:
  255. raise UserError(_('核销金额不能大于未核销金额。\n核销金额:%s 未核销金额:%s') % (
  256. line.this_reconcile, line.to_reconcile))
  257. # 更新每一行的已核销余额、未核销余额
  258. line.name.to_reconcile -= line.this_reconcile
  259. line.name.reconciled += line.this_reconcile
  260. for line in order.receivable_source_ids:
  261. invoice_reconcile += line.this_reconcile
  262. self._get_or_pay(line, order.business_type,
  263. order.partner_id,
  264. order.to_partner_id, order.name)
  265. for line in order.payable_source_ids:
  266. if self.business_type == 'adv_get_to_pay':
  267. invoice_reconcile += line.this_reconcile
  268. else:
  269. order_reconcile += line.this_reconcile
  270. self._get_or_pay(line, order.business_type,
  271. order.partner_id,
  272. order.to_partner_id, order.name)
  273. # 核销金额必须相同
  274. if order.business_type in ['adv_pay_to_get',
  275. 'adv_get_to_pay', 'get_to_pay']:
  276. decimal_amount = self.env.ref('core.decimal_amount')
  277. if float_compare(
  278. order_reconcile,
  279. invoice_reconcile,
  280. precision_digits=decimal_amount.digits) != 0:
  281. raise UserError(_('核销金额必须相同, %s 不等于 %s')
  282. % (order_reconcile, invoice_reconcile))
  283. order.state = 'done'
  284. return True
  285. def _get_or_pay_cancel(self, line, business_type, name):
  286. """
  287. 反核销时 对具体核销单行进行的操作
  288. """
  289. # 每一行的已核销金额减少、未核销金额增加
  290. line.name.to_reconcile += line.this_reconcile
  291. line.name.reconciled -= line.this_reconcile
  292. # 应收转应收、应付转应付、应收冲应付,找到生成的结算单反审核并删除
  293. if business_type in ['get_to_get', 'pay_to_pay', 'get_to_pay']:
  294. invoices = self.env['money.invoice'].search([('name', '=', name)])
  295. for inv in invoices:
  296. if inv.state == 'done':
  297. inv.reconciled = 0.0
  298. inv.money_invoice_draft()
  299. inv.unlink()
  300. return True
  301. def reconcile_order_draft(self):
  302. ''' 核销单的反审核按钮 '''
  303. for order in self:
  304. if order.state == 'draft':
  305. raise UserError(_('核销单%s已撤销,不能再次撤销。') % order.name)
  306. order_reconcile, invoice_reconcile = 0, 0
  307. if order.business_type in ['get_to_get', 'pay_to_pay'] \
  308. and order.partner_id == order.to_partner_id:
  309. raise UserError(
  310. _('业务伙伴和转入往来单位不能相同。\n业务伙伴:%s 转入往来单位:%s')
  311. % (order.partner_id.name, order.to_partner_id.name))
  312. # 反核销预收预付
  313. for line in order.advance_payment_ids:
  314. order_reconcile += line.this_reconcile
  315. # 每一行的已核销余额减少、未核销余额增加
  316. line.name.to_reconcile += line.this_reconcile
  317. line.name.reconciled -= line.this_reconcile
  318. # 反核销应收行
  319. for line in order.receivable_source_ids:
  320. invoice_reconcile += line.this_reconcile
  321. self._get_or_pay_cancel(line, order.business_type, order.name)
  322. # 反核销应付行
  323. for line in order.payable_source_ids:
  324. if order.business_type == 'adv_get_to_pay':
  325. invoice_reconcile += line.this_reconcile
  326. else:
  327. order_reconcile += line.this_reconcile
  328. self._get_or_pay_cancel(line, order.business_type, order.name)
  329. # 反核销时,金额必须相同
  330. if self.business_type in [
  331. 'adv_pay_to_get', 'adv_get_to_pay', 'get_to_pay']:
  332. decimal_amount = self.env.ref('core.decimal_amount')
  333. if float_compare(
  334. order_reconcile,
  335. invoice_reconcile,
  336. precision_digits=decimal_amount.digits) != 0:
  337. raise UserError(_('反核销时,金额必须相同, %s 不等于 %s')
  338. % (order_reconcile, invoice_reconcile))
  339. order.state = 'draft'
  340. return True
  341. class SourceOrderLine(models.Model):
  342. _name = 'source.order.line'
  343. _description = '待核销行'
  344. money_id = fields.Many2one('money.order', string='收付款单',
  345. ondelete='cascade',
  346. help='待核销行对应的收付款单') # 收付款单上的待核销行
  347. receivable_reconcile_id = fields.Many2one(
  348. 'reconcile.order',
  349. string='应收核销单', ondelete='cascade',
  350. help='核销单上的应收结算单明细') # 核销单上的应收结算单明细
  351. payable_reconcile_id = fields.Many2one('reconcile.order',
  352. string='应付核销单', ondelete='cascade',
  353. help='核销单上的应付结算单明细') # 核销单上的应付结算单明细
  354. name = fields.Many2one('money.invoice', string='发票号',
  355. copy=False, required=True,
  356. ondelete='cascade',
  357. help='待核销行对应的结算单')
  358. category_id = fields.Many2one('core.category', string='类别',
  359. required=True, ondelete='restrict',
  360. help='待核销行类别:采购 或者 销售等')
  361. date = fields.Date(string='单据日期',
  362. help='单据创建日期')
  363. amount = fields.Float(string='单据金额',
  364. digits='Amount',
  365. help='待核销行对应金额')
  366. reconciled = fields.Float(string='已核销金额',
  367. digits='Amount',
  368. help='待核销行已核销掉的金额')
  369. to_reconcile = fields.Float(string='未核销金额',
  370. digits='Amount',
  371. help='待核销行未核销掉的金额')
  372. this_reconcile = fields.Float(string='本次核销金额',
  373. digits='Amount',
  374. help='本次要核销掉的金额')
  375. invoice_date = fields.Date(string='开票日期',
  376. help='待核销行开票日期',
  377. related='name.invoice_date')
  378. date_due = fields.Date(string='到期日',
  379. help='待核销行的到期日')
  380. company_id = fields.Many2one(
  381. 'res.company',
  382. string='公司',
  383. change_default=True,
  384. default=lambda self: self.env.company)
  385. class AdvancePayment(models.Model):
  386. _name = 'advance.payment'
  387. _description = '核销单预收付款行'
  388. pay_reconcile_id = fields.Many2one('reconcile.order',
  389. string='核销单', ondelete='cascade',
  390. help='核销单预收付款行对应的核销单')
  391. name = fields.Many2one('money.order', string='预收/付款单',
  392. copy=False, required=True, ondelete='cascade',
  393. help='核销单预收/付款行对应的预收/付款单')
  394. note = fields.Text('备注', related='name.note')
  395. date = fields.Date(string='单据日期',
  396. help='单据创建日期')
  397. amount = fields.Float(string='单据金额',
  398. digits='Amount',
  399. help='预收/付款单的预收/付金额')
  400. reconciled = fields.Float(string='已核销金额',
  401. digits='Amount',
  402. help='已核销的预收/预付款金额')
  403. to_reconcile = fields.Float(string='未核销金额',
  404. digits='Amount',
  405. help='未核销的预收/预付款金额')
  406. this_reconcile = fields.Float(string='本次核销金额',
  407. digits='Amount',
  408. help='本次核销的预收/预付款金额')
  409. company_id = fields.Many2one(
  410. 'res.company',
  411. string='公司',
  412. change_default=True,
  413. default=lambda self: self.env.company)
上海开阖软件有限公司 沪ICP备12045867号-1