Coverage for ingadhoc-odoo-saas-adhoc / saas_provider_upgrade / models / saas_upgrade_client_data.py: 26%
222 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:05 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:05 +0000
1import datetime
2import logging
4from odoo import _, api, fields, models
5from odoo.exceptions import UserError, ValidationError
6from odoo.models import BaseModel
7from odoo.tools.safe_eval import safe_eval
9_logger = logging.getLogger(__name__)
11MIN_UPGRADE_VERSION = 16
12EPS = 1.0
13DIVISION_RATIO = 2
16class SaasUpgradeClientData(models.Model):
17 _name = "saas.upgrade.client.data"
18 _description = "Upgrade Client Data"
20 project_id = fields.Many2one(
21 "project.project",
22 )
23 ticket_ids = fields.One2many("helpdesk.ticket", "upgrade_client_data_id")
24 last_ticket_id = fields.Many2one("helpdesk.ticket")
25 service_level = fields.Selection(
26 [
27 ("standard", "Standard"),
28 ("advanced", "Advanced"),
29 ("premium", "Premium"),
30 ],
31 )
32 project_status = fields.Selection(
33 [
34 ("on_track", "Ok"),
35 ("at_risk", "Regular"),
36 ("off_track", "Mal"),
37 ("undefined", "No Definido"),
38 ],
39 )
40 upgrade_nps = fields.Char()
41 project_total_personalizations = fields.Integer(
42 help="Total number of personalization lines.",
43 )
44 project_interface_personalizations = fields.Integer(
45 help="Number of interface personalization lines.",
46 )
47 project_max_users = fields.Integer(
48 help="Maximum users contracted by the client.",
49 )
50 project_mig_count = fields.Integer(
51 help="Total migrations performed by the client",
52 )
53 project_complexity = fields.Integer(
54 help="Project complexity.",
55 )
56 supported_modules_code_count = fields.Integer(
57 help="Number of supported module lines",
58 )
59 unsupported_modules_code_count = fields.Integer(
60 help="Number of unsupported module lines",
61 )
62 companies_count = fields.Integer(help="Number of companies")
63 db_size = fields.Integer(help="Database size in GB.")
65 # Historical data
66 cns_qty = fields.Integer(
67 help="Customer Notes, weighted value.",
68 )
69 requests_qty = fields.Integer(
70 help="Total requests, weighted value.",
71 )
72 upgrade_tickets = fields.Integer(
73 help="Total tickets associated with Upgrade, weighted value.",
74 )
75 upgrade_tasks = fields.Integer(
76 help="Total tasks associated with Upgrade, weighted value.",
77 )
79 # Classification (higher number = higher difficulty)
80 project_classification = fields.Selection(
81 [
82 ("0", "0"),
83 ("1", "1"),
84 ("2", "2"),
85 ("3", "3"),
86 ("4", "4"),
87 ("5", "5"),
88 ],
89 default="0",
90 string="Classification",
91 help="Higher number implies higher migration difficulty",
92 )
93 manually_classified = fields.Boolean(
94 default=True,
95 help="Used to know if the record was manually classified or by the model.",
96 )
98 last_cron_updated_date = fields.Date(
99 help="Last update date from cron.",
100 default=fields.Date.today(),
101 )
102 cannot_update = fields.Boolean(
103 default=False,
104 help="Used to know if the record could not be updated.",
105 )
106 last_error = fields.Char(
107 help="Used to know the last error occurred.",
108 )
110 @api.constrains("project_id")
111 def _check_has_project(self):
112 for rec in self:
113 if not rec.project_id or not rec.project_id.main_database_id: 113 ↛ 114line 113 didn't jump to line 114 because the condition on line 113 was never true
114 raise ValidationError(_("Upgrade Client Data requires project with a main_database_id."))
116 def write(self, vals):
117 # Mark as manually classified if modified by non-superuser
118 if not self.env.is_superuser():
119 vals["manually_classified"] = True
120 res = super(SaasUpgradeClientData, self).write(vals)
121 return res
123 @api.model
124 def check_project_condition(self, project_id: BaseModel) -> BaseModel:
125 """
126 Validates if a project meets the conditions to create upgrade client data.
128 :param project_id: Project record to validate
129 :return: main_database_id recordset if valid, empty recordset otherwise
130 """
131 return project_id.main_database_id
133 def populate(self):
134 """
135 Populates the record with actual and historic data for upgrade analysis.
136 """
137 self.ensure_one()
138 self.cannot_update = False
139 self.last_error = False
140 self._populate_actual_data()
141 self._populate_historic_data()
143 def _populate_actual_data(self):
144 try:
145 self._set_ticket_ids()
146 self._set_service_level()
147 self._set_project_status()
148 self._set_upgrade_nps()
149 self._set_project_personalizations()
150 self._set_project_max_users()
151 self._set_project_mig_count()
152 self._set_project_complexity()
153 self._set_modules_count()
154 self._set_companies_count()
155 self._set_db_size()
156 except Exception as e:
157 self.cannot_update = True
158 self.last_error = str(e)
160 def _set_service_level(self):
161 service_level = self.project_id.service_level
162 if not service_level:
163 service_level = "standard"
164 self.service_level = service_level
166 def _set_project_status(self):
167 undefined_status = ["on_hold", "to_define", "done", "undefined"]
168 last_update_status = self.project_id.last_update_status
169 self.project_status = last_update_status if last_update_status not in undefined_status else "at_risk"
171 def _set_upgrade_nps(self):
172 project_update_type = self.env["project.update.type"].browse(1) # Customer Success type
173 properties_definition = project_update_type.update_properties_definition
174 property = list(filter(lambda x: x["string"] == "NPS Actua", properties_definition))[0]
175 property_id = property["name"]
176 selection_ids = property["selection"]
177 project_updates = self.env["project.update"].search(
178 [
179 ("project_id", "=", self.project_id.id),
180 ("project_update_type.id", "=", project_update_type.id),
181 ]
182 )
184 undefined_status = ["No aplica", "Sin medir", "No definido"]
185 undefined_value = "Neutro"
186 if not project_updates:
187 self.upgrade_nps = undefined_value
188 return
190 last_project_update = project_updates[0]
192 if not last_project_update.update_properties:
193 self.upgrade_nps = undefined_value
194 return
196 selection_id = last_project_update.update_properties[property_id]
197 selection = list(filter(lambda x: x[0] == selection_id, selection_ids))
199 if not selection:
200 self.upgrade_nps = undefined_value
201 return
203 selection = selection[0]
204 upgrade_nps = selection[1]
205 self.upgrade_nps = upgrade_nps if upgrade_nps not in undefined_status else undefined_value
207 def _set_project_personalizations(self):
208 db = self.project_id.main_database_id
209 db_client = None
210 if db.state != "active":
211 raise UserError(_("Client database is not active!"))
212 try:
213 db_client = db.get_client_cluster()
214 except Exception:
215 raise UserError(_("Cannot connect to client database!"))
217 db_client_dashboard = db_client.env["saas_client.dashboard"].create({})
218 personalizations_dict = safe_eval(db_client_dashboard.personalizations_amount_detail)
219 total_personalizations = 0
220 interface_personalizations = 0
221 for key, value in personalizations_dict.items():
222 total_personalizations += value
223 if not ("personalizations" in key or "p13n" in key):
224 interface_personalizations += value
226 self.project_total_personalizations = total_personalizations
227 self.project_interface_personalizations = interface_personalizations
229 def _set_project_max_users(self):
230 db = self.project_id.main_database_id
231 self.project_max_users = db.max_users
233 def _set_project_mig_count(self):
234 tickets = self.env["helpdesk.ticket"].search(
235 [("project_id", "=", self.project_id.id), ("upgrade_team", "=", True)]
236 )
237 self.project_mig_count = len(tickets)
239 def _set_project_complexity(self):
240 # Field created by Odoo Studio - may change
241 self.project_complexity = int(self.project_id.x_studio_complejidad_de_proyecto)
243 def _set_modules_count(self):
244 db = self.project_id.main_database_id
245 modules = db.module_ids
246 supported_count = 0
247 unsupported_count = 0
248 for module in modules:
249 support_type = module.support_type
250 line_qty = module.x_code_qty
251 supported_count += line_qty if support_type == "supported" else 0
252 unsupported_count += line_qty if support_type == "unsupported" else 0
254 self.supported_modules_code_count = supported_count
255 self.unsupported_modules_code_count = unsupported_count
257 def _set_companies_count(self):
258 db = self.project_id.main_database_id
259 companies_product_template_id = 202 # Keep track of this ID
260 count = db.active_subscription_id.order_line.filtered(
261 lambda x: x.product_template_id.id == companies_product_template_id
262 ).product_uom_qty
263 self.companies_count = int(count)
265 def _set_db_size(self):
266 self.db_size = self.project_id.main_database_id.pg_storage_size
268 def _populate_historic_data(self):
269 try:
270 self._set_cns_qty()
271 self._set_requests_qty()
272 self._set_upgrade_tickets_and_tasks()
273 except Exception as e:
274 self.cannot_update = True
275 self.last_error = str(e)
277 def _set_ticket_ids(self):
278 tickets = self.env["helpdesk.ticket"].search(
279 [
280 ("project_id", "=", self.project_id.id),
281 ("upgrade_team", "=", True),
282 ],
283 order="id",
284 )
286 if not tickets:
287 return
289 last_ticket_id = tickets[-1]
290 to_set_tickets = []
291 for ticket in tickets:
292 major_version = float(ticket.upgrade_type_id.target_odoo_version_group_id.major_version)
293 if major_version < MIN_UPGRADE_VERSION or ticket.ticket_upgrade_status == "upgraded":
294 continue
296 to_set_tickets += [(4, ticket.id, 0)]
298 self.write({"ticket_ids": to_set_tickets, "last_ticket_id": last_ticket_id})
299 return to_set_tickets
301 def _set_cns_qty(self):
302 eps = EPS
303 qty = 0
304 for ticket in self.ticket_ids:
305 cns = ticket.customer_note_ids
306 qty += int(eps * len(cns))
307 eps /= DIVISION_RATIO
309 self.cns_qty = qty
311 def _set_requests_qty(self):
312 eps = EPS
313 qty = 0
314 for ticket in self.ticket_ids:
315 requests = ticket.request_line_ids
316 qty += int(eps * len(requests))
317 eps /= DIVISION_RATIO
319 self.requests_qty = qty
321 def _set_upgrade_tickets_and_tasks(self):
322 eps = EPS
323 ticket_qty = task_qty = 0
324 for ticket in self.ticket_ids:
325 upgrade_tickets = ticket.child_ids
326 upgrade_tasks = ticket.task_ids
327 ticket_qty += int(eps * len(upgrade_tickets))
328 task_qty += int(eps * len(upgrade_tasks))
329 eps /= DIVISION_RATIO
331 self.upgrade_tickets = ticket_qty
332 self.upgrade_tasks = task_qty
334 @api.model
335 def _cron_create_client_data_records(self):
336 """
337 Cron that creates the client data records by project.
338 It process all the project, and if one of them does not have a 'data' record associated, it creates it
339 """
340 data_project_ids = self.search([]).mapped("project_id")
341 project_ids = self.env["project.project"].search([("id", "not in", data_project_ids.ids)])
342 new_data = [{"project_id": project.id} for project in project_ids if self.check_project_condition(project)]
343 if new_data:
344 self.create(new_data)
345 _logger.info(f"[ADV] create Client Data records: New records {len(new_data)}")
347 @api.model
348 def _cron_update_client_data_records(self):
349 """
350 Cron that updates the client data records by a batch_size.
351 """
352 all_client_data = self.search([("last_cron_updated_date", "<", datetime.date.today())])
353 if not all_client_data:
354 return
355 _logger.info("[ADV] Updating Client Data records: %s" % all_client_data.ids)
356 total_len = len(all_client_data)
357 self.env["ir.cron"]._commit_progress(remaining=total_len)
358 for data in all_client_data:
359 try:
360 data.populate()
361 data.last_cron_updated_date = datetime.date.today()
362 self.env["ir.cron"]._commit_progress(processed=1)
363 except Exception:
364 self.env.cr.rollback()