GoodERP
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

450 lines
21KB

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