forked from OCA/hr-expense
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
From expense sheet, add action "Create Invoice" from multiple expenses Change the way reference invoice_id is checked. - No more onchange invoice_id that set values to expense - Instead, check amount on expense and invoice during post entry - Change the way to allow reconcile with > 2 account move lines
Showing
15 changed files
with
393 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
from . import models | ||
from . import wizard |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
from . import hr_expense | ||
from . import hr_expense_sheet | ||
from . import account_payment | ||
from . import account_move_line |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Copyright 2019 Kitti U. <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from odoo import api, models | ||
|
||
|
||
class AccountMoveLine(models.Model): | ||
_inherit = 'account.move.line' | ||
|
||
@api.multi | ||
def reconcile(self, writeoff_acc_id=False, writeoff_journal_id=False): | ||
if self._context.get('use_hr_expense_invoice'): | ||
self = self.filtered(lambda l: not l.reconciled) | ||
res = super().reconcile(writeoff_acc_id=writeoff_acc_id, | ||
writeoff_journal_id=writeoff_journal_id) | ||
return res |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Copyright 2019 Kitti U. <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from odoo import models, _ | ||
from odoo.exceptions import ValidationError | ||
|
||
|
||
class AccountPayment(models.Model): | ||
_inherit = 'account.payment' | ||
|
||
def action_validate_invoice_payment(self): | ||
# Do not allow register payment for invoices from expenses | ||
expenses = self.env['hr.expense'].sudo().\ | ||
search([('invoice_id', 'in', self.invoice_ids.ids)]) | ||
if expenses: | ||
raise ValidationError(_("Register payment on expense's " | ||
"invoice is not allowed")) | ||
return super(AccountPayment, self).action_validate_invoice_payment() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,8 @@ | |
# Copyright 2017 Vicent Cubells <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import api, fields, models | ||
from odoo import api, fields, models, _ | ||
from odoo.exceptions import UserError | ||
|
||
|
||
class HrExpense(models.Model): | ||
|
@@ -13,38 +14,16 @@ class HrExpense(models.Model): | |
string='Vendor Bill', | ||
domain="[('type', '=', 'in_invoice'), ('state', '=', 'open')]", | ||
oldname='invoice', | ||
copy=False, | ||
) | ||
|
||
@api.onchange('invoice_id') | ||
def onchange_invoice_id(self): | ||
self.date = self.invoice_id.date_invoice | ||
self.name = self.invoice_id.number | ||
self.reference = self.invoice_id.number or self.invoice_id.reference | ||
self.analytic_account_id = False | ||
self.unit_amount = self.invoice_id.residual | ||
self.quantity = 1.0 | ||
self.total_amount = self.unit_amount | ||
self.description = self.invoice_id.reference | ||
|
||
def _check_vals(self, vals): | ||
if vals.get('invoice_id'): | ||
# Rewrite values because readonly fields are not stored | ||
invoice = self.env['account.invoice'].browse(vals['invoice_id']) | ||
vals['date'] = invoice.date_invoice | ||
vals['analytic_account_id'] = False | ||
vals['unit_amount'] = invoice.residual | ||
vals['total_amount'] = invoice.residual | ||
vals['quantity'] = 1.0 | ||
|
||
@api.model | ||
def create(self, vals): | ||
self._check_vals(vals) | ||
return super(HrExpense, self).create(vals) | ||
|
||
@api.multi | ||
def write(self, vals): | ||
self._check_vals(vals) | ||
return super(HrExpense, self).write(vals) | ||
@api.constrains('invoice_id') | ||
def _check_invoice_id(self): | ||
for expense in self: # Only non binding expense | ||
if not expense.sheet_id and expense.invoice_id and \ | ||
expense.invoice_id.state != 'open': | ||
raise UserError(_("Vendor bill state must be Open")) | ||
|
||
@api.multi | ||
def _get_account_move_line_values(self): | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,44 +2,89 @@ | |
# Copyright 2017 Vicent Cubells <[email protected]> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
|
||
from odoo import _, api, models | ||
from odoo import models, fields, api, _ | ||
from odoo.exceptions import UserError | ||
from odoo.tools import float_compare | ||
|
||
|
||
class HrExpenseSheet(models.Model): | ||
_inherit = "hr.expense.sheet" | ||
|
||
invoice_count = fields.Integer( | ||
compute='_compute_invoice_count', | ||
) | ||
invoice_fully_created = fields.Boolean( | ||
compute='_compute_invoice_count', | ||
) | ||
|
||
@api.multi | ||
def action_sheet_move_create(self): | ||
DecimalPrecision = self.env['decimal.precision'] | ||
precision = DecimalPrecision.precision_get('Product Price') | ||
expense_line_ids = \ | ||
self.mapped('expense_line_ids').filtered('invoice_id') | ||
self._validate_expense_invoice(expense_line_ids) | ||
res = super(HrExpenseSheet, self).action_sheet_move_create() | ||
move_lines = self.env['account.move'].search( | ||
[('ref', 'in', self.mapped('name'))], | ||
).mapped('line_ids') | ||
for line in expense_line_ids: | ||
partner = line.invoice_id.partner_id.commercial_partner_id | ||
c_move_lines = move_lines.filtered( | ||
lambda x: | ||
x.partner_id == partner and | ||
x.debit == line.invoice_id.residual and | ||
not x.reconciled | ||
) | ||
if len(c_move_lines) > 1: | ||
c_move_lines = c_move_lines[0] | ||
residual = line.invoice_id.residual | ||
c_move_lines |= line.invoice_id.move_id.line_ids.filtered( | ||
lambda x: | ||
x.account_id == line.invoice_id.account_id and | ||
float_compare(x.credit, residual, precision) == 0) | ||
if len(c_move_lines) != 2: | ||
raise UserError( | ||
_('Cannot reconcile supplier invoice payable with ' | ||
'generated line. Please check amounts and see ' | ||
'if the invoice is already added or paid. ' | ||
'Invoice: %s') % line.invoice_id.number) | ||
for sheet in self: | ||
move_lines = res[sheet.id].line_ids | ||
expense_line_ids = self.expense_line_ids.filtered('invoice_id') | ||
c_move_lines = self.env['account.move.line'] | ||
for line in expense_line_ids: | ||
partner = line.invoice_id.partner_id.commercial_partner_id | ||
c_move_lines |= move_lines.filtered( | ||
lambda x: | ||
x.partner_id == partner and x.debit and not x.reconciled) | ||
c_move_lines |= line.invoice_id.move_id.line_ids.filtered( | ||
lambda x: | ||
x.account_id == line.invoice_id.account_id | ||
and x.credit and not x.reconciled) | ||
c_move_lines.reconcile() | ||
return res | ||
|
||
@api.multi | ||
def _compute_invoice_count(self): | ||
Invoice = self.env['account.invoice'] | ||
can_read = Invoice.check_access_rights('read', raise_exception=False) | ||
for sheet in self: | ||
sheet.invoice_count = can_read and \ | ||
len(sheet.expense_line_ids.mapped('invoice_id')) or 0 | ||
sheet.invoice_fully_created = \ | ||
not any(self.mapped('expense_line_ids'). | ||
filtered(lambda l: not l.invoice_id)) | ||
|
||
@api.model | ||
def _validate_expense_invoice(self, expense_lines): | ||
DecimalPrecision = self.env['decimal.precision'] | ||
precision = DecimalPrecision.precision_get('Product Price') | ||
invoices = expense_lines.mapped('invoice_id') | ||
if not invoices: | ||
return | ||
# All invoices must confirmed | ||
if any(invoices.filtered(lambda i: i.state != 'open')): | ||
raise UserError(_('Vendor bill state must be Open')) | ||
expense_amount = sum(expense_lines.mapped('total_amount')) | ||
invoice_amount = sum(invoices.mapped('residual')) | ||
# Expense amount must equal invoice amount | ||
if float_compare(expense_amount, invoice_amount, precision) != 0: | ||
raise UserError( | ||
_('Vendor bill amount mismatch!\nPlease make sure amount in ' | ||
'vendor bills equal to amount of its expense lines')) | ||
|
||
@api.multi | ||
def action_view_invoices(self): | ||
self.ensure_one() | ||
action = { | ||
'name': _('Invoices'), | ||
'type': 'ir.actions.act_window', | ||
'res_model': 'account.invoice', | ||
'target': 'current', | ||
} | ||
invoice_ids = self.expense_line_ids.mapped('invoice_id').ids | ||
view = self.env.ref('account.invoice_supplier_form') | ||
if len(invoice_ids) == 1: | ||
invoice = invoice_ids[0] | ||
action['res_id'] = invoice | ||
action['view_mode'] = 'form' | ||
action['views'] = [(view.id, 'form')] | ||
else: | ||
action['view_mode'] = 'tree,form' | ||
action['domain'] = [('id', 'in', invoice_ids)] | ||
return action |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
This module should be used when a supplier invoice is paid by an employee. It | ||
allows to set a supplier invoice for each expense line, adding the | ||
corresponding journal items to transfer the debt to the employee. | ||
|
||
There are 2 ways to reference expense to invoice. | ||
|
||
1. On expense, directly select one invoice. | ||
2. On expense report, use button "Create Vendor Bill" to create one invoice | ||
for multiple expenses. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,15 @@ | ||
**Reference one invoice to an expense** | ||
|
||
* Create an expense sheet. | ||
* Add an expense line to sheet with an invoice_id selected or create one new. | ||
* Process expense sheet. | ||
* On paying expense sheet, you are reconciling supplier invoice too. | ||
|
||
**Create one invoice to multiple expenses** | ||
|
||
* Create an expense sheet with one or multiple expense lines | ||
* After approved, click button "Create Vendor Bill" | ||
* Select multiple expense to create an invoice, and process it. | ||
* New invoice will be create and link to the selected expense lines. | ||
* Validate newly create invoice. | ||
* On paying expense sheet, you are reconciling supplier invoice(s) too. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from . import expense_create_invoice | ||
from . import hr_expense_sheet_register_payment |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# Copyright 2019 Kitti U. <kittiu@ecosoft.co.th> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from odoo import models, fields, api, _ | ||
from odoo.exceptions import UserError | ||
|
||
|
||
class HRExpenseCreateInvoice(models.TransientModel): | ||
_name = 'hr.expense.create.invoice' | ||
_description = 'Create invoice from expense report' | ||
|
||
expense_ids = fields.Many2many( | ||
comodel_name='hr.expense', | ||
string='Expenses', | ||
domain=lambda self: self._domain_expense_ids(), | ||
required=True, | ||
) | ||
|
||
@api.model | ||
def view_init(self, fields): | ||
active_id = self._context.get('active_id') | ||
sheet = self.env['hr.expense.sheet'].browse(active_id) | ||
if sheet.state != 'approve': | ||
raise UserError(_('This action is allowed only in Approved sate')) | ||
return super().view_init(fields) | ||
|
||
@api.model | ||
def _domain_expense_ids(self): | ||
active_id = self._context.get('active_id') | ||
sheet = self.env['hr.expense.sheet'].browse(active_id) | ||
domain = [('id', 'in', sheet.expense_line_ids.ids), | ||
('invoice_id', '=', False)] | ||
return domain | ||
|
||
@api.multi | ||
def create_invoice(self): | ||
"""Use information from selected invoice to create invoice.""" | ||
self.ensure_one() | ||
expenses = self.expense_ids.filtered(lambda l: not l.invoice_id) | ||
if not expenses: | ||
raise UserError(_('No valid expenses to create invoice')) | ||
expense = expenses[0] | ||
invoice_lines = [ | ||
(0, 0, {'product_id': x.product_id.id, | ||
'name': x.name, | ||
'price_unit': x.unit_amount, | ||
'quantity': x.quantity, | ||
'date_invoice': x.date, | ||
'account_id': x.account_id.id, | ||
'invoice_line_tax_ids': [(6, 0, x.tax_ids.ids)], }) | ||
for x in expenses | ||
] | ||
invoice_vals = { | ||
'type': 'in_invoice', | ||
'journal_type': 'purchase', | ||
'reference': expense.reference, | ||
'date_invoice': expense.date, | ||
'invoice_line_ids': invoice_lines, } | ||
invoice = self.env['account.invoice'].create(invoice_vals) | ||
self.expense_ids.write({'invoice_id': invoice.id}) | ||
return invoice |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
|
||
<record model="ir.ui.view" id="hr_expense_create_invoice_form"> | ||
<field name="name">hr.expense.create.invoice.form</field> | ||
<field name="model">hr.expense.create.invoice</field> | ||
<field name="arch" type="xml"> | ||
<form string="Create Vendor Bill from Expense"> | ||
<separator string="Please select expense line to create new vendor bill:"/> | ||
<group> | ||
<field name="expense_ids" nolabel="1" options="{'no_create': True}"/> | ||
</group> | ||
<footer> | ||
<button name="create_invoice" string="Create Invoice" type="object" class="btn-primary"/> | ||
<button class="btn-secondary" special="cancel" string="Cancel" /> | ||
</footer> | ||
</form> | ||
</field> | ||
</record> | ||
|
||
</odoo> |
12 changes: 12 additions & 0 deletions
12
hr_expense_invoice/wizard/hr_expense_sheet_register_payment.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Copyright 2019 Kitti U. <kittiu@ecosoft.co.th> | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||
from odoo import api, models | ||
|
||
|
||
class HrExpenseSheetRegisterPaymentWizard(models.TransientModel): | ||
_inherit = 'hr.expense.sheet.register.payment.wizard' | ||
|
||
@api.multi | ||
def expense_post_payment(self): | ||
self = self.with_context(use_hr_expense_invoice=True) | ||
return super().expense_post_payment() |