From b2a42e4ce8dbe5f2abaa977952cafebc2f3f8278 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Fri, 9 Jun 2023 10:24:06 -0400 Subject: [PATCH] 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 + +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +