Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions account_invoice_move_currency/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Account Invoice Move Currency

This module extends Odoo's accounting functionality to enable dual-currency tracking on invoices. It allows storing invoice values in a secondary currency alongside the primary invoice currency, providing better visibility for businesses operating in multi-currency environments.

## Features

- **Secondary Currency Tracking**: Add a secondary currency to invoices to track values in an additional currency
- **Custom Exchange Rates**: Define specific exchange rates for the secondary currency independently from the primary currency
- **Real-time Rate Calculation**: Automatically calculate exchange rates based on company currency and selected secondary currency
- **Currency Change Integration**: Optionally save the original currency as secondary when changing invoice currency
- **Multi-currency Validation**: Prevent conflicts between primary and secondary currencies with built-in validation rules
- **Reporting Support**: Display secondary currency values on invoice reports

## Configuration

### Prerequisites

- Ensure currency rates are configured under Accounting > Configuration > Currencies

### Installation

1. Install the module from the Apps menu
2. The module will automatically extend invoice forms with secondary currency fields

## Usage

### Adding a Secondary Currency to an Invoice

1. Navigate to Accounting > Customers > Invoices (or Vendors > Bills)
2. Open a draft invoice
3. In the invoice form, locate the **Secondary Currency** field (visible only with multi-currency enabled)
4. Select a secondary currency from the dropdown
5. The **Account Move Secondary Currency Rate** will be automatically calculated
6. You can manually adjust the rate if needed
7. Save the invoice

### Important Constraints

- **Same Currency Restriction**: The secondary currency cannot be the same as the invoice currency
- **Company Currency Requirement**: Secondary currency can only be used when the invoice currency matches the company currency
- If these conditions are not met, the system will display a validation error

### Changing Invoice Currency

When using the "Change Currency" wizard:

1. Open a draft invoice
2. Use the currency change wizard (available from `account_ux` module)
3. Select the new currency
4. Check the **Save in secondary currency?** option if you want to preserve the original currency as the secondary currency
5. When changing to the company currency, this option allows tracking the original currency values
6. Confirm the change

### Example Use Case

**Scenario**: A company operates in USD but wants to track invoice values in EUR for reporting purposes.

1. Create an invoice with currency = USD (company currency)
2. Set Secondary Currency = EUR
3. The system calculates the EUR rate automatically (or enter manually)
4. The invoice values are now tracked in both USD (primary) and EUR (secondary)
5. The secondary currency information is available for reporting and analysis

## Technical Details

### Dependencies

- `account_ux`: Provides extended accounting functionality and the currency change wizard

### Models Extended

#### account.move

**New Fields:**
- `move_currency_id`: Many2one field to store the secondary currency
- `move_inverse_currency_rate`: Float field (16,4 digits) to store the exchange rate

**Methods:**
- `change_move_currency()`: Onchange method that calculates the exchange rate when secondary currency is selected
- `check_move_currency()`: Constraint method that validates currency selection rules

#### account.change.currency (TransientModel)

**New Fields:**
- `save_secondary_currency`: Boolean field to enable saving original currency as secondary
- `same_currency`: Computed field to determine if target currency matches company currency
- `currency_company_id`: Related field for company currency

**Methods:**
- `_compute_same_currency()`: Computes whether the target currency is the company currency
- `change_currency()`: Extended to handle secondary currency preservation logic

### Views

- **Invoice Form View**: Adds secondary currency fields to the invoice header (visible only for invoices/bills in multi-currency setups)
- **Change Currency Wizard**: Adds checkbox to save original currency as secondary
- **Invoice Report**: Shows secondary currency information on printed invoices

### Security

No additional security groups or access rights are required beyond standard Odoo accounting permissions.

## Known Issues / Limitations

- Secondary currency is only available when the invoice currency is the same as the company currency
- The secondary currency and invoice currency cannot be the same
- Exchange rate modifications are only allowed in draft state

## Bug Tracker

Bugs are tracked on [GitHub Issues](https://github.com/ingadhoc/account-invoicing/issues). In case of trouble, please check there if your issue has already been reported.

## Credits

### Authors

* ADHOC SA

### Contributors

* ADHOC SA <info@adhoc.com.ar>

### Maintainers

This module is maintained by ADHOC SA.

To learn more about ADHOC SA, visit [www.adhoc.com.ar](http://www.adhoc.com.ar).

## License

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
2 changes: 2 additions & 0 deletions account_invoice_move_currency/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizards
17 changes: 17 additions & 0 deletions account_invoice_move_currency/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Account Invoice Move Currency",
"version": "18.0.1.0.0",
"author": "ADHOC SA",
"category": "Accounting & Finance",
"depends": ["account_ux"],
"data": [
"views/account_move_views.xml",
"views/report_invoice.xml",
"wizards/account_change_currency_views.xml",
],
Comment thread
cav-adhoc marked this conversation as resolved.
"website": "www.adhoc.com.ar",
"license": "AGPL-3",
"installable": True,
"auto_install": False,
"application": False,
}
1 change: 1 addition & 0 deletions account_invoice_move_currency/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import account_move
36 changes: 36 additions & 0 deletions account_invoice_move_currency/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError


class AccountMove(models.Model):
_inherit = "account.move"

move_currency_id = fields.Many2one(
"res.currency",
"Secondary Currency",
readonly=True,
help="If you set a currency here, then this invoice values will be also stored in the related Account Move Secondary Currency",
)

move_inverse_currency_rate = fields.Float(
digits=(16, 4), string="Account Move Secondary Currency Rate", readonly=True
Comment thread
cav-adhoc marked this conversation as resolved.
)

@api.onchange("move_currency_id")
def change_move_currency(self):
if not self.move_currency_id:
self.move_inverse_currency_rate = False
else:
self.move_inverse_currency_rate = self.move_currency_id._convert(
1.0, self.company_id.currency_id, self.company_id, self.invoice_date or fields.Date.context_today(self)
)

@api.constrains("move_currency_id", "currency_id")
def check_move_currency(self):
for rec in self.filtered("move_currency_id"):
if rec.move_currency_id == rec.currency_id:
raise UserError(_("Secondary currency can not be the same as Invoice Currency"))
if rec.currency_id != rec.company_id.currency_id:
raise UserError(
_("Can not use Secondary currency if invoice is in a Currency different from Company Currency")
)
18 changes: 18 additions & 0 deletions account_invoice_move_currency/views/account_move_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="view_move_form">
<field name="name">account.move.view</field>
<field name="inherit_id" ref="account_ux.view_move_form"/>
<field name="model">account.move</field>
<field name="priority" eval="50"/>
<field name="arch" type="xml">
<div name="journal_div" position="after">
<label for="move_currency_id" name="Move Currency" groups="base.group_multi_currency" invisible="move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')"/>
<div groups="base.group_multi_currency" class="d-flex" invisible="move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')">
<field name="move_currency_id" options="{'no_create': True, 'no_open': True}" class="oe_inline" readonly="state != 'draft'"/>
<field name="move_inverse_currency_rate" class="oe_inline" placeholder="Rate..." invisible="not move_currency_id" readonly="state != 'draft'" required="move_currency_id"/>
</div>
</div>
</field>
</record>
</odoo>
9 changes: 9 additions & 0 deletions account_invoice_move_currency/views/report_invoice.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<odoo>
<template id="report_invoice_document" inherit_id="account.report_invoice_document">
<div name="comment" position="before">
<p t-if="o._fields.get('move_currency_id') and o.move_currency_id and o.move_inverse_currency_rate &gt; 0.0">
El total de este comprobante equivale a un total de <span t-out="o.amount_total / o.move_inverse_currency_rate" t-options="{'widget': 'monetary', 'display_currency': o.move_currency_id}"/> a un tipo de cambio consignado de <span t-field="o.move_inverse_currency_rate"/>
</p>
</div>
</template>
</odoo>
1 change: 1 addition & 0 deletions account_invoice_move_currency/wizards/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import account_change_currency
30 changes: 30 additions & 0 deletions account_invoice_move_currency/wizards/account_change_currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from odoo import api, fields, models


class AccountChangeCurrency(models.TransientModel):
_inherit = "account.change.currency"

save_secondary_currency = fields.Boolean("Save in secondary currency?")
same_currency = fields.Boolean(compute="_compute_same_currency")
currency_company_id = fields.Many2one("res.currency", related="move_id.company_id.currency_id", store=True)

@api.depends("currency_company_id", "currency_to_id")
def _compute_same_currency(self):
for rec in self:
if rec.currency_company_id == rec.currency_to_id:
rec.same_currency = True
else:
rec.same_currency = False

def change_currency(self):
# We set it false because if you change the currency to
# the same as the secondary currency they can not be the same
if self.move_id.move_currency_id == self.currency_to_id:
self.move_id.move_currency_id = False
self.move_id.move_inverse_currency_rate = False
currency_from_id = self.currency_from_id
res = super().change_currency()
if self.save_secondary_currency and self.same_currency:
self.move_id.move_currency_id = currency_from_id.id
self.move_id.move_inverse_currency_rate = self.conversion_rate
return res
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_account_change_currency" model="ir.ui.view">
<field name="name">Change Currency inherit</field>
<field name="model">account.change.currency</field>
<field name="inherit_id" ref="account_ux.view_account_change_currency"/>
<field name="arch" type="xml">
<field name="conversion_rate" position="after">
<field name="save_secondary_currency" invisible="not same_currency"/>
</field>
</field>
</record>
</odoo>