odoo-addon-contract 17.0.1.4.4__py3-none-any.whl → 18.0.2.0.8__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 (129) 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/am.po +141 -821
  8. odoo/addons/contract/i18n/ar.po +141 -821
  9. odoo/addons/contract/i18n/bg.po +141 -821
  10. odoo/addons/contract/i18n/bs.po +141 -821
  11. odoo/addons/contract/i18n/ca.po +831 -901
  12. odoo/addons/contract/i18n/ca_ES.po +141 -821
  13. odoo/addons/contract/i18n/contract.pot +140 -818
  14. odoo/addons/contract/i18n/cs.po +141 -821
  15. odoo/addons/contract/i18n/da.po +141 -821
  16. odoo/addons/contract/i18n/de.po +708 -954
  17. odoo/addons/contract/i18n/el_GR.po +141 -821
  18. odoo/addons/contract/i18n/en_GB.po +141 -821
  19. odoo/addons/contract/i18n/es.po +710 -948
  20. odoo/addons/contract/i18n/es_AR.po +548 -880
  21. odoo/addons/contract/i18n/es_CL.po +141 -821
  22. odoo/addons/contract/i18n/es_CO.po +141 -821
  23. odoo/addons/contract/i18n/es_CR.po +141 -821
  24. odoo/addons/contract/i18n/es_DO.po +141 -821
  25. odoo/addons/contract/i18n/es_EC.po +141 -821
  26. odoo/addons/contract/i18n/es_MX.po +141 -821
  27. odoo/addons/contract/i18n/es_PY.po +141 -821
  28. odoo/addons/contract/i18n/es_VE.po +141 -821
  29. odoo/addons/contract/i18n/et.po +141 -821
  30. odoo/addons/contract/i18n/eu.po +141 -821
  31. odoo/addons/contract/i18n/fa.po +141 -821
  32. odoo/addons/contract/i18n/fi.po +419 -850
  33. odoo/addons/contract/i18n/fr.po +706 -951
  34. odoo/addons/contract/i18n/fr_CA.po +141 -821
  35. odoo/addons/contract/i18n/fr_CH.po +141 -821
  36. odoo/addons/contract/i18n/fr_FR.po +449 -850
  37. odoo/addons/contract/i18n/gl.po +252 -846
  38. odoo/addons/contract/i18n/gl_ES.po +141 -821
  39. odoo/addons/contract/i18n/he.po +141 -821
  40. odoo/addons/contract/i18n/hi_IN.po +186 -831
  41. odoo/addons/contract/i18n/hr.po +206 -837
  42. odoo/addons/contract/i18n/hr_HR.po +218 -839
  43. odoo/addons/contract/i18n/hu.po +141 -821
  44. odoo/addons/contract/i18n/id.po +141 -821
  45. odoo/addons/contract/i18n/it.po +746 -900
  46. odoo/addons/contract/i18n/ja.po +141 -821
  47. odoo/addons/contract/i18n/ko.po +141 -821
  48. odoo/addons/contract/i18n/lt.po +141 -821
  49. odoo/addons/contract/i18n/lt_LT.po +141 -821
  50. odoo/addons/contract/i18n/lv.po +141 -821
  51. odoo/addons/contract/i18n/mk.po +141 -821
  52. odoo/addons/contract/i18n/mn.po +141 -821
  53. odoo/addons/contract/i18n/nb.po +141 -821
  54. odoo/addons/contract/i18n/nb_NO.po +141 -821
  55. odoo/addons/contract/i18n/nl.po +694 -953
  56. odoo/addons/contract/i18n/nl_BE.po +141 -821
  57. odoo/addons/contract/i18n/nl_NL.po +186 -831
  58. odoo/addons/contract/i18n/pl.po +141 -821
  59. odoo/addons/contract/i18n/pt.po +410 -839
  60. odoo/addons/contract/i18n/pt_BR.po +701 -949
  61. odoo/addons/contract/i18n/pt_PT.po +141 -821
  62. odoo/addons/contract/i18n/ro.po +141 -821
  63. odoo/addons/contract/i18n/ru.po +186 -831
  64. odoo/addons/contract/i18n/sk.po +141 -821
  65. odoo/addons/contract/i18n/sk_SK.po +141 -821
  66. odoo/addons/contract/i18n/sl.po +141 -821
  67. odoo/addons/contract/i18n/sr.po +141 -821
  68. odoo/addons/contract/i18n/sr@latin.po +141 -821
  69. odoo/addons/contract/i18n/sv.po +780 -934
  70. odoo/addons/contract/i18n/th.po +141 -821
  71. odoo/addons/contract/i18n/tr.po +556 -877
  72. odoo/addons/contract/i18n/tr_TR.po +216 -838
  73. odoo/addons/contract/i18n/uk.po +141 -821
  74. odoo/addons/contract/i18n/vi.po +141 -821
  75. odoo/addons/contract/i18n/vi_VN.po +141 -821
  76. odoo/addons/contract/i18n/zh_CN.po +407 -845
  77. odoo/addons/contract/i18n/zh_TW.po +145 -822
  78. odoo/addons/contract/migrations/18.0.2.0.0/end-migrate.py +27 -0
  79. odoo/addons/contract/migrations/18.0.2.0.0/pre-migrate.py +94 -0
  80. odoo/addons/contract/models/__init__.py +2 -6
  81. odoo/addons/contract/models/account_move.py +0 -8
  82. odoo/addons/contract/models/account_move_line.py +14 -0
  83. odoo/addons/contract/models/contract.py +272 -308
  84. odoo/addons/contract/models/contract_line.py +37 -859
  85. odoo/addons/contract/models/{contract_recurrency_mixin.py → contract_recurring_mixin.py} +101 -82
  86. odoo/addons/contract/models/contract_tag.py +1 -3
  87. odoo/addons/contract/models/contract_template.py +81 -2
  88. odoo/addons/contract/models/contract_template_line.py +250 -3
  89. odoo/addons/contract/report/contract_views.xml +0 -2
  90. odoo/addons/contract/report/report_contract.xml +13 -13
  91. odoo/addons/contract/security/contract_security.xml +6 -15
  92. odoo/addons/contract/security/contract_tag.xml +1 -3
  93. odoo/addons/contract/security/ir.model.access.csv +0 -2
  94. odoo/addons/contract/static/description/index.html +24 -18
  95. odoo/addons/contract/static/src/js/contract_portal_tour.esm.js +6 -4
  96. odoo/addons/contract/tests/test_contract.py +82 -928
  97. odoo/addons/contract/tests/test_multicompany.py +5 -4
  98. odoo/addons/contract/tests/test_portal.py +6 -3
  99. odoo/addons/contract/views/contract.xml +92 -235
  100. odoo/addons/contract/views/contract_line.xml +48 -117
  101. odoo/addons/contract/views/contract_portal_templates.xml +181 -222
  102. odoo/addons/contract/views/contract_tag.xml +3 -3
  103. odoo/addons/contract/views/contract_template.xml +100 -72
  104. odoo/addons/contract/views/contract_template_line.xml +76 -5
  105. odoo/addons/contract/views/res_config_settings.xml +5 -6
  106. odoo/addons/contract/views/res_partner_view.xml +0 -5
  107. odoo/addons/contract/wizards/__init__.py +0 -2
  108. odoo/addons/contract/wizards/contract_manually_create_invoice.py +6 -6
  109. odoo/addons/contract/wizards/contract_manually_create_invoice.xml +2 -3
  110. {odoo_addon_contract-17.0.1.4.4.dist-info → odoo_addon_contract-18.0.2.0.8.dist-info}/METADATA +18 -13
  111. odoo_addon_contract-18.0.2.0.8.dist-info/RECORD +132 -0
  112. {odoo_addon_contract-17.0.1.4.4.dist-info → odoo_addon_contract-18.0.2.0.8.dist-info}/WHEEL +1 -1
  113. odoo/addons/contract/data/contract_renew_cron.xml +0 -14
  114. odoo/addons/contract/models/abstract_contract.py +0 -82
  115. odoo/addons/contract/models/abstract_contract_line.py +0 -271
  116. odoo/addons/contract/models/contract_line_constraints.py +0 -429
  117. odoo/addons/contract/models/contract_terminate_reason.py +0 -14
  118. odoo/addons/contract/models/res_company.py +0 -15
  119. odoo/addons/contract/models/res_config_settings.py +0 -18
  120. odoo/addons/contract/security/contract_terminate_reason.xml +0 -23
  121. odoo/addons/contract/security/groups.xml +0 -9
  122. odoo/addons/contract/views/abstract_contract_line.xml +0 -117
  123. odoo/addons/contract/views/contract_terminate_reason.xml +0 -38
  124. odoo/addons/contract/wizards/contract_contract_terminate.py +0 -42
  125. odoo/addons/contract/wizards/contract_contract_terminate.xml +0 -33
  126. odoo/addons/contract/wizards/contract_line_wizard.py +0 -53
  127. odoo/addons/contract/wizards/contract_line_wizard.xml +0 -111
  128. odoo_addon_contract-17.0.1.4.4.dist-info/RECORD +0 -144
  129. {odoo_addon_contract-17.0.1.4.4.dist-info → odoo_addon_contract-18.0.2.0.8.dist-info}/top_level.txt +0 -0
