mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-24 09:32:05 +02:00
19.0 vanilla
This commit is contained in:
parent
a1137a1456
commit
e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import hr_expense_refuse_reason
|
||||
from . import account_payment_register
|
||||
from . import hr_expense_refuse_reason
|
||||
from . import hr_expense_post_wizard
|
||||
from . import hr_expense_approve_duplicate
|
||||
from . import hr_expense_split_wizard
|
||||
from . import hr_expense_split
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class AccountPaymentRegister(models.TransientModel):
|
||||
|
|
@ -14,9 +12,13 @@ class AccountPaymentRegister(models.TransientModel):
|
|||
def _get_line_batch_key(self, line):
|
||||
# OVERRIDE to set the bank account defined on the employee
|
||||
res = super()._get_line_batch_key(line)
|
||||
expense_sheet = self.env['hr.expense.sheet'].search([('payment_mode', '=', 'own_account'), ('account_move_id', 'in', line.move_id.ids)])
|
||||
if expense_sheet and not line.move_id.partner_bank_id:
|
||||
res['partner_bank_id'] = expense_sheet.employee_id.sudo().bank_account_id.id or line.partner_id.bank_ids and line.partner_id.bank_ids.ids[0]
|
||||
expense = line.move_id.expense_ids.filtered(lambda expense: expense.payment_mode == 'own_account')
|
||||
if expense and not line.move_id.partner_bank_id:
|
||||
res['partner_bank_id'] = (
|
||||
expense.employee_id.sudo().primary_bank_account_id.id
|
||||
or line.partner_id.bank_ids
|
||||
and line.partner_id.bank_ids.ids[0]
|
||||
)
|
||||
return res
|
||||
|
||||
def _init_payments(self, to_process, edit_mode=False):
|
||||
|
|
@ -25,15 +27,5 @@ class AccountPaymentRegister(models.TransientModel):
|
|||
for payment, vals in zip(payments, to_process):
|
||||
expenses = vals['batch']['lines'].expense_id
|
||||
if expenses:
|
||||
payment.line_ids.write({'expense_id': expenses[0].id})
|
||||
payment.move_id.line_ids.write({'expense_id': expenses[0].id})
|
||||
return payments
|
||||
|
||||
def _reconcile_payments(self, to_process, edit_mode=False):
|
||||
# OVERRIDE
|
||||
res = super()._reconcile_payments(to_process, edit_mode=edit_mode)
|
||||
for vals in to_process:
|
||||
expense_sheets = vals['batch']['lines'].expense_id.sheet_id
|
||||
for expense_sheet in expense_sheets:
|
||||
if expense_sheet.currency_id.is_zero(expense_sheet.amount_residual):
|
||||
expense_sheet.state = 'done'
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import Command, api, fields, models, _
|
||||
|
||||
|
||||
class HrExpenseApproveDuplicate(models.TransientModel):
|
||||
|
|
@ -10,25 +9,22 @@ class HrExpenseApproveDuplicate(models.TransientModel):
|
|||
approved. The user has the opportunity to still validate it or decline.
|
||||
"""
|
||||
|
||||
_name = "hr.expense.approve.duplicate"
|
||||
_name = 'hr.expense.approve.duplicate'
|
||||
_description = "Expense Approve Duplicate"
|
||||
|
||||
sheet_ids = fields.Many2many('hr.expense.sheet')
|
||||
expense_ids = fields.Many2many('hr.expense', readonly=True)
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super().default_get(fields)
|
||||
|
||||
if 'sheet_ids' in fields:
|
||||
res['sheet_ids'] = [(6, 0, self.env.context.get('default_sheet_ids', []))]
|
||||
if 'duplicate_expense_ids' in fields:
|
||||
res['expense_ids'] = [(6, 0, self.env.context.get('default_expense_ids', []))]
|
||||
|
||||
res['expense_ids'] = [Command.set(self.env.context.get('default_expense_ids', []))]
|
||||
return res
|
||||
|
||||
def action_approve(self):
|
||||
self.sheet_ids._do_approve()
|
||||
self.expense_ids.filtered(lambda expense: expense.state == 'submitted')._do_approve()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def action_refuse(self):
|
||||
self.sheet_ids.refuse_sheet(_('Duplicate Expense'))
|
||||
self.expense_ids.filtered(lambda expense: expense.state == 'submitted')._do_refuse(_('Duplicate Expense'))
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,22 @@
|
|||
<field name="model">hr.expense.approve.duplicate</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Expense Validate Duplicate">
|
||||
<field name="sheet_ids" invisible="1" />
|
||||
<p>The following approved expenses have similar employee, amount and category than some expenses of this report. Please verify this report does not contain duplicates.</p>
|
||||
<field name="expense_ids" nolabel="1">
|
||||
<tree>
|
||||
<list>
|
||||
<field name="date" readonly="1" />
|
||||
<field name="employee_id" readonly="1" />
|
||||
<field name="employee_id" readonly="1" widget="many2one_avatar_employee"/>
|
||||
<field name="product_id" readonly="1" />
|
||||
<field name="total_amount_company" readonly="1" />
|
||||
<field name="total_amount" readonly="1" />
|
||||
<field name="name" readonly="1" />
|
||||
<field name="approved_by" readonly="1" />
|
||||
<field name="approved_on" readonly="1" />
|
||||
</tree>
|
||||
<field name="manager_id" readonly="1" widget="many2one_avatar_user"/>
|
||||
<field name="approval_date" readonly="1" />
|
||||
</list>
|
||||
</field>
|
||||
<footer>
|
||||
<button string="Refuse" class="btn-primary" name="action_refuse" type="object" attrs="{'invisible': [('sheet_ids', '=', [])]}" data-hotkey="q" />
|
||||
<button string="Approve" class="btn-secondary" name="action_approve" type="object" attrs="{'invisible': [('sheet_ids', '=', [])]}" data-hotkey="w" />
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
<button string="Refuse" class="btn-primary" name="action_refuse" type="object" invisible="not expense_ids" data-hotkey="q" />
|
||||
<button string="Approve" class="btn-secondary" name="action_approve" type="object" invisible="not expense_ids" data-hotkey="w" />
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class HrExpensePostWizard(models.TransientModel):
|
||||
_name = 'hr.expense.post.wizard'
|
||||
_description = 'Expense Posting Wizard'
|
||||
|
||||
@api.model
|
||||
def _default_journal_id(self):
|
||||
"""
|
||||
The journal is determining the company of the accounting entries generated from expense.
|
||||
We need to force journal company and expense company to be the same.
|
||||
"""
|
||||
company_journal_id = self.env.company.expense_journal_id
|
||||
if company_journal_id:
|
||||
return company_journal_id.id
|
||||
closest_parent_company_journal = self.env.company.parent_ids[::-1].expense_journal_id[:1]
|
||||
if closest_parent_company_journal:
|
||||
return closest_parent_company_journal.id
|
||||
|
||||
journal = self.env['account.journal'].search([
|
||||
*self.env['account.journal']._check_company_domain(self.env.company.id),
|
||||
('type', '=', 'purchase'),
|
||||
], limit=1)
|
||||
return journal.id
|
||||
|
||||
company_id = fields.Many2one(comodel_name='res.company', default=lambda self: self.env.company, string='Company', readonly=True)
|
||||
|
||||
accounting_date = fields.Date( # The date used for the accounting entries or the one we'd like to use if not yet posted
|
||||
string="Accounting Date",
|
||||
default=fields.Date.context_today,
|
||||
help="Specify the bill date of the related vendor bill."
|
||||
)
|
||||
employee_journal_id = fields.Many2one(
|
||||
comodel_name='account.journal',
|
||||
string="Journal",
|
||||
default=_default_journal_id,
|
||||
check_company=True,
|
||||
domain=[('type', '=', 'purchase')],
|
||||
help="The journal used when the expense is paid by employee.",
|
||||
)
|
||||
|
||||
def action_post_entry(self):
|
||||
expenses = self.env['hr.expense'].browse(self.env.context['active_ids'])
|
||||
if not self.env['account.move'].has_access('create'):
|
||||
raise UserError(_("You don't have the rights to create accounting entries."))
|
||||
expense_receipt_vals_list = [
|
||||
{
|
||||
**new_receipt_vals,
|
||||
'journal_id': self.employee_journal_id.id,
|
||||
'invoice_date': self.accounting_date,
|
||||
}
|
||||
for new_receipt_vals in expenses._prepare_receipts_vals()
|
||||
]
|
||||
moves_sudo = self.env['account.move'].sudo().create(expense_receipt_vals_list)
|
||||
for move_sudo in moves_sudo:
|
||||
move_sudo._message_set_main_attachment_id(move_sudo.attachment_ids, force=True, filter_xml=False)
|
||||
moves_sudo.action_post()
|
||||
|
||||
if not self.company_id.expense_journal_id: # Sets the default one if not specified
|
||||
self.sudo().company_id.expense_journal_id = self.employee_journal_id.id
|
||||
|
||||
# Add the company_paid ids to the redirect
|
||||
moves_ids = moves_sudo.ids + self.env.context.get('company_paid_move_ids', tuple())
|
||||
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'account.move',
|
||||
}
|
||||
if len(moves_ids) == 1:
|
||||
action.update({
|
||||
'name': moves_sudo.ref,
|
||||
'view_mode': 'form',
|
||||
'res_id': moves_ids[0],
|
||||
})
|
||||
else:
|
||||
list_view = self.env.ref('hr_expense.view_move_list_expense', raise_if_not_found=False)
|
||||
action.update({
|
||||
'name': _("New expense entries"),
|
||||
'view_mode': 'list,form',
|
||||
'views': [(list_view and list_view.id, 'list'), (False, 'form')],
|
||||
'domain': [('id', 'in', moves_ids)],
|
||||
})
|
||||
return action
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="hr_expense.hr_expense_post_wizard_view" model="ir.ui.view">
|
||||
<field name="name">Post Expenses</field>
|
||||
<field name="model">hr.expense.post.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="employee_journal_id" string="Journal"/>
|
||||
<field name="accounting_date" string="Accounting date"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_post_entry" string="Post Expenses" type="object" class="oe_highlight" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -1,45 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrExpenseRefuseWizard(models.TransientModel):
|
||||
"""This wizard can be launched from an he.expense (an expense line)
|
||||
or from an hr.expense.sheet (En expense report)
|
||||
'hr_expense_refuse_model' must be passed in the context to differentiate
|
||||
the right model to use.
|
||||
"""
|
||||
""" Wizard to specify reason on expense refusal """
|
||||
|
||||
_name = "hr.expense.refuse.wizard"
|
||||
_name = 'hr.expense.refuse.wizard'
|
||||
_description = "Expense Refuse Reason Wizard"
|
||||
|
||||
reason = fields.Char(string='Reason', required=True)
|
||||
hr_expense_ids = fields.Many2many('hr.expense')
|
||||
hr_expense_sheet_id = fields.Many2one('hr.expense.sheet')
|
||||
expense_ids = fields.Many2many(comodel_name='hr.expense')
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
res = super(HrExpenseRefuseWizard, self).default_get(fields)
|
||||
active_ids = self.env.context.get('active_ids', [])
|
||||
refuse_model = self.env.context.get('hr_expense_refuse_model')
|
||||
if refuse_model == 'hr.expense':
|
||||
res.update({
|
||||
'hr_expense_ids': active_ids,
|
||||
'hr_expense_sheet_id': False,
|
||||
})
|
||||
elif refuse_model == 'hr.expense.sheet':
|
||||
res.update({
|
||||
'hr_expense_sheet_id': active_ids[0] if active_ids else False,
|
||||
'hr_expense_ids': [],
|
||||
})
|
||||
res = super().default_get(fields)
|
||||
if 'expense_ids' in fields:
|
||||
res['expense_ids'] = self.env.context.get('active_ids', [])
|
||||
return res
|
||||
|
||||
def expense_refuse_reason(self):
|
||||
self.ensure_one()
|
||||
if self.hr_expense_ids:
|
||||
self.hr_expense_ids.refuse_expense(self.reason)
|
||||
if self.hr_expense_sheet_id:
|
||||
self.hr_expense_sheet_id.refuse_sheet(self.reason)
|
||||
|
||||
def action_refuse(self):
|
||||
self.expense_ids._do_refuse(self.reason)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
<field name="model">hr.expense.refuse.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Expense refuse reason">
|
||||
<separator string="Reason to refuse Expense"/>
|
||||
<field name="hr_expense_ids" invisible="1"/>
|
||||
<field name="hr_expense_sheet_id" invisible="1"/>
|
||||
<field name="reason" class="w-100"/>
|
||||
<field name="expense_ids" invisible="1"/>
|
||||
<group>
|
||||
<field string="Reason" name="reason" class="w-100"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string='Refuse' name="expense_refuse_reason" type="object" class="oe_highlight" data-hotkey="q"/>
|
||||
<button string="Cancel" class="oe_link" special="cancel" data-hotkey="z"/>
|
||||
<button string='Refuse' name="action_refuse" type="object" class="oe_highlight" data-hotkey="q"/>
|
||||
<button string="Cancel" class="oe_link" special="cancel" data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
|
@ -23,5 +23,6 @@
|
|||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="hr_expense_refuse_wizard_view_form"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{'dialog_size': 'medium'}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,55 +1,88 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from copy import deepcopy
|
||||
|
||||
from odoo import fields, models, api
|
||||
from odoo import fields, models, api, Command
|
||||
from odoo.tools import float_compare
|
||||
|
||||
from odoo.addons.hr_expense.models.hr_expense import EXPENSE_APPROVAL_STATE
|
||||
|
||||
|
||||
class HrExpenseSplit(models.TransientModel):
|
||||
|
||||
_name = 'hr.expense.split'
|
||||
_inherit = ['analytic.mixin']
|
||||
_description = 'Expense Split'
|
||||
_check_company_auto = True
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
result = super(HrExpenseSplit, self).default_get(fields)
|
||||
result = super().default_get(fields)
|
||||
if 'expense_id' in result:
|
||||
expense = self.env['hr.expense'].browse(result['expense_id'])
|
||||
result['total_amount'] = 0.0
|
||||
result['total_amount_currency'] = 0.0
|
||||
result['name'] = expense.name
|
||||
result['tax_ids'] = expense.tax_ids
|
||||
result['product_id'] = expense.product_id
|
||||
result['company_id'] = expense.company_id
|
||||
result['analytic_distribution'] = expense.analytic_distribution
|
||||
result['analytic_distribution'] = deepcopy(expense.analytic_distribution) or {}
|
||||
result['employee_id'] = expense.employee_id
|
||||
result['currency_id'] = expense.currency_id
|
||||
result['approval_state'] = expense.approval_state
|
||||
result['approval_date'] = expense.approval_date
|
||||
result['manager_id'] = expense.manager_id
|
||||
return result
|
||||
|
||||
name = fields.Char('Description', required=True)
|
||||
wizard_id = fields.Many2one('hr.expense.split.wizard')
|
||||
expense_id = fields.Many2one('hr.expense', string='Expense')
|
||||
product_id = fields.Many2one('product.product', string='Product', required=True, domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
tax_ids = fields.Many2many('account.tax', domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]")
|
||||
total_amount = fields.Monetary("Total In Currency", required=True, compute='_compute_from_product_id', store=True, readonly=False)
|
||||
amount_tax = fields.Monetary(string='Tax amount in Currency', compute='_compute_amount_tax')
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", required=True)
|
||||
company_id = fields.Many2one('res.company')
|
||||
currency_id = fields.Many2one('res.currency')
|
||||
product_has_tax = fields.Boolean("Whether tax is defined on a selected product", compute='_compute_product_has_tax')
|
||||
product_has_cost = fields.Boolean("Is product with non zero cost selected", compute='_compute_from_product_id', store=True)
|
||||
name = fields.Char(string='Description', required=True)
|
||||
wizard_id = fields.Many2one(comodel_name='hr.expense.split.wizard')
|
||||
expense_id = fields.Many2one(comodel_name='hr.expense', string='Expense')
|
||||
product_id = fields.Many2one(comodel_name='product.product', string='Product', required=True, check_company=True, domain=[('can_be_expensed', '=', True)],)
|
||||
tax_ids = fields.Many2many(
|
||||
comodel_name='account.tax',
|
||||
check_company=True,
|
||||
domain="[('type_tax_use', '=', 'purchase')]",
|
||||
)
|
||||
total_amount_currency = fields.Monetary(
|
||||
string="Total In Currency",
|
||||
required=True,
|
||||
compute='_compute_from_product_id', store=True, readonly=False,
|
||||
)
|
||||
tax_amount_currency = fields.Monetary(string='Tax amount in Currency', compute='_compute_tax_amount_currency')
|
||||
employee_id = fields.Many2one(comodel_name='hr.employee', string="Employee", required=True)
|
||||
company_id = fields.Many2one(comodel_name='res.company')
|
||||
currency_id = fields.Many2one(comodel_name='res.currency')
|
||||
product_has_tax = fields.Boolean(
|
||||
string="Whether tax is defined on a selected product",
|
||||
compute='_compute_product_has_tax',
|
||||
)
|
||||
product_has_cost = fields.Boolean(
|
||||
string="Is product with non zero cost selected",
|
||||
compute='_compute_from_product_id', store=True,
|
||||
)
|
||||
approval_state = fields.Selection(selection=EXPENSE_APPROVAL_STATE, copy=False, readonly=True)
|
||||
approval_date = fields.Datetime(string="Approval Date", readonly=True)
|
||||
manager_id = fields.Many2one(
|
||||
comodel_name='res.users',
|
||||
string="Manager",
|
||||
readonly=True,
|
||||
domain=lambda self: [('all_group_ids', 'in', self.env.ref('hr_expense.group_hr_expense_team_approver').id)],
|
||||
)
|
||||
|
||||
@api.depends('total_amount', 'tax_ids')
|
||||
def _compute_amount_tax(self):
|
||||
@api.depends('total_amount_currency', 'tax_ids')
|
||||
def _compute_tax_amount_currency(self):
|
||||
for split in self:
|
||||
taxes = split.tax_ids.with_context(force_price_include=True).compute_all(price_unit=split.total_amount, currency=split.currency_id, quantity=1, product=split.product_id)
|
||||
split.amount_tax = taxes['total_included'] - taxes['total_excluded']
|
||||
taxes = split.tax_ids.with_context(force_price_include=True).compute_all(
|
||||
price_unit=split.total_amount_currency,
|
||||
currency=split.currency_id,
|
||||
quantity=1,
|
||||
product=split.product_id
|
||||
)
|
||||
split.tax_amount_currency = taxes['total_included'] - taxes['total_excluded']
|
||||
|
||||
@api.depends('product_id')
|
||||
def _compute_from_product_id(self):
|
||||
for split in self:
|
||||
split.product_has_cost = split.product_id and (float_compare(split.product_id.standard_price, 0.0, precision_digits=2) != 0)
|
||||
if split.product_has_cost:
|
||||
split.total_amount = split.product_id.price_compute('standard_price', currency=split.currency_id)[split.product_id.id]
|
||||
split.total_amount_currency = split.product_id._price_compute('standard_price', currency=split.currency_id)[split.product_id.id]
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
|
|
@ -57,27 +90,33 @@ class HrExpenseSplit(models.TransientModel):
|
|||
In case we switch to the product without taxes defined on it, taxes should be removed.
|
||||
Computed method won't be good for this purpose, as we don't want to recompute and reset taxes in case they are removed on purpose during splitting.
|
||||
"""
|
||||
self.tax_ids = self.tax_ids if self.product_has_tax and self.tax_ids else self.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == self.company_id)
|
||||
if self.product_has_tax and self.tax_ids:
|
||||
self.tax_ids = self.tax_ids
|
||||
else:
|
||||
self.tax_ids = self.product_id.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(self.company_id))
|
||||
|
||||
@api.depends('product_id')
|
||||
def _compute_product_has_tax(self):
|
||||
for split in self:
|
||||
split.product_has_tax = split.product_id and split.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == split.company_id)
|
||||
split.product_has_tax = split.product_id and split.product_id.supplier_taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(split.company_id))
|
||||
|
||||
def _get_values(self):
|
||||
self.ensure_one()
|
||||
vals = {
|
||||
'name': self.name,
|
||||
'product_id': self.product_id.id,
|
||||
'total_amount': self.total_amount,
|
||||
'tax_ids': [(6, 0, self.tax_ids.ids)],
|
||||
'total_amount_currency': self.total_amount_currency,
|
||||
'total_amount': self.expense_id.currency_id.round(self.expense_id.currency_rate * self.total_amount_currency),
|
||||
'tax_ids': [Command.set(self.tax_ids.ids)],
|
||||
'analytic_distribution': self.analytic_distribution,
|
||||
'employee_id': self.employee_id.id,
|
||||
'product_uom_id': self.product_id.uom_id.id,
|
||||
'unit_amount': self.product_id.price_compute('standard_price', currency=self.currency_id)[self.product_id.id]
|
||||
'approval_state': self.approval_state,
|
||||
'approval_date': self.approval_date,
|
||||
'manager_id': self.manager_id.id,
|
||||
}
|
||||
|
||||
account = self.product_id.product_tmpl_id._get_product_accounts()['expense']
|
||||
account = self.product_id.with_company(self.company_id).product_tmpl_id._get_product_accounts()['expense']
|
||||
if account:
|
||||
vals['account_id'] = account.id
|
||||
return vals
|
||||
|
|
|
|||
|
|
@ -1,35 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api, _
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
||||
class HrExpenseSplitWizard(models.TransientModel):
|
||||
_name = 'hr.expense.split.wizard'
|
||||
_description = 'Expense Split Wizard'
|
||||
|
||||
expense_id = fields.Many2one('hr.expense', string='Expense', required=True)
|
||||
expense_split_line_ids = fields.One2many('hr.expense.split', 'wizard_id')
|
||||
total_amount = fields.Monetary('Total Amount', compute='_compute_total_amount', currency_field='currency_id')
|
||||
total_amount_original = fields.Monetary('Total amount original', related='expense_id.total_amount', currency_field='currency_id', help='Total amount of the original Expense that we are splitting')
|
||||
total_amount_taxes = fields.Monetary('Taxes', currency_field='currency_id', compute='_compute_total_amount_taxes')
|
||||
expense_id = fields.Many2one(comodel_name='hr.expense', string='Expense', required=True)
|
||||
expense_split_line_ids = fields.One2many(comodel_name='hr.expense.split', inverse_name='wizard_id')
|
||||
total_amount_currency = fields.Monetary(string='Total Amount', compute='_compute_total_amount_currency', currency_field='currency_id')
|
||||
total_amount_currency_original = fields.Monetary(
|
||||
string='Total amount original', related='expense_id.total_amount_currency',
|
||||
currency_field='currency_id',
|
||||
help='Total amount of the original Expense that we are splitting',
|
||||
)
|
||||
tax_amount_currency = fields.Monetary(
|
||||
string='Taxes',
|
||||
currency_field='currency_id',
|
||||
compute='_compute_tax_amount_currency',
|
||||
)
|
||||
split_possible = fields.Boolean(help='The sum of after split shut remain the same', compute='_compute_split_possible')
|
||||
currency_id = fields.Many2one('res.currency', related='expense_id.currency_id')
|
||||
currency_id = fields.Many2one(comodel_name='res.currency', related='expense_id.currency_id')
|
||||
|
||||
@api.depends('expense_split_line_ids.total_amount')
|
||||
def _compute_total_amount(self):
|
||||
@api.depends('expense_split_line_ids.total_amount_currency')
|
||||
def _compute_total_amount_currency(self):
|
||||
for wizard in self:
|
||||
wizard.total_amount = sum(wizard.expense_split_line_ids.mapped('total_amount'))
|
||||
wizard.total_amount_currency = sum(wizard.expense_split_line_ids.mapped('total_amount_currency'))
|
||||
|
||||
@api.depends('expense_split_line_ids.amount_tax')
|
||||
def _compute_total_amount_taxes(self):
|
||||
@api.depends('expense_split_line_ids.tax_amount_currency')
|
||||
def _compute_tax_amount_currency(self):
|
||||
for wizard in self:
|
||||
wizard.total_amount_taxes = sum(wizard.expense_split_line_ids.mapped('amount_tax'))
|
||||
wizard.tax_amount_currency = sum(wizard.expense_split_line_ids.mapped('tax_amount_currency'))
|
||||
|
||||
@api.depends('total_amount_original', 'total_amount')
|
||||
@api.depends('total_amount_currency_original', 'total_amount_currency')
|
||||
def _compute_split_possible(self):
|
||||
for wizard in self:
|
||||
wizard.split_possible = wizard.total_amount_original and (float_compare(wizard.total_amount_original, wizard.total_amount, precision_digits=2) == 0)
|
||||
wizard.split_possible = wizard.total_amount_currency_original \
|
||||
and wizard.currency_id.compare_amounts(wizard.total_amount_currency_original, wizard.total_amount_currency) == 0
|
||||
|
||||
def action_split_expense(self):
|
||||
self.ensure_one()
|
||||
|
|
@ -41,22 +49,21 @@ class HrExpenseSplitWizard(models.TransientModel):
|
|||
self.expense_split_line_ids -= expense_split
|
||||
if self.expense_split_line_ids:
|
||||
for split in self.expense_split_line_ids:
|
||||
copied_expenses |= self.expense_id.copy(split._get_values())
|
||||
copied_expenses |= self.expense_id.with_context({'from_split_wizard': True}).copy(split._get_values())
|
||||
|
||||
attachment_ids = self.env['ir.attachment'].search([
|
||||
('res_model', '=', 'hr.expense'),
|
||||
('res_id', '=', self.expense_id.id)
|
||||
])
|
||||
|
||||
for coplied_expense in copied_expenses:
|
||||
for copied_expense in copied_expenses:
|
||||
for attachment in attachment_ids:
|
||||
attachment.copy({'res_model': 'hr.expense', 'res_id': coplied_expense.id})
|
||||
attachment.copy({'res_model': 'hr.expense', 'res_id': copied_expense.id})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'hr.expense',
|
||||
'name': _('Split Expenses'),
|
||||
'view_mode': 'tree,form',
|
||||
'target': 'current',
|
||||
'domain': [('id', 'in', (copied_expenses | self.expense_split_line_ids.expense_id).ids)],
|
||||
}
|
||||
split_expense_ids = self.env['hr.expense']
|
||||
if self.expense_id.split_expense_origin_id:
|
||||
split_expense_ids = self.env['hr.expense'].search([('split_expense_origin_id', '=', self.expense_id.split_expense_origin_id.id)])
|
||||
(self.expense_id | copied_expenses).split_expense_origin_id = self.expense_id.split_expense_origin_id or self.expense_id
|
||||
all_related_expenses = copied_expenses | self.expense_id | split_expense_ids
|
||||
|
||||
return all_related_expenses._get_records_action(name=_("Split Expenses"))
|
||||
|
|
|
|||
|
|
@ -6,39 +6,49 @@
|
|||
<field name="model">hr.expense.split.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="total_amount_original" invisible="1"/>
|
||||
<div class="alert alert-warning w-100 d-flex align-items-center gap-1"
|
||||
invisible="split_possible" role="alert">
|
||||
<span>The total amount doesn't match the original amount.</span>
|
||||
</div>
|
||||
<field name="total_amount_currency_original" invisible="1"/>
|
||||
<field name="expense_id" invisible="1"/>
|
||||
<field name="expense_split_line_ids" widget="one2many" context="{'default_expense_id': expense_id}">
|
||||
<tree editable="bottom">
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="expense_id" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="product_has_tax" invisible="1"/>
|
||||
<field name="product_has_cost" invisible="1"/>
|
||||
<list editable="bottom">
|
||||
<field name="currency_id" column_invisible="True"/>
|
||||
<field name="expense_id" column_invisible="True"/>
|
||||
<field name="company_id" column_invisible="True"/>
|
||||
<field name="product_has_cost" column_invisible="True"/>
|
||||
<field name="name"/>
|
||||
<field name="product_id"/>
|
||||
<field name="total_amount" force_save="1" attrs="{'readonly': [('product_has_cost', '=', True)]}"/>
|
||||
<field name="tax_ids" widget="many2many_tags" attrs="{'readonly': [('product_has_tax', '=', False)]}"/>
|
||||
<field name="amount_tax"/>
|
||||
<field name="product_id"
|
||||
context="{
|
||||
'default_type': 'service',
|
||||
'default_can_be_expensed': 1,
|
||||
'list_view_ref': 'hr_expense.product_product_expense_tree_view',
|
||||
'form_view_ref': 'hr_expense.product_product_expense_form_view',
|
||||
}"
|
||||
/>
|
||||
<field name="employee_id" widget="many2one_avatar_user"/>
|
||||
<field name="tax_ids" widget="many2many_tags"/>
|
||||
<field name="tax_amount_currency"/>
|
||||
<field name="analytic_distribution" widget="analytic_distribution"
|
||||
optional="show"
|
||||
groups="analytic.group_analytic_accounting"/>
|
||||
<field name="employee_id" widget="many2one_avatar_employee"/>
|
||||
</tree>
|
||||
<field name="total_amount_currency" force_save="1" readonly="product_has_cost"/>
|
||||
</list>
|
||||
</field>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<group class="oe_subtotal_footer oe_right" colspan="2" name="expense_total">
|
||||
<label for="total_amount" attrs="{'invisible': [('split_possible', '=', True)]}"/>
|
||||
<field name="total_amount" nolabel="1" class="text-danger" attrs="{'invisible': [('split_possible', '=', True)]}"/>
|
||||
<field name="total_amount" attrs="{'invisible': [('split_possible', '=', False)]}"/>
|
||||
<field name="total_amount_original" widget='monetary' string="Original Amount"/>
|
||||
<field name="total_amount_taxes" widget='monetary' string="Taxes"/>
|
||||
<group class="oe_subtotal_footer" colspan="2" name="expense_total">
|
||||
<label for="total_amount_currency" invisible="split_possible"/>
|
||||
<field name="total_amount_currency" nolabel="1" class="text-danger" invisible="split_possible"/>
|
||||
<field name="total_amount_currency" invisible="not split_possible"/>
|
||||
<field name="total_amount_currency_original" widget='monetary' string="Original Amount"/>
|
||||
<field name="tax_amount_currency" widget='monetary' string="Taxes"/>
|
||||
</group>
|
||||
<field name="split_possible" invisible="1"/>
|
||||
<footer>
|
||||
<button name="action_split_expense" attrs="{'invisible': [ ('split_possible', '=', True)]}" string="Split Expense" type="object" class="oe_highlight" disabled="disabled" data-hotkey="q"/>
|
||||
<button name="action_split_expense" string="Split Expense" attrs="{'invisible': [('split_possible', '=', False)]}" type="object" class="oe_highlight" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
<button name="action_split_expense" invisible="split_possible" string="Split Expense" type="object" class="oe_highlight" disabled="disabled" data-hotkey="q"/>
|
||||
<button name="action_split_expense" string="Split Expense" invisible="not split_possible" type="object" class="oe_highlight" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue