From b2a42e4ce8dbe5f2abaa977952cafebc2f3f8278 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Fri, 9 Jun 2023 10:24:06 -0400 Subject: [PATCH 01/20] bemade_fsm: Initial commit as submodule. --- __init__.py | 1 + __manifest__.py | 22 ++++ __pycache__/__init__.cpython-310.pyc | Bin 0 -> 185 bytes models/__init__.py | 3 + models/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 271 bytes .../product_template.cpython-310.pyc | Bin 0 -> 561 bytes models/__pycache__/sale_order.cpython-310.pyc | Bin 0 -> 3727 bytes .../__pycache__/task_template.cpython-310.pyc | Bin 0 -> 1738 bytes models/product_template.py | 7 ++ models/sale_order.py | 61 +++++++++++ models/task_template.py | 31 ++++++ security/ir.model.access.csv | 3 + static/tests/tours/task_template_tour.js | 47 ++++++++ tests/__init__.py | 1 + tests/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 203 bytes .../test_task_template.cpython-310.pyc | Bin 0 -> 3685 bytes tests/test_task_template.py | 97 +++++++++++++++++ views/product_views.xml | 17 +++ views/task_template_views.xml | 101 ++++++++++++++++++ 19 files changed, 391 insertions(+) create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 __pycache__/__init__.cpython-310.pyc create mode 100644 models/__init__.py create mode 100644 models/__pycache__/__init__.cpython-310.pyc create mode 100644 models/__pycache__/product_template.cpython-310.pyc create mode 100644 models/__pycache__/sale_order.cpython-310.pyc create mode 100644 models/__pycache__/task_template.cpython-310.pyc create mode 100644 models/product_template.py create mode 100644 models/sale_order.py create mode 100644 models/task_template.py create mode 100644 security/ir.model.access.csv create mode 100644 static/tests/tours/task_template_tour.js create mode 100644 tests/__init__.py create mode 100644 tests/__pycache__/__init__.cpython-310.pyc create mode 100644 tests/__pycache__/test_task_template.cpython-310.pyc create mode 100644 tests/test_task_template.py create mode 100644 views/product_views.xml create mode 100644 views/task_template_views.xml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..a49a539 --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,22 @@ +{ + 'name': 'Durpro Field Service Management', + 'version': '15.0.1.0.0', + 'summary': 'Adds functionality necessary for managing field service operations at Durpro.', + 'description': 'Adds functionality necessary for managing field service operations at Durpro.', + 'category': 'Services/Field Service', + 'author': 'Bemade Inc.', + 'website': 'http://www.bemade.org', + 'license': 'LGPL-3', + 'depends': ['project', 'stock', 'sale', 'sale_project', 'sale_stock'], + 'data': ['views/task_template_views.xml', + 'security/ir.model.access.csv', + 'views/product_views.xml', + ], + 'assets': { + 'web.assets_tests': [ + 'durpro_fsm/static/tests/tours/task_template_tour.js', + ], + }, + 'installable': True, + 'auto_install': False +} diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5c96a5891ed01033b6e9eb2c68aa3f783f873dc GIT binary patch literal 185 zcmd1j<>g`k0@ja>DWX97F^Gc(44TX@8G*u@ zjJMcw^HWlDiv2X1ZZQ;r^sHnkVgXWM;+L&{XmM&$v3_n!X;ErHezAUWQL?^EX;DE@ zzOJFEo`HU1N=klSu|7~5C={PooU0!npP83g5+AQuPO=-fIcU)|o)lrN zb}e%3bg`VxRR!R0r1+XGeI}=z2V+TTeYstN*0bi`4OVQn)~w^26oW@%B**d#SFA#k literal 0 HcmV?d00001 diff --git a/models/__pycache__/product_template.cpython-310.pyc b/models/__pycache__/product_template.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcb03c950de54401f9155e2ab158e26c021b6eee GIT binary patch literal 561 zcmYjOu};G<5Ov}tM2ON3g}G~yXaI=~p$>^q5J31!Sp*T*-mu+EC85d8Oq6wz8u8exm%zxZkR3$kA~PeC>id7VGedW)KsF~v?X=T%w?&g~}WI6_l6dyDgjCM|v=G0&?zp>m^qq;KO% zS}l%bC0Nh(<||4(hc}|OF{M@C7s<62N+%^WO4L#(T4l*uqmcOi;qmAoNdaV~6AUB7 zZ*-X~|1kL-_OJ4&UMTm4Zdp1)6_dd0`zV!Z literal 0 HcmV?d00001 diff --git a/models/__pycache__/sale_order.cpython-310.pyc b/models/__pycache__/sale_order.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f6b12a27e37324ba3e73b55e632c11c82ccb915 GIT binary patch literal 3727 zcmd^COOG4J5$>KBAK}`{T9zYQ4mx%Oi-1M?0bhjGB7g-e2S+lz&V`6D=$&qoL(NOw z!(HrR_Tm+=^uaI;_!b19fn4;L`kIq}f_;&zdWI`f*%%6tLjVskJ^ilg`l`Na=384` z0?#*hzZBnX5%Mo=tUn$!{sgc70180_O-M>dG<8N!>WY-_kVzO@G-$SmWj|NNOK_*ZCqZ&hK8o~ah|3;6A{i^f83^_8B`=0Q9(wI zpd{6-v$i>&3SX3=A473Q z&ghwQ=8io9dp~Y}Ma~?M;tCJybm7d%jDF=VT>WP0oRXzm(eLSrQ#pHNK`Od%g@5Xv zI16X#iJ)?3ehlN3l!0iS;_BASw`JSz)E>K6$cYQ{I+ZKhU&Bn?so&w(C?V{w%%tL_ zWF^-R**I6MoJux{4`jx4&hRkrGO!rd9OV? zpc!Iu#tx@(G__)^Og+u#iD3JZMM{Du;$jI(mUER^HP`n?ZJTv=th=%L{()2~7I3

u{1z=PTK;fdH z1-(j^^n0>&Di`4LCgj9lxHI<-d2s0@SP(nMxk?sp<-wYu@|Ru}AaIXft-Pf#+%i~# z8w>w2sr)1-T#6WxXULz(UL*n+{-h` zIBpwjFk_Nz*bt*8;&Ye^2L`W%1rQ5i8K+WDr7XkRi$gR{SSSe*CBp-r%%#53DA3cZVnYz7g$vWvy4F|s|Igj3@b+|ZuXO4~Tz7_M-7Hs_h3e%rV* zJ20M}i`)b?P^P^KViWM9keM+4p^8hX0#M1g;P?HOX|1ft3cF?b2IPY3wnf6WWIcw;f-J$8fR^3LT~a|I69QfT9c~feTMm5Pe|Qbm(wSSl0ps3P zg5?fm%ls|Ko*uM(XwjKJ3!om@ddt>ZP&mdEPjX6JaAIorwU1OE#BXR<{#kIFG11mFuF}&qCNOQ zwDbQm+Fv9&Zj#VFGy^2iSR6n20`suVUsK3j`x0)nbsz~FkGI$kXHzA|x8G)927WV_ zH*G?=jrpLy%%2a$;`Vj1dtL7g`7XP0UH2gmdHcCI>UT{S7+B1s65+UHrLUM6dwe}F zq%v17hD2RP>!I0|4~1#lGz9A`D5eLO1I;HggnU*41lHCThu7_FD=pQNC)9MpFiHTq zFjQ~gc9wkKhZ-ge`?qGh)w|sOt%p!_dOu#Gj^j}0>9*rM^`7=zfai}d0#83M?Jz{w z3`5fmLonGK(r0LTVfbjylV+qH#@SRtwo<5r*=n?lWRie}=UH2QbgnBBw*8BQ3b%R6R;NP$FZegl&R*Nv3PCfN_D-$kE?ze@F1|*tI6njH CF;hqY literal 0 HcmV?d00001 diff --git a/models/__pycache__/task_template.cpython-310.pyc b/models/__pycache__/task_template.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35d4aa6dd291e6794bdf0174d3700ab91e6b7ad9 GIT binary patch literal 1738 zcma)7OLN>r5Z36m+Fh-^e#QZkh}`zTE~F}Op{P(c1v$i|%I2brN~mXTEqY}}YsV@s zRC4t{;KDKgfnPFLPC0T77rI9hxXgjf*0j2Ndb(%2zn(28laYbL`STa}%`uEWNVqv{ z6rSLeb0lB@%Z!|bEH^_lw?ZqoLz@}j8eoF;+5n5QmGjC9U9eA#ne!I+7&BYBc?mp| z%3Y>B1F5XIN)?L|TE0146rSLeza!yB$iN6qV4=nBmD7fJ#X^@4Rvy^kfO~C*LzG4^ z0PmWGW8{4p!U)FTUt8e>f^98Nwfzq4!k(75^g4rm^k#pdw685cgpc4Jcj5lE9o~Tl5WF_R1Na!P{!sa+vHaVv`Nj}F zTE2U__(5_Z7dfm2uS&U)B3T@*1xEekv&ZvK7coFt$OSH=Dq6~X(UxRE=ZX6B%&RLk zjgqwxyr`ot)_~4qBJs@T-pxzRZ(BAaS`JRrI^(B!WLMTAEzXoD_=|NaIH>Uvmx)NL zIxUMC6PxI@8451vYdU_jb;OtPI;(@PrA*HX&Smq(H<-cl3fUlTl@QcrK*yFrA~+@& zf~6?)pgvD!@Xt+VruMGvbDmYrm||41s0%KdX&>cqE$cEz&F8%)=`_Kz6#pNi+3c&@ zeVfhR(et+4&EsCtS-huXzjs{6XR`5oC#NJm>LprxPk+15V=8IT)~7?Caxo*QZMII< zKT<-29bx^j}`i95uX1`DH|C zR)i$YWy&Qs1!dP)6{of-YW}juYniC@5ZYQ6^!DivYRLpeHEqL0WyOn#(z1_QNo{7D zCbJn$_pe*6aC^`3>@J4Uxr-t-is)GDj3e(y(TjDQQC3lM+Vs*Zs~p{uVgq?$k-|Pa zkA8a&0coN=K%*;QJNx+-Hb#X~w7CE^C5lnE*(T|wSQ;u$ux%@NzMd^YWV8~F9v zHuFtwdpsK`yM(e7^sea33feF59|-gCjQ%aO;f+8#t}c-GWt+^e2yc@19T@x8-(lYV A9{>OV literal 0 HcmV?d00001 diff --git a/models/product_template.py b/models/product_template.py new file mode 100644 index 0000000..c7ecb0a --- /dev/null +++ b/models/product_template.py @@ -0,0 +1,7 @@ +from odoo import fields, models, api + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + task_template_id = fields.Many2one("project.task.template", string="Task Template", ondelete='restrict') diff --git a/models/sale_order.py b/models/sale_order.py new file mode 100644 index 0000000..6cbc9cb --- /dev/null +++ b/models/sale_order.py @@ -0,0 +1,61 @@ +from odoo import fields, models, api, _, Command + +class SaleOrderLine(models.Model): + _inherit = 'sale.order.line' + + def _timesheet_create_task(self, project): + """ Generate task for the given so line, and link it. + :param project: record of project.project in which the task should be created + :return task: record of the created task + + Override to add the logic needed to implement task templates.""" + + def _create_task_from_template(project, template, parent): + """ Recursively generates the task and any subtasks from a project.task.template. Subtasks are left without + a specific project_id so that only the top-level tasks shows up on the project task list/kanban views. + + :param project: project.project record to set on the task's project_id field. + Pass the project.project model to leave task project_id blank. DO NOT pass False or None as this will + cause an error in _timesheet_create_task_prepare_values(project). + :param template: project.task.template to use to create the task. + :param parent: project.task to set as the parent to this task. + """ + values = _timesheet_create_task_prepare_values_from_template(project, template, parent) + task = self.env['project.task'].sudo().create(values) + subtasks = [] + for subtask in tmpl.subtasks: + # Recurse, but pass the Project model so that no project_id gets set on the subtasks + subtask = _create_task_from_template(self.env['project.project'], tmpl, task) + subtasks.append(subtask) + task.write({'child_ids': [Command.set([t.id for t in subtasks])]}) + return task + + def _timesheet_create_task_prepare_values_from_template(project, template, parent): + """ Copies the values from a project.task.template over to the set of values used to create a project.task. + + :param project: project.project record to set on the task's project_id field. + Pass the project.project model or an empty recordset to leave task project_id blank. + DO NOT pass False or None as this will cause an error in _timesheet_create_task_prepare_values(project). + :param template: project.task.template to use to create the task. + :param parent: project.task to set as the parent to this task. + """ + vals = self._timesheet_create_task_prepare_values(project) + vals.update({'name': f"{vals['name']} ({template.name})"}) + vals['description'] = template.description or vals['description'] + vals['parent_id'] = parent and parent.id + vals['user_ids'] = template.assignees.ids + vals['tag_ids'] = template.tags.ids + return vals + + tmpl = self.product_id.task_template_id + if not tmpl: + task = super()._timesheet_create_task(project) + else: + task = _create_task_from_template(project, tmpl, None) + self.write({'task_id': task.id}) + # post message on task + task_msg = _( + "This task has been created from: %s (%s)") % ( + self.order_id.id, self.order_id.name, self.product_id.name) + task.message_post(body=task_msg) + return task diff --git a/models/task_template.py b/models/task_template.py new file mode 100644 index 0000000..b02f763 --- /dev/null +++ b/models/task_template.py @@ -0,0 +1,31 @@ +from odoo import models, fields, api, _ + + +class TaskTemplate(models.Model): + _name = 'project.task.template' + + @api.model + def _current_company(self): + return self.env.company + + name = fields.Char(string="Task Title", required=True) + description = fields.Html(string="Description") + assignees = fields.Many2many("res.users", string="Default Assignees", help="Employees assigned to tasks created from this template.") + customer = fields.Many2one("res.partner", string="Default Customer", help="Default customer for tasks created from this template.") + project = fields.Many2one("project.project", string="Default Project", help="Default project for tasks created from this template.") + tags = fields.Many2many("project.tags", string="Default Tags", help="Default tags for tasks created from this template.") + parent = fields.Many2one("project.task.template", string="Parent Task Template") + subtasks = fields.One2many("project.task.template", inverse_name="parent", string="Subtask Templates") + sequence = fields.Integer(string="Sequence") + company_id = fields.Many2one("res.company", string="Company", index=1, default=_current_company) + + def action_open_task(self): + return { + 'view_mode': 'form', + 'res_model': 'project.task.template', + 'res_id': self.id, + 'type': 'ir.actions.act_window', + 'context': self._context + } + + diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..52439aa --- /dev/null +++ b/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_durpro_fsm_task_template,durpro_fsm_task_template,model_project_task_template,project.group_project_manager,1,1,1,1 +access_durpro_fsm_task_template,durpro_fsm_task_template,model_project_task_template,project.group_project_user,1,1,1,1 \ No newline at end of file diff --git a/static/tests/tours/task_template_tour.js b/static/tests/tours/task_template_tour.js new file mode 100644 index 0000000..f1bf5d9 --- /dev/null +++ b/static/tests/tours/task_template_tour.js @@ -0,0 +1,47 @@ +/** @odoo-module */ + +import tour from 'web_tour.tour'; + +tour.register('task_template_tour', { + test: true, + url: '/web', + }, /* Make sure the test task is visible via the Project > Task Templates path */ + [tour.stepUtils.showAppsMenuItem(), { + trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', + }, { + content: 'Open the task templates list view.', + trigger: '.o_nav_entry[data-menu-xmlid="durpro_fsm.task_template_menu"]', + }, { + content: 'Open the existing task template.', + trigger: '.o_data_cell:contains("Template 1"):parent()', + }, { + content: 'Confirm that the header contains the task title.', + trigger: 'span[name="name"]:contains("Template 1")', + }, /* Make sure we can see the task template from the product */ + tour.stepUtils.toggleHomeMenu(), + { + content: 'Open the sales menu.', + trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', + }, { + content: 'Open the product dropdown menu.', + trigger: 'button[data-menu-xmlid="sale.product_menu_catalog"]', + }, { + content: 'Click the products menu item.', + trigger: 'a.dropdown-item[data-menu-xmlid="sale.menu_product_template_action"]', + }, { + content: 'Search for the test product.', + trigger: 'input.o_searchview_input', + extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Products))', + run: 'text Test Product 1', + }, { + trigger: '.o_menu_item.o_selection_focus', + content: 'Validate search', + run: 'click', + }, { + content: 'Open the test product.', + trigger: 'div.o_kanban_record:has(span:contains(Test Product 1))', + }, { + content: 'Ensure the product_template_id field is displayed with the "Template 1" mention.', + trigger: 'a.o_quick_editable[name="task_template_id"]:first-child:contains("Template 1")', + }, + ]) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..5bad71e --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +from . import test_task_template diff --git a/tests/__pycache__/__init__.cpython-310.pyc b/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3928a60353938d55cd761926b4afdb7f713351c4 GIT binary patch literal 203 zcmYjKF$%&!5ZsFjA_RgzuuP$=g`J3?wT;-=9Kk&yn9GIROJV7A{8CygOIs`VtQ^=G zW|rM?X*vSI;C-iw=rfvsBw{u)oCy(-s=%s~Qb5T8^4?2HoU3je3$E9KZpDsd^M8M|VWaY?I6WhxcaZ~!iM zHIFqgtH;#p61vK#l>dMZ^wEDH*ZdD$bJ9<+4q>`6GvxA-EDIQ*yU`7N=x#Lg~HOzhMN9b$ZHFpJq=8_ee9 z$O>IlEBkPEStp!e-`M~kH2|OEk9$R`~^gA!Xp3%2dZz!TnMsYFD zv+YQ7<$YEZQ$2EvXf)!iPgMJ%70f z82OGoC-vBbv00BT7+dw&hOu3b9T+?H*oCn>Cv$Ud&7HYDa-O&5uJ~}?I=1J)t8B2= zP2<>M9&3-$mN)l~UFNeT`0KD`)@8jpnY896Gz*$DX^rdyYed+}3(zq)sE5|rDrjl5 zYa`GxWo+$vTT|D8>ay!>19gL}uc@0g^+ruy($t%pdW*frUWfS-*fJ(Q^zEv#+b>#L zM`uC$4UqoP1-YQ!*Rqy-v$pkCZL6!PZzB~(-ARkR!+!h%kcHj#H18)huQOSJc?TqZ zs=uo+`WcLR@Lgm5dNpR9-rk1sBt_?4{7(E5PC7d=Tep>F$a_@a67H zjCz3{-*=UrMJZQKl8?rj@*2I$;b}BZR9lWmSyapfS8a`=q+?dLW_db**0%)4$}94_ z(Eco+i9eLr8wd*su|ZMCn3gL2Cgg&&ejc*amHWbeU6~P?{D`S2=J)d*Wf^i zxgO)ezAB!nr3C_l0(jC{HZGJ`nL#j$=qcFmuPH~)rd%i=yzk+3I|2Vw7SA3l3pS(N zXgcK?Q=KQ{;t>2gEBMnwxv}7oVU-DrZ1iB|N*;;$P&o)7++uwZ;cLxKuDH!`^BnUG zby!{j1e?M@)SIp@0znVEGEXR8e#Png%8C;iuHjB-l@icD^Jkogn+wExQ#$&>TURjZ zh!1Zp@N}3rxgflLiGzfi+#ILL2X$lt%u+cIK~9G4Lx?6x;iA5W*N@T|@uu{9s`MgZH)~#_~*V0UJp4K&D%m0Hp5eIcFbCpNV$>s1`I? zLg$+ua`xw$Cb3U!({B{gSB zLzVL4FlYL7YPp6l=1D@S_y7)Cb?@WoL+mggI?yBzJi|0Mh^CcGccOxhGg(AQqB;Qg z>FiP=V#3mUsN;3$nnFaEAVz3abyH|FEJ9qha`Ck1$`1@=585_r`wet+8w#5vtkTE>*v_+qu}I;82#P}<=xoy9Rwc3qrg5evsz(PA(gj7J zU(~(6BXA3LRR!ENQYv^`?mNP*O@X`7Gy4Xz2HhNg88;a zq*7IDj<9s567~(@fNSo98j0_+Z@LiAF6ol;{i~t<|BCP|zXCnt*Vv&a3WQjEj@?-t z>5-13kAU$u)Yi=mzSodyd&mY0JCZp2pW7q>iwoOtfuF;LJA^}VAeymt&a z47Yz^SYQ?C?3MP`6TTl_)2KH-P76>cFwXi!Ea?xtBL%!D73cRrS~#S3mtf~=WU+>`~@)nipJ38J)>g@r0n(EYBgthu1!cR_26F` zfglSDJCs&EEy170e2N}ZN3bph@s6~ng#(`&Ro-u_T>BB?ExXXI8y&J@c{|sLw|ay8 E7kCaG)Bpeg literal 0 HcmV?d00001 diff --git a/tests/test_task_template.py b/tests/test_task_template.py new file mode 100644 index 0000000..abdf09c --- /dev/null +++ b/tests/test_task_template.py @@ -0,0 +1,97 @@ +from odoo.tests.common import TransactionCase, HttpCase, tagged +from odoo.exceptions import UserError + + +class TestTaskTemplateCommon(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + user_group_employee = cls.env.ref('base.group_user') + user_group_project_user = cls.env.ref('project.group_project_user') + user_group_project_manager = cls.env.ref('project.group_project_manager') + user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager') + user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman') + user_product_customer = cls.env.ref('customer_product_code.group_product_customer_code_user') + + group_ids = [user_group_employee, user_group_project_user, user_group_project_manager, user_group_sales_user, + user_group_sales_manager] + if user_product_customer: + group_ids.append(user_product_customer) + + # Test user with project access rights for the various tests + Users = cls.env['res.users'].with_context({'no_reset_password': True}) + cls.user = Users.create({ + 'name': 'Project Manager', + 'login': 'misterpm', + 'password': 'misterpm', + 'email': 'mrpm@testco.com', + 'signature': 'Mr. PM', + 'groups_id': [(6, 0, [user_group_employee.id, user_group_project_user.id, user_group_project_manager.id, + user_group_sales_user.id])] + }) + hours_uom = cls.env['uom.uom'].search([('name', '=', 'Hour')]) or False + # Test product to use with the various tests + cls.task1 = cls.env['project.task.template'].create({ + 'name': 'Template 1', + }) + cls.project = cls.env['project.project'].create({ + 'name': 'Test Project', + }) + cls.product_task_global_project = cls.env['product.product'].create({ + 'name': 'Test Product 1', + 'type': 'service', + 'service_tracking': 'task_global_project', + 'project_id': cls.project.id, + 'task_template_id': cls.task1.id, + 'uom_id': hours_uom.id, + 'uom_po_id': hours_uom.id, + }) + cls.product_task_in_project = cls.env['product.product'].create({ + 'name': 'Test Product 2', + 'type': 'service', + 'service_tracking': 'task_in_project', + 'task_template_id': cls.task1.id, + 'uom_po_id': hours_uom.id, + 'uom_id': hours_uom.id, + }) + cls.partner = cls.env['res.partner'].create({'name': 'Test Partner'}) + cls.sale_order = cls.env['sale.order'].create({ + 'partner_id': cls.partner.id, + 'client_order_ref': 'TEST ORDER', + }) + cls.sol_serv_order = cls.env['sale.order.line'].create({ + 'name': cls.product_task_global_project.name, + 'product_id': cls.product_task_global_project.id, + 'product_uom_qty': 1, + 'product_uom': cls.product_task_global_project.uom_id.id, + 'price_unit': 120.0, + 'order_id': cls.sale_order.id, + 'tax_id': False, + }) + + +@tagged('-at_install', 'post_install') +class TestTaskTemplate(TestTaskTemplateCommon): + + def test_delete_task_template(self): + """User should never be able to delete a task template used on a product""" + with self.assertRaises(UserError): + self.task1.unlink() + + def test_order_confirmation_single_task(self): + """ Confirming the order should create a task in the global project. """ + self.sale_order.action_confirm() + so = self.sale_order + sol = self.sale_order.order_line[0] + task = sol.task_id + self.assertTrue(task) + + + +@tagged('-at_install', 'post_install') +class TestTaskTemplateTour(HttpCase, TestTaskTemplateCommon): + + def test_task_template_tour(self): + self.start_tour('/web', 'task_template_tour', + login='misterpm', ) diff --git a/views/product_views.xml b/views/product_views.xml new file mode 100644 index 0000000..52f31c6 --- /dev/null +++ b/views/product_views.xml @@ -0,0 +1,17 @@ + + + + + + durpro_fsm.product.template.form + product.template + + + + + + + + + \ No newline at end of file diff --git a/views/task_template_views.xml b/views/task_template_views.xml new file mode 100644 index 0000000..dbc7fed --- /dev/null +++ b/views/task_template_views.xml @@ -0,0 +1,101 @@ + + + + + + durpro_fsm.task_template.form + project.task.template + +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3104c3ed52d27e0f77d82d7966cbc540ff1bb97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20V=C3=A9zina?= Date: Wed, 14 Jun 2023 15:55:35 -0400 Subject: [PATCH 09/20] bemade_fsm: Update .gitignore --- .gitignore | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0d20b64..9c283fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,74 @@ -*.pyc +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +/.venv +/.pytest_cache + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +*.eggs + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Pycharm +.idea + +# Eclipse +.settings + +# Visual Studio cache/options directory +.vs/ +.vscode + +# OSX Files +.DS_Store + +# Django stuff: +*.log + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Sphinx documentation +docs/_build/ + +# Backup files +*~ +*.swp + +# OCA rules +!static/lib/ From aab631fc8545a773074b8a5eae13fb2a81e2ff7b Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 14 Jun 2023 16:01:50 -0400 Subject: [PATCH 10/20] bemade_fsm: Updates to equipment.xml in bemade_fsm to get rid of durpro_fso references. --- views/equipment.xml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/views/equipment.xml b/views/equipment.xml index 0041182..505d613 100644 --- a/views/equipment.xml +++ b/views/equipment.xml @@ -2,8 +2,8 @@ - durpro_fso.equipment.form - durpro_fso.equipment + bemade_fsm.equipment.form + bemade_fsm.equipment
@@ -24,18 +24,18 @@ - - - - - - - - - - - - + + + + + + + + + + + +
@@ -48,8 +48,8 @@ - durpro_fso.equipment.tree - durpro_fso.equipment + bemade_fsm.equipment.tree + bemade_fsm.equipment @@ -63,8 +63,8 @@ bemade_fsm.equipment.location.form bemade_fsm.equipment.location @@ -54,7 +51,6 @@ -
@@ -77,8 +73,6 @@ - - Equipment Location bemade_fsm.equipment.location tree,form - - - - - diff --git a/views/task_template_views.xml b/views/task_template_views.xml index e89eef2..2ced6c2 100644 --- a/views/task_template_views.xml +++ b/views/task_template_views.xml @@ -95,7 +95,5 @@ - \ No newline at end of file From 1a539226c30ab14b389b83c75dce090f3bda95ea Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 15 Jun 2023 13:16:37 -0400 Subject: [PATCH 12/20] bemade_fsm: Improvements to the UI and data model for site contacts and equipment. Re: #11. --- __manifest__.py | 2 +- models/equipment.py | 85 +++++++------------- models/res_partner.py | 62 +++++++++------ security/ir.model.access.csv | 3 +- static/tests/tours/task_equipment_tour.js | 14 ++++ tests/__init__.py | 2 + tests/test_bemade_fsm_common.py | 42 ++++++++++ tests/test_equipment.py | 32 ++++++++ tests/test_task_template.py | 28 +------ views/equipment.xml | 1 - views/equipment_location.xml | 96 ----------------------- views/menus.xml | 32 ++++++++ views/res_partner.xml | 56 ++++++++++--- views/task_views.xml | 16 ++++ 14 files changed, 254 insertions(+), 217 deletions(-) create mode 100644 static/tests/tours/task_equipment_tour.js create mode 100644 tests/test_bemade_fsm_common.py create mode 100644 tests/test_equipment.py delete mode 100644 views/equipment_location.xml create mode 100644 views/menus.xml create mode 100644 views/task_views.xml diff --git a/__manifest__.py b/__manifest__.py index d4715c4..3c9b327 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -37,11 +37,11 @@ ], 'data': ['views/task_template_views.xml', 'views/equipment.xml', - 'views/equipment_location.xml', 'security/ir.model.access.csv', 'views/product_views.xml', 'views/res_partner.xml', 'views/menus.xml', + 'views/task_views.xml', ], 'assets': { 'web.assets_tests': [ diff --git a/models/equipment.py b/models/equipment.py index c928988..203037e 100644 --- a/models/equipment.py +++ b/models/equipment.py @@ -2,7 +2,6 @@ from odoo import api, fields, models class EquipmentTag(models.Model): - _name = "bemade_fsm.equipment.tag" _description = 'Field service equipment category' @@ -21,36 +20,6 @@ class EquipmentType(models.Model): name = fields.Char(string='Intervention Name', required=True, translate=True) - -class EquipmentLocation(models.Model): - _name = 'bemade_fsm.equipment.location' - _description = 'Field service location for equipment' - _inherit = ['mail.thread', 'mail.activity.mixin'] - - name = fields.Char(string='Name', required=True, translate=True) - - more_info = fields.Char(string='MOre info') - - partner_id = fields.Many2one(comodel_name='res.partner', - string="Address", - tracking=True, - domain="[('is_site', '=', True)]", - required=True) - - equipment_count = fields.Integer(compute='_compute_equipment_count', - string='Equipment Count') - - equipment_ids = fields.One2many(comodel_name='bemade_fsm.equipment', - inverse_name='equipment_location_id', - string='Equipments') - - @api.depends('equipment_ids') - def _compute_equipment_count(self): - for rec in self: - all_equipmemt_ids = self.env['bemade_fsm.equipment'].search([('equipment_location_id', '=', rec.id)]) - rec.equipment_count = len(all_equipmemt_ids) - - class Equipment(models.Model): _name = 'bemade_fsm.equipment' _rec_name = 'complete_name' @@ -68,35 +37,26 @@ class Equipment(models.Model): tracking=True, help="Classify and analyze your equipment categories like: Boiler, Laboratory, " "Waste water, Pure water") - partner_id = fields.Many2one('res.partner', string="Owner", - tracking=True, - domain="[('is_company', '=', True)]", - required=True) - + compute="_compute_partner") description = fields.Text(string="Description", - tracking=True, - ) + tracking=True) partner_location_id = fields.Many2one('res.partner', - string="Physical Location", - tracking=True, - domain="[('parent_id', '=', partner_id), ('is_site', '=', 'True')]", - required=True) - - equipment_location_id = fields.Many2one('bemade_fsm.equipment.location', - string="Location", - tracking=True, - domain="[('partner_id', '=', partner_location_id)]") + string="Physical Address", + tracking=True,) location_notes = fields.Text(string="Physical Location Notes", - tracking=True, - ) + tracking=True) + task_ids = fields.One2many(comodel_name='project.task', + inverse_name='equipment_id', + string='Interventions') - intervention_ids = fields.One2many(comodel_name='project.task', - inverse_name='equipment_id', - string='Interventions') + @api.depends('partner_location_id') + def _compute_partner(self): + for rec in self: + rec.partner_id = rec.partner_location_id and rec.partner_location_id.get_root_ancestor() @api.depends('pid_tag', 'name') def _compute_complete_name(self): @@ -108,12 +68,23 @@ class Equipment(models.Model): args = args or [] if name: equipments = self.search([ - '|', '|', '|', - ('pid_tag', operator, name), - ('name', operator, name), - ('partner_id.name', operator, name), - ('partner_location_id.name', operator, name)], + '|', '|', '|', + ('pid_tag', operator, name), + ('name', operator, name), + ('partner_id.name', operator, name), + ('partner_location_id.name', operator, name)], limit=limit) else: equipments = self.search(args, limit=limit) return equipments.name_get() + + def action_view_equipment(self): + return { + 'name': 'Equipment', + 'view_type': 'form', + 'view_mode': 'form', + 'res_id': self.id, + 'context': self.env.context, + 'res_model': 'bemade_fsm.equipment', + 'type': 'ir.actions.act_window', + } \ No newline at end of file diff --git a/models/res_partner.py b/models/res_partner.py index ab516d3..574a0f2 100644 --- a/models/res_partner.py +++ b/models/res_partner.py @@ -1,5 +1,4 @@ -from odoo import api, fields, models - +from odoo import api, fields, models, Command class Partner(models.Model): _inherit = 'res.partner' @@ -7,26 +6,37 @@ class Partner(models.Model): equipment_count = fields.Integer(compute='_compute_equipment_count', string='Equipment Count') + owned_equipment_ids = fields.One2many(comodel_name="bemade_fsm.equipment", + inverse_name="partner_id", + string="Owned Equipments") + + equipment_ids = fields.One2many(comodel_name='bemade_fsm.equipment', - inverse_name='partner_id', - string='Equipments') - - equipment_location_count = fields.Integer(compute='_compute_equipment_location_count', - string='Equipment Location Count') - - equipment_location_ids = fields.One2many(comodel_name='bemade_fsm.equipment.location', - inverse_name='partner_id', - string='Equipment Location') - - is_site = fields.Boolean(string='Is an Equipment Site', - tracking=True, - default=False, - help="Check if the contact is an equipment site, otherwise it is not in that list") + inverse_name='partner_location_id', + string='Site Equipment') is_site_contact = fields.Boolean(string='Is a site contact', - tracking=True, - default=False, - help="Check if the contact is a site contact, otherwise it is a not in that list") + compute="_compute_is_site_contact") + + site_ids = fields.Many2many(string='Work Sites', + comodel_name='res.partner', + relation='res_partner_site_contact_rel', + column1='site_contact_id', + column2='site_id', + tracking=True) + + site_contacts = fields.Many2many(string='Site Contacts', + comodel_name='res.partner', + relation='res_partner_site_contact_rel', + column1='site_id', + column2='site_contact_id', + domain=[('is_company', '=', False)], + tracking=True) + + @api.depends('site_ids') + def _compute_is_site_contact(self): + for rec in self: + rec.is_site_contact = rec.site_ids is not False @api.depends('equipment_ids') def _compute_equipment_count(self): @@ -34,8 +44,12 @@ class Partner(models.Model): all_equipmemt_ids = self.env['bemade_fsm.equipment'].search([('partner_id', '=', rec.id)]) rec.equipment_count = len(all_equipmemt_ids) - @api.depends('equipment_location_ids') - def _compute_equipment_location_count(self): - for rec in self: - all_equipmemt_location_ids = self.env['bemade_fsm.equipment.location'].search([('partner_id', '=', rec.id)]) - rec.equipment_location_count = len(all_equipmemt_location_ids) + def get_root_ancestor(self): + """ Returns the partner at the top of the parent-child hierarchy. """ + self.ensure_one() + return self.parent_id and self.parent_id.get_root_ancestor() or self + + def get_first_company_ancestor(self): + """ Returns the first ancestor that is a company """ + self.ensure_one() + return self.is_company and self or self.parent_id and self.parent_id.get_first_company_ancestor() diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv index 086f935..82eb4f7 100644 --- a/security/ir.model.access.csv +++ b/security/ir.model.access.csv @@ -3,5 +3,4 @@ access_bemade_fsm_task_template,bemade_fsm_task_template,model_project_task_temp access_bemade_fsm_task_template,bemade_fsm_task_template,model_project_task_template,project.group_project_user,1,1,1,1 access_bemade_fsm_equipment,bemade_fsm_equipment,model_bemade_fsm_equipment,base.group_user,1,1,1,1 access_bemade_fsm_equipment_tag,bemade_fsm_equipment_tag,model_bemade_fsm_equipment_tag,base.group_user,1,1,1,1 -access_bemade_fsm_equipment_type,access_bemade_fsm_equipment_type,model_bemade_fsm_equipment_type,base.group_user,1,0,0,0 -access_bemade_fsm_equipment_location,access_bemade_fsm_equipment_location,model_bemade_fsm_equipment_location,base.group_user,1,0,0,0 \ No newline at end of file +access_bemade_fsm_equipment_type,access_bemade_fsm_equipment_type,model_bemade_fsm_equipment_type,base.group_user,1,0,0,0 \ No newline at end of file diff --git a/static/tests/tours/task_equipment_tour.js b/static/tests/tours/task_equipment_tour.js new file mode 100644 index 0000000..8abdd1d --- /dev/null +++ b/static/tests/tours/task_equipment_tour.js @@ -0,0 +1,14 @@ +/** @odoo-module **/ + +// Navigate to the Service > Clients > Equipment menu +// Click the Create button +// Fill in the required fields, including creating a new Client Location and PID Tag +// Navigate to the client location, then to the client, making sure that the equipment and client location links appear + +// Now make another one, but from the Partner interface +// Navigate to Service > Clients > Clients +// Search for and open the test client +// On the "Locations" tab, add a location +// Open the location and add a new Equipment to its equipment list + +// Create a task in the test project diff --git a/tests/__init__.py b/tests/__init__.py index 9df0958..a66b934 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,2 +1,4 @@ +from . import test_bemade_fsm_common from . import test_task_template from . import test_task_template_sale_order +from . import test_equipment diff --git a/tests/test_bemade_fsm_common.py b/tests/test_bemade_fsm_common.py new file mode 100644 index 0000000..1d74b7a --- /dev/null +++ b/tests/test_bemade_fsm_common.py @@ -0,0 +1,42 @@ +from odoo.tests.common import TransactionCase, HttpCase, tagged +from odoo.tools import mute_logger +from odoo.exceptions import MissingError +from odoo import Command +from psycopg2.errors import ForeignKeyViolation + + +class FSMManagerUserTransactionCase(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + user_group_employee = cls.env.ref('base.group_user') + user_group_project_user = cls.env.ref('project.group_project_user') + user_group_project_manager = cls.env.ref('project.group_project_manager') + user_group_fsm_user = cls.env.ref('industry_fsm.group_fsm_user') + user_group_fsm_manager = cls.env.ref('industry_fsm.group_fsm_manager') + user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager') + user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman') + user_product_customer = cls.env.ref('customer_product_code.group_product_customer_code_user') + + group_ids = [user_group_employee, + user_group_project_user, + user_group_project_manager, + user_group_fsm_user, + user_group_fsm_manager, + user_group_sales_user, + user_group_sales_manager, ] + if user_product_customer: + group_ids.append(user_product_customer) + + # Test user with project access rights for the various tests + Users = cls.env['res.users'].with_context({'no_reset_password': True}) + cls.user = Users.create({ + 'name': 'Project Manager', + 'login': 'misterpm', + 'password': 'misterpm', + 'email': 'mrpm@testco.com', + 'signature': 'Mr. PM', + 'groups_id': [(6, 0, [user_group_employee.id, user_group_project_user.id, user_group_project_manager.id, + user_group_sales_user.id])], + }) diff --git a/tests/test_equipment.py b/tests/test_equipment.py new file mode 100644 index 0000000..0920878 --- /dev/null +++ b/tests/test_equipment.py @@ -0,0 +1,32 @@ +from odoo.tests.common import HttpCase, tagged +from odoo.tools import mute_logger +from odoo.exceptions import MissingError +from odoo import Command +from psycopg2.errors import ForeignKeyViolation +from .test_bemade_fsm_common import FSMManagerUserTransactionCase + +class TestEquipmentCommon(FSMManagerUserTransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Set up the test partner + cls.partner_company = cls.env['res.partner'].create({ + 'name': 'Test Partner Company', + 'company_type': 'company', + 'street': '123 Street St.', + 'city': 'Montreal', + 'state_id': cls.env['res.country.state'].search([('name','ilike','Quebec%')]), + 'country_id': cls.env['res.country'].search([('name','=','Canada')]) + }) + + cls.partner_contact = cls.env['res.partner'].create({ + 'name': 'Site Contact', + 'company_type': 'person', + 'parent_id': cls.partner_company.id, + }) + + cls.equipment = cls.env['bemade_fsm.equipment'].create({ + 'name': 'Test Equipment 1', + }) \ No newline at end of file diff --git a/tests/test_task_template.py b/tests/test_task_template.py index ad84734..ec42aca 100644 --- a/tests/test_task_template.py +++ b/tests/test_task_template.py @@ -1,38 +1,16 @@ -from odoo.tests.common import TransactionCase, HttpCase, tagged +from .test_bemade_fsm_common import FSMManagerUserTransactionCase +from odoo.tests.common import HttpCase, tagged from odoo.tools import mute_logger from odoo.exceptions import MissingError from odoo import Command from psycopg2.errors import ForeignKeyViolation -class TestTaskTemplateCommon(TransactionCase): +class TestTaskTemplateCommon(FSMManagerUserTransactionCase): @classmethod def setUpClass(cls): super().setUpClass() - user_group_employee = cls.env.ref('base.group_user') - user_group_project_user = cls.env.ref('project.group_project_user') - user_group_project_manager = cls.env.ref('project.group_project_manager') - user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager') - user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman') - user_product_customer = cls.env.ref('customer_product_code.group_product_customer_code_user') - - group_ids = [user_group_employee, user_group_project_user, user_group_project_manager, user_group_sales_user, - user_group_sales_manager] - if user_product_customer: - group_ids.append(user_product_customer) - - # Test user with project access rights for the various tests - Users = cls.env['res.users'].with_context({'no_reset_password': True}) - cls.user = Users.create({ - 'name': 'Project Manager', - 'login': 'misterpm', - 'password': 'misterpm', - 'email': 'mrpm@testco.com', - 'signature': 'Mr. PM', - 'groups_id': [(6, 0, [user_group_employee.id, user_group_project_user.id, user_group_project_manager.id, - user_group_sales_user.id])], - }) hours_uom = cls.env['uom.uom'].search([('name', '=', 'Hour')]) or False # Test product to use with the various tests cls.task1 = cls.env['project.task.template'].create({ diff --git a/views/equipment.xml b/views/equipment.xml index 505d613..4047df6 100644 --- a/views/equipment.xml +++ b/views/equipment.xml @@ -20,7 +20,6 @@ groups="sale.group_delivery_invoice_address" context="{'default_type': 'delivery', 'show_address': 1}" options='{"always_reload": True}'/> - diff --git a/views/equipment_location.xml b/views/equipment_location.xml deleted file mode 100644 index 9f2cd0b..0000000 --- a/views/equipment_location.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - bemade_fsm.partner.equipment.location.form - res.partner - - - - - - - - - - - -
-
- - -
-
-
-
-
-
-
-
-
-
- - - bemade_fsm.equipment.location.form - bemade_fsm.equipment.location - - - - - - - - - - - - - - - - - - - - -
- - - -
- -
-
- - - bemade_fsm.equipment.location.tree - bemade_fsm.equipment.location - - - - - - - - - - - bemade_fsm.equipment.location.search - bemade_fsm.equipment.location - - - - - - - - - - - - - - Equipment Location - bemade_fsm.equipment.location - tree,form - -
diff --git a/views/menus.xml b/views/menus.xml new file mode 100644 index 0000000..76c5464 --- /dev/null +++ b/views/menus.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/views/res_partner.xml b/views/res_partner.xml index 165a83f..d451cb9 100644 --- a/views/res_partner.xml +++ b/views/res_partner.xml @@ -8,29 +8,63 @@ [("partner_id", "=", active_id)] - - res.partner.view.buttons.fso + + bemade_fsm.partner.equipment.location.form res.partner - - + + + + + + + + + + + + + + + + + + +
- - - - -
- diff --git a/views/task_views.xml b/views/task_views.xml new file mode 100644 index 0000000..6d2ccce --- /dev/null +++ b/views/task_views.xml @@ -0,0 +1,16 @@ + + + + + bemade_fsm.project_task.form + project.task + + + + + + + + + + \ No newline at end of file From 641342d5c06b2a91c53c6ccb2e2f9923f1316b1a Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 15 Jun 2023 14:00:50 -0400 Subject: [PATCH 13/20] bemade_fsm: Constrain project.task.equipment_id to equipments at task.partner_id. --- views/task_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/task_views.xml b/views/task_views.xml index 6d2ccce..fb488aa 100644 --- a/views/task_views.xml +++ b/views/task_views.xml @@ -8,7 +8,7 @@ - + From 7e334d54eacc819e0eb248040a3464bb57202a5c Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 15 Jun 2023 16:18:36 -0400 Subject: [PATCH 14/20] bemade_fsm: Added and modified some tests for #11. --- __manifest__.py | 1 + static/tests/tours/task_equipment_tour.js | 37 +++++++++++++++++++++-- static/tests/tours/task_template_tour.js | 2 +- tests/test_bemade_fsm_common.py | 6 +--- tests/test_equipment.py | 18 ++++++----- 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/__manifest__.py b/__manifest__.py index 3c9b327..3632f50 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -46,6 +46,7 @@ 'assets': { 'web.assets_tests': [ 'bemade_fsm/static/tests/tours/task_template_tour.js', + 'bemade_fsm/static/tests/tours/task_equipment_tour.js', ], }, 'installable': True, diff --git a/static/tests/tours/task_equipment_tour.js b/static/tests/tours/task_equipment_tour.js index 8abdd1d..e36b2cd 100644 --- a/static/tests/tours/task_equipment_tour.js +++ b/static/tests/tours/task_equipment_tour.js @@ -1,7 +1,39 @@ /** @odoo-module **/ -// Navigate to the Service > Clients > Equipment menu -// Click the Create button +import tour from 'web_tour.tour'; + +tour.register('task_equipment_tour', { + test: true, + url: '/web', +}, [tour.stepUtils.showAppsMenuItem(), { + content: 'Navigate to the Service menu', + trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', +}, { + content: 'Navigate to the Clients submenu', + trigger: '.o_app[data-menu-xmlid="bemade_fsm.menu_service_client"]', +}, { + content: 'Navigate to the Equipment menu', + trigger: '.o_app[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', +}, { + content: 'Click the create button', + trigger: '.o_list_button_add', + extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Equipment))', +}, { + content: 'Add a tag', + trigger: 'input[name="pid_tag"]', + run: 'text TestPIDTag', +}, { + content: 'Set the name', + trigger: 'input[name="name"]', + run: 'text Test Tag Name', +}, { + content: 'Set the partner', + trigger: 'input[id="o_field_input_303"]', + run: 'text Test Partner Company', +}, { + content: 'Click the partner in the dropdown', + trigger: 'li a.dropdown-item:contains(Test Partner Company)', +} // Fill in the required fields, including creating a new Client Location and PID Tag // Navigate to the client location, then to the client, making sure that the equipment and client location links appear @@ -12,3 +44,4 @@ // Open the location and add a new Equipment to its equipment list // Create a task in the test project +]) \ No newline at end of file diff --git a/static/tests/tours/task_template_tour.js b/static/tests/tours/task_template_tour.js index 549803d..3573a45 100644 --- a/static/tests/tours/task_template_tour.js +++ b/static/tests/tours/task_template_tour.js @@ -10,7 +10,7 @@ tour.register('task_template_tour', { trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', }, { content: 'Open the task templates list view.', - trigger: '.o_nav_entry[data-menu-xmlid="bemade_fsm.task_template_menu"]', + trigger: '.o_nav_entry[data-menu-xmlid="bemade_fsm.project_task_template_menu"]', }, { content: 'Open the existing task template.', trigger: '.o_data_cell:contains("Template 1"):parent()', diff --git a/tests/test_bemade_fsm_common.py b/tests/test_bemade_fsm_common.py index 1d74b7a..ecd334a 100644 --- a/tests/test_bemade_fsm_common.py +++ b/tests/test_bemade_fsm_common.py @@ -1,8 +1,4 @@ -from odoo.tests.common import TransactionCase, HttpCase, tagged -from odoo.tools import mute_logger -from odoo.exceptions import MissingError -from odoo import Command -from psycopg2.errors import ForeignKeyViolation +from odoo.tests.common import TransactionCase class FSMManagerUserTransactionCase(TransactionCase): diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 0920878..6ed9116 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -1,8 +1,4 @@ from odoo.tests.common import HttpCase, tagged -from odoo.tools import mute_logger -from odoo.exceptions import MissingError -from odoo import Command -from psycopg2.errors import ForeignKeyViolation from .test_bemade_fsm_common import FSMManagerUserTransactionCase class TestEquipmentCommon(FSMManagerUserTransactionCase): @@ -17,8 +13,8 @@ class TestEquipmentCommon(FSMManagerUserTransactionCase): 'company_type': 'company', 'street': '123 Street St.', 'city': 'Montreal', - 'state_id': cls.env['res.country.state'].search([('name','ilike','Quebec%')]), - 'country_id': cls.env['res.country'].search([('name','=','Canada')]) + 'state_id': cls.env['res.country.state'].search([('name','ilike','Quebec%')]).id, + 'country_id': cls.env['res.country'].search([('name','=','Canada')]).id }) cls.partner_contact = cls.env['res.partner'].create({ @@ -29,4 +25,12 @@ class TestEquipmentCommon(FSMManagerUserTransactionCase): cls.equipment = cls.env['bemade_fsm.equipment'].create({ 'name': 'Test Equipment 1', - }) \ No newline at end of file + }) + + +@tagged('-at_install', 'post_install') +class TestEquipmentTour(HttpCase, TestEquipmentCommon): + + def test_equipment_tour(self): + self.start_tour('/web', 'task_equipment_tour', + login=self.user.login, ) From 3a5f31a5b4a0219d3b131033d8563257d8e317a1 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 15 Jun 2023 17:00:41 -0400 Subject: [PATCH 15/20] bemade_fsm: First functional test written for creating equipment linked to a client. Ref #11. --- static/tests/tours/task_equipment_tour.js | 12 ++++++++---- tests/test_bemade_fsm_common.py | 21 ++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/static/tests/tours/task_equipment_tour.js b/static/tests/tours/task_equipment_tour.js index e36b2cd..bf2fa16 100644 --- a/static/tests/tours/task_equipment_tour.js +++ b/static/tests/tours/task_equipment_tour.js @@ -5,15 +5,16 @@ import tour from 'web_tour.tour'; tour.register('task_equipment_tour', { test: true, url: '/web', -}, [tour.stepUtils.showAppsMenuItem(), { +}, /* Create a new "Test Equipment" and link it to "Test Partner Company" */ + [tour.stepUtils.showAppsMenuItem(), { content: 'Navigate to the Service menu', trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', }, { content: 'Navigate to the Clients submenu', - trigger: '.o_app[data-menu-xmlid="bemade_fsm.menu_service_client"]', + trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', }, { content: 'Navigate to the Equipment menu', - trigger: '.o_app[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', + trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', }, { content: 'Click the create button', trigger: '.o_list_button_add', @@ -28,11 +29,14 @@ tour.register('task_equipment_tour', { run: 'text Test Tag Name', }, { content: 'Set the partner', - trigger: 'input[id="o_field_input_303"]', + trigger: 'div[name="partner_location_id"] div div input', run: 'text Test Partner Company', }, { content: 'Click the partner in the dropdown', trigger: 'li a.dropdown-item:contains(Test Partner Company)', +}, { + content: 'Save', + trigger: 'button.o_form_button_save', } // Fill in the required fields, including creating a new Client Location and PID Tag // Navigate to the client location, then to the client, making sure that the equipment and client location links appear diff --git a/tests/test_bemade_fsm_common.py b/tests/test_bemade_fsm_common.py index ecd334a..47942ac 100644 --- a/tests/test_bemade_fsm_common.py +++ b/tests/test_bemade_fsm_common.py @@ -1,5 +1,5 @@ from odoo.tests.common import TransactionCase - +from odoo import Command class FSMManagerUserTransactionCase(TransactionCase): @@ -15,15 +15,15 @@ class FSMManagerUserTransactionCase(TransactionCase): user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman') user_product_customer = cls.env.ref('customer_product_code.group_product_customer_code_user') - group_ids = [user_group_employee, - user_group_project_user, - user_group_project_manager, - user_group_fsm_user, - user_group_fsm_manager, - user_group_sales_user, - user_group_sales_manager, ] + group_ids = [user_group_employee.id, + user_group_project_user.id, + user_group_project_manager.id, + user_group_fsm_user.id, + user_group_fsm_manager.id, + user_group_sales_user.id, + user_group_sales_manager.id, ] if user_product_customer: - group_ids.append(user_product_customer) + group_ids.append(user_product_customer.id) # Test user with project access rights for the various tests Users = cls.env['res.users'].with_context({'no_reset_password': True}) @@ -33,6 +33,5 @@ class FSMManagerUserTransactionCase(TransactionCase): 'password': 'misterpm', 'email': 'mrpm@testco.com', 'signature': 'Mr. PM', - 'groups_id': [(6, 0, [user_group_employee.id, user_group_project_user.id, user_group_project_manager.id, - user_group_sales_user.id])], + 'groups_id': [Command.set(group_ids)], }) From edf917664f000b4708a1a355cfcf54c0ab79babd Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 15 Jun 2023 19:34:40 -0400 Subject: [PATCH 16/20] bemade_fsm: Completed integration test tour for adding equipment to a client. Closes #11. --- static/tests/tours/task_equipment_tour.js | 116 +++++++++++++--------- tests/test_equipment.py | 1 + views/menus.xml | 2 +- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/static/tests/tours/task_equipment_tour.js b/static/tests/tours/task_equipment_tour.js index bf2fa16..4599fb5 100644 --- a/static/tests/tours/task_equipment_tour.js +++ b/static/tests/tours/task_equipment_tour.js @@ -1,51 +1,75 @@ /** @odoo-module **/ import tour from 'web_tour.tour'; - +const TEST_COMPANY = "Test Partner Company"; +const TEST_EQPT1 = "Test Equipment 1"; +const TEST_EQPT2 = "Test Equipment 2"; tour.register('task_equipment_tour', { - test: true, - url: '/web', -}, /* Create a new "Test Equipment" and link it to "Test Partner Company" */ + test: true, + url: '/web', + }, /* Create a new "Test Equipment" and link it to "Test Partner Company" */ [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Service menu', - trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', -}, { - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', -}, { - content: 'Navigate to the Equipment menu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', -}, { - content: 'Click the create button', - trigger: '.o_list_button_add', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Equipment))', -}, { - content: 'Add a tag', - trigger: 'input[name="pid_tag"]', - run: 'text TestPIDTag', -}, { - content: 'Set the name', - trigger: 'input[name="name"]', - run: 'text Test Tag Name', -}, { - content: 'Set the partner', - trigger: 'div[name="partner_location_id"] div div input', - run: 'text Test Partner Company', -}, { - content: 'Click the partner in the dropdown', - trigger: 'li a.dropdown-item:contains(Test Partner Company)', -}, { - content: 'Save', - trigger: 'button.o_form_button_save', -} -// Fill in the required fields, including creating a new Client Location and PID Tag -// Navigate to the client location, then to the client, making sure that the equipment and client location links appear - -// Now make another one, but from the Partner interface -// Navigate to Service > Clients > Clients -// Search for and open the test client -// On the "Locations" tab, add a location -// Open the location and add a new Equipment to its equipment list - -// Create a task in the test project -]) \ No newline at end of file + content: 'Navigate to the Service menu', + trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', + }, { + content: 'Navigate to the Clients submenu', + trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', + }, { + content: 'Navigate to the Equipment menu', + trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', + }, { + content: 'Click the create button', + trigger: '.o_list_button_add', + extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Equipment))', + }, { + content: 'Add a tag', + trigger: 'input[name="pid_tag"]', + run: 'text TestPIDTag', + }, { + content: 'Set the name', + trigger: 'input[name="name"]', + run: `text ${TEST_EQPT2}`, + }, { + content: 'Set the partner', + trigger: 'div[name="partner_location_id"] div div input', + run: 'text Test Partner Company', + }, { + content: 'Click the partner in the dropdown', + trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`, + }, { + content: 'Save equipment', + trigger: 'button.o_form_button_save', + }, { + /* Navigate to the client and make sure that there are two equipments saved (one from the Python test case) */ + content: 'Navigate to the Clients submenu', + trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', + }, { + content: 'Click on the clients submenu', + trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_clients"]', + }, { + content: 'Search for the Test Partner Company', + trigger: 'input.o_searchview_input', + extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Customers))', + run: `text ${TEST_COMPANY}` + }, { + content: 'Validate Search', + trigger: '.o_menu_item.o_selection_focus', + run: 'click', + }, { + content: 'Open the test client.', + trigger: `div.o_kanban_record:has(span:contains(${TEST_COMPANY}))`, + }, { + content: 'Click the Field Service tab.', + trigger: 'a.nav-link:contains(Field Service)', + extra_trigger: `h1 span.o_field_partner_autocomplete[name="name"]:contains(${TEST_COMPANY})`, + }, { + content: 'Make sure we have a first test equipment', + /*trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT1}))`,*/ + trigger: `td:contains(${TEST_EQPT1})`, + run: function() {}, + }, { + content: 'Make sure we have a second test equipment', + trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT2}))`, + run: function() {}, + } + ]) \ No newline at end of file diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 6ed9116..2ca0485 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -25,6 +25,7 @@ class TestEquipmentCommon(FSMManagerUserTransactionCase): cls.equipment = cls.env['bemade_fsm.equipment'].create({ 'name': 'Test Equipment 1', + 'partner_location_id': cls.partner_company.id, }) diff --git a/views/menus.xml b/views/menus.xml index 76c5464..d5dce9e 100644 --- a/views/menus.xml +++ b/views/menus.xml @@ -18,7 +18,7 @@ groups="industry_fsm.group_fsm_user"/> From c927840a1b700a4e71f1289d15fcd2f602f23ffc Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 19 Jun 2023 09:27:26 -0400 Subject: [PATCH 17/20] bemade_fsm: Sub-task templates are no longer linked to the SO/SO Line. Fixes #50. --- models/sale_order.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/models/sale_order.py b/models/sale_order.py index fa04c34..bf51ace 100644 --- a/models/sale_order.py +++ b/models/sale_order.py @@ -1,5 +1,6 @@ from odoo import fields, models, api, _, Command + class SaleOrderLine(models.Model): _inherit = 'sale.order.line' @@ -24,6 +25,8 @@ class SaleOrderLine(models.Model): subtask = _create_task_from_template(project, t, task) subtasks.append(subtask) task.write({'child_ids': [Command.set([t.id for t in subtasks])]}) + # We don't want to see the sub-tasks on the SO + task.child_ids.write({'sale_order_id': None, 'sale_line_id': None, }) return task def _timesheet_create_task_prepare_values_from_template(project, template, parent): @@ -36,7 +39,7 @@ class SaleOrderLine(models.Model): :param parent: project.task to set as the parent to this task. """ vals = self._timesheet_create_task_prepare_values(project) - vals.update({'name': f"{vals['name']} ({template.name})"}) + vals['name'] = f"{vals['name']} ({template.name})" if not parent else template.name vals['description'] = template.description or vals['description'] vals['parent_id'] = parent and parent.id vals['user_ids'] = template.assignees.ids @@ -52,6 +55,6 @@ class SaleOrderLine(models.Model): # post message on task task_msg = _( "This task has been created from: %s (%s)") % ( - self.order_id.id, self.order_id.name, self.product_id.name) + self.order_id.id, self.order_id.name, self.product_id.name) task.message_post(body=task_msg) return task From 34a7b0f77e286d4b523c1fda01fea96205e54702 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 19 Jun 2023 09:43:21 -0400 Subject: [PATCH 18/20] bemade_fsm: Added field planned_hours to project.task.template and passed it along to tasks on order confirmation. Fixes #51. --- models/sale_order.py | 1 + models/task_template.py | 1 + tests/test_task_template.py | 2 ++ tests/test_task_template_sale_order.py | 1 + views/task_template_views.xml | 3 +++ 5 files changed, 8 insertions(+) diff --git a/models/sale_order.py b/models/sale_order.py index bf51ace..0605e28 100644 --- a/models/sale_order.py +++ b/models/sale_order.py @@ -44,6 +44,7 @@ class SaleOrderLine(models.Model): vals['parent_id'] = parent and parent.id vals['user_ids'] = template.assignees.ids vals['tag_ids'] = template.tags.ids + vals['planned_hours'] = template.planned_hours return vals tmpl = self.product_id.task_template_id diff --git a/models/task_template.py b/models/task_template.py index 128edbd..f95b3c7 100644 --- a/models/task_template.py +++ b/models/task_template.py @@ -18,6 +18,7 @@ class TaskTemplate(models.Model): subtasks = fields.One2many("project.task.template", inverse_name="parent", string="Subtask Templates") sequence = fields.Integer(string="Sequence") company_id = fields.Many2one("res.company", string="Company", index=1, default=_current_company) + planned_hours = fields.Float("Initially Planned Hours") def action_open_task(self): return { diff --git a/tests/test_task_template.py b/tests/test_task_template.py index ec42aca..2857b6c 100644 --- a/tests/test_task_template.py +++ b/tests/test_task_template.py @@ -11,6 +11,7 @@ class TestTaskTemplateCommon(FSMManagerUserTransactionCase): @classmethod def setUpClass(cls): super().setUpClass() + cls.PLANNED_HOURS = 6 hours_uom = cls.env['uom.uom'].search([('name', '=', 'Hour')]) or False # Test product to use with the various tests cls.task1 = cls.env['project.task.template'].create({ @@ -45,6 +46,7 @@ class TestTaskTemplateCommon(FSMManagerUserTransactionCase): # Set up a task template tree with 2 children and 1 grandchild cls.parent_task = cls.env['project.task.template'].create({ 'name': 'Parent Template', + 'planned_hours': cls.PLANNED_HOURS, }) cls.child_task_1 = cls.env['project.task.template'].create({ 'name': 'Child Template 1', diff --git a/tests/test_task_template_sale_order.py b/tests/test_task_template_sale_order.py index c19a892..176c222 100644 --- a/tests/test_task_template_sale_order.py +++ b/tests/test_task_template_sale_order.py @@ -67,6 +67,7 @@ class TestTaskTemplateSalesOrder(TestTaskTemplateCommon): self.assertTrue(task2) self.assertTrue(self.task1.name in task1.name) self.assertTrue(self.task1.name in task2.name) + self.assertTrue(self.task1.planned_hours == task1.planned_hours) def test_order_confirmation_tree_template(self): def assert_structure(sol): diff --git a/views/task_template_views.xml b/views/task_template_views.xml index 2ced6c2..bbdaed1 100644 --- a/views/task_template_views.xml +++ b/views/task_template_views.xml @@ -19,6 +19,7 @@ + @@ -57,6 +58,7 @@ + @@ -71,6 +73,7 @@ + From 88aa52a7671eef0718809e23c9e703f654c80dc7 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 19 Jun 2023 11:52:13 -0400 Subject: [PATCH 19/20] bemade_fsm: Updated to require bemade_partner_root_ancestor, refactored out call to Partner.get_root_ancestor(). --- __manifest__.py | 1 + models/equipment.py | 2 +- models/res_partner.py | 10 ---------- tests/test_task_template.py | 1 - 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/__manifest__.py b/__manifest__.py index 3632f50..0a0e459 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -34,6 +34,7 @@ 'sale_stock', 'industry_fsm', 'industry_fsm_sale', + 'bemade_partner_root_ancestor', ], 'data': ['views/task_template_views.xml', 'views/equipment.xml', diff --git a/models/equipment.py b/models/equipment.py index 203037e..b82c143 100644 --- a/models/equipment.py +++ b/models/equipment.py @@ -56,7 +56,7 @@ class Equipment(models.Model): @api.depends('partner_location_id') def _compute_partner(self): for rec in self: - rec.partner_id = rec.partner_location_id and rec.partner_location_id.get_root_ancestor() + rec.partner_id = rec.partner_location_id and rec.partner_location_id.root_ancestor @api.depends('pid_tag', 'name') def _compute_complete_name(self): diff --git a/models/res_partner.py b/models/res_partner.py index 574a0f2..c9f1877 100644 --- a/models/res_partner.py +++ b/models/res_partner.py @@ -43,13 +43,3 @@ class Partner(models.Model): for rec in self: all_equipmemt_ids = self.env['bemade_fsm.equipment'].search([('partner_id', '=', rec.id)]) rec.equipment_count = len(all_equipmemt_ids) - - def get_root_ancestor(self): - """ Returns the partner at the top of the parent-child hierarchy. """ - self.ensure_one() - return self.parent_id and self.parent_id.get_root_ancestor() or self - - def get_first_company_ancestor(self): - """ Returns the first ancestor that is a company """ - self.ensure_one() - return self.is_company and self or self.parent_id and self.parent_id.get_first_company_ancestor() diff --git a/tests/test_task_template.py b/tests/test_task_template.py index 2857b6c..bd46e0a 100644 --- a/tests/test_task_template.py +++ b/tests/test_task_template.py @@ -1,6 +1,5 @@ from .test_bemade_fsm_common import FSMManagerUserTransactionCase from odoo.tests.common import HttpCase, tagged -from odoo.tools import mute_logger from odoo.exceptions import MissingError from odoo import Command from psycopg2.errors import ForeignKeyViolation From 825080129f36645241bcfa752c5ee71d823e485f Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 22 Jun 2023 15:36:19 -0400 Subject: [PATCH 20/20] bemade_fsm: Added test skeleton for upcoming features. --- __init__.py => bemade_fsm/__init__.py | 0 __manifest__.py => bemade_fsm/__manifest__.py | 0 {models => bemade_fsm/models}/__init__.py | 0 {models => bemade_fsm/models}/equipment.py | 0 .../models}/product_template.py | 0 {models => bemade_fsm/models}/res_partner.py | 0 {models => bemade_fsm/models}/sale_order.py | 0 {models => bemade_fsm/models}/task.py | 0 .../models}/task_template.py | 0 .../security}/ir.model.access.csv | 0 .../tests/tours/task_equipment_tour.js | 0 .../static}/tests/tours/task_template_tour.js | 0 {tests => bemade_fsm/tests}/__init__.py | 0 .../tests}/test_bemade_fsm_common.py | 0 {tests => bemade_fsm/tests}/test_equipment.py | 0 bemade_fsm/tests/test_fsm_contact_setting.py | 30 +++++++++++++++++++ .../tests}/test_task_template.py | 0 .../tests}/test_task_template_sale_order.py | 0 {views => bemade_fsm/views}/equipment.xml | 0 {views => bemade_fsm/views}/menus.xml | 0 {views => bemade_fsm/views}/product_views.xml | 0 {views => bemade_fsm/views}/res_partner.xml | 0 .../views}/task_template_views.xml | 0 {views => bemade_fsm/views}/task_views.xml | 0 24 files changed, 30 insertions(+) rename __init__.py => bemade_fsm/__init__.py (100%) rename __manifest__.py => bemade_fsm/__manifest__.py (100%) rename {models => bemade_fsm/models}/__init__.py (100%) rename {models => bemade_fsm/models}/equipment.py (100%) rename {models => bemade_fsm/models}/product_template.py (100%) rename {models => bemade_fsm/models}/res_partner.py (100%) rename {models => bemade_fsm/models}/sale_order.py (100%) rename {models => bemade_fsm/models}/task.py (100%) rename {models => bemade_fsm/models}/task_template.py (100%) rename {security => bemade_fsm/security}/ir.model.access.csv (100%) rename {static => bemade_fsm/static}/tests/tours/task_equipment_tour.js (100%) rename {static => bemade_fsm/static}/tests/tours/task_template_tour.js (100%) rename {tests => bemade_fsm/tests}/__init__.py (100%) rename {tests => bemade_fsm/tests}/test_bemade_fsm_common.py (100%) rename {tests => bemade_fsm/tests}/test_equipment.py (100%) create mode 100644 bemade_fsm/tests/test_fsm_contact_setting.py rename {tests => bemade_fsm/tests}/test_task_template.py (100%) rename {tests => bemade_fsm/tests}/test_task_template_sale_order.py (100%) rename {views => bemade_fsm/views}/equipment.xml (100%) rename {views => bemade_fsm/views}/menus.xml (100%) rename {views => bemade_fsm/views}/product_views.xml (100%) rename {views => bemade_fsm/views}/res_partner.xml (100%) rename {views => bemade_fsm/views}/task_template_views.xml (100%) rename {views => bemade_fsm/views}/task_views.xml (100%) diff --git a/__init__.py b/bemade_fsm/__init__.py similarity index 100% rename from __init__.py rename to bemade_fsm/__init__.py diff --git a/__manifest__.py b/bemade_fsm/__manifest__.py similarity index 100% rename from __manifest__.py rename to bemade_fsm/__manifest__.py diff --git a/models/__init__.py b/bemade_fsm/models/__init__.py similarity index 100% rename from models/__init__.py rename to bemade_fsm/models/__init__.py diff --git a/models/equipment.py b/bemade_fsm/models/equipment.py similarity index 100% rename from models/equipment.py rename to bemade_fsm/models/equipment.py diff --git a/models/product_template.py b/bemade_fsm/models/product_template.py similarity index 100% rename from models/product_template.py rename to bemade_fsm/models/product_template.py diff --git a/models/res_partner.py b/bemade_fsm/models/res_partner.py similarity index 100% rename from models/res_partner.py rename to bemade_fsm/models/res_partner.py diff --git a/models/sale_order.py b/bemade_fsm/models/sale_order.py similarity index 100% rename from models/sale_order.py rename to bemade_fsm/models/sale_order.py diff --git a/models/task.py b/bemade_fsm/models/task.py similarity index 100% rename from models/task.py rename to bemade_fsm/models/task.py diff --git a/models/task_template.py b/bemade_fsm/models/task_template.py similarity index 100% rename from models/task_template.py rename to bemade_fsm/models/task_template.py diff --git a/security/ir.model.access.csv b/bemade_fsm/security/ir.model.access.csv similarity index 100% rename from security/ir.model.access.csv rename to bemade_fsm/security/ir.model.access.csv diff --git a/static/tests/tours/task_equipment_tour.js b/bemade_fsm/static/tests/tours/task_equipment_tour.js similarity index 100% rename from static/tests/tours/task_equipment_tour.js rename to bemade_fsm/static/tests/tours/task_equipment_tour.js diff --git a/static/tests/tours/task_template_tour.js b/bemade_fsm/static/tests/tours/task_template_tour.js similarity index 100% rename from static/tests/tours/task_template_tour.js rename to bemade_fsm/static/tests/tours/task_template_tour.js diff --git a/tests/__init__.py b/bemade_fsm/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to bemade_fsm/tests/__init__.py diff --git a/tests/test_bemade_fsm_common.py b/bemade_fsm/tests/test_bemade_fsm_common.py similarity index 100% rename from tests/test_bemade_fsm_common.py rename to bemade_fsm/tests/test_bemade_fsm_common.py diff --git a/tests/test_equipment.py b/bemade_fsm/tests/test_equipment.py similarity index 100% rename from tests/test_equipment.py rename to bemade_fsm/tests/test_equipment.py diff --git a/bemade_fsm/tests/test_fsm_contact_setting.py b/bemade_fsm/tests/test_fsm_contact_setting.py new file mode 100644 index 0000000..a8874c2 --- /dev/null +++ b/bemade_fsm/tests/test_fsm_contact_setting.py @@ -0,0 +1,30 @@ +from odoo.tests import TransactionCase, HttpCase, tagged +from odoo import Command +from test_bemade_fsm_common import FSMManagerUserTransactionCase + +class SaleOrderFSMContactsCase(FSMManagerUserTransactionCase): + + def test_default_site_contacts(self): + # Make sure the SO pulls the defaults from the partner correctly + pass + + def test_default_billing_contacts(self): + # Make sure the SO pulls the defaults from the partner correctly + pass + + def test_default_workorder_contacts(self): + # Make sure the SO pulls the defaults from the partner correctly + pass + + def test_change_site_contacts(self): + # Make sure the SO contacts can be updated without feeding back to the partner + pass + + def test_change_workorder_contacts(self): + # Make sure the SO contacts can be updated without feeding back to the partner + pass + + def test_change_billing_contacts(self): + # Make sure the SO contacts can be updated without feedin back to the partner + pass + diff --git a/tests/test_task_template.py b/bemade_fsm/tests/test_task_template.py similarity index 100% rename from tests/test_task_template.py rename to bemade_fsm/tests/test_task_template.py diff --git a/tests/test_task_template_sale_order.py b/bemade_fsm/tests/test_task_template_sale_order.py similarity index 100% rename from tests/test_task_template_sale_order.py rename to bemade_fsm/tests/test_task_template_sale_order.py diff --git a/views/equipment.xml b/bemade_fsm/views/equipment.xml similarity index 100% rename from views/equipment.xml rename to bemade_fsm/views/equipment.xml diff --git a/views/menus.xml b/bemade_fsm/views/menus.xml similarity index 100% rename from views/menus.xml rename to bemade_fsm/views/menus.xml diff --git a/views/product_views.xml b/bemade_fsm/views/product_views.xml similarity index 100% rename from views/product_views.xml rename to bemade_fsm/views/product_views.xml diff --git a/views/res_partner.xml b/bemade_fsm/views/res_partner.xml similarity index 100% rename from views/res_partner.xml rename to bemade_fsm/views/res_partner.xml diff --git a/views/task_template_views.xml b/bemade_fsm/views/task_template_views.xml similarity index 100% rename from views/task_template_views.xml rename to bemade_fsm/views/task_template_views.xml diff --git a/views/task_views.xml b/bemade_fsm/views/task_views.xml similarity index 100% rename from views/task_views.xml rename to bemade_fsm/views/task_views.xml