[FIX] sale_timesheet: Billable project
taskID 2323997 closes odoo/odoo#56798 Related: odoo/enterprise#12793 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
@@ -666,8 +666,8 @@
|
||||
<group>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="partner_id" class="o_task_customer_field"/>
|
||||
<field name="partner_phone" widget="phone" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
||||
<field name="partner_email" widget="email" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
||||
<field name="partner_phone" widget="phone" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
||||
<field name="legend_blocked" invisible="1"/>
|
||||
<field name="legend_normal" invisible="1"/>
|
||||
<field name="legend_done" invisible="1"/>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='service_type']" position="after">
|
||||
<field name="service_tracking" widget="radio" attrs="{'invisible': [('type','!=','service')]}"/>
|
||||
<field name="project_id" attrs="{'invisible':[('service_tracking','!=','task_global_project')], 'required':[('service_tracking','==','task_global_project')]}"/>
|
||||
<field name="project_template_id" context="{'active_test': False}" attrs="{'invisible':[('service_tracking','not in',['task_in_project', 'project_only'])]}"/>
|
||||
<field name="project_id" context="{'default_allow_billable': True, 'default_bill_type': 'customer_task'}" attrs="{'invisible':[('service_tracking','!=','task_global_project')], 'required':[('service_tracking','==','task_global_project')]}"/>
|
||||
<field name="project_template_id" context="{'active_test': False, 'default_allow_billable': True, 'default_bill_type': 'customer_project', 'default_pricing_type': 'fixed_rate'}" attrs="{'invisible':[('service_tracking','not in',['task_in_project', 'project_only'])]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
string="Sales Order"
|
||||
groups="sales_team.group_sale_salesman"/>
|
||||
</div>
|
||||
<xpath expr="//field[@name='partner_email']" position="after">
|
||||
<xpath expr="//field[@name='partner_phone']" position="after">
|
||||
<field name="sale_line_id" string="Sales Order Item" attrs="{'invisible': ['|', ('partner_id', '=', False), '&', ('sale_order_id', '!=', False), ('sale_line_id', '=', False)]}" options='{"no_open": True}' readonly="1" context="{'create': False, 'edit': False, 'delete': False}" groups="sales_team.group_sale_salesman"/>
|
||||
<field name="sale_order_id" attrs="{'invisible': [('project_id', '!=', False)]}" groups="sales_team.group_sale_salesman"/>
|
||||
<field name="commercial_partner_id" invisible="1" />
|
||||
|
||||
@@ -23,11 +23,14 @@ class AccountAnalyticLine(models.Model):
|
||||
timesheet_invoice_id = fields.Many2one('account.move', string="Invoice", readonly=True, copy=False, help="Invoice created from the timesheet")
|
||||
non_allow_billable = fields.Boolean("Non-Billable", help="Your timesheet will not be billed.")
|
||||
|
||||
@api.depends('so_line.product_id', 'project_id', 'task_id', 'non_allow_billable')
|
||||
@api.depends('so_line.product_id', 'project_id', 'task_id', 'non_allow_billable', 'task_id.bill_type', 'task_id.pricing_type', 'task_id.non_allow_billable')
|
||||
def _compute_timesheet_invoice_type(self):
|
||||
non_allowed_billable = self.filtered('non_allow_billable')
|
||||
non_allowed_billable.timesheet_invoice_type = 'non_billable_timesheet'
|
||||
for timesheet in self - non_allowed_billable:
|
||||
non_allowed_billable_task = (self - non_allowed_billable).filtered(lambda t: t.task_id.bill_type == 'customer_project' and t.task_id.pricing_type == 'employee_rate' and t.task_id.non_allow_billable)
|
||||
non_allowed_billable_task.timesheet_invoice_type = 'non_billable'
|
||||
|
||||
for timesheet in self - non_allowed_billable - non_allowed_billable_task:
|
||||
if timesheet.project_id: # AAL will be set to False
|
||||
invoice_type = 'non_billable_project' if not timesheet.task_id else 'non_billable'
|
||||
if timesheet.task_id and timesheet.so_line.product_id.type == 'service':
|
||||
@@ -116,7 +119,7 @@ class AccountAnalyticLine(models.Model):
|
||||
return task.sale_line_id
|
||||
if task.pricing_type == 'fixed_rate':
|
||||
return task.sale_line_id
|
||||
elif task.pricing_type == 'employee_rate':
|
||||
elif task.pricing_type == 'employee_rate' and not task.non_allow_billable:
|
||||
map_entry = self.env['project.sale.line.employee.map'].search([('project_id', '=', task.project_id.id), ('employee_id', '=', employee.id)])
|
||||
if map_entry:
|
||||
return map_entry.sale_line_id
|
||||
|
||||
@@ -82,7 +82,8 @@ class Project(models.Model):
|
||||
@api.depends('pricing_type', 'allow_timesheets', 'allow_billable', 'sale_line_employee_ids', 'sale_line_employee_ids.employee_id', 'bill_type')
|
||||
def _compute_warning_employee_rate(self):
|
||||
projects = self.filtered(lambda p: p.allow_billable and p.allow_timesheets and p.bill_type == 'customer_project' and p.pricing_type == 'employee_rate')
|
||||
employees = self.env['account.analytic.line'].read_group([('project_id', 'in', projects.ids)], ['employee_id', 'project_id'], ['employee_id', 'project_id'], lazy=False)
|
||||
tasks = projects.task_ids.filtered(lambda t: not t.non_allow_billable)
|
||||
employees = self.env['account.analytic.line'].read_group([('task_id', 'in', tasks.ids)], ['employee_id', 'project_id'], ['employee_id', 'project_id'], lazy=False)
|
||||
dict_project_employee = defaultdict(list)
|
||||
for line in employees:
|
||||
dict_project_employee[line['project_id'][0]] += [line['employee_id'][0]]
|
||||
@@ -171,6 +172,18 @@ class Project(models.Model):
|
||||
},
|
||||
}
|
||||
|
||||
def action_view_so(self):
|
||||
self.ensure_one()
|
||||
action_window = {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "sale.order",
|
||||
"name": "Sales Order",
|
||||
"views": [[False, "form"]],
|
||||
"context": {"create": False, "show_sale": True},
|
||||
"res_id": self.sale_order_id.id
|
||||
}
|
||||
return action_window
|
||||
|
||||
|
||||
class ProjectTask(models.Model):
|
||||
_inherit = "project.task"
|
||||
@@ -204,6 +217,8 @@ class ProjectTask(models.Model):
|
||||
'|', ('company_id', '=', False), ('company_id', '=', company_id)]""",
|
||||
help='Select a Service product with which you would like to bill your time spent on this task.')
|
||||
|
||||
non_allow_billable = fields.Boolean("Non-Billable", help="Your timesheets linked to this task will not be billed.")
|
||||
|
||||
@api.depends(
|
||||
'allow_billable', 'allow_timesheets', 'sale_order_id')
|
||||
def _compute_display_create_order(self):
|
||||
@@ -247,10 +262,12 @@ class ProjectTask(models.Model):
|
||||
for task in self:
|
||||
task.analytic_account_active = task.analytic_account_active or task.analytic_account_id.active
|
||||
|
||||
@api.depends('sale_line_id', 'project_id', 'allow_billable', 'bill_type')
|
||||
@api.depends('sale_line_id', 'project_id', 'allow_billable', 'bill_type', 'pricing_type', 'non_allow_billable')
|
||||
def _compute_sale_order_id(self):
|
||||
for task in self:
|
||||
if task.allow_billable and task.bill_type == 'customer_project':
|
||||
if task.allow_billable and task.bill_type == 'customer_project' and task.pricing_type == 'employee_rate' and task.non_allow_billable:
|
||||
task.sale_order_id = False
|
||||
elif task.allow_billable and task.bill_type == 'customer_project':
|
||||
task.sale_order_id = task.project_id.sale_order_id
|
||||
elif task.allow_billable and task.bill_type == 'customer_task':
|
||||
task.sale_order_id = task.sale_line_id.sudo().order_id
|
||||
@@ -298,6 +315,19 @@ class ProjectTask(models.Model):
|
||||
project = self.env['project.project'].browse(values.get('project_id'))
|
||||
if project.allow_timesheets:
|
||||
timesheet_ids.write({'project_id': values.get('project_id')})
|
||||
if 'non_allow_billable' in values and self.filtered('allow_timesheets').sudo().timesheet_ids:
|
||||
timesheet_ids = self.filtered('allow_timesheets').timesheet_ids.filtered(
|
||||
lambda t: (not t.timesheet_invoice_id or t.timesheet_invoice_id.state == 'cancel')
|
||||
)
|
||||
if values['non_allow_billable']:
|
||||
timesheet_ids.write({'so_line': False})
|
||||
else:
|
||||
# We write project on timesheet lines to call _timesheet_preprocess. This function will set correct the SOL
|
||||
for project in timesheet_ids.project_id:
|
||||
current_timesheet_ids = timesheet_ids.filtered(lambda t: t.project_id == project)
|
||||
for employee in current_timesheet_ids.employee_id:
|
||||
current_timesheet_ids.filtered(lambda t: t.employee_id == employee).write({'project_id': project.id})
|
||||
|
||||
return res
|
||||
|
||||
def action_make_billable(self):
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
<field name="arch" type="xml">
|
||||
<div class="oe_button_box" position="inside">
|
||||
<button string="Project Overview" class="oe_stat_button" type="object" name="action_view_timesheet" icon="fa-puzzle-piece" attrs="{'invisible': [('allow_billable', '=', False)]}"/>
|
||||
<button class="d-none d-md-inline oe_stat_button"
|
||||
type="object" name="action_view_so" icon="fa-dollar"
|
||||
attrs="{'invisible': ['|', '|', ('allow_billable', '=', False), ('sale_order_id', '=', False), ('bill_type', '!=', 'customer_project')]}"
|
||||
string="Sales Order"
|
||||
groups="sales_team.group_sale_salesman"/>
|
||||
</div>
|
||||
<xpath expr="//header" position="inside">
|
||||
<button name="action_make_billable" string="Create Sales Order" type="object" attrs="{'invisible': [('display_create_order', '=', False)]}" groups="sales_team.group_sale_salesman"/>
|
||||
@@ -18,9 +23,13 @@
|
||||
<field name="display_create_order" invisible="1"/>
|
||||
<field name="bill_type" widget="radio"/>
|
||||
<field name="pricing_type" attrs="{'invisible': ['|', ('allow_billable', '=', False), ('bill_type', '!=', 'customer_project')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" widget="radio"/>
|
||||
<field name="timesheet_product_id" attrs="{'invisible': ['|', '|', ('allow_timesheets', '=', False), ('sale_order_id', '!=', False), '&', ('pricing_type', '!=', 'fixed_rate'), ('bill_type', '!=', 'customer_task')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" context="{'default_type': 'service'}"/>
|
||||
<field name="sale_order_id" attrs="{'invisible': ['|', ('sale_order_id', '=', False), ('bill_type', '!=', 'customer_project')], 'readonly': [('sale_order_id', '!=', False)]}" context="{'default_partner_id': partner_id}"/>
|
||||
<field name="sale_line_id" attrs="{'invisible': ['|', '|', ('sale_order_id', '=', False), ('bill_type', '!=', 'customer_project'), ('pricing_type', '!=', 'fixed_rate')], 'readonly': [('sale_order_id', '=', False)]}" options="{'no_create': True, 'no_edit': True, 'delete': False}"/>
|
||||
<div class="o_td_label" attrs="{'invisible': ['|', '|', ('allow_timesheets', '=', False), ('sale_order_id', '!=', False), '&', ('pricing_type', '!=', 'fixed_rate'), ('bill_type', '!=', 'customer_task')]}">
|
||||
<label for="timesheet_product_id" string="Default Service" attrs="{'invisible': [('bill_type', '!=', 'customer_task')]}"/>
|
||||
<label for="timesheet_product_id" string="Service" attrs="{'invisible': [('bill_type', '=', 'customer_task')]}"/>
|
||||
</div>
|
||||
<field name="timesheet_product_id" nolabel="1" attrs="{'invisible': ['|', '|', ('allow_timesheets', '=', False), ('sale_order_id', '!=', False), '&', ('pricing_type', '!=', 'fixed_rate'), ('bill_type', '!=', 'customer_task')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" context="{'default_type': 'service', 'default_service_policy': 'delivered_timesheet', 'default_service_type': 'timesheet'}"/>
|
||||
<field name="sale_order_id" invisible="1"/>
|
||||
<field name="sale_line_id" string="Default Sales Order Item" attrs="{'invisible': ['|', '|', ('sale_order_id', '=', False), ('bill_type', '!=', 'customer_project'), ('pricing_type', '!=', 'fixed_rate')], 'readonly': [('sale_order_id', '=', False)]}" options="{'no_create': True, 'no_edit': True, 'delete': False}"/>
|
||||
</group>
|
||||
<field name="sale_line_employee_ids" attrs="{'invisible': ['|', ('bill_type', '!=', 'customer_project'), ('pricing_type', '!=', 'employee_rate')]}">
|
||||
<tree editable="top">
|
||||
@@ -64,7 +73,11 @@
|
||||
<field name="bill_type" attrs="{'invisible': ['|', ('allow_billable', '=', False), ('allow_timesheets', '=', False)], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" widget="radio"/>
|
||||
<field name="partner_id" attrs="{'invisible': ['|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), ('bill_type', '!=', 'customer_project')], 'required': [('bill_type', '=', 'customer_project')]}"/>
|
||||
<field name="pricing_type" attrs="{'invisible': ['|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), ('bill_type', '!=', 'customer_project')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" widget="radio"/>
|
||||
<field name="timesheet_product_id" string="Default Service" attrs="{'invisible': ['|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), '&', ('bill_type', '=', 'customer_project'), ('pricing_type', '=', 'employee_rate')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}"/>
|
||||
<div class="o_td_label" attrs="{'invisible': ['|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), '&', ('bill_type', '=', 'customer_project'), ('pricing_type', '=', 'employee_rate')]}">
|
||||
<label for="timesheet_product_id" string="Default Service" attrs="{'invisible': [('bill_type', '!=', 'customer_task')]}"/>
|
||||
<label for="timesheet_product_id" string="Service" attrs="{'invisible': [('bill_type', '=', 'customer_task')]}"/>
|
||||
</div>
|
||||
<field name="timesheet_product_id" nolabel="1" attrs="{'invisible': ['|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), '&', ('bill_type', '=', 'customer_project'), ('pricing_type', '=', 'employee_rate')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" context="{'default_type': 'service', 'default_service_policy': 'delivered_timesheet', 'default_service_type': 'timesheet'}"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
@@ -80,7 +93,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_kanban_card_manage_pane')]" position="after">
|
||||
<t t-if="record.warning_employee_rate.raw_value">
|
||||
<i class="float-right mx-4 fa fa-exclamation-triangle text-danger" role="img" title="Some of the employees who are recording time on this project are not linked to any Sales Order Item. This means that their time will be considered as non-billable." aria-label="Some of the employees who are recording time on this project are not linked to any Sales Order Item. This means that their time will be considered as non-billable."/>
|
||||
<i class="float-right mr-5 mt-1 fa fa-exclamation-triangle text-danger" role="img" title="Some of the employees who are recording time on this project are not linked to any Sales Order Item. This means that their time will be considered as non-billable." aria-label="Some of the employees who are recording time on this project are not linked to any Sales Order Item. This means that their time will be considered as non-billable."/>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//a[@name='action_view_account_analytic_line']" position="attributes">
|
||||
@@ -125,15 +138,16 @@
|
||||
<field name="is_project_map_empty" invisible="1"/>
|
||||
<field name="has_multi_sol" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='partner_email']" position="after">
|
||||
<xpath expr="//field[@name='partner_phone']" position="after">
|
||||
<field name="bill_type" invisible="1"/>
|
||||
<field name="pricing_type" invisible="1"/>
|
||||
<field name="timesheet_product_id" attrs="{'invisible': ['|', '|', '|', '|', ('allow_billable', '=', False), ('partner_id', '=', False), ('allow_timesheets', '=', False), ('sale_order_id', '!=', False), '&', ('pricing_type', '!=', 'fixed_rate'), ('bill_type', '!=', 'customer_task')]}" context="{'default_type': 'service'}" placeholder="Leave empty if non-billable"/>
|
||||
<field name="timesheet_product_id" attrs="{'invisible': ['|', '|', '|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), '&', ('partner_id', '=', False), ('bill_type', '=', 'customer_project'), ('sale_order_id', '!=', False), '&', ('pricing_type', '!=', 'fixed_rate'), ('bill_type', '!=', 'customer_task')]}" context="{'default_type': 'service', 'default_service_policy': 'delivered_timesheet', 'default_service_type': 'timesheet'}" placeholder="Leave empty if non-billable"/>
|
||||
<field name="non_allow_billable" attrs="{'invisible': ['|', '|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), ('pricing_type', '!=', 'employee_rate'), ('bill_type', '=', 'customer_task')]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='timesheet_ids']/tree" position="inside">
|
||||
<field name="timesheet_invoice_id" invisible="1"/>
|
||||
<field name="so_line" readonly="1" attrs="{'column_invisible': [('parent.has_multi_sol', '=', False), '|', ('parent.is_project_map_empty', '=', True), ('parent.pricing_type', '!=', 'employee_rate')]}" optional="hide"/>
|
||||
<field name="non_allow_billable" attrs="{'column_invisible': ['|', ('parent.pricing_type', '=', 'employee_rate'), '&', ('parent.timesheet_product_id', '=', False), ('parent.sale_line_id', '=', False)]}" optional="hide"/>
|
||||
<field name="non_allow_billable" attrs="{'column_invisible': ['|', '&', ('parent.timesheet_product_id', '=', False), ('parent.sale_line_id', '=', False), '&', '&', ('parent.bill_type', '=', 'customer_project'), ('parent.pricing_type', '=', 'employee_rate'), ('parent.non_allow_billable', '=', True)]}" optional="hide"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -145,7 +159,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='sale_line_id']" position="attributes">
|
||||
<attribute name="attrs">
|
||||
{'invisible': ['|', '|', ('sale_order_id', '=', False), '&', ('bill_type', '=', 'customer_project'), ('pricing_type', '=', 'employee_rate'), ('partner_id', '=', False)], 'readonly': [('sale_order_id', '=', False)]}
|
||||
{'invisible': ['|', '|', '&', ('sale_order_id', '=', False), ('bill_type', '!=', 'customer_task'), '&', ('bill_type', '=', 'customer_project'), ('pricing_type', '=', 'employee_rate'), ('partner_id', '=', False)]}
|
||||
</attribute>
|
||||
<attribute name="placeholder">Leave empty if non-billable</attribute>
|
||||
</xpath>
|
||||
|
||||
@@ -32,7 +32,7 @@ class ProjectCreateSalesOrder(models.TransientModel):
|
||||
(0, 0, {
|
||||
'employee_id': e.employee_id.id,
|
||||
'product_id': e.timesheet_product_id.id or default_product.id,
|
||||
'price_unit': e.price_unit or default_product.lst_price
|
||||
'price_unit': e.price_unit if e.timesheet_product_id else default_product.lst_price
|
||||
}) for e in project.sale_line_employee_ids]
|
||||
employee_from_timesheet = project.task_ids.timesheet_ids.employee_id - project.sale_line_employee_ids.employee_id
|
||||
result['line_ids'] += [
|
||||
@@ -61,6 +61,28 @@ class ProjectCreateSalesOrder(models.TransientModel):
|
||||
domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]")
|
||||
|
||||
line_ids = fields.One2many('project.create.sale.order.line', 'wizard_id', string='Lines')
|
||||
info_invoice = fields.Char(compute='_compute_info_invoice')
|
||||
|
||||
@api.depends('sale_order_id', 'link_selection')
|
||||
def _compute_info_invoice(self):
|
||||
for line in self:
|
||||
tasks = line.project_id.tasks.filtered(lambda t: not t.non_allow_billable)
|
||||
domain = self.env['sale.order.line']._timesheet_compute_delivered_quantity_domain()
|
||||
timesheet = self.env['account.analytic.line'].read_group(domain + [('task_id', 'in', tasks.ids), ('so_line', '=', False), ('timesheet_invoice_id', '=', False)], ['unit_amount'], ['task_id'])
|
||||
unit_amount = round(timesheet[0].get('unit_amount', 0), 2) if timesheet else 0
|
||||
if not unit_amount:
|
||||
line.info_invoice = False
|
||||
continue
|
||||
company_uom = self.env.company.timesheet_encode_uom_id
|
||||
label = _("hours")
|
||||
if company_uom == self.env.ref('uom.product_uom_day'):
|
||||
label = _("days")
|
||||
if line.link_selection == 'create':
|
||||
line.info_invoice = _("%(amount)s %(label)s will be added to the new Sales Order.", amount=unit_amount, label=label)
|
||||
elif line.sale_order_id:
|
||||
line.info_invoice = _("%(amount)s %(label)s will be added to the selected Sales Order.", amount=unit_amount, label=label)
|
||||
else:
|
||||
line.info_invoice = False
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def _onchange_partner_id(self):
|
||||
@@ -90,9 +112,12 @@ class ProjectCreateSalesOrder(models.TransientModel):
|
||||
lines_already_present[wizard_line.employee_id.id].write({
|
||||
'sale_line_id': wizard_line.sale_line_id.id
|
||||
})
|
||||
|
||||
self.project_id.tasks.filtered(lambda task: task.non_allow_billable).sale_line_id = False
|
||||
tasks = self.project_id.tasks.filtered(lambda t: not t.non_allow_billable)
|
||||
# assign SOL to timesheets
|
||||
for map_entry in self.project_id.sale_line_employee_ids:
|
||||
self.env['account.analytic.line'].search([('task_id', 'in', self.project_id.tasks.ids), ('employee_id', '=', map_entry.employee_id.id), ('so_line', '=', False)]).write({
|
||||
self.env['account.analytic.line'].search([('task_id', 'in', tasks.ids), ('employee_id', '=', map_entry.employee_id.id), ('so_line', '=', False)]).write({
|
||||
'so_line': map_entry.sale_line_id.id
|
||||
})
|
||||
else:
|
||||
@@ -219,6 +244,7 @@ class ProjectCreateSalesOrder(models.TransientModel):
|
||||
lines_already_present = dict([(l.employee_id.id, l) for l in self.project_id.sale_line_employee_ids])
|
||||
|
||||
non_billable_tasks = self.project_id.tasks.filtered(lambda task: not task.sale_line_id)
|
||||
non_allow_billable_tasks = self.project_id.tasks.filtered(lambda task: task.non_allow_billable)
|
||||
|
||||
map_entries = self.env['project.sale.line.employee.map']
|
||||
EmployeeMap = self.env['project.sale.line.employee.map'].sudo()
|
||||
@@ -264,10 +290,12 @@ class ProjectCreateSalesOrder(models.TransientModel):
|
||||
'partner_id': sale_order.partner_id.id,
|
||||
'email_from': sale_order.partner_id.email,
|
||||
})
|
||||
non_allow_billable_tasks.sale_line_id = False
|
||||
|
||||
tasks = self.project_id.tasks.filtered(lambda t: not t.non_allow_billable)
|
||||
# assign SOL to timesheets
|
||||
for map_entry in map_entries:
|
||||
self.env['account.analytic.line'].search([('task_id', 'in', self.project_id.tasks.ids), ('employee_id', '=', map_entry.employee_id.id), ('so_line', '=', False)]).write({
|
||||
self.env['account.analytic.line'].search([('task_id', 'in', tasks.ids), ('employee_id', '=', map_entry.employee_id.id), ('so_line', '=', False)]).write({
|
||||
'so_line': map_entry.sale_line_id.id
|
||||
})
|
||||
|
||||
@@ -282,7 +310,7 @@ class ProjectCreateSalesOrderLine(models.TransientModel):
|
||||
wizard_id = fields.Many2one('project.create.sale.order', required=True)
|
||||
product_id = fields.Many2one('product.product', domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')], string="Service",
|
||||
help="Product of the sales order item. Must be a service invoiced based on timesheets on tasks.")
|
||||
price_unit = fields.Float("Unit Price", compute="_compute_price_unit", store=True, readonly=False, default=1.0, help="Unit price of the sales order item.")
|
||||
price_unit = fields.Float("Unit Price", help="Unit price of the sales order item.")
|
||||
currency_id = fields.Many2one('res.currency', string="Currency")
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", help="Employee that has timesheets on the project.")
|
||||
sale_line_id = fields.Many2one('sale.order.line', "Sale Order Item", compute='_compute_sale_line_id', store=True, readonly=False)
|
||||
@@ -291,15 +319,14 @@ class ProjectCreateSalesOrderLine(models.TransientModel):
|
||||
('unique_employee_per_wizard', 'UNIQUE(wizard_id, employee_id)', "An employee cannot be selected more than once in the mapping. Please remove duplicate(s) and try again."),
|
||||
]
|
||||
|
||||
@api.depends('product_id', 'sale_line_id')
|
||||
def _compute_price_unit(self):
|
||||
for line in self:
|
||||
if line.wizard_id.link_selection == 'link':
|
||||
line.price_unit = line.sale_line_id.price_unit
|
||||
line.currency_id = line.sale_line_id.currency_id
|
||||
else:
|
||||
line.price_unit = line.product_id.lst_price or 0
|
||||
line.currency_id = line.product_id.currency_id
|
||||
@api.onchange('product_id', 'sale_line_id')
|
||||
def _onchange_product_id(self):
|
||||
if self.wizard_id.link_selection == 'link':
|
||||
self.price_unit = self.sale_line_id.price_unit
|
||||
self.currency_id = self.sale_line_id.currency_id
|
||||
else:
|
||||
self.price_unit = self.product_id.lst_price or 0
|
||||
self.currency_id = self.product_id.currency_id
|
||||
|
||||
@api.depends('wizard_id.sale_order_id')
|
||||
def _compute_sale_line_id(self):
|
||||
|
||||
@@ -25,13 +25,16 @@
|
||||
<field name="line_ids" nolabel="1" attrs="{'required': [('pricing_type', '=', 'employee_rate')]}">
|
||||
<tree editable="bottom">
|
||||
<field name="employee_id" options="{'no_create_edit': True, 'no_create': True}" attrs="{'column_invisible': [('parent.pricing_type', '=', 'fixed_rate')], 'required': [('parent.pricing_type', '=', 'employee_rate')]}"/>
|
||||
<field name="sale_line_id" options="{'no_create_edit': True, 'no_create': True}" domain="[('is_service', '=', True), ('order_id', '=', parent.sale_order_id)]" attrs="{'column_invisible': ['|', ('parent.link_selection', '!=', 'link'), ('parent.pricing_type', '=', 'fixed_rate')], 'required': ['&', ('parent.link_selection', '=', 'link'), ('parent.pricing_type', '=', 'employee_rate')]}"/>
|
||||
<field name="sale_line_id" options="{'no_create_edit': True, 'no_create': True, 'no_open': True}" domain="[('is_service', '=', True), ('order_id', '=', parent.sale_order_id)]" attrs="{'column_invisible': ['|', ('parent.link_selection', '!=', 'link'), ('parent.pricing_type', '=', 'fixed_rate')], 'required': ['&', ('parent.link_selection', '=', 'link'), ('parent.pricing_type', '=', 'employee_rate')]}"/>
|
||||
<field name="product_id" options="{'no_create_edit': True, 'no_create': True}" attrs="{'column_invisible': [('parent.link_selection', '=', 'link')], 'required': [('parent.link_selection', '!=', 'link')]}"/>
|
||||
<field name="price_unit" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}" attrs="{'readonly': [('parent.link_selection', '=', 'link')]}"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('info_invoice', '=', False)]}">
|
||||
<field name="info_invoice" nolabel="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Link to Sales Order" type="object" name="action_link_sale_order" class="oe_highlight" attrs="{'invisible': [('link_selection', '!=', 'link')]}"/>
|
||||
<button string="Create Sales Order" type="object" name="action_create_sale_order" class="oe_highlight" attrs="{'invisible': [('link_selection', '=', 'link')]}"/>
|
||||
|
||||
@@ -50,14 +50,17 @@ class ProjectTaskCreateSalesOrder(models.TransientModel):
|
||||
domain = self.env['sale.order.line']._timesheet_compute_delivered_quantity_domain()
|
||||
timesheet = self.env['account.analytic.line'].read_group(domain + [('task_id', '=', self.task_id.id), ('so_line', '=', False), ('timesheet_invoice_id', '=', False)], ['unit_amount'], ['task_id'])
|
||||
unit_amount = round(timesheet[0].get('unit_amount', 0), 2) if timesheet else 0
|
||||
if not unit_amount:
|
||||
line.info_invoice = False
|
||||
continue
|
||||
company_uom = self.env.company.timesheet_encode_uom_id
|
||||
label = _("hours")
|
||||
if company_uom == self.env.ref('uom.product_uom_day'):
|
||||
label = _("days")
|
||||
if line.link_selection == 'create' and line.price_unit:
|
||||
line.info_invoice = _("%(amount)s %(label)s will be added to the new Sales Order Item.", amount=unit_amount, label=label)
|
||||
line.info_invoice = _("%(amount)s %(label)s will be added to the new Sales Order.", amount=unit_amount, label=label)
|
||||
elif line.sale_line_id:
|
||||
line.info_invoice = _("%(amount)s %(label)s will be added to the selected Sales Order Item.", amount=unit_amount, label=label)
|
||||
line.info_invoice = _("%(amount)s %(label)s will be added to the selected Sales Order.", amount=unit_amount, label=label)
|
||||
else:
|
||||
line.info_invoice = False
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
<field name="sale_line_id" options="{'no_create': True, 'no_create_edit': True}" attrs="{'required': [('link_selection', '=', 'link')]}"/>
|
||||
<field name="commercial_partner_id" invisible="1"/>
|
||||
</group>
|
||||
<field name="info_invoice"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('info_invoice', '=', False)]}">
|
||||
<field name="info_invoice" nolabel="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Link to Sales Order" type="object" name="action_link_sale_order" class="oe_highlight" attrs="{'invisible': [('link_selection', '!=', 'link')]}"/>
|
||||
|
||||
Reference in New Issue
Block a user