From 73099f6e26623000080585716ca87d8aca1bbe49 Mon Sep 17 00:00:00 2001 From: Juliana <juliana@le-filament.com> Date: Mon, 16 Aug 2021 14:57:28 +0200 Subject: [PATCH] [MIG] Migration 14.0 --- __init__.py | 1 - __manifest__.py | 2 +- models/__init__.py | 3 +- models/__pycache__/__init__.cpython-36.pyc | Bin 301 -> 0 bytes .../product_template.cpython-36.pyc | Bin 680 -> 0 bytes models/__pycache__/res_company.cpython-36.pyc | Bin 483 -> 0 bytes .../res_config_settings.cpython-36.pyc | Bin 556 -> 0 bytes models/__pycache__/sale_order.cpython-36.pyc | Bin 7121 -> 0 bytes models/project_overview.py | 161 ++++++++++++++++++ models/sale_order.py | 7 +- 10 files changed, 165 insertions(+), 9 deletions(-) delete mode 100644 models/__pycache__/__init__.cpython-36.pyc delete mode 100644 models/__pycache__/product_template.cpython-36.pyc delete mode 100644 models/__pycache__/res_company.cpython-36.pyc delete mode 100644 models/__pycache__/res_config_settings.cpython-36.pyc delete mode 100644 models/__pycache__/sale_order.cpython-36.pyc create mode 100644 models/project_overview.py diff --git a/__init__.py b/__init__.py index 09f29e1..db3c96a 100644 --- a/__init__.py +++ b/__init__.py @@ -2,4 +2,3 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import models -from . import controllers diff --git a/__manifest__.py b/__manifest__.py index d212f92..fea4a3c 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -3,7 +3,7 @@ 'summary': """Filament - Lien entre commandes et projets""", 'author': "Le Filament", 'website': "https://www.le-filament.com", - 'version': '12.0.1.0.1', + 'version': '13.0.1.0.1', 'license': "AGPL-3", 'category': 'Sale Management', 'depends': ['sale_timesheet', 'project'], diff --git a/models/__init__.py b/models/__init__.py index a3fa0c8..eaa1c10 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,7 +1,8 @@ # Copyright 2019 Le Filament (<http://www.le-filament.com>) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import sale_order from . import res_company from . import res_config_settings from . import product_template +from . import project_overview +from . import sale_order diff --git a/models/__pycache__/__init__.cpython-36.pyc b/models/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index f3dd9c2928ea769333c10a98dcae3dab63e3dfc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcmXr!<>hjpaXRiB0|Ucj1|-15z`)?Zz`#&!!oa|g!jQt4!;s4u#mER^GvzSkGDR_g z*~~c%xy(__U^Yt*LoQ1c3nN1cYcPW*+e=0U1_n*WTU^D7IjQmaMJcI8nvA!&i&Bf@ zlk;;667woG8E*+gxOr)r>G8#>B_)}8>BX9iw*(4`@>5EaOX5pXa|?13OH%zbS#L2E zfm~I@%)r1<!~!B%85kH=G8D0c*dXGUi+)CaZmNEMN`5|=Ff`InE-fy}&(%*%Ny*PE z*3Zez%Z>-Tr=Tc5D>b=9KQ})mHK$lVK0Y%qvm`!Vub}c4hfQvNN@-529mtYmHU<U; I9!3xZ0P+-15C8xG diff --git a/models/__pycache__/product_template.cpython-36.pyc b/models/__pycache__/product_template.cpython-36.pyc deleted file mode 100644 index b540924c439385ccaf2b7d8c3adedcaa94b79c0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 680 zcmXr!<>e}yc{=VVBLl-@1|-13z`)?Zz`#(f#lXOj!jQt4!;s4u#hA+!#gxk&#mvay z&XB^C!ra1;!ko&O#nQ|i#hStr%%I8o5@d)b^DVaA{FKz3;#+KKnW;G`#kZIf3o?^I zaxlz=Y(pwT6k`fQ6jKUg6mu#|7Awf?6qXd$6t)!h6pj?m6s}&5D7F;tU<OT|Tl@h< z`6;EzB_XM~1v!Z&sZ|065COdsn22COQGQlxa*19^VsW-!No7H56sv1VVnOOHj-u4! zlA_Gyl3N`4dC3`xdFiPkw?yNMQWI0+(~I&;3*w7Q64O)TGgFGIl%h4%p$38Y>N*PQ zw(2?x2qDc_H%<0iY#;;k(m@8Mq~@fSq}~!JDN0PvjxWnB&P>Y8$t<b7#hRIyl3H<# z2VzBPML}X-$}P5({M^LMyjv{E`304Jn(VhY;^XrYb5rBvZ*j%P=jNxB=788d@$rSF zi8)Xij`+;HjMSpck|JgX28LT4zKMC2M)`TEx5Q!Y%E`>jPECRPV<kh87y|=@_~oOY zk)NBYpP!PS4<-za^pi`AOY(E|6H`+1^NRIBn&XQTb5i4>#_2;MQXd+|@vtz~E2u1D zWnf@n2c;5FWO6XFFmW-mFfuWM#r-tdZn1#uDPjWI4svV}C{^8J^#!F2uo{HJAS`jP Rk8B`5w*#pz25A;y1^^;&$S(i@ diff --git a/models/__pycache__/res_company.cpython-36.pyc b/models/__pycache__/res_company.cpython-36.pyc deleted file mode 100644 index 11f9a18f35dc764f26ec0cb4c2b3d96049554cc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 483 zcmXr!<>k6Bcs9<Ek%8ec0}^0iU|?`yU|=ZLVqjoMVMt-jVaR2SV$5ZVV#;NXVrFD; zXGmd6VQyhaVNPYtVrgcMVohNQX3%7P2{J^J`4(GleoAUi@h!Hr%+#Ee;#<s#1)0ep zIT(i9AjiPKkjfCnn8Fanl)@OroXV2L3Nkx|rI#s+Erm6hL6hwkyK{bSL1JEI6?ai; zv0gHS6Y|oVfq_Aj=@v&(YGG++QEJL9_LS7L#L}D+KTY;q9P#maiMgrq@wd3*<8$*< zN^?MLp7{8}(!?C73`cxsUPfwBW=Rn<0|Ub?cF(+$)b!M%TRbI+r4{iR`9+DDMX4(p ziuf5AAjB_s{fzwFRQ>#v{CqHBXr!N9T3nK!tDl&XlAl+spOcxF9bcT7lNw)8l%JKF zT%r#N3jLzg;&`ab^$IGBSQ!`?*g!$d!N9=4!N|hI!pOu37WLC)yTt;spoj@%Ey!g> ipy0a2>I(`~uo{GmAS?lp^EqrF-n0X$E(U29VFCa@ntO@> diff --git a/models/__pycache__/res_config_settings.cpython-36.pyc b/models/__pycache__/res_config_settings.cpython-36.pyc deleted file mode 100644 index 4c248db163efc306fe2c0f5125066e7b4d418b4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 556 zcmXr!<>k6Bcs8zvk%8ec0}^0iU|?`yU|=ZLVqjoMVMt-jVaR2SV$5ZVV#;NXVrFD; zXGmd6VQyhaVNPYtVrgcMVohNQX3%7P2{J^J`4(GleoAUi@h!Hr%+#Ee;#<s#1)0ep zIT&Vz*`UC{z>vxi#hAhn#gxJr#hl8L#R@Vzg(ZcxmpO_pg)Nvtll_)pP-?MreqLH; zdT?q<NoHPpag}gUYO!82SWK@NCdd<#SX!ZwkzbUUS(I8Oo}8askeF8)pP8apl2}?1 z50!A!WV*#(l$w)RlA3ahqbM~oB|k5x(od897Ds%1USe))eEco0`1suXl+qj!n<qZL zurx6TD#H<<nU|4Tlvz^5%)r2Ki`_G?BsD#?=oSyM6)PEv_!t-<#4msSjQreG{rr^t zd@x~Xq@P?`T#}!wpO})8pI5A(lbM$tU!0he8edS9pOu<iq7Ml`{i4+3c!(e4VSdyr zs4M~n9~&rwxEL51I2c)&SQwcYnHa(1ewu8zSU^@4F@YQaa$^xFLT~Yf6eZ>rXQt+r e_<~{wtQg@)2#X)&BMuvg&+S0kib1A`FaZGUgP2<Y diff --git a/models/__pycache__/sale_order.cpython-36.pyc b/models/__pycache__/sale_order.cpython-36.pyc deleted file mode 100644 index d030919e351b2b5206f75ccee555db1128fc88cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7121 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 Y-r}%<jL+DCTwDxl6N@nNFbmlM0EAL5&Hw-a diff --git a/models/project_overview.py b/models/project_overview.py new file mode 100644 index 0000000..fe1dcde --- /dev/null +++ b/models/project_overview.py @@ -0,0 +1,161 @@ +# Copyright 2021 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models, api, _ + + +class Project(models.Model): + _inherit = 'project.project' + + # ------------------------------------------------------ + # Fields declaration + # ------------------------------------------------------ + + # ------------------------------------------------------ + # SQL Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Default methods + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Computed fields / Search Fields + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Onchange / Constraints + # ------------------------------------------------------ + + # ------------------------------------------------------ + # CRUD methods (ORM overrides) + # ------------------------------------------------------ + def _table_get_line_values(self): + """ return the header and the rows informations of the table """ + if not self: + return False + + uom_hour = self.env.ref('uom.product_uom_hour') + + # build SQL query and fetch raw data + query, query_params = self._table_rows_sql_query() + self.env.cr.execute(query, query_params) + raw_data = self.env.cr.dictfetchall() + rows_employee = self._table_rows_get_employee_lines(raw_data) + default_row_vals = self._table_row_default() + + empty_line_ids, empty_order_ids = self._table_get_empty_so_lines() + + # extract row labels + sale_line_ids = set() + sale_order_ids = set() + for key_tuple, row in rows_employee.items(): + if row[0]['sale_line_id']: + sale_line_ids.add(row[0]['sale_line_id']) + if row[0]['sale_order_id']: + sale_order_ids.add(row[0]['sale_order_id']) + + sale_orders = self.env['sale.order'].sudo().browse(sale_order_ids | empty_order_ids) + sale_order_lines = self.env['sale.order.line'].sudo().browse(sale_line_ids | empty_line_ids) + map_so_names = {so.id: so.name for so in sale_orders} + map_so_cancel = {so.id: so.state == 'cancel' for so in sale_orders} + map_sol = {sol.id: sol for sol in sale_order_lines} + map_sol_names = {sol.id: sol.name.split('\n')[0] if sol.name else _('No Sales Order Line') for sol in + sale_order_lines} + map_sol_so = {sol.id: sol.order_id.id for sol in sale_order_lines} + + rows_sale_line = {} # (so, sol) -> [INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted] + for sale_line_id in empty_line_ids: # add service SO line having no timesheet + sale_line_row_key = (map_sol_so.get(sale_line_id), sale_line_id) + sale_line = map_sol.get(sale_line_id) + is_milestone = sale_line.product_id.invoice_policy == 'delivery' and sale_line.product_id.service_type == 'manual' if sale_line else False + rows_sale_line[sale_line_row_key] = [{'label': map_sol_names.get(sale_line_id, _('No Sales Order Line')), + 'res_id': sale_line_id, 'res_model': 'sale.order.line', + 'type': 'sale_order_line', + 'is_milestone': is_milestone}] + default_row_vals[:] + if not is_milestone: + # ***** Modif Filament ***** + rows_sale_line[sale_line_row_key][ + -2] = sale_line.product_uom_qty * sale_line.price_unit / sale_line.order_id.taux_horaire if sale_line else 0.0 + # rows_sale_line[sale_line_row_key][-2] = sale_line.product_uom._compute_quantity( + # sale_line.product_uom_qty, uom_hour, raise_if_failure=False) if sale_line else 0.0 + + for row_key, row_employee in rows_employee.items(): + sale_line_id = row_key[1] + sale_order_id = row_key[0] + # sale line row + sale_line_row_key = (sale_order_id, sale_line_id) + if sale_line_row_key not in rows_sale_line: + sale_line = map_sol.get(sale_line_id, self.env['sale.order.line']) + is_milestone = sale_line.product_id.invoice_policy == 'delivery' and sale_line.product_id.service_type == 'manual' if sale_line else False + rows_sale_line[sale_line_row_key] = [{'label': map_sol_names.get(sale_line.id) if sale_line else _( + 'No Sales Order Line'), 'res_id': sale_line_id, 'res_model': 'sale.order.line', + 'type': 'sale_order_line', + 'is_milestone': is_milestone}] + default_row_vals[ + :] # INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted + if not is_milestone: + # ***** Modif Filament ***** + rows_sale_line[sale_line_row_key][ + -2] = sale_line.product_uom_qty * sale_line.price_unit / sale_line.order_id.taux_horaire if sale_line else 0.0 + # rows_sale_line[sale_line_row_key][-2] = sale_line.product_uom._compute_quantity( + # sale_line.product_uom_qty, uom_hour, raise_if_failure=False) if sale_line else 0.0 + + for index in range(len(rows_employee[row_key])): + if index != 0: + rows_sale_line[sale_line_row_key][index] += rows_employee[row_key][index] + if not rows_sale_line[sale_line_row_key][0].get('is_milestone'): + rows_sale_line[sale_line_row_key][-1] = rows_sale_line[sale_line_row_key][-2] - \ + rows_sale_line[sale_line_row_key][5] + else: + rows_sale_line[sale_line_row_key][-1] = 0 + + rows_sale_order = {} # so -> [INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted] + rows_sale_order_done_sold = {key: dict(sold=0.0, done=0.0) for key in + set(map_sol_so.values()) | set([None])} # SO id -> {'sold':0.0, 'done': 0.0} + for row_key, row_sale_line in rows_sale_line.items(): + sale_order_id = row_key[0] + # sale order row + if sale_order_id not in rows_sale_order: + rows_sale_order[sale_order_id] = [{'label': map_so_names.get(sale_order_id, _('No Sales Order')), + 'canceled': map_so_cancel.get(sale_order_id, False), + 'res_id': sale_order_id, 'res_model': 'sale.order', + 'type': 'sale_order'}] + default_row_vals[ + :] # INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted + + for index in range(len(rows_sale_line[row_key])): + if index != 0: + rows_sale_order[sale_order_id][index] += rows_sale_line[row_key][index] + + # do not sum the milestone SO line for sold and done (for remaining computation) + if not rows_sale_line[row_key][0].get('is_milestone'): + rows_sale_order_done_sold[sale_order_id]['sold'] += rows_sale_line[row_key][-2] + rows_sale_order_done_sold[sale_order_id]['done'] += rows_sale_line[row_key][5] + + # remaining computation of SO row, as Sold - Done (timesheet total) + for sale_order_id, done_sold_vals in rows_sale_order_done_sold.items(): + if sale_order_id in rows_sale_order: + rows_sale_order[sale_order_id][-1] = done_sold_vals['sold'] - done_sold_vals['done'] + + # group rows SO, SOL and their related employee rows. + timesheet_forecast_table_rows = [] + for sale_order_id, sale_order_row in rows_sale_order.items(): + timesheet_forecast_table_rows.append(sale_order_row) + for sale_line_row_key, sale_line_row in rows_sale_line.items(): + if sale_order_id == sale_line_row_key[0]: + timesheet_forecast_table_rows.append(sale_line_row) + for employee_row_key, employee_row in rows_employee.items(): + if sale_order_id == employee_row_key[0] and sale_line_row_key[1] == employee_row_key[1]: + timesheet_forecast_table_rows.append(employee_row) + + # complete table data + return { + 'header': self._table_header(), + 'rows': timesheet_forecast_table_rows + } + # ------------------------------------------------------ + # Actions + # ------------------------------------------------------ + + # ------------------------------------------------------ + # Business methods + # ------------------------------------------------------ diff --git a/models/sale_order.py b/models/sale_order.py index 24323b3..8c78f1b 100644 --- a/models/sale_order.py +++ b/models/sale_order.py @@ -21,7 +21,7 @@ class SaleOrder(models.Model): taux_horaire = fields.Integer( 'Taux horaire', - default=lambda self: self.env.user.company_id.taux_horaire) + default=lambda self: self.env.company.taux_horaire) @api.onchange("partner_id", "order_line") def _project_name_to_create(self): @@ -49,7 +49,6 @@ class SaleOrder(models.Model): if so_line_new_project_with_tasks and self.partner_id: self.project_name_to_create = self.partner_id.name + str(' - ') - @api.multi def action_confirm(self): # on différencie so_line_new_project (dans _timesheet_service_generation) de so_line_new_project_with_tasks # car on laisse le fonctionnement natif pour les articles où on crée le projet sans les tâches @@ -74,7 +73,6 @@ class SaleOrder(models.Model): class SaleOrderLine(models.Model): _inherit = "sale.order.line" - @api.multi def _convert_qty_company_hours(self): """ Reprise de la fonction native pour changer le mode de calcul des heures planifiées dans timesheet """ @@ -87,7 +85,6 @@ class SaleOrderLine(models.Model): planned_hours = (self.product_uom_qty * self.price_unit) / taux_horaire return planned_hours - @api.multi def _timesheet_create_task(self, project): """ Pour gérer le stage_id et le nom des tâches pour les projets maintenance et support """ @@ -103,7 +100,6 @@ class SaleOrderLine(models.Model): task.write({'name': client_name}) return task - @api.multi def _timesheet_create_project(self, name_project): """ Genère le projet de la même manière mais lui donne le nom choisi """ @@ -111,7 +107,6 @@ class SaleOrderLine(models.Model): project.name = name_project # on réécrit le nom du projet return project - @api.multi def _timesheet_service_generation(self): """ Réécriture de la fonction native de manière quasi-identique mais qui permet d'associer chaque ligne du devis à un projet -- GitLab