odoo-addon-contract 17.0.1.4.5.1__py3-none-any.whl → 18.0.2.0.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. odoo/addons/contract/README.rst +14 -10
  2. odoo/addons/contract/__manifest__.py +3 -10
  3. odoo/addons/contract/controllers/main.py +1 -8
  4. odoo/addons/contract/data/contract_cron.xml +0 -2
  5. odoo/addons/contract/data/mail_template.xml +18 -17
  6. odoo/addons/contract/data/template_mail_notification.xml +1 -1
  7. odoo/addons/contract/i18n/contract.pot +141 -823
  8. odoo/addons/contract/migrations/18.0.2.0.0/pre-migrate.py +92 -0
  9. odoo/addons/contract/models/__init__.py +2 -6
  10. odoo/addons/contract/models/account_move.py +0 -8
  11. odoo/addons/contract/models/account_move_line.py +14 -0
  12. odoo/addons/contract/models/contract.py +266 -308
  13. odoo/addons/contract/models/contract_line.py +34 -861
  14. odoo/addons/contract/models/{contract_recurrency_mixin.py → contract_recurring_mixin.py} +101 -82
  15. odoo/addons/contract/models/contract_tag.py +1 -3
  16. odoo/addons/contract/models/contract_template.py +81 -2
  17. odoo/addons/contract/models/contract_template_line.py +249 -3
  18. odoo/addons/contract/report/contract_views.xml +0 -2
  19. odoo/addons/contract/report/report_contract.xml +13 -13
  20. odoo/addons/contract/security/contract_security.xml +6 -15
  21. odoo/addons/contract/security/contract_tag.xml +1 -3
  22. odoo/addons/contract/security/ir.model.access.csv +0 -2
  23. odoo/addons/contract/static/description/index.html +24 -18
  24. odoo/addons/contract/static/src/js/contract_portal_tour.esm.js +6 -3
  25. odoo/addons/contract/tests/test_contract.py +42 -927
  26. odoo/addons/contract/tests/test_multicompany.py +5 -4
  27. odoo/addons/contract/tests/test_portal.py +6 -3
  28. odoo/addons/contract/views/contract.xml +91 -234
  29. odoo/addons/contract/views/contract_line.xml +48 -117
  30. odoo/addons/contract/views/contract_portal_templates.xml +181 -222
  31. odoo/addons/contract/views/contract_tag.xml +3 -3
  32. odoo/addons/contract/views/contract_template.xml +100 -72
  33. odoo/addons/contract/views/contract_template_line.xml +76 -5
  34. odoo/addons/contract/views/res_config_settings.xml +5 -6
  35. odoo/addons/contract/views/res_partner_view.xml +0 -5
  36. odoo/addons/contract/wizards/__init__.py +0 -2
  37. odoo/addons/contract/wizards/contract_manually_create_invoice.py +6 -6
  38. odoo/addons/contract/wizards/contract_manually_create_invoice.xml +2 -3
  39. {odoo_addon_contract-17.0.1.4.5.1.dist-info → odoo_addon_contract-18.0.2.0.0.9.dist-info}/METADATA +17 -13
  40. {odoo_addon_contract-17.0.1.4.5.1.dist-info → odoo_addon_contract-18.0.2.0.0.9.dist-info}/RECORD +42 -55
  41. odoo/addons/contract/data/contract_renew_cron.xml +0 -14
  42. odoo/addons/contract/models/abstract_contract.py +0 -82
  43. odoo/addons/contract/models/abstract_contract_line.py +0 -271
  44. odoo/addons/contract/models/contract_line_constraints.py +0 -429
  45. odoo/addons/contract/models/contract_terminate_reason.py +0 -14
  46. odoo/addons/contract/models/res_company.py +0 -15
  47. odoo/addons/contract/models/res_config_settings.py +0 -18
  48. odoo/addons/contract/security/contract_terminate_reason.xml +0 -23
  49. odoo/addons/contract/security/groups.xml +0 -9
  50. odoo/addons/contract/views/abstract_contract_line.xml +0 -117
  51. odoo/addons/contract/views/contract_terminate_reason.xml +0 -38
  52. odoo/addons/contract/wizards/contract_contract_terminate.py +0 -42
  53. odoo/addons/contract/wizards/contract_contract_terminate.xml +0 -33
  54. odoo/addons/contract/wizards/contract_line_wizard.py +0 -53
  55. odoo/addons/contract/wizards/contract_line_wizard.xml +0 -111
  56. {odoo_addon_contract-17.0.1.4.5.1.dist-info → odoo_addon_contract-18.0.2.0.0.9.dist-info}/WHEEL +0 -0
  57. {odoo_addon_contract-17.0.1.4.5.1.dist-info → odoo_addon_contract-18.0.2.0.0.9.dist-info}/top_level.txt +0 -0
