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.

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