|
1 | 1 | # © 2023 Deltatech |
2 | 2 | # See README.rst file on addons root folder for license details |
| 3 | +import base64 |
| 4 | +import io |
| 5 | + |
| 6 | +import xlsxwriter |
3 | 7 |
|
4 | 8 | from odoo import _, api, fields, models |
| 9 | +from odoo.exceptions import UserError |
| 10 | + |
| 11 | +# from odoo.tools import date_utils |
5 | 12 |
|
6 | 13 |
|
7 | 14 | class BusinessProject(models.Model): |
@@ -52,12 +59,44 @@ class BusinessProject(models.Model): |
52 | 59 | ) |
53 | 60 | project_type = fields.Selection([("remote", "Remote"), ("local", "Local")], string="Project Type", default="remote") |
54 | 61 |
|
| 62 | + attachment_ids = fields.One2many( |
| 63 | + "ir.attachment", |
| 64 | + compute="_compute_attachment_ids", |
| 65 | + string="Main Attachments", |
| 66 | + help="Attachments that don't come from a message.", |
| 67 | + ) |
| 68 | + |
55 | 69 | @api.model |
56 | | - def create(self, vals): |
57 | | - if not vals.get("code", False): |
58 | | - vals["code"] = self.env["ir.sequence"].next_by_code(self._name) |
59 | | - result = super().create(vals) |
60 | | - return result |
| 70 | + def _get_attachments_search_domain(self, model, res_ids): |
| 71 | + return [("res_id", "in", res_ids), ("res_model", "=", model)] |
| 72 | + |
| 73 | + def _compute_attachment_ids(self): |
| 74 | + for project in self: |
| 75 | + domain = project._get_attachments_search_domain(project._name, project.ids) |
| 76 | + attachments = self.env["ir.attachment"].search(domain) |
| 77 | + attachments |= project.mapped("message_ids.attachment_ids") |
| 78 | + field_name = [ |
| 79 | + "process_ids", |
| 80 | + "process_ids.step_ids", |
| 81 | + "process_ids.test_ids", |
| 82 | + "process_ids.development_ids", |
| 83 | + "process_ids.step_ids.development_ids", |
| 84 | + "process_ids.test_ids.test_step_ids", |
| 85 | + "process_ids.test_ids.test_step_ids.issue_ids", |
| 86 | + ] |
| 87 | + for field in field_name: |
| 88 | + if "attachment_ids" in project.mapped(field): |
| 89 | + attachments |= project.mapped(field).mapped("attachment_ids") |
| 90 | + if "message_ids" in project.mapped(field): |
| 91 | + attachments |= project.mapped(field).mapped("message_ids.attachment_ids") |
| 92 | + project.attachment_ids = attachments |
| 93 | + |
| 94 | + @api.model_create_multi |
| 95 | + def create(self, vals_list): |
| 96 | + for vals in vals_list: |
| 97 | + if not vals.get("code", False): |
| 98 | + vals["code"] = self.env["ir.sequence"].next_by_code(self._name) |
| 99 | + return super().create(vals) |
61 | 100 |
|
62 | 101 | def name_get(self): |
63 | 102 | self.browse(self.ids).read(["name", "code"]) |
@@ -111,7 +150,7 @@ def get_attachment_domain(self): |
111 | 150 | def _compute_attached_docs_count(self): |
112 | 151 | for order in self: |
113 | 152 | domain = order.get_attachment_domain() |
114 | | - order.doc_count = self.env["ir.attachment"].search_count(domain) |
| 153 | + order.doc_count = self.env["ir.attachment"].sudo().search_count(domain) |
115 | 154 |
|
116 | 155 | def attachment_tree_view(self): |
117 | 156 | domain = self.get_attachment_domain() |
@@ -158,3 +197,113 @@ def calculate_total_project_duration(self): |
158 | 197 | [("project_id", "=", project.id), ("approved", "not in", ("draft", "rejected"))] |
159 | 198 | ): |
160 | 199 | project.total_project_duration += development.development_duration |
| 200 | + |
| 201 | + def float_to_time(self, float_hours): |
| 202 | + hours = int(float_hours) |
| 203 | + minutes = int((float_hours - hours) * 60) |
| 204 | + if minutes % 5 != 0: |
| 205 | + minutes = round(minutes / 5) * 5 |
| 206 | + if minutes < 10: |
| 207 | + minutes = f"0{minutes}" |
| 208 | + if hours < 10: |
| 209 | + hours = f"0{hours}" |
| 210 | + return f"{hours}:{minutes}" |
| 211 | + |
| 212 | + def generate_excel_report(self): |
| 213 | + output = io.BytesIO() |
| 214 | + workbook = xlsxwriter.Workbook(output, {"in_memory": True}) |
| 215 | + worksheet = workbook.add_worksheet() |
| 216 | + header_format = workbook.add_format({"bg_color": "#D0F0C0", "bold": True}) |
| 217 | + red_text_format = workbook.add_format({"font_color": "red"}) |
| 218 | + |
| 219 | + # Add headers |
| 220 | + headers = [ |
| 221 | + "Code", |
| 222 | + "Name", |
| 223 | + "Configuration Duration", |
| 224 | + "Training duration", |
| 225 | + "Testing duration", |
| 226 | + "Data Migration Duration", |
| 227 | + "Total Duration", |
| 228 | + ] |
| 229 | + for col_num, header in enumerate(headers): |
| 230 | + worksheet.write(0, col_num, header, header_format) |
| 231 | + worksheet.set_column(col_num, col_num, len(header) + 2) |
| 232 | + |
| 233 | + area_processes = {} |
| 234 | + for process in self.process_ids: |
| 235 | + if process.area_id: |
| 236 | + if process.area_id not in area_processes: |
| 237 | + area_processes[process.area_id] = [] |
| 238 | + area_processes[process.area_id].append(process) |
| 239 | + area_format = workbook.add_format({"bg_color": "#FFFF99", "bold": True, "align": "center"}) |
| 240 | + |
| 241 | + row = 1 |
| 242 | + configuration_duration = 0 |
| 243 | + instructing_duration = 0 |
| 244 | + data_migration_duration = 0 |
| 245 | + duration_for_completion = 0 |
| 246 | + duration_for_testing = 0 |
| 247 | + for area in sorted(area_processes.keys(), key=lambda a: a.name): |
| 248 | + processes = area_processes[area] |
| 249 | + processes.sort(key=lambda p: p.code) |
| 250 | + worksheet.merge_range(row, 0, row, 6, f"{area.name} - {len(processes)}", area_format) |
| 251 | + row += 1 |
| 252 | + for process in processes: |
| 253 | + format_to_use = red_text_format if process.duration_for_completion == 0 else None |
| 254 | + worksheet.write(row, 0, process.code, format_to_use) |
| 255 | + worksheet.write(row, 1, process.name, format_to_use) |
| 256 | + worksheet.write(row, 2, self.float_to_time(process.configuration_duration), format_to_use) |
| 257 | + configuration_duration += process.configuration_duration |
| 258 | + worksheet.write(row, 3, self.float_to_time(process.instructing_duration), format_to_use) |
| 259 | + instructing_duration += process.instructing_duration |
| 260 | + worksheet.write(row, 4, self.float_to_time(process.data_migration_duration), format_to_use) |
| 261 | + data_migration_duration += process.data_migration_duration |
| 262 | + worksheet.write(row, 5, self.float_to_time(process.testing_duration), format_to_use) |
| 263 | + duration_for_testing += process.testing_duration |
| 264 | + worksheet.write(row, 6, self.float_to_time(process.duration_for_completion), format_to_use) |
| 265 | + duration_for_completion += process.duration_for_completion |
| 266 | + row += 1 |
| 267 | + worksheet.write(row, 1, "Total", header_format) |
| 268 | + worksheet.write(row, 2, self.float_to_time(configuration_duration), header_format) |
| 269 | + worksheet.write(row, 3, self.float_to_time(instructing_duration), header_format) |
| 270 | + worksheet.write(row, 4, self.float_to_time(data_migration_duration), header_format) |
| 271 | + worksheet.write(row, 5, self.float_to_time(duration_for_testing), header_format) |
| 272 | + worksheet.write(row, 6, self.float_to_time(duration_for_completion), header_format) |
| 273 | + # for project in self: |
| 274 | + # worksheet.write(row, 0, project.code) |
| 275 | + # worksheet.write(row, 1, project.name) |
| 276 | + # worksheet.write(row, 2, project.customer_id.name) |
| 277 | + # worksheet.write(row, 3, project.state) |
| 278 | + # worksheet.write(row, 4, project.date_start and project.date_start.strftime('%Y-%m-%d') or '') |
| 279 | + # worksheet.write(row, 5, project.date_go_live and project.date_go_live.strftime('%Y-%m-%d') or '') |
| 280 | + # row += 1 |
| 281 | + # worksheet.autofit() |
| 282 | + |
| 283 | + workbook.close() |
| 284 | + output.seek(0) |
| 285 | + return output.read() |
| 286 | + |
| 287 | + def action_download_excel_report(self): |
| 288 | + active_id = self.env.context.get("active_id") |
| 289 | + if not active_id: |
| 290 | + raise UserError(_("No active project found.")) |
| 291 | + |
| 292 | + project = self.browse(active_id) |
| 293 | + excel_data = project.generate_excel_report() |
| 294 | + |
| 295 | + attachment = self.env["ir.attachment"].create( |
| 296 | + { |
| 297 | + "name": "Project_Report.xlsx", |
| 298 | + "type": "binary", |
| 299 | + "datas": base64.b64encode(excel_data), |
| 300 | + "store_fname": "Project_Report.xlsx", |
| 301 | + "mimetype": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", |
| 302 | + } |
| 303 | + ) |
| 304 | + |
| 305 | + return { |
| 306 | + "type": "ir.actions.act_url", |
| 307 | + "url": f"/web/content/{attachment.id}?download=true", |
| 308 | + "target": "self", |
| 309 | + } |
0 commit comments