@@ -6,14 +6,14 @@
6
6
  # Copyright 2018 ACSONE SA/NV
7
7
  # Copyright 2021 Tecnativa - Víctor Martínez
8
8
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
9
+
9
10
  import logging
10
11
 
11
12
  from markupsafe import Markup
12
13
 
13
14
  from odoo import Command, api, fields, models
14
- from odoo.exceptions import UserError, ValidationError
15
+ from odoo.exceptions import ValidationError
15
16
  from odoo.osv import expression
16
- from odoo.tools.translate import _
17
17
 
18
18
  _logger = logging.getLogger(__name__)
19
19
 
@@ -23,24 +23,52 @@ class ContractContract(models.Model):
23
23
  _description = "Contract"
24
24
  _order = "code, name asc"
25
25
  _inherit = [
26
+ "contract.template",
27
+ "portal.mixin",
26
28
  "mail.thread",
27
29
  "mail.activity.mixin",
28
- "contract.abstract.contract",
29
- "contract.recurrency.mixin",
30
- "portal.mixin",
31
30
  ]
32
31
 
33
- active = fields.Boolean(
34
- default=True,
35
- )
36
- code = fields.Char(
37
- string="Reference",
32
+ # === Basic Information ===
33
+ active = fields.Boolean(default=True)
34
+ code = fields.Char(string="Reference")
35
+ name = fields.Char()
36
+ user_id = fields.Many2one(
37
+ comodel_name="res.users",
38
+ string="Responsible",
39
+ index=True,
40
+ default=lambda self: self.env.user,
38
41
  )
39
42
  group_id = fields.Many2one(
40
43
  string="Group",
41
44
  comodel_name="account.analytic.account",
42
45
  ondelete="restrict",
43
46
  )
47
+ tag_ids = fields.Many2many(comodel_name="contract.tag", string="Tags")
48
+ note = fields.Text(string="Notes")
49
+
50
+ # === Partner and Commercial Info ===
51
+ partner_id = fields.Many2one(
52
+ comodel_name="res.partner",
53
+ inverse="_inverse_partner_id",
54
+ required=True,
55
+ )
56
+ invoice_partner_id = fields.Many2one(
57
+ string="Invoicing contact",
58
+ comodel_name="res.partner",
59
+ ondelete="restrict",
60
+ domain="['|',('id', 'parent_of', partner_id), ('id', 'child_of', partner_id)]",
61
+ )
62
+ commercial_partner_id = fields.Many2one(
63
+ "res.partner",
64
+ compute_sudo=True,
65
+ related="partner_id.commercial_partner_id",
66
+ store=True,
67
+ string="Commercial Entity",
68
+ index=True,
69
+ )
70
+
71
+ # === Financial & Invoicing Info ===
44
72
  currency_id = fields.Many2one(
45
73
  compute="_compute_currency_id",
46
74
  inverse="_inverse_currency_id",
@@ -51,8 +79,25 @@ class ContractContract(models.Model):
51
79
  comodel_name="res.currency",
52
80
  readonly=True,
53
81
  )
82
+ payment_term_id = fields.Many2one(
83
+ comodel_name="account.payment.term",
84
+ string="Payment Terms",
85
+ index=True,
86
+ )
87
+ fiscal_position_id = fields.Many2one(
88
+ comodel_name="account.fiscal.position",
89
+ string="Fiscal Position",
90
+ ondelete="restrict",
91
+ )
92
+ invoice_count = fields.Integer(compute="_compute_invoice_count")
93
+ create_invoice_visibility = fields.Boolean(
94
+ compute="_compute_create_invoice_visibility"
95
+ )
96
+
97
+ # === Contract Template and Lines ===
54
98
  contract_template_id = fields.Many2one(
55
- string="Contract Template", comodel_name="contract.template"
99
+ string="Contract Template",
100
+ comodel_name="contract.template",
56
101
  )
57
102
  contract_line_ids = fields.One2many(
58
103
  string="Contract lines",
@@ -61,11 +106,6 @@ class ContractContract(models.Model):
61
106
  copy=True,
62
107
  context={"active_test": False},
63
108
  )
64
- # Trick for being able to have 2 different views for the same o2m
65
- # We need this as one2many widget doesn't allow to define in the view
66
- # the same field 2 times with different views. 2 views are needed because
67
- # one of them must be editable inline and the other not, which can't be
68
- # parametrized through attrs.
69
109
  contract_line_fixed_ids = fields.One2many(
70
110
  string="Contract lines (fixed)",
71
111
  comodel_name="contract.line",
@@ -73,189 +113,22 @@ class ContractContract(models.Model):
73
113
  context={"active_test": False},
74
114
  )
75
115
 
76
- user_id = fields.Many2one(
77
- comodel_name="res.users",
78
- string="Responsible",
79
- index=True,
80
- default=lambda self: self.env.user,
81
- )
82
- create_invoice_visibility = fields.Boolean(
83
- compute="_compute_create_invoice_visibility"
84
- )
85
- date_end = fields.Date(compute="_compute_date_end", store=True, readonly=False)
86
- payment_term_id = fields.Many2one(
87
- comodel_name="account.payment.term", string="Payment Terms", index=True
88
- )
89
- invoice_count = fields.Integer(compute="_compute_invoice_count")
90
- fiscal_position_id = fields.Many2one(
91
- comodel_name="account.fiscal.position",
92
- string="Fiscal Position",
93
- ondelete="restrict",
94
- )
95
- invoice_partner_id = fields.Many2one(
96
- string="Invoicing contact",
97
- comodel_name="res.partner",
98
- ondelete="restrict",
99
- domain="['|',('id', 'parent_of', partner_id), ('id', 'child_of', partner_id)]",
100
- )
101
- partner_id = fields.Many2one(
102
- comodel_name="res.partner", inverse="_inverse_partner_id", required=True
103
- )
104
-
105
- commercial_partner_id = fields.Many2one(
106
- "res.partner",
107
- compute_sudo=True,
108
- related="partner_id.commercial_partner_id",
109
- store=True,
110
- string="Commercial Entity",
111
- index=True,
112
- )
113
- tag_ids = fields.Many2many(comodel_name="contract.tag", string="Tags")
114
- note = fields.Text(string="Notes")
115
- is_terminated = fields.Boolean(string="Terminated", readonly=True, copy=False)
116
- terminate_reason_id = fields.Many2one(
117
- comodel_name="contract.terminate.reason",
118
- string="Termination Reason",
119
- ondelete="restrict",
120
- readonly=True,
121
- copy=False,
122
- tracking=True,
123
- )
124
- terminate_comment = fields.Text(
125
- string="Termination Comment",
126
- readonly=True,
127
- copy=False,
128
- tracking=True,
129
- )
130
- terminate_date = fields.Date(
131
- string="Termination Date",
132
- readonly=True,
133
- copy=False,
134
- tracking=True,
135
- )
116
+ # === Modification tracking ===
136
117
  modification_ids = fields.One2many(
137
118
  comodel_name="contract.modification",
138
119
  inverse_name="contract_id",
139
120
  string="Modifications",
140
121
  )
141
122
 
142
- def get_formview_id(self, access_uid=None):
143
- if self.contract_type == "sale":
144
- return self.env.ref("contract.contract_contract_customer_form_view").id
145
- else:
146
- return self.env.ref("contract.contract_contract_supplier_form_view").id
147
-
148
- @api.model_create_multi
149
- def create(self, vals_list):
150
- records = super().create(vals_list)
151
- records._set_start_contract_modification()
152
- return records
153
-
154
- def write(self, vals):
155
- if "modification_ids" in vals:
156
- res = super(
157
- ContractContract, self.with_context(bypass_modification_send=True)
158
- ).write(vals)
159
- self._modification_mail_send()
160
- else:
161
- res = super().write(vals)
162
- return res
163
-
164
- @api.model
165
- def _set_start_contract_modification(self):
166
- subtype_id = self.env.ref("contract.mail_message_subtype_contract_modification")
167
- for record in self:
168
- if record.contract_line_ids:
169
- date_start = min(record.contract_line_ids.mapped("date_start"))
170
- else:
171
- date_start = record.create_date
172
- record.message_subscribe(
173
- partner_ids=[record.partner_id.id], subtype_ids=[subtype_id.id]
174
- )
175
- record.with_context(skip_modification_mail=True).write(
176
- {
177
- "modification_ids": [
178
- (0, 0, {"date": date_start, "description": _("Contract start")})
179
- ]
180
- }
181
- )
123
+ # === Dates ===
124
+ date_end = fields.Date(compute="_compute_date_end", store=True, readonly=False)
182
125
 
183
- @api.model
184
- def _modification_mail_send(self):
185
- for record in self:
186
- modification_ids_not_sent = record.modification_ids.filtered(
187
- lambda x: not x.sent
188
- )
189
- if modification_ids_not_sent:
190
- if not self.env.context.get("skip_modification_mail"):
191
- subtype_id = self.env["ir.model.data"]._xmlid_to_res_id(
192
- "contract.mail_message_subtype_contract_modification"
193
- )
194
- template_id = self.env.ref(
195
- "contract.mail_template_contract_modification"
196
- )
197
- record.message_post_with_source(
198
- template_id,
199
- subtype_id=subtype_id,
200
- email_layout_xmlid="contract.template_contract_modification",
201
- )
202
- modification_ids_not_sent.write({"sent": True})
126
+ # === Compute Methods ===
203
127
 
204
128
  def _compute_access_url(self):
205
129
  for record in self:
206
130
  record.access_url = f"/my/contracts/{record.id}"
207
131
 
208
- def action_preview(self):
209
- """Invoked when 'Preview' button in contract form view is clicked."""
210
- self.ensure_one()
211
- return {
212
- "type": "ir.actions.act_url",
213
- "target": "self",
214
- "url": self.get_portal_url(),
215
- }
216
-
217
- def _inverse_partner_id(self):
218
- for rec in self:
219
- if not rec.invoice_partner_id:
220
- rec.invoice_partner_id = rec.partner_id.address_get(["invoice"])[
221
- "invoice"
222
- ]
223
-
224
- def _get_related_invoices(self):
225
- self.ensure_one()
226
-
227
- invoices = (
228
- self.env["account.move.line"]
229
- .search(
230
- [
231
- (
232
- "contract_line_id",
233
- "in",
234
- self.contract_line_ids.ids,
235
- )
236
- ]
237
- )
238
- .mapped("move_id")
239
- )
240
- # we are forced to always search for this for not losing possible <=v11
241
- # generated invoices
242
- invoices |= self.env["account.move"].search([("old_contract_id", "=", self.id)])
243
- return invoices
244
-
245
- def _get_computed_currency(self):
246
- """Helper method for returning the theoretical computed currency."""
247
- self.ensure_one()
248
- currency = self.env["res.currency"]
249
- if any(self.contract_line_ids.mapped("automatic_price")):
250
- # Use pricelist currency
251
- currency = (
252
- self.pricelist_id.currency_id
253
- or self.partner_id.with_company(
254
- self.company_id
255
- ).property_product_pricelist.currency_id
256
- )
257
- return currency or self.journal_id.currency_id or self.company_id.currency_id
258
-
259
132
  @api.depends(
260
133
  "manual_currency_id",
261
134
  "pricelist_id",
@@ -284,42 +157,16 @@ class ContractContract(models.Model):
284
157
  for rec in self:
285
158
  rec.invoice_count = len(rec._get_related_invoices())
286
159
 
287
- def action_show_invoices(self):
288
- self.ensure_one()
289
- tree_view = self.env.ref("account.view_invoice_tree", raise_if_not_found=False)
290
- form_view = self.env.ref("account.view_move_form", raise_if_not_found=False)
291
- ctx = dict(self.env.context)
292
- if ctx.get("default_contract_type"):
293
- ctx["default_move_type"] = (
294
- "out_invoice"
295
- if ctx.get("default_contract_type") == "sale"
296
- else "in_invoice"
297
- )
298
- action = {
299
- "type": "ir.actions.act_window",
300
- "name": "Invoices",
301
- "res_model": "account.move",
302
- "view_mode": "tree,kanban,form,calendar,pivot,graph,activity",
303
- "domain": [("id", "in", self._get_related_invoices().ids)],
304
- "context": ctx,
305
- }
306
- if tree_view and form_view:
307
- action["views"] = [(tree_view.id, "tree"), (form_view.id, "form")]
308
- return action
309
-
310
- @api.depends("contract_line_ids.date_end")
311
- def _compute_date_end(self):
312
- for contract in self:
313
- contract.date_end = False
314
- date_end = contract.contract_line_ids.mapped("date_end")
315
- if date_end and all(date_end):
316
- contract.date_end = max(date_end)
317
-
318
160
  @api.depends(
161
+ "next_period_date_start",
162
+ "recurring_invoicing_type",
163
+ "recurring_invoicing_offset",
164
+ "recurring_rule_type",
165
+ "recurring_interval",
166
+ "date_end",
319
167
  "contract_line_ids.recurring_next_date",
320
168
  "contract_line_ids.is_canceled",
321
169
  )
322
- # pylint: disable=missing-return
323
170
  def _compute_recurring_next_date(self):
324
171
  for contract in self:
325
172
  recurring_next_date = contract.contract_line_ids.filtered(
@@ -335,7 +182,14 @@ class ContractContract(models.Model):
335
182
  and contract._origin.date_start != contract.date_start
336
183
  or not recurring_next_date
337
184
  ):
338
- super(ContractContract, contract)._compute_recurring_next_date()
185
+ contract.recurring_next_date = self.get_next_invoice_date(
186
+ contract.next_period_date_start,
187
+ contract.recurring_invoicing_type,
188
+ contract.recurring_invoicing_offset,
189
+ contract.recurring_rule_type,
190
+ contract.recurring_interval,
191
+ max_date_end=contract.date_end,
192
+ )
339
193
  else:
340
194
  contract.recurring_next_date = min(recurring_next_date)
341
195
 
@@ -346,6 +200,23 @@ class ContractContract(models.Model):
346
200
  contract.contract_line_ids.mapped("create_invoice_visibility")
347
201
  )
348
202
 
203
+ @api.depends("contract_line_ids.date_end")
204
+ def _compute_date_end(self):
205
+ for contract in self:
206
+ contract.date_end = False
207
+ date_end = contract.contract_line_ids.mapped("date_end")
208
+ if date_end and all(date_end):
209
+ contract.date_end = max(date_end)
210
+
211
+ def _inverse_partner_id(self):
212
+ for rec in self:
213
+ if not rec.invoice_partner_id:
214
+ rec.invoice_partner_id = rec.partner_id.address_get(["invoice"])[
215
+ "invoice"
216
+ ]
217
+
218
+ # === Onchange Methods ===
219
+
349
220
  @api.onchange("contract_template_id")
350
221
  def _onchange_contract_template_id(self):
351
222
  """Update the contract fields with that of the template.
@@ -392,6 +263,178 @@ class ContractContract(models.Model):
392
263
  self.payment_term_id = partner.property_payment_term_id
393
264
  self.invoice_partner_id = self.partner_id.address_get(["invoice"])["invoice"]
394
265
 
266
+ # === CRUD ===
267
+ @api.model_create_multi
268
+ def create(self, vals_list):
269
+ records = super().create(vals_list)
270
+ records._set_start_contract_modification()
271
+ return records
272
+
273
+ def write(self, vals):
274
+ if "modification_ids" in vals:
275
+ res = super(
276
+ ContractContract, self.with_context(bypass_modification_send=True)
277
+ ).write(vals)
278
+ self._modification_mail_send()
279
+ else:
280
+ res = super().write(vals)
281
+ return res
282
+
283
+ # === Actions ===
284
+
285
+ def action_preview(self):
286
+ """Invoked when 'Preview' button in contract form view is clicked."""
287
+ self.ensure_one()
288
+ return {
289
+ "type": "ir.actions.act_url",
290
+ "target": "self",
291
+ "url": self.get_portal_url(),
292
+ }
293
+
294
+ def action_show_invoices(self):
295
+ self.ensure_one()
296
+ tree_view = self.env.ref("account.view_invoice_tree", raise_if_not_found=False)
297
+ form_view = self.env.ref("account.view_move_form", raise_if_not_found=False)
298
+ ctx = dict(self.env.context)
299
+ if ctx.get("default_contract_type"):
300
+ ctx["default_move_type"] = (
301
+ "out_invoice"
302
+ if ctx.get("default_contract_type") == "sale"
303
+ else "in_invoice"
304
+ )
305
+ action = {
306
+ "type": "ir.actions.act_window",
307
+ "name": "Invoices",
308
+ "res_model": "account.move",
309
+ "view_mode": "list,kanban,form,calendar,pivot,graph,activity",
310
+ "domain": [("id", "in", self._get_related_invoices().ids)],
311
+ "context": ctx,
312
+ }
313
+ if tree_view and form_view:
314
+ action["views"] = [(tree_view.id, "list"), (form_view.id, "form")]
315
+ return action
316
+
317
+ def action_contract_send(self):
318
+ self.ensure_one()
319
+ template = self.env.ref("contract.email_contract_template", False)
320
+ compose_form = self.env.ref("mail.email_compose_message_wizard_form")
321
+ ctx = dict(
322
+ default_model="contract.contract",
323
+ default_res_ids=self.ids,
324
+ default_use_template=bool(template),
325
+ default_template_id=template and template.id or False,
326
+ default_composition_mode="comment",
327
+ )
328
+ return {
329
+ "name": self.env._("Compose Email"),
330
+ "type": "ir.actions.act_window",
331
+ "view_mode": "form",
332
+ "res_model": "mail.compose.message",
333
+ "views": [(compose_form.id, "form")],
334
+ "view_id": compose_form.id,
335
+ "target": "new",
336
+ "context": ctx,
337
+ }
338
+
339
+ def recurring_create_invoice(self):
340
+ """
341
+ This method triggers the creation of the next invoices of the contracts
342
+ even if their next invoicing date is in the future.
343
+ """
344
+ invoices = self._recurring_create_invoice()
345
+ for invoice in invoices:
346
+ body = Markup(
347
+ self.env._("Contract manually invoiced: %(invoice_link)s")
348
+ ) % {"invoice_link": invoice._get_html_link(title=invoice.name)}
349
+ self.message_post(body=body)
350
+ return invoices
351
+
352
+ # === Helpers and Utilities ===
353
+
354
+ def get_formview_id(self, access_uid=None):
355
+ if self.contract_type == "sale":
356
+ return self.env.ref("contract.contract_contract_customer_form_view").id
357
+ else:
358
+ return self.env.ref("contract.contract_contract_supplier_form_view").id
359
+
360
+ def _set_start_contract_modification(self):
361
+ subtype_id = self.env.ref("contract.mail_message_subtype_contract_modification")
362
+ for record in self:
363
+ if record.contract_line_ids:
364
+ date_start = min(record.contract_line_ids.mapped("date_start"))
365
+ else:
366
+ date_start = record.create_date
367
+ record.message_subscribe(
368
+ partner_ids=[record.partner_id.id], subtype_ids=[subtype_id.id]
369
+ )
370
+ record.with_context(skip_modification_mail=True).write(
371
+ {
372
+ "modification_ids": [
373
+ Command.create(
374
+ {
375
+ "date": date_start,
376
+ "description": self.env._("Contract start"),
377
+ }
378
+ )
379
+ ]
380
+ }
381
+ )
382
+
383
+ def _modification_mail_send(self):
384
+ for record in self:
385
+ modification_ids_not_sent = record.modification_ids.filtered(
386
+ lambda x: not x.sent
387
+ )
388
+ if modification_ids_not_sent:
389
+ if not self.env.context.get("skip_modification_mail"):
390
+ subtype_id = self.env["ir.model.data"]._xmlid_to_res_id(
391
+ "contract.mail_message_subtype_contract_modification"
392
+ )
393
+ template_id = self.env.ref(
394
+ "contract.mail_template_contract_modification"
395
+ )
396
+ record.message_post_with_source(
397
+ template_id,
398
+ subtype_id=subtype_id,
399
+ email_layout_xmlid="contract.template_contract_modification",
400
+ )
401
+ modification_ids_not_sent.write({"sent": True})
402
+
403
+ def _get_related_invoices(self):
404
+ self.ensure_one()
405
+
406
+ invoices = (
407
+ self.env["account.move.line"]
408
+ .search(
409
+ [
410
+ (
411
+ "contract_line_id",
412
+ "in",
413
+ self.contract_line_ids.ids,
414
+ )
415
+ ]
416
+ )
417
+ .mapped("move_id")
418
+ )
419
+ # we are forced to always search for this for not losing possible <=v11
420
+ # generated invoices
421
+ invoices |= self.env["account.move"].search([("old_contract_id", "=", self.id)])
422
+ return invoices
423
+
424
+ def _get_computed_currency(self):
425
+ """Helper method for returning the theoretical computed currency."""
426
+ self.ensure_one()
427
+ currency = self.env["res.currency"]
428
+ if any(self.contract_line_ids.mapped("automatic_price")):
429
+ # Use pricelist currency
430
+ currency = (
431
+ self.pricelist_id.currency_id
432
+ or self.partner_id.with_company(
433
+ self.company_id
434
+ ).property_product_pricelist.currency_id
435
+ )
436
+ return currency or self.journal_id.currency_id or self.company_id.currency_id
437
+
395
438
  def _convert_contract_lines(self, contract):
396
439
  self.ensure_one()
397
440
  new_lines = self.env["contract.line"]
@@ -403,7 +446,6 @@ class ContractContract(models.Model):
403
446
  vals["date_start"] = fields.Date.context_today(contract_line)
404
447
  vals["recurring_next_date"] = fields.Date.context_today(contract_line)
405
448
  new_lines += contract_line_model.new(vals)
406
- new_lines._onchange_is_auto_renew()
407
449
  return new_lines
408
450
 
409
451
  def _prepare_invoice(self, date_invoice, journal=None):
@@ -426,7 +468,7 @@ class ContractContract(models.Model):
426
468
  )
427
469
  if not journal:
428
470
  raise ValidationError(
429
- _(
471
+ self.env._(
430
472
  "Please define a %(contract_type)s journal "
431
473
  "for the company '%(company)s'."
432
474
  )
@@ -469,28 +511,6 @@ class ContractContract(models.Model):
469
511
  )
470
512
  return vals
471
513
 
472
- def action_contract_send(self):
473
- self.ensure_one()
474
- template = self.env.ref("contract.email_contract_template", False)
475
- compose_form = self.env.ref("mail.email_compose_message_wizard_form")
476
- ctx = dict(
477
- default_model="contract.contract",
478
- default_res_ids=self.ids,
479
- default_use_template=bool(template),
480
- default_template_id=template and template.id or False,
481
- default_composition_mode="comment",
482
- )
483
- return {
484
- "name": _("Compose Email"),
485
- "type": "ir.actions.act_window",
486
- "view_mode": "form",
487
- "res_model": "mail.compose.message",
488
- "views": [(compose_form.id, "form")],
489
- "view_id": compose_form.id,
490
- "target": "new",
491
- "context": ctx,
492
- }
493
-
494
514
  @api.model
495
515
  def _get_contracts_to_invoice_domain(self, date_ref=None):
496
516
  """
@@ -580,22 +600,9 @@ class ContractContract(models.Model):
580
600
  )