@@ -3,22 +3,20 @@
3
3
  # Copyright 2020 Tecnativa - Pedro M. Baeza
4
4
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
5
5
 
6
- from datetime import timedelta
6
+
7
+ import warnings
7
8
 
8
9
  from dateutil.relativedelta import relativedelta
9
10
 
10
- from odoo import _, api, fields, models
11
+ from odoo import api, fields, models
11
12
  from odoo.exceptions import ValidationError
12
13
 
13
- from .contract_line_constraints import get_allowed
14
-
15
14
 
16
15
  class ContractLine(models.Model):
17
16
  _name = "contract.line"
18
17
  _description = "Contract Line"
19
18
  _inherit = [
20
- "contract.abstract.contract.line",
21
- "contract.recurrency.mixin",
19
+ "contract.template.line",
22
20
  "analytic.mixin",
23
21
  ]
24
22
  _order = "sequence,id"
@@ -33,366 +31,21 @@ class ContractLine(models.Model):
33
31
  ondelete="cascade",
34
32
  )
35
33
  currency_id = fields.Many2one(related="contract_id.currency_id")
36
- date_start = fields.Date(required=True)
37
- date_end = fields.Date(compute="_compute_date_end", store=True, readonly=False)
38
- termination_notice_date = fields.Date(
39
- compute="_compute_termination_notice_date",
40
- store=True,
41
- copy=False,
42
- )
43
34
  create_invoice_visibility = fields.Boolean(
44
35
  compute="_compute_create_invoice_visibility"
45
36
  )
