odoo-addon-contract 17.0.1.4.5.1__py3-none-any.whl → 18.0.2.0.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 (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 +90 -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.8.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.8.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.8.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.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,16 @@ 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
  )
102
-
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)
159
- 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
- )
43
+ product_id = fields.Many2one(index=True)
396
44
 
397
45
  @api.model
398
46
  def _compute_first_recurring_next_date(
@@ -414,53 +62,15 @@ class ContractLine(models.Model):
414
62
  max_date_end=False,
415
63
  )
416
64
 
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
65
  @api.constrains("recurring_next_date", "date_start")
453
66
  def _check_recurring_next_date_start_date(self):
454
67
  for line in self:
455
- if (
456
- line.display_type in ("line_section", "line_note")
457
- or not line.recurring_next_date
458
- ):
68
+ if line.display_type == "line_section" or not line.recurring_next_date:
459
69
  continue
460
70
  if line.date_start and line.recurring_next_date:
461
71
  if line.date_start > line.recurring_next_date:
462
72
  raise ValidationError(
463
- _(
73
+ self.env._(
464
74
  "You can't have a date of next invoice anterior "
465
75
  "to the start of the contract line '%s'"
466
76
  )
@@ -474,7 +84,7 @@ class ContractLine(models.Model):
474
84
  for rec in self.filtered("last_date_invoiced"):
475
85
  if rec.date_end and rec.date_end < rec.last_date_invoiced:
476
86
  raise ValidationError(
477
- _(
87
+ self.env._(
478
88
  "You can't have the end date before the date of last "
479
89
  "invoice for the contract line '%s'"
480
90
  )
@@ -484,7 +94,7 @@ class ContractLine(models.Model):
484
94
  continue
485
95
  if rec.date_start and rec.date_start > rec.last_date_invoiced:
486
96
  raise ValidationError(
487
- _(
97
+ self.env._(
488
98
  "You can't have the start date after the date of last "
489
99
  "invoice for the contract line '%s'"
490
100
  )
@@ -495,7 +105,7 @@ class ContractLine(models.Model):
495
105
  and rec.recurring_next_date <= rec.last_date_invoiced
496
106
  ):
497
107
  raise ValidationError(
498
- _(
108
+ self.env._(
499
109
  "You can't have the next invoice date before the date "
500
110
  "of last invoice for the contract line '%s'"
501
111
  )
@@ -511,7 +121,7 @@ class ContractLine(models.Model):
511
121
  or rec.last_date_invoiced < rec.date_end
512
122
  ):
513
123
  raise ValidationError(
514
- _(
124
+ self.env._(
515
125
  "You must supply a date of next invoice for contract "
516
126
  "line '%s'"
517
127
  )
@@ -524,7 +134,7 @@ class ContractLine(models.Model):
524
134
  if line.date_start and line.date_end:
525
135
  if line.date_start > line.date_end:
526
136
  raise ValidationError(
527
- _(
137
+ self.env._(
528
138
  "Contract line '%s' start date can't be later than"
529
139
  " end date"
530
140
  )
@@ -597,18 +207,18 @@ class ContractLine(models.Model):
597
207
 
598
208
  def _translate_marker_month_name(self, month_name):
599
209
  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"),
210
+ "01": self.env._("January"),
211
+ "02": self.env._("February"),
212
+ "03": self.env._("March"),
213
+ "04": self.env._("April"),
214
+ "05": self.env._("May"),
215
+ "06": self.env._("June"),
216
+ "07": self.env._("July"),
217
+ "08": self.env._("August"),
218
+ "09": self.env._("September"),
219
+ "10": self.env._("October"),
220
+ "11": self.env._("November"),
221
+ "12": self.env._("December"),
612
222
  }
613
223
  return months[month_name]
614
224
 
@@ -629,8 +239,15 @@ class ContractLine(models.Model):
629
239
  return name
630
240
 
631
241
  def _update_recurring_next_date(self):
632
- # FIXME: Change method name according to real updated field
633
- # e.g.: _update_last_date_invoiced()
242
+ warnings.warn(
243
+ "Deprecated _update_recurring_next_date, "
244
+ "use _update_last_date_invoiced instead",
245
+ DeprecationWarning,
246
+ stacklevel=2,
247
+ )
248
+ return self._update_last_date_invoiced()
249
+
250
+ def _update_last_date_invoiced(self):
634
251
  for rec in self:
635
252
  last_date_invoiced = rec.next_period_date_end
636
253
  rec.write(
@@ -639,447 +256,10 @@ class ContractLine(models.Model):
639
256
  }
640
257
  )
641
258
 
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
259
  @api.model
1080
260
  def get_view(self, view_id=None, view_type="form", **options):
1081
261
  default_contract_type = self.env.context.get("default_contract_type")
1082
- if view_type == "tree" and default_contract_type == "purchase":
262
+ if view_type == "list" and default_contract_type == "purchase":
1083
263
  view_id = self.env.ref("contract.contract_line_supplier_tree_view").id
1084
264
  if view_type == "form":
1085
265
  if default_contract_type == "purchase":
@@ -1088,13 +268,6 @@ class ContractLine(models.Model):
1088
268
  view_id = self.env.ref("contract.contract_line_customer_form_view").id
1089
269
  return super().get_view(view_id, view_type, **options)
1090
270
 
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
271
  def _get_quantity_to_invoice(
1099
272
  self, period_first_date, period_last_date, invoice_date
1100
273
  ):