[ADD] timesheet_exception: timesheet created should not exceed 8 in spent hours

H11047
This commit is contained in:
percyhibou
2022-10-01 04:11:39 +00:00
parent 58666c3806
commit 368770f814
12 changed files with 340 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import models
from . import wizard

View File

@@ -0,0 +1,30 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
{
'name': "Timesheet Exception Rule",
'version': '15.0.1.0.0',
'author': "Hibou Corp.",
'license': 'OPL-1',
'category': 'Generic Modules',
'summary': """
Module for Timesheet Exception Rule""",
'description': """
The ability to run and trigger exceptions to block the moving of a timesheet based on rules
""",
'website': "http://www.hibou.io/",
'depends': [
'base_exception_user',
'timesheet_grid',
'sale_timesheet' # We need to clarify if this module has to be installed/present to run the Tests
],
'data': [
'security/ir.model.access.csv',
'views/timesheet_views.xml',
'wizard/timesheet_exception_confirm_views.xml',
],
'demo': [
'demo/timesheet_exception_demo.xml',
],
'installable': True,
'auto_install': False,
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="except_unit_amount_over_eight_hours" model="exception.rule">
<field name="name">Unit amount exceeds 8 hrs</field>
<field name="description">Unit amount over 8 hrs</field>
<field name="sequence">50</field>
<field name="model">account.analytic.line</field>
<field name="code">if timesheet.unit_amount > 8: failed=True</field>
<field name="active" eval="False"/>
</record>
<record id="except_unit_amount_over_ordered_quantity" model="exception.rule">
<field name="name">SO delivered qty plus unit amount exceeds ordered qty</field>
<field name="description">SO delivered qty plus unit amount exceeds ordered qty</field>
<field name="sequence">50</field>
<field name="model">account.analytic.line</field>
<field name="code">if timesheet.so_line.product_template_id.service_policy == 'ordered_timesheet' and timesheet.so_line.qty_delivered + timesheet.unit_amount > timesheet.so_line.product_uom_qty: failed=True</field>
<field name="active" eval="False"/>
</record>
</odoo>

View File

@@ -0,0 +1,3 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import analytic

View File

@@ -0,0 +1,59 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, models, fields
class ExceptionRule(models.Model):
_inherit = 'exception.rule'
model = fields.Selection(
selection_add=[
('account.analytic.line', 'AnalyticLine'),
],
ondelete={
'account.analytic.line': 'cascade',
},
)
timesheet_ids = fields.Many2many(
'account.analytic.line',
string="Timesheet")
class AnalyticLine(models.Model):
_inherit = ['account.analytic.line', 'base.exception']
_name = 'account.analytic.line'
_order = 'main_exception_id asc'
@api.model
def create(self, values):
res = super().create(values)
res.detect_exceptions()
return res
@api.model
def _exception_rule_eval_context(self, rec):
res = super(AnalyticLine, self)._exception_rule_eval_context(rec)
res['timesheet'] = rec
return res
@api.model
def _reverse_field(self):
return 'timesheet_ids'
def write(self, vals):
if not vals.get('ignore_exception'):
for timesheet in self:
if timesheet.detect_exceptions():
return self._popup_exceptions()
return super().write(vals)
@api.model
def _get_popup_action(self):
return self.env.ref('timesheet_exception.action_timesheet_exception_confirm')
def detect_exceptions(self):
res = False
if not self._context.get("detect_exceptions"):
self = self.with_context(detect_exceptions=True)
res = super(AnalyticLine, self).detect_exceptions()
return res

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
timesheet_exception.access_timesheet_exception_confirm,access_timesheet_exception_confirm,timesheet_exception.model_timesheet_exception_confirm,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 timesheet_exception.access_timesheet_exception_confirm access_timesheet_exception_confirm timesheet_exception.model_timesheet_exception_confirm base.group_user 1 1 1 1

View File

@@ -0,0 +1,2 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import test_timesheet_exception

View File

@@ -0,0 +1,99 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import fields
from odoo.tests import common, Form
class TestTimesheetException(common.TransactionCase):
def setUp(self):
super().setUp()
self.env = self.env(context=dict(self.env.context, tracking_disable=True))
def test_timesheet_create_exception(self):
exception = self.env.ref('timesheet_exception.except_unit_amount_over_eight_hours')
exception.active = True
project = self.env['project.project'].with_context(tracking_disable=True).create({
'name': 'Project for selling timesheets',
})
today = fields.Date.today()
service_category_id = self.env['product.category'].create({
'name': 'Services',
'parent_id': self.env.ref('product.product_category_1').id,
}).id
uom_hour_id = self.env.ref('uom.product_uom_hour').id
product = self.env['product.product'].create({
'name': 'Service Product (Prepaid Hours)',
'categ_id': service_category_id,
'type': 'service',
'list_price': 250.00,
'standard_price': 190.00,
'invoice_policy': 'delivery',
'uom_id': uom_hour_id,
'uom_po_id': uom_hour_id,
'default_code': 'SERV-DELI1',
'taxes_id': False,
})
product.product_tmpl_id.service_policy = 'ordered_timesheet'
partner = self.env['res.partner'].create({
'name': 'Test Partner',
'company_id': False,
})
so = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
'partner_id': partner.id,
'project_id': project.id,
})
so_line = self.env['sale.order.line'].create([{
'order_id': so.id,
'name': product.name,
'product_id': product.id,
'product_uom_qty': 10,
'qty_delivered': 2,
'price_unit': product.list_price,
'project_id': project.id,
}])
timesheet = self.env['account.analytic.line'].with_user(self.env.user).create({
'name': "my timesheet 1",
'project_id': project.id,
'date': today,
'unit_amount': 9.0,
})
# Created exceptions on create.
self.assertTrue(timesheet.exception_ids)
# Will return action on write, which may or not be followed.
action = timesheet.write({
'name': 'Test Timesheet - Test Written',
})
self.assertTrue(timesheet.exception_ids)
self.assertTrue(action)
self.assertEqual(action.get('res_model'), 'timesheet.exception.confirm')
# Simulation the opening of the wizard timesheet_exception_confirm and
# set ignore_exception to True
timesheet_exception_confirm = Form(self.env[action['res_model']].with_context(action['context'])).save()
timesheet_exception_confirm.ignore = True
timesheet_exception_confirm.action_confirm()
self.assertTrue(timesheet.ignore_exception)
# No exceptions should be on create with a lower than 8 in unit_amount
# Also no exceptions because timesheet.so_line.product_template_id.service_policy == 'ordered_timesheet' and timesheet.so_line.qty_delivered + timesheet.unit_amount < timesheet.so_line.product_uom_qty
timesheet = self.env['account.analytic.line'].with_user(self.env.user).create({
'name': "my timesheet 1",
'project_id': project.id,
'date': today,
'unit_amount': 7.0,
})
self.assertFalse(timesheet.exception_ids)
# timesheet.unit_amount = 17.0
timesheet = self.env['account.analytic.line'].with_user(self.env.user).create({
'name': "my timesheet 1",
'project_id': project.id,
'date': today,
'unit_amount': 17.0,
})
# Exceptions because timesheet.so_line.product_template_id.service_policy == 'ordered_timesheet' and timesheet.so_line.qty_delivered + timesheet.unit_amount > timesheet.so_line.product_uom_qty
self.assertTrue(timesheet.exception_ids)

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_timesheet_test_tree" model="ir.actions.act_window">
<field name="name">Timesheet Exception Rules</field>
<field name="res_model">exception.rule</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="base_exception.view_exception_rule_tree"/>
<field name="domain">[('model', '=', 'account.analytic.line')]</field>
<field name="context">{'active_test': False, 'default_model' : 'account.analytic.line'}</field>
</record>
<menuitem
action="action_timesheet_test_tree"
id="menu_timesheet_test"
sequence="10"
parent="timesheet_grid.menu_timesheet_grid_validate"
groups="base_exception.group_exception_rule_manager"
/>
<record id="my_timesheet_form_view" model="ir.ui.view">
<field name="name">account.analytic.line.form.inherit.exception</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="timesheet_grid.timesheet_view_form"/>
<field name="arch" type="xml">
<sheet position="before">
<div class="alert alert-danger" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': [('exceptions_summary','=',False)]}">
<p><strong>There are exceptions blocking the confirmation of this Timesheet:</strong></p>
<field name="exceptions_summary"/>
<button name="action_ignore_exceptions" type="object" class="btn-danger"
string="Ignore Exceptions" help="Click here to be able to confirm this Timesheet regardless of the exceptions."
groups="base_exception.group_exception_rule_manager"/>
</div>
</sheet>
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="ignore_exception" />
</xpath>
</field>
</record>
<record id="hr_timesheet_line_tree" model="ir.ui.view">
<field name="name">taccount.analytic.line.tree.inherit.exception</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
<field name="arch" type="xml">
<field name="unit_amount" position="after">
<field name="main_exception_id"/>
</field>
</field>
</record>
<record id="hr_timesheet_line_search" model="ir.ui.view">
<field name="name">taccount.analytic.line.search.form.inherit.exception</field>
<field name="model">account.analytic.line</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search" />
<field name="arch" type="xml">
<filter name="validated" position="after">
<separator orientation="vertical"/>
<filter icon="terp-emblem-important" name="tofix" string="Blocked by exceptions" domain="[('main_exception_id','!=',False)]"/>
</filter>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,3 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from . import timesheet_exception_confirm