46
- successor_contract_line_id = fields.Many2one(
47
- comodel_name="contract.line",
48
- string="Successor Contract Line",
49
- required=False,
50
- readonly=True,
51
- index=True,
52
- copy=False,
53
- help="In case of restart after suspension, this field contain the new "
54
- "contract line created.",
55
- )
56
- predecessor_contract_line_id = fields.Many2one(
57
- comodel_name="contract.line",
58
- string="Predecessor Contract Line",
59
- required=False,
60
- readonly=True,
61
- index=True,
62
- copy=False,
63
- help="Contract Line origin of this one.",
64
- )
65
- manual_renew_needed = fields.Boolean(
66
- default=False,
67
- help="This flag is used to make a difference between a definitive stop"
68
- "and temporary one for which a user is not able to plan a"
69
- "successor in advance",
70
- )
71
- is_plan_successor_allowed = fields.Boolean(
72
- string="Plan successor allowed?", compute="_compute_allowed"
73
- )
74
- is_stop_plan_successor_allowed = fields.Boolean(
75
- string="Stop/Plan successor allowed?", compute="_compute_allowed"
76
- )
77
- is_stop_allowed = fields.Boolean(string="Stop allowed?", compute="_compute_allowed")
78
- is_cancel_allowed = fields.Boolean(
79
- string="Cancel allowed?", compute="_compute_allowed"
80
- )
81
- is_un_cancel_allowed = fields.Boolean(
82
- string="Un-Cancel allowed?", compute="_compute_allowed"
83
- )
84
- state = fields.Selection(
85
- selection=[
86
- ("upcoming", "Upcoming"),
87
- ("in-progress", "In-progress"),
88
- ("to-renew", "To renew"),
89
- ("upcoming-close", "Upcoming Close"),
90
- ("closed", "Closed"),
91
- ("canceled", "Canceled"),
92
- ],
93
- compute="_compute_state",
94
- search="_search_state",
95
- )
96
37
  active = fields.Boolean(
97
38
  string="Active",
98
39
  related="contract_id.active",
99
40
  store=True,
100
41
  readonly=True,
101
42
  )
43
+ product_id = fields.Many2one(index=True)
102
44
 
103
- @api.depends(
104
- "last_date_invoiced",
105
- "date_start",
106
- "date_end",
107
- "contract_id.last_date_invoiced",
108
- "contract_id.contract_line_ids.last_date_invoiced",
109
- )
110
- # pylint: disable=missing-return
111
- def _compute_next_period_date_start(self):
112
- """Rectify next period date start if another line in the contract has been
113
- already invoiced previously when the recurrence is by contract.
114
- """
115
- rest = self.filtered(lambda x: x.contract_id.line_recurrence)
116
- for rec in self - rest:
117
- lines = rec.contract_id.contract_line_ids
118
- if not rec.last_date_invoiced and any(lines.mapped("last_date_invoiced")):
119
- next_period_date_start = max(
120
- lines.filtered("last_date_invoiced").mapped("last_date_invoiced")
121
- ) + relativedelta(days=1)
122
- if rec.date_end and next_period_date_start > rec.date_end:
123
- next_period_date_start = False
124
- rec.next_period_date_start = next_period_date_start
125
- else:
126
- rest |= rec
127
- super(ContractLine, rest)._compute_next_period_date_start()
128
-
129
- @api.depends("contract_id.date_end", "contract_id.line_recurrence")
130
- def _compute_date_end(self):
131
- self._set_recurrence_field("date_end")
132
-
133
- @api.depends(
134
- "date_end",
135
- "termination_notice_rule_type",
136
- "termination_notice_interval",
137
- )
138
- def _compute_termination_notice_date(self):
139
- for rec in self:
140
- if rec.date_end:
141
- rec.termination_notice_date = rec.date_end - self.get_relative_delta(
142
- rec.termination_notice_rule_type,
143
- rec.termination_notice_interval,
144
- )
145
- else:
146
- rec.termination_notice_date = False
147
-
148
- @api.depends(
149
- "is_canceled",
150
- "date_start",
151
- "date_end",
152
- "is_auto_renew",
153
- "manual_renew_needed",
154
- "termination_notice_date",
155
- "successor_contract_line_id",
156
- )
157
- def _compute_state(self):
158
- today = fields.Date.context_today(self)
45
+ @api.depends("name", "date_start")
46
+ def _compute_display_name(self):
159
47
  for rec in self:
160
- rec.state = False
161
- if rec.display_type:
162
- continue
163
- if rec.is_canceled:
164
- rec.state = "canceled"
165
- continue
166
-
167
- if rec.date_start and rec.date_start > today:
168
- # Before period
169
- rec.state = "upcoming"
170
- continue
171
- if (
172
- rec.date_start
173
- and rec.date_start <= today
174
- and (not rec.date_end or rec.date_end >= today)
175
- ):
176
- # In period
177
- if (
178
- rec.termination_notice_date
179
- and rec.termination_notice_date < today
180
- and not rec.is_auto_renew
181
- and not rec.manual_renew_needed
182
- ):
183
- rec.state = "upcoming-close"
184
- else:
185
- rec.state = "in-progress"
186
- continue
187
- if rec.date_end and rec.date_end < today:
188
- # After
189
- if (
190
- rec.manual_renew_needed
191
- and not rec.successor_contract_line_id
192
- or rec.is_auto_renew
193
- ):
194
- rec.state = "to-renew"
195
- else:
196
- rec.state = "closed"
197
-
198
- @api.model
199
- def _get_state_domain(self, state):
200
- today = fields.Date.context_today(self)
201
- if state == "upcoming":
202
- return [
203
- "&",
204
- ("date_start", ">", today),
205
- ("is_canceled", "=", False),
206
- ]
207
- if state == "in-progress":
208
- return [
209
- "&",
210
- "&",
211
- "&",
212
- ("date_start", "<=", today),
213
- ("is_canceled", "=", False),
214
- "|",
215
- ("date_end", ">=", today),
216
- ("date_end", "=", False),
217
- "|",
218
- ("is_auto_renew", "=", True),
219
- "&",
220
- ("is_auto_renew", "=", False),
221
- ("termination_notice_date", ">", today),
222
- ]
223
- if state == "to-renew":
224
- return [
225
- "&",
226
- "&",
227
- ("is_canceled", "=", False),
228
- ("date_end", "<", today),
229
- "|",
230
- "&",
231
- ("manual_renew_needed", "=", True),
232
- ("successor_contract_line_id", "=", False),
233
- ("is_auto_renew", "=", True),
234
- ]
235
- if state == "upcoming-close":
236
- return [
237
- "&",
238
- "&",
239
- "&",
240
- "&",
241
- "&",
242
- ("date_start", "<=", today),
243
- ("is_auto_renew", "=", False),
244
- ("manual_renew_needed", "=", False),
245
- ("is_canceled", "=", False),
246
- ("termination_notice_date", "<", today),
247
- ("date_end", ">=", today),
248
- ]
249
- if state == "closed":
250
- return [
251
- "&",
252
- "&",
253
- "&",
254
- ("is_canceled", "=", False),
255
- ("date_end", "<", today),
256
- ("is_auto_renew", "=", False),
257
- "|",
258
- "&",
259
- ("manual_renew_needed", "=", True),
260
- ("successor_contract_line_id", "!=", False),
261
- ("manual_renew_needed", "=", False),
262
- ]
263
- if state == "canceled":
264
- return [("is_canceled", "=", True)]
265
- if not state:
266
- return [("display_type", "!=", False)]
267
-
268
- @api.model
269
- def _search_state(self, operator, value):
270
- states = [
271
- "upcoming",
272
- "in-progress",
273
- "to-renew",
274
- "upcoming-close",
275
- "closed",
276
- "canceled",
277
- False,
278
- ]
279
- if operator == "=":
280
- return self._get_state_domain(value)
281
- if operator == "!=":
282
- domain = []
283
- for state in states:
284
- if state != value:
285
- if domain:
286
- domain.insert(0, "|")
287
- domain.extend(self._get_state_domain(state))
288
- return domain
289
- if operator == "in":
290
- domain = []
291
- for state in value:
292
- if domain:
293
- domain.insert(0, "|")
294
- domain.extend(self._get_state_domain(state))
295
- return domain
296
-
297
- if operator == "not in":
298
- if set(value) == set(states):
299
- return [("id", "=", False)]
300
- return self._search_state(
301
- "in", [state for state in states if state not in value]
302
- )
303
-
304
- @api.depends(
305
- "date_start",
306
- "date_end",
307
- "last_date_invoiced",
308
- "is_auto_renew",
309
- "successor_contract_line_id",
310
- "predecessor_contract_line_id",
311
- "is_canceled",
312
- "contract_id.is_terminated",
313
- )
314
- def _compute_allowed(self):
315
- for rec in self:
316
- rec.update(
317
- {
318
- "is_plan_successor_allowed": False,
319
- "is_stop_plan_successor_allowed": False,
320
- "is_stop_allowed": False,
321
- "is_cancel_allowed": False,
322
- "is_un_cancel_allowed": False,
323
- }
324
- )
325
- if rec.contract_id.is_terminated:
326
- continue
327
- if rec.date_start:
328
- allowed = get_allowed(
329
- rec.date_start,
330
- rec.date_end,
331
- rec.last_date_invoiced,
332
- rec.is_auto_renew,
333
- rec.successor_contract_line_id,
334
- rec.predecessor_contract_line_id,
335
- rec.is_canceled,
336
- )
337
- if allowed:
338
- rec.update(
339
- {
340
- "is_plan_successor_allowed": allowed.plan_successor,
341
- "is_stop_plan_successor_allowed": (
342
- allowed.stop_plan_successor
343
- ),
344
- "is_stop_allowed": allowed.stop,
345
- "is_cancel_allowed": allowed.cancel,
346
- "is_un_cancel_allowed": allowed.uncancel,
347
- }
348
- )
349
-
350
- @api.constrains("is_auto_renew", "successor_contract_line_id", "date_end")
351
- def _check_allowed(self):
352
- """
353
- logical impossible combination:
354
- * a line with is_auto_renew True should have date_end and
355
- couldn't have successor_contract_line_id
356
- * a line without date_end can't have successor_contract_line_id
357
-
358
- """
359
- for rec in self:
360
- if rec.is_auto_renew:
361
- if rec.successor_contract_line_id:
362
- raise ValidationError(
363
- _(
364
- "A contract line with a successor "
365
- "can't be set to auto-renew"
366
- )
367
- )
368
- if not rec.date_end:
369
- raise ValidationError(_("An auto-renew line must have a end date"))
370
- else:
371
- if not rec.date_end and rec.successor_contract_line_id:
372
- raise ValidationError(
373
- _("A contract line with a successor " "must have a end date")
374
- )
375
-
376
- @api.constrains("successor_contract_line_id", "date_end")
377
- def _check_overlap_successor(self):
378
- for rec in self:
379
- if rec.date_end and rec.successor_contract_line_id:
380
- if rec.date_end >= rec.successor_contract_line_id.date_start:
381
- raise ValidationError(
382
- _("Contract line and its successor overlapped")
383
- )
384
-
385
- @api.constrains("predecessor_contract_line_id", "date_start")
386
- def _check_overlap_predecessor(self):
387
- for rec in self:
388
- if (
389
- rec.predecessor_contract_line_id
390
- and rec.predecessor_contract_line_id.date_end
391
- ):
392
- if rec.date_start <= rec.predecessor_contract_line_id.date_end:
393
- raise ValidationError(
394
- _("Contract line and its predecessor overlapped")
395
- )
48
+ rec.display_name = f"{rec.date_start} - {rec.name}"
396
49
 
397
50
  @api.model
398
51
  def _compute_first_recurring_next_date(
@@ -414,53 +67,15 @@ class ContractLine(models.Model):
414
67
  max_date_end=False,
415
68
  )
