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 18:05 +0000

1############################################################################## 

2# For copyright and license notices, see __manifest__.py file in module root 

3# directory 

4############################################################################## 

5 

6from typing import TYPE_CHECKING, Any 

7 

8from odoo import _, api, fields, models 

9from odoo.exceptions import UserError 

10 

11if TYPE_CHECKING: 

12 from ..models.helpdesk_ticket import HelpdeskTicket as Ticket 

13 from ..models.saas_upgrade_line import SaasUpgradeLine as UpgradeLine 

14 

15 

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" 

22 

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 ) 

86 

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) 

92 

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 

106 

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 

169 

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 

174 

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 

189 

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. 

196 

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 ) 

214 

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. 

227 

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 ) 

246 

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. 

259 

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 ) 

279 

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). 

293 

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 ) 

318 

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. 

323 

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) 

375 

376 def to_json(self) -> dict: 

377 """ 

378 Converts the config lines to a JSON-serializable dictionary. 

379 

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