View File

@@ -0,0 +1,26 @@
# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details.
from odoo import api, fields, models
class TimesheetExceptionConfirm(models.TransientModel):
_name = 'timesheet.exception.confirm'
_inherit = ['exception.rule.confirm']
_description = 'Timesheet Exception Confirm Wizard'
related_model_id = fields.Many2one('account.analytic.line', 'AnalyticLine')
def action_confirm(self):
self.ensure_one()
if self.ignore:
self.related_model_id.ignore_exception = True
res = super().action_confirm()
if self.ignore:
return True
else:
return res
def _action_ignore(self):
self.related_model_id.ignore_exception = True
super()._action_ignore()
return True

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_timesheet_exception_confirm" model="ir.ui.view">
<field name="name">Timesheet Exceptions Rules</field>
<field name="model">timesheet.exception.confirm</field>
<field name="inherit_id" ref="base_exception.view_exception_rule_confirm"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//footer" position="inside">
<button class="oe_link" special="cancel" string="Cancel"/>
</xpath>
</field>
</record>
<record id="action_timesheet_exception_confirm" model="ir.actions.act_window">
<field name="name">Blocked due to exceptions</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">timesheet.exception.confirm</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_timesheet_exception_confirm"/>
<field name="target">new</field>
</record>
</odoo>