416
69
 
417
- @api.model
418
- def _get_first_date_end(
419
- self, date_start, auto_renew_rule_type, auto_renew_interval
420
- ):
421
- return (
422
- date_start
423
- + self.get_relative_delta(auto_renew_rule_type, auto_renew_interval)
424
- - relativedelta(days=1)
425
- )
426
-
427
- @api.onchange(
428
- "date_start",
429
- "is_auto_renew",
430
- "auto_renew_rule_type",
431
- "auto_renew_interval",
432
- )
433
- def _onchange_is_auto_renew(self):
434
- """Date end should be auto-computed if a contract line is set to
435
- auto_renew"""
436
- for rec in self.filtered("is_auto_renew"):
437
- if rec.date_start:
438
- rec.date_end = self._get_first_date_end(
439
- rec.date_start,
440
- rec.auto_renew_rule_type,
441
- rec.auto_renew_interval,
442
- )
443
-
444
- @api.constrains("is_canceled", "is_auto_renew")
445
- def _check_auto_renew_canceled_lines(self):
446
- for rec in self:
447
- if rec.is_canceled and rec.is_auto_renew:
448
- raise ValidationError(
449
- _("A canceled contract line can't be set to auto-renew")
450
- )
451
-
452
70
  @api.constrains("recurring_next_date", "date_start")
453
71
  def _check_recurring_next_date_start_date(self):
454
72
  for line in self:
455
- if (
456
- line.display_type in ("line_section", "line_note")
457
- or not line.recurring_next_date
458
- ):
73
+ if line.display_type == "line_section" or not line.recurring_next_date:
459
74
  continue
460
75
  if line.date_start and line.recurring_next_date:
461
76
  if line.date_start > line.recurring_next_date:
462
77
  raise ValidationError(
463
- _(
78
+ self.env._(
464
79
  "You can't have a date of next invoice anterior "
465
80
  "to the start of the contract line '%s'"
466
81
  )
@@ -474,7 +89,7 @@ class ContractLine(models.Model):
474
89
  for rec in self.filtered("last_date_invoiced"):
475
90
  if rec.date_end and rec.date_end < rec.last_date_invoiced:
476
91
  raise ValidationError(
477
- _(
92
+ self.env._(
478
93
  "You can't have the end date before the date of last "
479
94
  "invoice for the contract line '%s'"
480
95
  )
@@ -484,7 +99,7 @@ class ContractLine(models.Model):
484
99
  continue
485
100
  if rec.date_start and rec.date_start > rec.last_date_invoiced:
486
101
  raise ValidationError(
487
- _(
102
+ self.env._(
488
103
  "You can't have the start date after the date of last "
489
104
  "invoice for the contract line '%s'"
490
105
  )
@@ -495,7 +110,7 @@ class ContractLine(models.Model):
495
110
  and rec.recurring_next_date <= rec.last_date_invoiced
496
111
  ):
497
112
  raise ValidationError(
498
- _(
113
+ self.env._(
499
114
  "You can't have the next invoice date before the date "
500
115
  "of last invoice for the contract line '%s'"
501
116
  )
@@ -511,7 +126,7 @@ class ContractLine(models.Model):
511
126
  or rec.last_date_invoiced < rec.date_end
512
127
  ):
513
128
  raise ValidationError(
514
- _(
129
+ self.env._(
515
130
  "You must supply a date of next invoice for contract "
516
131
  "line '%s'"
517
132
  )
@@ -524,7 +139,7 @@ class ContractLine(models.Model):
524
139
  if line.date_start and line.date_end:
525
140
  if line.date_start > line.date_end:
526
141
  raise ValidationError(
527
- _(
142
+ self.env._(
528
143
  "Contract line '%s' start date can't be later than"
529
144
  " end date"
530
145
  )
@@ -597,18 +212,18 @@ class ContractLine(models.Model):
597
212
 
598
213
  def _translate_marker_month_name(self, month_name):
599
214
  months = {
600
- "01": _("January"),
601
- "02": _("February"),
602
- "03": _("March"),
603
- "04": _("April"),
604
- "05": _("May"),
605
- "06": _("June"),
606
- "07": _("July"),
607
- "08": _("August"),
608
- "09": _("September"),
609
- "10": _("October"),
610
- "11": _("November"),
611
- "12": _("December"),
215
+ "01": self.env._("January"),
216
+ "02": self.env._("February"),
217
+ "03": self.env._("March"),
218
+ "04": self.env._("April"),
219
+ "05": self.env._("May"),
220
+ "06": self.env._("June"),
221
+ "07": self.env._("July"),
222
+ "08": self.env._("August"),
223
+ "09": self.env._("September"),
224
+ "10": self.env._("October"),
225
+ "11": self.env._("November"),
226
+ "12": self.env._("December"),
612
227
  }
613
228
  return months[month_name]
614
229
 
@@ -629,8 +244,15 @@ class ContractLine(models.Model):
629
244
  return name
630
245
 
631
246
  def _update_recurring_next_date(self):
632
- # FIXME: Change method name according to real updated field
633
- # e.g.: _update_last_date_invoiced()
247
+ warnings.warn(
248
+ "Deprecated _update_recurring_next_date, "
249
+ "use _update_last_date_invoiced instead",
250
+ DeprecationWarning,
251
+ stacklevel=2,
252
+ )
253
+ return self._update_last_date_invoiced()
254
+
255
+ def _update_last_date_invoiced(self):
634
256
  for rec in self:
635
257
  last_date_invoiced = rec.next_period_date_end
636
258
  rec.write(
@@ -639,447 +261,10 @@ class ContractLine(models.Model):
639
261
  }
640
262
  )
641
263
 
642
- def _delay(self, delay_delta):
643
- """
644
- Delay a contract line
645
- :param delay_delta: delay relative delta
646
- :return: delayed contract line
647
- """
648
- for rec in self:
649
- if rec.last_date_invoiced:
650
- raise ValidationError(
651
- _("You can't delay a contract line " "invoiced at least one time.")
652
- )
653
- new_date_start = rec.date_start + delay_delta
654
- if rec.date_end:
655
- new_date_end = rec.date_end + delay_delta
656
- else:
657
- new_date_end = False
658
- new_recurring_next_date = self.get_next_invoice_date(
659
- new_date_start,
660
- rec.recurring_invoicing_type,
661
- rec.recurring_invoicing_offset,
662
- rec.recurring_rule_type,
663
- rec.recurring_interval,
664
- max_date_end=new_date_end,
665
- )
666
- rec.write(
667
- {
668
- "date_start": new_date_start,
669
- "date_end": new_date_end,
670
- "recurring_next_date": new_recurring_next_date,
671
- }
672
- )
673
-
674
- def _prepare_value_for_stop(self, date_end, manual_renew_needed):
675
- self.ensure_one()
676
- return {
677
- "date_end": date_end,
678
- "is_auto_renew": False,
679
- "manual_renew_needed": manual_renew_needed,
680
- "recurring_next_date": self.get_next_invoice_date(
681
- self.next_period_date_start,
682
- self.recurring_invoicing_type,
683
- self.recurring_invoicing_offset,
684
- self.recurring_rule_type,
685
- self.recurring_interval,
686
- max_date_end=date_end,
687
- ),
688
- }
689
-
690
- def stop(self, date_end, manual_renew_needed=False, post_message=True):
691
- """
692
- Put date_end on contract line
693
- We don't consider contract lines that end's before the new end date
694
- :param date_end: new date end for contract line
695
- :return: True
696
- """
697
- if not all(self.mapped("is_stop_allowed")):
698
- raise ValidationError(_("Stop not allowed for this line"))
699
- for rec in self:
700
- if date_end < rec.date_start:
701
- rec.cancel()
702
- else:
703
- if not rec.date_end or rec.date_end > date_end:
704
- old_date_end = rec.date_end
705
- rec.write(
706
- rec._prepare_value_for_stop(date_end, manual_renew_needed)
707
- )
708
- if post_message:
709
- msg = _(
710
- """Contract line for <strong>%(product)s</strong>
711
- stopped: <br/>
712
- - <strong>End</strong>: %(old_end)s -- %(new_end)s
713
- """
714
- ) % {
715
- "product": rec.name,
716
- "old_end": old_date_end,
717
- "new_end": rec.date_end,
718
- }
719
- rec.contract_id.message_post(body=msg)
720
- else:
721
- rec.write(
722
- {
723
- "is_auto_renew": False,
724
- "manual_renew_needed": manual_renew_needed,
725
- }
726
- )
727
- return True
728
-
729
- def _prepare_value_for_plan_successor(
730
- self, date_start, date_end, is_auto_renew, recurring_next_date=False
731
- ):
732
- self.ensure_one()
733
- if not recurring_next_date:
734
- recurring_next_date = self.get_next_invoice_date(
735
- date_start,
736
- self.recurring_invoicing_type,
737
- self.recurring_invoicing_offset,
738
- self.recurring_rule_type,
739
- self.recurring_interval,
740
- max_date_end=date_end,
741
- )
742
- new_vals = self.read()[0]
743
- new_vals.pop("id", None)
744
- new_vals.pop("last_date_invoiced", None)
745
- values = self._convert_to_write(new_vals)
746
- values["date_start"] = date_start
747
- values["date_end"] = date_end
748
- values["recurring_next_date"] = recurring_next_date
749
- values["is_auto_renew"] = is_auto_renew
750
- values["predecessor_contract_line_id"] = self.id
751
- return values
752
-
753
- def plan_successor(
754
- self,
755
- date_start,
756
- date_end,
757
- is_auto_renew,
758
- recurring_next_date=False,
759
- post_message=True,
760
- ):
761
- """
762
- Create a copy of a contract line in a new interval
763
- :param date_start: date_start for the successor_contract_line
764
- :param date_end: date_end for the successor_contract_line
765
- :param is_auto_renew: is_auto_renew option for successor_contract_line
766
- :param recurring_next_date: recurring_next_date for the
767
- successor_contract_line
768
- :return: successor_contract_line
769
- """
770
- contract_line = self.env["contract.line"]
771
- for rec in self:
772
- if not rec.is_plan_successor_allowed:
773
- raise ValidationError(_("Plan successor not allowed for this line"))
774
- rec.is_auto_renew = False
775
- new_line = self.create(
776
- rec._prepare_value_for_plan_successor(
777
- date_start, date_end, is_auto_renew, recurring_next_date
778
- )
779
- )
780
- rec.successor_contract_line_id = new_line
781
- contract_line |= new_line
782
- if post_message:
783
- msg = _(
784
- """Contract line for <strong>%(product)s</strong>
785
- planned a successor: <br/>
786
- - <strong>Start</strong>: %(new_date_start)s
787
- <br/>
788
- - <strong>End</strong>: %(new_date_end)s
789
- """
790
- ) % {
791
- "product": rec.name,
792
- "new_date_start": new_line.date_start,
793
- "new_date_end": new_line.date_end,
794
- }
795
- rec.contract_id.message_post(body=msg)
796
- return contract_line
797
-
798
- def stop_plan_successor(self, date_start, date_end, is_auto_renew):
799
- """
800
- Stop a contract line for a defined period and start it later
801
- Cases to consider:
802
- * contract line end's before the suspension period:
803
- -> apply stop
804
- * contract line start before the suspension period and end in it
805
- -> apply stop at suspension start date
806
- -> apply plan successor:
807
- - date_start: suspension.date_end
808
- - date_end: date_end + (contract_line.date_end
809
- - suspension.date_start)
810
- * contract line start before the suspension period and end after it
811
- -> apply stop at suspension start date
812
- -> apply plan successor:
813
- - date_start: suspension.date_end
814
- - date_end: date_end + (suspension.date_end
815
- - suspension.date_start)
816
- * contract line start and end's in the suspension period
817
- -> apply delay
818
- - delay: suspension.date_end - contract_line.date_start
819
- * contract line start in the suspension period and end after it
820
- -> apply delay
821
- - delay: suspension.date_end - contract_line.date_start
822
- * contract line start and end after the suspension period
823
- -> apply delay
824
- - delay: suspension.date_end - suspension.start_date
825
- :param date_start: suspension start date
826
- :param date_end: suspension end date
827
- :param is_auto_renew: is the new line is set to auto_renew
828
- :return: created contract line
829
- """
830
- if not all(self.mapped("is_stop_plan_successor_allowed")):
831
- raise ValidationError(_("Stop/Plan successor not allowed for this line"))
832
- contract_line = self.env["contract.line"]
833
- for rec in self:
834
- if rec.date_start >= date_start:
835
- if rec.date_start < date_end:
836
- delay = (date_end - rec.date_start) + timedelta(days=1)
837
- else:
838
- delay = (date_end - date_start) + timedelta(days=1)
839
- rec._delay(delay)
840
- contract_line |= rec
841
- else:
842
- if rec.date_end and rec.date_end < date_start:
843
- rec.stop(date_start, post_message=False)
844
- elif (
845
- rec.date_end
846
- and rec.date_end > date_start
847
- and rec.date_end < date_end
848
- ):
849
- new_date_start = date_end + relativedelta(days=1)
850
- new_date_end = (
851
- date_end + (rec.date_end - date_start) + relativedelta(days=1)
852
- )
853
- rec.stop(
854
- date_start - relativedelta(days=1),
855
- manual_renew_needed=True,
856
- post_message=False,
857
- )
858
- contract_line |= rec.plan_successor(
859
- new_date_start,
860
- new_date_end,
861
- is_auto_renew,
862
- post_message=False,
863
- )
864
- else:
865
- new_date_start = date_end + relativedelta(days=1)
866
- if rec.date_end:
867
- new_date_end = (
868
- rec.date_end
869
- + (date_end - date_start)
870
- + relativedelta(days=1)
871
- )
872
- else:
873
- new_date_end = rec.date_end
874
-
875
- rec.stop(
876
- date_start - relativedelta(days=1),
877
- manual_renew_needed=True,
878
- post_message=False,
879
- )
880
- contract_line |= rec.plan_successor(
881
- new_date_start,
882
- new_date_end,
883
- is_auto_renew,
884
- post_message=False,
885
- )
886
- msg = _(
887
- """Contract line for <strong>%(product)s</strong>
888
- suspended: <br/>
889
- - <strong>Suspension Start</strong>: %(new_date_start)s
890
- <br/>
891
- - <strong>Suspension End</strong>: %(new_date_end)s
892
- """
893
- ) % {
894
- "product": rec.name,
895
- "new_date_start": date_start,
896
- "new_date_end": date_end,
897
- }
898
- rec.contract_id.message_post(body=msg)
899
- return contract_line
900
-
901
- def cancel(self):
902
- if not all(self.mapped("is_cancel_allowed")):
903
- raise ValidationError(_("Cancel not allowed for this line"))
904
- for contract in self.mapped("contract_id"):
905
- lines = self.filtered(lambda line, c=contract: line.contract_id == c)
906
- msg = _(
907
- "Contract line canceled: %s",
908
- "<br/>- ".join(
909
- [f"<strong>{name}</strong>" for name in lines.mapped("name")]
910
- ),
911
- )
912
- contract.message_post(body=msg)
913
- self.mapped("predecessor_contract_line_id").write(
914
- {"successor_contract_line_id": False}
915
- )
916
- return self.write({"is_canceled": True, "is_auto_renew": False})
917
-
918
- def uncancel(self, recurring_next_date):
919
- if not all(self.mapped("is_un_cancel_allowed")):
920
- raise ValidationError(_("Un-cancel not allowed for this line"))
921
- for contract in self.mapped("contract_id"):
922
- lines = self.filtered(lambda line, c=contract: line.contract_id == c)
923
- msg = _(
924
- "Contract line Un-canceled: %s",
925
- "<br/>- ".join(
926
- [f"<strong>{name}</strong>" for name in lines.mapped("name")]
927
- ),
928
- )
929
- contract.message_post(body=msg)
930
- for rec in self:
931
- if rec.predecessor_contract_line_id:
932
- predecessor_contract_line = rec.predecessor_contract_line_id
933
- assert not predecessor_contract_line.successor_contract_line_id
934
- predecessor_contract_line.successor_contract_line_id = rec
935
- rec.is_canceled = False
936
- rec.recurring_next_date = recurring_next_date
937
- return True
938
-
939
- def action_uncancel(self):
940
- self.ensure_one()
941
- context = {
942
- "default_contract_line_id": self.id,
943
- "default_recurring_next_date": fields.Date.context_today(self),
944
- }
945
- context.update(self.env.context)
946
- view_id = self.env.ref("contract.contract_line_wizard_uncancel_form_view").id
947
- return {
948
- "type": "ir.actions.act_window",
949
- "name": "Un-Cancel Contract Line",
950
- "res_model": "contract.line.wizard",
951
- "view_mode": "form",
952
- "views": [(view_id, "form")],
953
- "target": "new",
954
- "context": context,
955
- }
956
-
957
- def action_plan_successor(self):
958
- self.ensure_one()
959
- context = {
960
- "default_contract_line_id": self.id,
961
- "default_is_auto_renew": self.is_auto_renew,
962
- }
963
- context.update(self.env.context)
964
- view_id = self.env.ref(
965
- "contract.contract_line_wizard_plan_successor_form_view"
966
- ).id
967
- return {
968
- "type": "ir.actions.act_window",
969
- "name": "Plan contract line successor",
970
- "res_model": "contract.line.wizard",
971
- "view_mode": "form",
972
- "views": [(view_id, "form")],
973
- "target": "new",
974
- "context": context,
975
- }
976
-
977
- def action_stop(self):
978
- self.ensure_one()
979
- context = {
980
- "default_contract_line_id": self.id,
981
- "default_date_end": self.date_end,
982
- }
983
- context.update(self.env.context)
984
- view_id = self.env.ref("contract.contract_line_wizard_stop_form_view").id
985
- return {
986
- "type": "ir.actions.act_window",
987
- "name": "Terminate contract line",
988
- "res_model": "contract.line.wizard",
989
- "view_mode": "form",
990
- "views": [(view_id, "form")],
991
- "target": "new",
992
- "context": context,
993
- }
994
-
995
- def action_stop_plan_successor(self):
996
- self.ensure_one()
997
- context = {
998
- "default_contract_line_id": self.id,
999
- "default_is_auto_renew": self.is_auto_renew,
1000
- }
1001
- context.update(self.env.context)
1002
- view_id = self.env.ref(
1003
- "contract.contract_line_wizard_stop_plan_successor_form_view"
1004
- ).id
1005
- return {
1006
- "type": "ir.actions.act_window",
1007
- "name": "Suspend contract line",
1008
- "res_model": "contract.line.wizard",
1009
- "view_mode": "form",
1010
- "views": [(view_id, "form")],
1011
- "target": "new",
1012
- "context": context,
1013
- }
1014
-
1015
- def _get_renewal_new_date_end(self):
1016
- self.ensure_one()
1017
- date_start = self.date_end + relativedelta(days=1)
1018
- date_end = self._get_first_date_end(
1019
- date_start, self.auto_renew_rule_type, self.auto_renew_interval
1020
- )
1021
- return date_end
1022
-
1023
- def _renew_create_line(self, date_end):
1024
- self.ensure_one()
1025
- date_start = self.date_end + relativedelta(days=1)
1026
- is_auto_renew = self.is_auto_renew
1027
- self.stop(self.date_end, post_message=False)
1028
- new_line = self.plan_successor(
1029
- date_start, date_end, is_auto_renew, post_message=False
1030
- )
1031
- return new_line
1032
-
1033
- def _renew_extend_line(self, date_end):
1034
- self.ensure_one()
1035
- self.date_end = date_end
1036
- return self
1037
-
1038
- def renew(self):
1039
- res = self.env["contract.line"]
1040
- for rec in self:
1041
- company = rec.contract_id.company_id
1042
- date_end = rec._get_renewal_new_date_end()
1043
- date_start = rec.date_end + relativedelta(days=1)
1044
- if company.create_new_line_at_contract_line_renew:
1045
- new_line = rec._renew_create_line(date_end)
1046
- else:
1047
- new_line = rec._renew_extend_line(date_end)
1048
- res |= new_line
1049
- msg = _(
1050
- """Contract line for <strong>%(product)s</strong>
1051
- renewed: <br/>
1052
- - <strong>Start</strong>: %(new_date_start)s
1053
- <br/>
1054
- - <strong>End</strong>: %(new_date_end)s
1055
- """
1056
- ) % {
1057
- "product": rec.name,
1058
- "new_date_start": date_start,
1059
- "new_date_end": date_end,
1060
- }
1061
- rec.contract_id.message_post(body=msg)
1062
- return res
1063
-
1064
- @api.model
1065
- def _contract_line_to_renew_domain(self):
1066
- return [
1067
- ("contract_id.is_terminated", "=", False),
1068
- ("is_auto_renew", "=", True),
1069
- ("is_canceled", "=", False),
1070
- ("termination_notice_date", "<=", fields.Date.context_today(self)),
1071
- ]
1072
-
1073
- @api.model
1074
- def cron_renew_contract_line(self):
1075
- domain = self._contract_line_to_renew_domain()
1076
- to_renew = self.search(domain)
1077
- to_renew.renew()
1078
-
1079
264
  @api.model
1080
265
  def get_view(self, view_id=None, view_type="form", **options):
1081
266
  default_contract_type = self.env.context.get("default_contract_type")
1082
- if view_type == "tree" and default_contract_type == "purchase":
267
+ if view_type == "list" and default_contract_type == "purchase":
1083
268
  view_id = self.env.ref("contract.contract_line_supplier_tree_view").id
1084
269
  if view_type == "form":
1085
270
  if default_contract_type == "purchase":
@@ -1088,13 +273,6 @@ class ContractLine(models.Model):
1088
273
  view_id = self.env.ref("contract.contract_line_customer_form_view").id
1089
274
  return super().get_view(view_id, view_type, **options)
1090
275
 
1091
- def unlink(self):
1092
- """stop unlink uncnacled lines"""
1093
- for record in self:
1094
- if not (record.is_canceled or record.display_type):
1095
- raise ValidationError(_("Contract line must be canceled before delete"))
1096
- return super().unlink()
1097
-
1098
276
  def _get_quantity_to_invoice(
1099
277
  self, period_first_date, period_last_date, invoice_date
1100
278
  ):