[IMP] hr_expense: Split outstanding lines per expense
When an expense sheet move is created, there is only one outstanding line, but it is usual to pay separately. That makes the reconciliation step harder. This makes sure one outstanding line is created per invoice_line and then if only one payment is made it can go through the same process as a batch payment Task-3292188 closes odoo/odoo#120450 Related: odoo/upgrade#4625 Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
@@ -88,13 +88,18 @@ class HrExpense(models.Model):
|
||||
("company_account", "Company")
|
||||
], default='own_account', tracking=True, states={'done': [('readonly', True)], 'approved': [('readonly', True)], 'reported': [('readonly', True)]}, string="Paid By")
|
||||
attachment_number = fields.Integer('Number of Attachments', compute='_compute_attachment_number')
|
||||
state = fields.Selection([
|
||||
('draft', 'To Submit'),
|
||||
('reported', 'Submitted'),
|
||||
('approved', 'Approved'),
|
||||
('done', 'Paid'),
|
||||
('refused', 'Refused')
|
||||
], compute='_compute_state', string='Status', copy=False, index=True, readonly=True, store=True, default='draft')
|
||||
state = fields.Selection(
|
||||
selection=[
|
||||
('draft', 'To Report'),
|
||||
('reported', 'To Submit'),
|
||||
('submitted', 'Submitted'),
|
||||
('approved', 'Approved'),
|
||||
('done', 'Done'),
|
||||
('refused', 'Refused')
|
||||
],
|
||||
string='Status', default='draft', index=True, copy=False,
|
||||
compute='_compute_state', readonly=True, store=True,
|
||||
)
|
||||
sheet_id = fields.Many2one('hr.expense.sheet', string="Expense Report", domain="[('employee_id', '=', employee_id), ('company_id', '=', company_id)]", readonly=True, copy=False)
|
||||
sheet_is_editable = fields.Boolean(compute='_compute_sheet_is_editable')
|
||||
approved_by = fields.Many2one('res.users', string='Approved By', related='sheet_id.user_id', tracking=False)
|
||||
@@ -152,16 +157,18 @@ class HrExpense(models.Model):
|
||||
@api.depends('sheet_id', 'sheet_id.account_move_id', 'sheet_id.state')
|
||||
def _compute_state(self):
|
||||
for expense in self:
|
||||
if not expense.sheet_id or expense.sheet_id.state == 'draft':
|
||||
expense.state = "draft"
|
||||
elif expense.sheet_id.state == "cancel":
|
||||
expense.state = "refused"
|
||||
elif expense.sheet_id.state == "approve" or expense.sheet_id.state == "post":
|
||||
expense.state = "approved"
|
||||
if not expense.sheet_id:
|
||||
expense.state = 'draft'
|
||||
elif expense.sheet_id.state == 'draft':
|
||||
expense.state = 'reported'
|
||||
elif expense.sheet_id.state == 'cancel':
|
||||
expense.state = 'refused'
|
||||
elif expense.sheet_id.state in {'approve', 'post'}:
|
||||
expense.state = 'approved'
|
||||
elif not expense.sheet_id.account_move_id:
|
||||
expense.state = "reported"
|
||||
expense.state = 'submitted'
|
||||
else:
|
||||
expense.state = "done"
|
||||
expense.state = 'done'
|
||||
|
||||
@api.depends('quantity', 'unit_amount', 'tax_ids')
|
||||
def _compute_amount(self):
|
||||
@@ -213,7 +220,7 @@ class HrExpense(models.Model):
|
||||
def _compute_is_editable(self):
|
||||
is_account_manager = self.env.user.has_group('account.group_account_user') or self.env.user.has_group('account.group_account_manager')
|
||||
for expense in self:
|
||||
if expense.state == 'draft' or expense.sheet_id.state in ['draft', 'submit']:
|
||||
if expense.state in {'draft', 'reported'} or expense.sheet_id.state in {'draft', 'submit'}:
|
||||
expense.is_editable = True
|
||||
elif expense.sheet_id.state == 'approve':
|
||||
expense.is_editable = is_account_manager
|
||||
@@ -229,7 +236,7 @@ class HrExpense(models.Model):
|
||||
def _compute_is_ref_editable(self):
|
||||
is_account_manager = self.env.user.has_group('account.group_account_user') or self.env.user.has_group('account.group_account_manager')
|
||||
for expense in self:
|
||||
if expense.state == 'draft' or expense.sheet_id.state in ['draft', 'submit']:
|
||||
if expense.state in {'draft', 'reported'} or expense.sheet_id.state in {'draft', 'submit'}:
|
||||
expense.is_ref_editable = True
|
||||
else:
|
||||
expense.is_ref_editable = is_account_manager
|
||||
@@ -478,8 +485,7 @@ class HrExpense(models.Model):
|
||||
|
||||
if not expenses:
|
||||
raise UserError(_('You have no expense to report'))
|
||||
else:
|
||||
return expenses.action_submit_expenses()
|
||||
return expenses.action_submit_expenses()
|
||||
|
||||
def action_submit_expenses(self):
|
||||
context_vals = self._get_default_expense_sheet_values()
|
||||
@@ -582,19 +588,22 @@ class HrExpense(models.Model):
|
||||
@api.model
|
||||
def get_expense_dashboard(self):
|
||||
expense_state = {
|
||||
'draft': {
|
||||
'description': _('to report'),
|
||||
'to_submit': {
|
||||
'description': _('to submit'),
|
||||
'amount': 0.0,
|
||||
'tooltip': _("Expenses that need to be submitted to the approver."),
|
||||
'currency': self.env.company.currency_id.id,
|
||||
},
|
||||
'reported': {
|
||||
'submitted': {
|
||||
'description': _('under validation'),
|
||||
'amount': 0.0,
|
||||
'tooltip': _("Expenses from which the report has been submitted to the approver and is waiting for approval."),
|
||||
'currency': self.env.company.currency_id.id,
|
||||
},
|
||||
'approved': {
|
||||
'description': _('to be reimbursed'),
|
||||
'amount': 0.0,
|
||||
'tooltip': _("Expenses from which the report is approved or posted. The payment still needs to be done."),
|
||||
'currency': self.env.company.currency_id.id,
|
||||
}
|
||||
}
|
||||
@@ -605,9 +614,11 @@ class HrExpense(models.Model):
|
||||
[
|
||||
('employee_id', 'in', self.env.user.employee_ids.ids),
|
||||
('payment_mode', '=', 'own_account'),
|
||||
('state', 'in', ['draft', 'reported', 'approved'])
|
||||
('state', 'in', ('draft', 'reported', 'submitted', 'approved'))
|
||||
], ['state', 'currency_id'], ['total_amount:sum'])
|
||||
for state, currency, total_amount_sum in expenses:
|
||||
if state in {'draft', 'reported'}: # Fusion the two states into only one "To Submit" state
|
||||
state = 'to_submit'
|
||||
currency = currency or target_currency
|
||||
amount = currency._convert(
|
||||
total_amount_sum, target_currency, self.env.company, fields.Date.today())
|
||||
@@ -800,7 +811,7 @@ class HrExpenseSheet(models.Model):
|
||||
product_ids = fields.Many2many('product.product', compute='_compute_product_ids', search='_search_product_ids', string='Categories')
|
||||
expense_number = fields.Integer(compute='_compute_expense_number', string='Number of Expenses')
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('draft', 'To Submit'),
|
||||
('submit', 'Submitted'),
|
||||
('approve', 'Approved'),
|
||||
('post', 'Posted'),
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<t t-foreach="Object.entries(state.expenses)" t-as="expense" t-key="expense[0]">
|
||||
<t t-set="name" t-value="expense[0]"/>
|
||||
<t t-set="data" t-value="expense[1]"/>
|
||||
<div t-attf-class="o_expense_card o_arrow_button flex-grow-1 d-flex flex-column p-3 border-bottom text-center">
|
||||
<div t-attf-class="o_expense_card o_arrow_button flex-grow-1 d-flex flex-column p-3 border-bottom text-center"
|
||||
t-att-data-tooltip="data['tooltip']">
|
||||
<span t-esc="renderMonetaryField(data['amount'], data['currency'])" class="h2 m-0 text-odoo"/>
|
||||
<b class="mx-2" t-esc="data['description']"/>
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,11 @@ export class ExpenseListController extends ListController {
|
||||
return records.length && records.every(record => record.data.state === 'draft') && this.isExpenseSheet;
|
||||
}
|
||||
|
||||
displayCreateReport() {
|
||||
const records = this.model.root.selection;
|
||||
return !this.isExpenseSheet && (records.length === 0 || records.some(record => record.data.state === "draft"))
|
||||
}
|
||||
|
||||
displayApprove() {
|
||||
const records = this.model.root.selection;
|
||||
return this.userIsExpenseTeamApprover && records.length && records.every(record => record.data.state === 'submit') && this.isExpenseSheet;
|
||||
@@ -70,6 +75,13 @@ export class ExpenseListController extends ListController {
|
||||
this.render(true);
|
||||
}
|
||||
|
||||
async action_show_expenses_to_submit () {
|
||||
const records = this.model.root.selection;
|
||||
const res = await this.orm.call(this.model.rootParams.resModel, 'get_expenses_to_submit', [records.map((record) => record.resId)]);
|
||||
if (res) {
|
||||
await this.actionService.doAction(res, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
patch(ExpenseListController.prototype, 'expense_list_controller_upload', ExpenseDocumentUpload);
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<button type="button" class="d-none d-md-inline o_button_upload_expense btn btn-primary mx-1" t-on-click.prevent="uploadDocument">
|
||||
Scan
|
||||
</button>
|
||||
<button t-if="displayCreateReport()" class="d-none d-md-block btn btn-secondary"
|
||||
t-on-click="() => this.action_show_expenses_to_submit()">
|
||||
Create Report
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div" position="inside">
|
||||
|
||||
@@ -45,9 +45,6 @@
|
||||
<field name="model">hr.expense</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Expenses" multi_edit="1" sample="1" js_class="hr_expense_tree" decoration-info="state == 'draft'">
|
||||
<header>
|
||||
<button name="get_expenses_to_submit" string="Create Report" type="object" display="always"/>
|
||||
</header>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
<field name="attachment_number" invisible="1"/>
|
||||
@@ -69,12 +66,15 @@
|
||||
<field name="unit_amount_display" string="Unit Price" optional="hide" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
|
||||
<field name="quantity" optional="hide"/>
|
||||
<field name="tax_ids" optional="hide" widget="many2many_tags" groups="account.group_account_invoice,account.group_account_readonly"/>
|
||||
<field name="amount_tax_company" optional="hide" groups="account.group_account_invoice,account.group_account_readonly"/>
|
||||
<field name="amount_tax_company" sum="Total Taxes"
|
||||
optional="hide" groups="account.group_account_invoice,account.group_account_readonly"/>
|
||||
<field name="attachment_number" nolabel="1" class='fa fa-paperclip pe-0' attrs="{'invisible': [('attachment_number', '=', 0)]}"/>
|
||||
<field name="total_amount_company" widget='monetary' options="{'currency_field': 'company_currency_id'}" decoration-bf="1"/>
|
||||
<field name="total_amount_company" sum="Total Amount" widget='monetary'
|
||||
options="{'currency_field': 'company_currency_id'}" decoration-bf="1"/>
|
||||
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}" optional="hide" decoration-bf="1" groups="base.group_multi_currency"/>
|
||||
<field name="currency_id" optional="hide" groups="base.group_multi_currency"/>
|
||||
<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"/>
|
||||
<field name="state" optional="show" readonly="1" decoration-info="state in ['draft', 'reported']" decoration-success="state in ['approved', 'done']"
|
||||
decoration-warning="state == 'submitted'" decoration-danger="state == 'refused'" widget="badge"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -117,7 +117,10 @@
|
||||
<widget name="attach_document" string="Attach Receipt" action="attach_document" attrs="{'invisible': [('attachment_number', '<', 1)]}"/>
|
||||
<widget name="attach_document" string="Attach Receipt" action="attach_document" highlight="1" attrs="{'invisible': [('attachment_number', '>=', 1)]}"/>
|
||||
<button name="action_submit_expenses" string="Create Report" type="object" class="o_expense_submit" attrs="{'invisible': ['|', ('attachment_number', '>=', 1), ('sheet_id', '!=', False)]}" data-hotkey="v"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,reported,approved,done,refused"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,reported,submitted,approved,done"
|
||||
attrs="{'invisible': [('state', '=', 'refused')]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,reported,submitted,refused"
|
||||
attrs="{'invisible': [('state', '!=', 'refused')]}"/>
|
||||
<button name="action_view_sheet" type="object" string="View Report" class="oe_highlight" attrs="{'invisible': [('sheet_id', '=', False)]}" data-hotkey="w"/>
|
||||
<button name="action_split_wizard" string="Split Expense" type="object" attrs="{'invisible': ['|', ('sheet_id', '!=', False), ('product_has_cost', '=', True)]}"/>
|
||||
</header>
|
||||
@@ -270,7 +273,7 @@
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span class="float-end text-end">
|
||||
<field name="state" widget="label_selection" options="{'classes': {'draft': 'default', 'reported': 'primary', 'refused': 'danger', 'done': 'warning',
|
||||
<field name="state" widget="label_selection" options="{'classes': {'draft': 'default', 'reported': 'primary', 'submitted': 'warning', 'refused': 'danger', 'done': 'warning',
|
||||
'approved': 'success'}}"/>
|
||||
</span>
|
||||
</div>
|
||||
@@ -629,8 +632,10 @@
|
||||
<field name="activity_ids" widget="list_activity" optional="show" readonly="1"/>
|
||||
<field name="journal_id" optional="hide"/>
|
||||
<field name="total_amount" sum="Total Amount" decoration-bf="1" widget="monetary"/>
|
||||
<field name="state" optional="show" decoration-info="state == 'draft'" decoration-success="state in ['submit', 'approve', 'post', 'done']" decoration-danger="state == 'cancel'" widget="badge"/>
|
||||
<field name="payment_state" optional="show" decoration-info="payment_state in ('partial','in_payment')" decoration-success="payment_state == 'paid'" decoration-danger="payment_state in ('reversed','not_paid')" widget="badge"/>
|
||||
<field name="state" optional="show" decoration-info="state == 'draft'" decoration-success="state in ['approve', 'post', 'done']"
|
||||
decoration-warning="state == 'submit'" decoration-danger="state == 'cancel'" widget="badge"/>
|
||||
<field name="payment_state" optional="show" decoration-info="payment_state in ('partial','in_payment')" decoration-success="payment_state == 'paid'"
|
||||
decoration-danger="payment_state in ('reversed','not_paid')" widget="badge" attrs="{'invisible': [('state', 'in', ['draft', 'submit', 'cancel'])]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -708,7 +713,11 @@
|
||||
data-hotkey="c"
|
||||
attrs="{'invisible': [('state', '!=', 'post')]}"
|
||||
groups="account.group_account_readonly,account.group_account_invoice"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,submit,approve,post,done" force_save="1"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,submit,approve,post,done"
|
||||
force_save="1" attrs="{'invisible': [('state', '=', 'cancel')]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,submit,cancel"
|
||||
force_save="1" attrs="{'invisible': [('state', '!=', 'cancel')]}"/>
|
||||
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
@@ -763,7 +772,7 @@
|
||||
widget="many2many"
|
||||
mode="tree,kanban"
|
||||
domain="[
|
||||
('state', '=', 'draft'),
|
||||
('state', '=', 'reported'),
|
||||
('employee_id', '=', employee_id),
|
||||
('company_id', '=', company_id),
|
||||
('payment_mode', '=?', payment_mode),
|
||||
@@ -934,6 +943,8 @@
|
||||
<field string="Journal" name="journal_id"/>
|
||||
<filter string="My Reports" name="my_reports" domain="[('employee_id.user_id', '=', uid)]"/>
|
||||
<filter string="My Team" name="my_team_reports" domain="[('employee_id.parent_id.user_id', '=', uid)]" groups="hr_expense.group_hr_expense_manager" help="Expenses of Your Team Member"/>
|
||||
<separator invisible="1"/>
|
||||
<filter string="Not Refused" name="not_refused_reports" domain="[('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]" invisible="1"/>
|
||||
<separator />
|
||||
<filter string="Date" name="filter_accounting_date" date="accounting_date"/>
|
||||
<separator/>
|
||||
@@ -999,8 +1010,7 @@
|
||||
<field name="res_model">hr.expense.sheet</field>
|
||||
<field name="view_mode">tree,kanban,form,pivot,graph,activity</field>
|
||||
<field name="search_view_id" ref="hr_expense_sheet_view_search"/>
|
||||
<field name="domain">[('state', '!=', 'cancel')]</field>
|
||||
<field name="context">{'search_default_my_reports': 1}</field>
|
||||
<field name="context">{'search_default_my_reports': 1, 'search_default_not_refused_reports': 1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No expense report found. Let's create one!
|
||||
|
||||
Reference in New Issue
Block a user