[IMP] hr_expense: amend multicurrency

In order to make the expense form more intuitive, it was necessary to distinguish between whether the product has cost different from
 zero or not. Depending ot this context, some fields are shown or not. Also 'Bill Reference' is shown only to account managers.
 Now the expense can be submitted in different currency and it can be reimbursed in company’s currency.
 In case the latter two currencies differ from each other,
 the conversion is done automatically and the rate is also displayed.
 Suggestion - untaxed_amount can be removed

Task - 2366775

closes odoo/odoo#61920

Related: odoo/enterprise#14838
Related: odoo/upgrade#2028
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Sofie Gvaladze
2021-01-11 14:04:06 +00:00
committed by sofiagvaladze
parent d2217d1c36
commit 2de15d5c0c
2 changed files with 77 additions and 20 deletions
+50 -11
View File
@@ -5,7 +5,7 @@ import re
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import email_split, float_is_zero
from odoo.tools import email_split, float_is_zero, float_repr
class HrExpense(models.Model):
@@ -72,14 +72,14 @@ class HrExpense(models.Model):
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id',
compute='_compute_from_product_id_company_id', store=True, readonly=False,
domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]", string='Taxes')
# TODO SGV can be removed
untaxed_amount = fields.Float("Subtotal", store=True, compute='_compute_amount', digits='Account')
total_amount = fields.Monetary("Total", compute='_compute_amount', store=True, currency_field='currency_id', tracking=True)
amount_residual = fields.Monetary(string='Amount Due', compute='_compute_amount_residual')
company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='sheet_id.currency_id', store=True, readonly=False)
total_amount = fields.Monetary("Amount paid", compute='_compute_amount', store=True, currency_field='currency_id', tracking=True, readonly=False)
company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='company_id.currency_id', readonly=True)
total_amount_company = fields.Monetary("Total (Company Currency)", compute='_compute_total_amount_company', store=True, currency_field='company_currency_id')
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company)
# TODO make required in master (sgv)
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company.currency_id)
currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=False, store=True, states={'reported': [('readonly', True)], 'approved': [('readonly', True)], 'done': [('readonly', True)]}, compute='_compute_currency_id', default=lambda self: self.env.company.currency_id)
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', check_company=True)
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
account_id = fields.Many2one('account.account', compute='_compute_from_product_id_company_id', store=True, readonly=False, string='Account',
@@ -103,8 +103,32 @@ class HrExpense(models.Model):
is_editable = fields.Boolean("Is Editable By Current User", compute='_compute_is_editable')
is_ref_editable = fields.Boolean("Reference Is Editable By Current User", compute='_compute_is_ref_editable')
product_has_cost = fields.Boolean("Is product with non zero cost selected", compute='_compute_product_has_cost')
same_currency = fields.Boolean("Is currency_id different from the company_currency_id", compute='_compute_same_currency')
sample = fields.Boolean()
label_total_amount_company = fields.Char(compute='_compute_label_total_amount_company')
label_convert_rate = fields.Char(compute='_compute_label_convert_rate')
@api.depends("product_has_cost")
def _compute_currency_id(self):
for expense in self.filtered("product_has_cost"):
expense.currency_id = expense.company_currency_id
@api.depends_context('lang')
@api.depends("company_currency_id")
def _compute_label_total_amount_company(self):
for expense in self:
expense.label_total_amount_company = _("Total %s", expense.company_currency_id.name)
@api.depends('currency_id', 'company_currency_id')
def _compute_same_currency(self):
for expense in self:
expense.same_currency = bool(expense.currency_id and expense.currency_id == expense.company_currency_id)
@api.depends('product_id')
def _compute_product_has_cost(self):
for expense in self:
expense.product_has_cost = bool(expense.product_id and expense.unit_amount)
@api.depends('sheet_id', 'sheet_id.account_move_id', 'sheet_id.state')
def _compute_state(self):
@@ -141,17 +165,30 @@ class HrExpense(models.Model):
.filtered(lambda line: line.expense_id == self and line.account_internal_type in ('receivable', 'payable'))
expense.amount_residual = -sum(payment_term_lines.mapped(residual_field))
@api.depends('date', 'total_amount', 'company_currency_id')
@api.depends('date', 'total_amount', 'currency_id', 'company_currency_id')
def _compute_total_amount_company(self):
for expense in self:
amount = 0
if expense.company_currency_id:
date_expense = expense.date
if expense.same_currency:
amount = expense.total_amount
else:
date_expense = expense.date or fields.Date.today()
amount = expense.currency_id._convert(
expense.total_amount, expense.company_currency_id,
expense.company_id, date_expense or fields.Date.today())
expense.company_id, date_expense)
expense.total_amount_company = amount
@api.depends('date', 'total_amount', 'currency_id', 'company_currency_id')
def _compute_label_convert_rate(self):
records_with_diff_currency = self.filtered(lambda x: not x.same_currency and x.currency_id)
(self - records_with_diff_currency).label_convert_rate = False
for expense in records_with_diff_currency:
date_expense = expense.date or fields.Date.today()
rate = expense.currency_id._get_conversion_rate(
expense.currency_id, expense.company_currency_id, expense.company_id, date_expense)
rate_txt = _('1 %(exp_cur)s = %(rate)s %(comp_cur)s', exp_cur=expense.currency_id.name, rate=float_repr(rate, expense.company_currency_id.decimal_places), comp_cur=expense.company_currency_id.name)
expense.label_convert_rate = rate_txt
def _compute_attachment_number(self):
attachment_data = self.env['ir.attachment'].read_group([('res_model', '=', 'hr.expense'), ('res_id', 'in', self.ids)], ['res_id'], ['res_id'])
attachment = dict((data['res_id'], data['res_id_count']) for data in attachment_data)
@@ -428,7 +465,9 @@ Or send your receipts at <a href="mailto:%(email)s?subject=Lunch%%20with%%20cust
company_currency = expense.company_id.currency_id
move_line_values = []
taxes = expense.tax_ids.with_context(round=True).compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id)
unit_amount = expense.unit_amount or expense.total_amount
quantity = expense.quantity if expense.unit_amount else 1
taxes = expense.tax_ids.with_context(round=True).compute_all(unit_amount, expense.currency_id,quantity,expense.product_id)
total_amount = 0.0
total_amount_currency = 0.0
partner_id = expense.employee_id.sudo().address_home_id.commercial_partner_id.id
+27 -9
View File
@@ -48,10 +48,12 @@
<header>
<button name="action_submit_expenses" type="object" string="Create Report"/>
</header>
<field name="company_currency_id" invisible="1"/>
<field name="attachment_number" invisible="True"/>
<field name="date" optional="show"/>
<field name="accounting_date" optional="hide" groups="account.group_account_invoice,account.group_account_readonly" readonly="0"/>
<field name="product_id"/>
<field name="name"/>
<field name="accounting_date" optional="hide" groups="account.group_account_invoice,account.group_account_readonly" readonly="0"/>
<field name="employee_id" widget="many2one_avatar_employee"/>
<field name="sheet_id" optional="show" invisible="not context.get('show_report', False)" readonly="1"/>
<field name="payment_mode" optional="show"/>
@@ -66,6 +68,7 @@
<field name="activity_ids" widget="list_activity" optional="show"/>
<field name="tax_ids" optional="show" widget="many2many_tags" groups="account.group_account_user"/>
<field name="total_amount" optional="show" sum="Total Amount" widget="monetary" options="{'currency_field': 'currency_id'}" decoration-bf="True"/>
<field name="total_amount_company" optional="show" sum="Total Company Currency" widget='monetary' options="{'currency_field': 'company_currency_id'}" decoration-bf="True" />
<button name="action_get_attachment_view" string="Attachments" type="object" icon="fa-paperclip" attrs="{'invisible': [('attachment_number', '=', 0)]}"/>
<field name="message_unread" invisible="1"/>
<field name="state" optional="show" readonly="1" decoration-info="state == 'draft'" decoration-success="state in ['reported', 'approved', 'done']" decoration-danger="state in 'refused'" widget="badge"/>
@@ -131,33 +134,47 @@
</div>
<group>
<group>
<field name="product_has_cost" invisible="1"/>
<field name="same_currency" invisible="1"/>
<field name="is_editable" invisible="1"/>
<field name="is_ref_editable" invisible="1"/>
<field name="company_currency_id" invisible="1"/>
<field name="product_id" required="1" context="{'default_can_be_expensed': 1, 'tree_view_ref': 'hr_expense.product_product_expense_tree_view'}"
widget="many2one_barcode"
/>
<field name="unit_amount" required="1" widget="monetary" options="{'currency_field': 'currency_id', 'field_digits': True}"/>
<field name="unit_amount" required="1" widget="monetary" options="{'currency_field': 'currency_id', 'field_digits': True}" attrs="{'invisible': [('product_has_cost', '=', False)]}"/>
<field name="product_uom_category_id" invisible="1"/>
<label for="quantity"/>
<div class="o_row">
<label for="quantity" attrs="{'invisible': [('product_has_cost', '=', False)]}"/>
<div class="o_row" attrs="{'invisible': [('product_has_cost', '=', False)]}">
<field name="quantity" class="oe_inline"/>
<field name="product_uom_id" required="1" widget="selection" class="oe_inline" groups="uom.group_uom"/>
</div>
<field name="tax_ids" widget="many2many_tags" groups="account.group_account_readonly" attrs="{'readonly': [('is_editable', '=', False)]}" context="{'default_company_id': company_id}"/>
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>
<label for="total_amount" attrs="{'invisible': [('product_has_cost', '=', True)]}"/>
<div class="o_row" attrs="{'invisible': [('product_has_cost', '=', True)]}">
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>
<field name="currency_id" groups="base.group_multi_currency"/>
</div>
<field name="amount_residual" widget='monetary' options="{'currency_field': 'currency_id'}"/>
<div class="o_td_label" attrs="{'invisible': [('same_currency', '=', True), ('product_has_cost', '=', False)]}">
<field name="label_total_amount_company" nolabel="1" class="o_form_label" />
</div>
<div class="o_row" attrs="{'invisible': [('same_currency', '=', True), ('product_has_cost', '=', False)]}">
<field name="total_amount_company" widget='monetary' options="{'currency_field': 'company_currency_id'}" />
<field name="label_convert_rate"/>
</div>
<field name="tax_ids" widget="many2many_tags" groups="account.group_account_readonly" attrs="{'readonly': [('is_editable', '=', False)], 'invisible': [('product_has_cost', '=', True)]}" context="{'default_company_id': company_id}"/>
</group><group>
<field name="reference" attrs="{'readonly': [('is_ref_editable', '=', False)]}"/>
<field name="reference" groups="account.group_account_readonly" attrs="{'readonly': [('is_ref_editable', '=', False)], 'invisible': [('product_has_cost', '=', True)]}"/>
<field name="date"/>
<field name="accounting_date" attrs="{'invisible': ['|', ('accounting_date', '=', False), ('state', 'not in', ['approved', 'done'])]}" />
<field name="account_id" options="{'no_create': True}" domain="[('internal_type', '=', 'other'), ('company_id', '=', company_id)]" groups="account.group_account_readonly" attrs="{'readonly': [('is_editable', '=', False)]}" context="{'default_company_id': company_id}"/>
<field name="employee_id" groups="hr_expense.group_hr_expense_team_approver" context="{'default_company_id': company_id}"/>
<field name="sheet_id" invisible="1"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="analytic_account_id" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]" groups="analytic.group_analytic_accounting" attrs="{'readonly': [('is_editable', '=', False)]}"/>
<field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_tags" attrs="{'readonly': [('is_editable', '=', False)]}"/>
<field name="company_id" groups="base.group_multi_company"/>
</group><group>
</group><group attrs="{'invisible': [('product_has_cost', '=', True)]}">
<label for="payment_mode"/>
<div>
<field name="payment_mode" widget="radio"/>
@@ -703,6 +720,7 @@
<field name="expense_line_ids" nolabel="1" widget="many2many" domain="[('state', '=', 'draft'), ('employee_id', '=', employee_id), ('company_id', '=', company_id)]" options="{'reload_on_button': True}" context="{'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id}">
<tree decoration-danger="is_refused" editable="bottom">
<field name="date" optional="show"/>
<field name="product_id"/>
<field name="name"/>
<field name="state" invisible="1"/>
<field name="reference" optional="hide"/>