Coverage for ingadhoc-odoo-saas-adhoc / saas_provider_upgrade / models / saas_upgrade_client_config_line.py: 25%
106 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 20:33 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 20:33 +0000
1##############################################################################
2# For copyright and license notices, see __manifest__.py file in module root
3# directory
4##############################################################################
6from typing import TYPE_CHECKING, Any
8from odoo import _, api, fields, models
9from odoo.exceptions import UserError
11if TYPE_CHECKING:
12 from ..models.helpdesk_ticket import HelpdeskTicket as Ticket
13 from ..models.saas_upgrade_line import SaasUpgradeLine as UpgradeLine
16class SaasUpgradeClientConfigLine(models.Model):
17 _name = "saas.upgrade.client.config.line"
18 _description = "Odoo Saas Upgrade Client Config Line"
19 _inherit = ["saas.provider.upgrade.util"]
20 _rec_name = "title"
21 _order = "write_date desc"
23 key = fields.Char(
24 required=True,
25 readonly=True,
26 help="Key for the config line",
27 )
28 values = fields.Json(
29 required=True,
30 readonly=True,
31 help="Possible values for the config line. List of strings or booleans.",
32 )
33 selected_values = fields.Json(
34 store=True,
35 help="Values selected by the user for the config line."
36 "If usage is 'single', it should be a single value."
37 "If usage is 'multi', it should be a list of values."
38 "If usage is 'matrix', it should be a list of lists.",
39 )
40 type = fields.Selection(
41 selection=[
42 ("boolean", "Boolean"),
43 ("selection", "Selection"),
44 ],
45 required=True,
46 readonly=True,
47 help="Type of the config line",
48 )
49 usage = fields.Selection(
50 selection=[
51 ("single", "Single"),
52 ("multi", "Multi"),
53 ("matrix", "Matrix"),
54 ],
55 required=True,
56 readonly=True,
57 help="Defines how the configuration line will be used.",
58 )
59 title = fields.Char(
60 required=True,
61 help="Title to show in the portal for the config line",
62 )
63 upgrade_line_id = fields.Many2one(
64 "saas.upgrade.line",
65 readonly=True,
66 )
67 ticket_id = fields.Many2one(
68 "helpdesk.ticket",
69 readonly=True,
70 string="Ticket",
71 )
72 customer_note_id = fields.Many2one(
73 "helpdesk.ticket.customer_note",
74 readonly=True,
75 ondelete="cascade",
76 )
77 values_display = fields.Text(
78 compute="_compute_json_displays",
79 )
80 selected_values_display = fields.Text(
81 compute="_compute_json_displays",
82 )
83 published = fields.Boolean(
84 help="Indicates if the config line is visible in the customer portal",
85 )
87 @api.depends("values", "selected_values")
88 def _compute_json_displays(self):
89 for rec in self:
90 rec.values_display = self.json_to_display(rec.values)
91 rec.selected_values_display = self.json_to_display(rec.selected_values)
93 def create_config_line(
94 self,
95 upgrade_line: "UpgradeLine",
96 ticket: "Ticket",
97 key: str,
98 type: str,
99 values: list | dict,
100 default_values: Any = None,
101 usage: str = "single",
102 title: str = "",
103 ) -> bool:
104 """
105 Creates or updates a configuration line for the client in the upgrade line
107 :param key: The key of the configuration line
108 :param type: The type of the configuration line (e.g., 'boolean', 'selection')
109 :param values: The values of the configuration line
110 :param usage: The usage of the configuration line (default: 'single')
111 :param title: The title of the configuration line (default: None)
112 :return: True if the configuration line was created or updated, False otherwise
113 """
114 config_line_model = self.env["saas.upgrade.client.config.line"]
115 existing_config_line = config_line_model.search(
116 [
117 ("key", "=", key),
118 ("upgrade_line_id.id", "=", upgrade_line.id),
119 ("ticket_id.id", "=", ticket.id),
120 ],
121 limit=1,
122 )
123 publish = not self.env.context.get("test_dev", False)
124 if not values:
125 return False
126 if existing_config_line:
127 if existing_config_line.type != type and existing_config_line.selected_values:
128 raise UserError(_("Cannot change the type of an existing configuration line: %s") % key)
129 # Merge and deduplicate values
130 if usage == "matrix":
131 if not isinstance(values, dict):
132 raise UserError(_("Values for matrix configuration must be a dictionary: %s") % values)
133 dict_values: dict = values
134 rows = existing_config_line.values.get("rows", [])
135 cols = existing_config_line.values.get("cols", [])
136 new_rows = dict_values.get("rows", [])
137 new_cols = dict_values.get("cols", [])
138 values = {
139 "rows": list(set(rows + new_rows)),
140 "cols": list(set(cols + new_cols)),
141 }
142 else:
143 values = list(set(existing_config_line.values + (values or [])))
144 existing_config_line.write(
145 {
146 "values": values or [],
147 "selected_values": existing_config_line.selected_values,
148 "type": type,
149 "usage": usage,
150 "title": title or key,
151 "published": existing_config_line.published or publish,
152 }
153 )
154 else:
155 config_line_model.create(
156 {
157 "key": key,
158 "type": type,
159 "values": values or [],
160 "selected_values": default_values or [],
161 "usage": usage,
162 "title": title or key,
163 "upgrade_line_id": upgrade_line.id,
164 "ticket_id": ticket.id,
165 "published": publish,
166 }
167 )
168 return True
170 @api.model
171 def get_config_selected_values(self, upgrade_line: "UpgradeLine", ticket: "Ticket", key: str) -> Any:
172 """
173 Gets the selected values from the customer note configuration by key
175 :param key: The key to search in the customer note configuration
176 :return: The selected values if found, otherwise raises a UserError
177 """
178 config_line = self.env["saas.upgrade.client.config.line"].search(
179 [
180 ("ticket_id.id", "=", ticket.id),
181 ("upgrade_line_id.id", "=", upgrade_line.id),
182 ("key", "=", key),
183 ],
184 limit=1,
185 )
186 if not config_line:
187 raise UserError(_("No configuration line found for key: %s") % key)
188 return config_line.selected_values
190 @api.model
191 def add_config_boolean(
192 self, upgrade_line: "UpgradeLine", ticket: "Ticket", key: str, default_value: bool = False, title: str = ""
193 ) -> bool:
194 """
195 Defines a boolean configuration.
197 :param key: The key for the configuration line
198 :param default_value: The default boolean value
199 :param title: The title for the configuration line
200 :return: True if the configuration line was created or updated, False otherwise
201 """
202 if not isinstance(default_value, bool):
203 raise UserError(_("Default value for boolean configuration must be a boolean: %s") % default_value)
204 return self.create_config_line(
205 upgrade_line,
206 ticket,
207 key=key,
208 values=[True, False],
209 type="boolean",
210 default_values=default_value,
211 usage="single",
212 title=title,
213 )
215 @api.model
216 def add_config_selection(
217 self,
218 upgrade_line: "UpgradeLine",
219 ticket: "Ticket",
220 key: str,
221 values: list,
222 default_value: Any = None,
223 title: str = "",
224 ) -> bool:
225 """
226 Defines a single-selection configuration.
228 :param key: The key for the configuration line
229 :param values: List of possible values
230 :param default_value: The default selected value
231 :param title: The title for the configuration line
232 :return: True if the configuration line was created or updated, False otherwise
233 """
234 if default_value and default_value not in values:
235 raise UserError(_("Invalid default value for selection configuration: %s") % default_value)
236 return self.create_config_line(
237 upgrade_line,
238 ticket,
239 key=key,
240 type="selection",
241 values=values,
242 default_values=default_value,
243 usage="single",
244 title=title,
245 )
247 @api.model
248 def add_config_selection_multi(
249 self,
250 upgrade_line: "UpgradeLine",
251 ticket: "Ticket",
252 key: str,
253 values: list,
254 default_values: Any = None,
255 title: str = "",
256 ) -> bool:
257 """
258 Defines a multi-selection configuration.
260 :param key: The key for the configuration line
261 :param values: List of possible values
262 :param default_values: List of default selected values
263 :param title: The title for the configuration line
264 :return: True if the configuration line was created or updated, False otherwise
265 """
266 for value in default_values:
267 if value not in values:
268 raise UserError(_("Invalid default value for multi-selection configuration: %s") % value)
269 return self.create_config_line(
270 upgrade_line,
271 ticket,
272 key=key,
273 type="selection",
274 values=values,
275 default_values=default_values,
276 usage="multi",
277 title=title,
278 )
280 @api.model
281 def add_config_matrix(
282 self,
283 upgrade_line: "UpgradeLine",
284 ticket: "Ticket",
285 key: str,
286 rows: list,
287 cols: list,
288 default_values: dict | None = None,
289 title: str = "",
290 ) -> bool:
291 """
292 Defines a matrix-type configuration (like a survey matrix).
294 :param key: The key for the configuration line
295 :param rows: List of rows
296 :param cols: List of columns
297 :param default_values: A dictionary with row as key and column as value for default selections
298 :param title: The title for the configuration line
299 :return: True if the configuration line was created or updated, False otherwise
300 """
301 if not default_values:
302 default_values = {}
303 values = {"rows": rows, "cols": cols}
304 if default_values:
305 for row, col in default_values.items():
306 if row not in rows or col not in cols:
307 raise UserError(_("Invalid default value for matrix configuration: %s, %s") % (row, col))
308 return self.create_config_line(
309 upgrade_line,
310 ticket,
311 key=key,
312 type="selection",
313 values=values,
314 default_values=default_values,
315 usage="matrix",
316 title=title,
317 )
319 @api.model
320 def create_from_json(self, upgrade_line: "UpgradeLine", ticket: "Ticket", vals_list: list[dict]) -> None:
321 """
322 Creates config lines from a JSON-serializable list of dictionaries.
324 :param upgrade_line: The upgrade line to associate the config lines with
325 :param vals_list: List of dictionaries representing the config lines
326 """
327 for vals in vals_list:
328 key = vals.get("key")
329 type = vals.get("type")
330 title = vals.get("title", "")
331 match type:
332 case "boolean":
333 self.add_config_boolean(
334 upgrade_line,
335 ticket,
336 key=key,
337 default_value=vals.get("default_values", False),
338 title=title,
339 )
340 case "selection":
341 usage = vals.get("usage", "single")
342 if usage == "single":
343 self.add_config_selection(
344 upgrade_line,
345 ticket,
346 key=key,
347 values=vals.get("values", []),
348 default_value=vals.get("default_values", None),
349 title=title,
350 )
351 elif usage == "multi":
352 self.add_config_selection_multi(
353 upgrade_line,
354 ticket,
355 key=key,
356 values=vals.get("values", []),
357 default_values=vals.get("default_values", []),
358 title=title,
359 )
360 elif usage == "matrix":
361 values: dict = vals.get("values", {})
362 self.add_config_matrix(
363 upgrade_line,
364 ticket,
365 key=key,
366 rows=values.get("rows", []),
367 cols=values.get("cols", []),
368 default_values=vals.get("default_values", {}),
369 title=title,
370 )
371 else:
372 raise UserError(_("Invalid usage for selection configuration: %s") % usage)
373 case _:
374 raise UserError(_("Invalid configuration line type: %s") % type)
376 def to_json(self) -> dict:
377 """
378 Converts the config lines to a JSON-serializable dictionary.
380 :return: List of dictionaries representing the config lines
381 """
382 res = {}
383 for rec in self:
384 res[rec.key] = {
385 "type": rec.type,
386 "values": rec.values,
387 "selected_values": rec.selected_values,
388 "usage": rec.usage,
389 "title": rec.title,
390 }
391 return res