581
601
  invoices_values.append(invoice_vals)
582
602
  # Force the recomputation of journal items
583
- contract_lines._update_recurring_next_date()
603
+ contract_lines._update_last_date_invoiced()
584
604
  return invoices_values
585
605
 
586
- def recurring_create_invoice(self):
587
- """
588
- This method triggers the creation of the next invoices of the contracts
589
- even if their next invoicing date is in the future.
590
- """
591
- invoices = self._recurring_create_invoice()
592
- for invoice in invoices:
593
- body = Markup(_("Contract manually invoiced: %(invoice_link)s")) % {
594
- "invoice_link": invoice._get_html_link(title=invoice.name)
595
- }
596
- self.message_post(body=body)
597
- return invoices
598
-
599
606
  @api.model
600
607
  def _invoice_followers(self, invoices):
601
608
  invoice_create_subtype = self.env.ref(
@@ -614,7 +621,7 @@ class ContractContract(models.Model):
614
621
  def _add_contract_origin(self, invoices):
615
622
  for item in self:
616
623
  for move in invoices & item._get_related_invoices():
617
- translation = _("by contract")
624
+ translation = self.env._("by contract")
618
625
  move.message_post(
619
626
  body=Markup(
620
627
  f"{move._creation_message()} {translation} "
@@ -674,52 +681,3 @@ class ContractContract(models.Model):
674
681
  @api.model
675
682
  def cron_recurring_create_invoice(self, date_ref=None):
676
683
  return self._cron_recurring_create(date_ref, create_type="invoice")
677
-
678
- def action_terminate_contract(self):
679
- self.ensure_one()
680
- context = {"default_contract_id": self.id}
681
- return {
682
- "type": "ir.actions.act_window",
683
- "name": _("Terminate Contract"),
684
- "res_model": "contract.contract.terminate",
685
- "view_mode": "form",
686
- "target": "new",
687
- "context": context,
688
- }
689
-
690
- def _terminate_contract(
691
- self,
692
- terminate_reason_id,
693
- terminate_comment,
694
- terminate_date,
695
- terminate_lines_with_last_date_invoiced=False,
696
- ):
697
- self.ensure_one()
698
- if not self.env.user.has_group("contract.can_terminate_contract"):
699
- raise UserError(_("You are not allowed to terminate contracts."))
700
- for line in self.contract_line_ids.filtered("is_stop_allowed"):
701
- line.stop(
702
- max(terminate_date, line.last_date_invoiced)
703
- if terminate_lines_with_last_date_invoiced and line.last_date_invoiced
704
- else terminate_date
705
- )
706
- self.write(
707
- {
708
- "is_terminated": True,
709
- "terminate_reason_id": terminate_reason_id.id,
710
- "terminate_comment": terminate_comment,
711
- "terminate_date": terminate_date,
712
- }
713
- )
714
- return True
715
-
716
- def action_cancel_contract_termination(self):
717
- self.ensure_one()
718
- self.write(
719
- {
720
- "is_terminated": False,
721
- "terminate_reason_id": False,
722
- "terminate_comment": False,
723
- "terminate_date": False,
724
- }
725
- )