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.

272 lines
10 KiB

  1. # Copyright 2004-2010 OpenERP SA
  2. # Copyright 2014 Angel Moya <angel.moya@domatix.com>
  3. # Copyright 2015 Pedro M. Baeza <pedro.baeza@tecnativa.com>
  4. # Copyright 2016-2018 Carlos Dauden <carlos.dauden@tecnativa.com>
  5. # Copyright 2016-2017 LasLabs Inc.
  6. # Copyright 2018 ACSONE SA/NV
  7. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  8. from odoo import api, fields, models
  9. from odoo.exceptions import ValidationError
  10. from odoo.tools.translate import _
  11. class AccountAnalyticAccount(models.Model):
  12. _name = 'account.analytic.account'
  13. _inherit = [
  14. 'account.analytic.account',
  15. 'account.abstract.analytic.contract',
  16. ]
  17. contract_template_id = fields.Many2one(
  18. string='Contract Template', comodel_name='account.analytic.contract'
  19. )
  20. recurring_invoice_line_ids = fields.One2many(
  21. string='Invoice Lines',
  22. comodel_name='account.analytic.invoice.line',
  23. inverse_name='contract_id',
  24. copy=True,
  25. )
  26. recurring_invoices = fields.Boolean(
  27. string='Generate recurring invoices automatically'
  28. )
  29. user_id = fields.Many2one(
  30. comodel_name='res.users',
  31. string='Responsible',
  32. index=True,
  33. default=lambda self: self.env.user,
  34. )
  35. create_invoice_visibility = fields.Boolean(
  36. compute='_compute_create_invoice_visibility'
  37. )
  38. recurring_next_date = fields.Date(
  39. compute='_compute_recurring_next_date',
  40. string='Date of Next Invoice',
  41. store=True,
  42. )
  43. date_end = fields.Date(
  44. compute='_compute_date_end', string='Date End', store=True
  45. )
  46. @api.depends('recurring_invoice_line_ids.date_end')
  47. def _compute_date_end(self):
  48. for contract in self:
  49. contract.date_end = False
  50. date_end = contract.recurring_invoice_line_ids.mapped('date_end')
  51. if date_end and all(date_end):
  52. contract.date_end = max(date_end)
  53. @api.depends(
  54. 'recurring_invoice_line_ids.recurring_next_date',
  55. 'recurring_invoice_line_ids.is_canceled',
  56. )
  57. def _compute_recurring_next_date(self):
  58. for contract in self:
  59. recurring_next_date = contract.recurring_invoice_line_ids.filtered(
  60. lambda l: l.recurring_next_date and not l.is_canceled
  61. ).mapped('recurring_next_date')
  62. if recurring_next_date:
  63. contract.recurring_next_date = min(recurring_next_date)
  64. @api.depends('recurring_invoice_line_ids.create_invoice_visibility')
  65. def _compute_create_invoice_visibility(self):
  66. for contract in self:
  67. contract.create_invoice_visibility = any(
  68. contract.recurring_invoice_line_ids.mapped(
  69. 'create_invoice_visibility'
  70. )
  71. )
  72. @api.onchange('contract_template_id')
  73. def _onchange_contract_template_id(self):
  74. """Update the contract fields with that of the template.
  75. Take special consideration with the `recurring_invoice_line_ids`,
  76. which must be created using the data from the contract lines. Cascade
  77. deletion ensures that any errant lines that are created are also
  78. deleted.
  79. """
  80. contract_template_id = self.contract_template_id
  81. if not contract_template_id:
  82. return
  83. for field_name, field in contract_template_id._fields.items():
  84. if field.name == 'recurring_invoice_line_ids':
  85. lines = self._convert_contract_lines(contract_template_id)
  86. self.recurring_invoice_line_ids = lines
  87. elif not any(
  88. (
  89. field.compute,
  90. field.related,
  91. field.automatic,
  92. field.readonly,
  93. field.company_dependent,
  94. field.name in self.NO_SYNC,
  95. )
  96. ):
  97. self[field_name] = self.contract_template_id[field_name]
  98. @api.onchange('partner_id')
  99. def _onchange_partner_id(self):
  100. self.pricelist_id = self.partner_id.property_product_pricelist.id
  101. @api.constrains('partner_id', 'recurring_invoices')
  102. def _check_partner_id_recurring_invoices(self):
  103. for contract in self.filtered('recurring_invoices'):
  104. if not contract.partner_id:
  105. raise ValidationError(
  106. _("You must supply a customer for the contract '%s'")
  107. % contract.name
  108. )
  109. @api.multi
  110. def _convert_contract_lines(self, contract):
  111. self.ensure_one()
  112. new_lines = []
  113. for contract_line in contract.recurring_invoice_line_ids:
  114. vals = contract_line._convert_to_write(contract_line.read()[0])
  115. # Remove template link field
  116. vals.pop('contract_template_id', False)
  117. vals['date_start'] = fields.Date.context_today(contract_line)
  118. vals['recurring_next_date'] = fields.Date.context_today(
  119. contract_line
  120. )
  121. self.recurring_invoice_line_ids._onchange_date_start()
  122. new_lines.append((0, 0, vals))
  123. return new_lines
  124. @api.multi
  125. def _prepare_invoice(self, date_invoice, journal=None):
  126. self.ensure_one()
  127. if not self.partner_id:
  128. if self.contract_type == 'purchase':
  129. raise ValidationError(
  130. _("You must first select a Supplier for Contract %s!")
  131. % self.name
  132. )
  133. else:
  134. raise ValidationError(
  135. _("You must first select a Customer for Contract %s!")
  136. % self.name
  137. )
  138. if not journal:
  139. journal = (
  140. self.journal_id
  141. if self.journal_id.type == self.contract_type
  142. else self.env['account.journal'].search(
  143. [
  144. ('type', '=', self.contract_type),
  145. ('company_id', '=', self.company_id.id),
  146. ],
  147. limit=1,
  148. )
  149. )
  150. if not journal:
  151. raise ValidationError(
  152. _("Please define a %s journal for the company '%s'.")
  153. % (self.contract_type, self.company_id.name or '')
  154. )
  155. currency = (
  156. self.pricelist_id.currency_id
  157. or self.partner_id.property_product_pricelist.currency_id
  158. or self.company_id.currency_id
  159. )
  160. invoice_type = 'out_invoice'
  161. if self.contract_type == 'purchase':
  162. invoice_type = 'in_invoice'
  163. return {
  164. 'reference': self.code,
  165. 'type': invoice_type,
  166. 'partner_id': self.partner_id.address_get(['invoice'])[
  167. 'invoice'
  168. ],
  169. 'currency_id': currency.id,
  170. 'date_invoice': date_invoice,
  171. 'journal_id': journal.id,
  172. 'origin': self.name,
  173. 'company_id': self.company_id.id,
  174. 'contract_id': self.id,
  175. 'user_id': self.partner_id.user_id.id,
  176. }
  177. @api.multi
  178. def action_contract_send(self):
  179. self.ensure_one()
  180. template = self.env.ref('contract.email_contract_template', False)
  181. compose_form = self.env.ref('mail.email_compose_message_wizard_form')
  182. ctx = dict(
  183. default_model='account.analytic.account',
  184. default_res_id=self.id,
  185. default_use_template=bool(template),
  186. default_template_id=template and template.id or False,
  187. default_composition_mode='comment',
  188. )
  189. return {
  190. 'name': _('Compose Email'),
  191. 'type': 'ir.actions.act_window',
  192. 'view_type': 'form',
  193. 'view_mode': 'form',
  194. 'res_model': 'mail.compose.message',
  195. 'views': [(compose_form.id, 'form')],
  196. 'view_id': compose_form.id,
  197. 'target': 'new',
  198. 'context': ctx,
  199. }
  200. @api.model
  201. def _get_recurring_create_invoice_domain(self, contract=False):
  202. domain = []
  203. date_ref = fields.Date.context_today(self)
  204. if contract:
  205. contract.ensure_one()
  206. date_ref = contract.recurring_next_date
  207. domain.append(('id', '=', contract.id))
  208. domain.extend(
  209. [
  210. ('recurring_invoices', '=', True),
  211. ('recurring_next_date', '<=', date_ref),
  212. ]
  213. )
  214. return domain
  215. @api.multi
  216. def _get_lines_to_invoice(self, date_ref=False):
  217. self.ensure_one()
  218. if not date_ref:
  219. date_ref = fields.Date.context_today(self)
  220. return self.recurring_invoice_line_ids.filtered(
  221. lambda l: not l.is_canceled and l.recurring_next_date <= date_ref)
  222. @api.multi
  223. def recurring_create_invoice(self):
  224. invoice_model = self.env['account.invoice']
  225. invoices_values = []
  226. for contract in self:
  227. contract_lines = contract._get_lines_to_invoice()
  228. if not contract_lines:
  229. continue
  230. invoice_values = contract._prepare_invoice(
  231. contract.recurring_next_date)
  232. for line in contract_lines:
  233. invoice_values.setdefault('invoice_line_ids', [])
  234. invoice_values['invoice_line_ids'].append(
  235. (0, 0, line._prepare_invoice_line(False))
  236. )
  237. # If no account on the product, the invoice lines account is
  238. # taken from the invoice's journal in _onchange_product_id
  239. # This code is not in finalize_creation_from_contract because it's
  240. # not possible to create an invoice line with no account
  241. new_invoice = invoice_model.new(invoice_values)
  242. for invoice_line in new_invoice.invoice_line_ids:
  243. invoice_line.invoice_id = new_invoice
  244. invoice_line._onchange_product_id()
  245. invoice_values = new_invoice._convert_to_write(new_invoice._cache)
  246. invoices_values.append(invoice_values)
  247. contract_lines._update_recurring_next_date()
  248. invoices = invoice_model.create(invoices_values)
  249. invoices.finalize_creation_from_contract()
  250. @api.model
  251. def cron_recurring_create_invoice(self):
  252. domain = self._get_recurring_create_invoice_domain()
  253. contracts_to_invoice = self.search(domain)
  254. contracts_to_invoice.recurring_create_invoice()