Skip to content
Open
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
6 changes: 1 addition & 5 deletions purchase_manual_currency/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

==========================
Purchase - Manual Currency
==========================
Expand All @@ -17,7 +13,7 @@ Purchase - Manual Currency
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github
Expand Down
2 changes: 1 addition & 1 deletion purchase_manual_currency/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{
"name": "Purchase - Manual Currency",
"version": "18.0.1.0.0",
"version": "18.0.2.0.0",
"category": "Purchase Management",
"summary": "Allows to manual currency of Purchase",
"author": "Ecosoft, Odoo Community Association (OCA)",
Expand Down
11 changes: 11 additions & 0 deletions purchase_manual_currency/migrations/18.0.2.0.0/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright 2026 Ecosoft Co., Ltd (https://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from openupgradelib import openupgrade


@openupgrade.migrate()
def migrate(env, version):
# Recompute
orders = env["purchase.order"].search([("manual_currency", "=", True)])
orders._amount_all()
16 changes: 16 additions & 0 deletions purchase_manual_currency/migrations/18.0.2.0.0/pre-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2026 Ecosoft Co., Ltd (https://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from openupgradelib import openupgrade


@openupgrade.migrate()
def migrate(env, version):
openupgrade.logged_query(
env.cr,
"""
UPDATE purchase_order
SET currency_rate = manual_currency_rate
WHERE manual_currency = TRUE
""",
)
127 changes: 33 additions & 94 deletions purchase_manual_currency/models/purchase.py
Original file line number Diff line number Diff line change
@@ -1,135 +1,72 @@
# Copyright 2020 Ecosoft Co., Ltd. (http://ecosoft.co.th)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from lxml import etree

from odoo import _, api, fields, models
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError


class PurchaseOrder(models.Model):
_inherit = "purchase.order"

manual_currency = fields.Boolean()
is_manual = fields.Boolean(compute="_compute_currency")
type_currency = fields.Selection(
selection=lambda self: self._get_label_currency_name(),
default=lambda self: self._get_label_currency_name()[0][0],
)
manual_currency_rate = fields.Float(
digits="Manual Currency",
tracking=True,
help="Set new currency rate to apply on the invoice\n."
"This rate will be taken in order to convert amounts between the "
"currency on the purchase order and last currency",
)
company_currency_id = fields.Many2one(
comodel_name="res.currency",
related="company_id.currency_id",
string="Company Currency",
)
total_company_currency = fields.Monetary(
compute="_compute_total_company_currency",
store=True,
currency_field="company_currency_id",
)
currency_diff = fields.Boolean(
compute="_compute_currency_diff",
store=True,
)

@api.depends("currency_id")
def _compute_currency_diff(self):
for rec in self:
rec.currency_diff = rec.company_currency_id != rec.currency_id

@api.depends("order_line.subtotal_company_currency")
def _compute_total_company_currency(self):
"""Convert total currency to company currency"""
for rec in self:
# check manual currency
if rec.manual_currency:
rate = (
rec.manual_currency_rate
if rec.type_currency == "inverse_company_rate"
else (1.0 / rec.manual_currency_rate)
)
rec.total_company_currency = rec.amount_total * rate
# default rate currency
else:
rec.total_company_currency = rec.currency_id._convert(
rec.amount_total,
rec.company_currency_id,
rec.company_id,
fields.Date.today(),
)

def _get_label_currency_name(self):
"""Get label related currency"""
names = {
"company_currency_name": (
self.env["res.company"].browse(self._context.get("company_id"))
self.env["res.company"].browse(self.env.context.get("company_id"))
or self.env.company
).currency_id.name,
"rate_currency_name": "Currency",
}
return [
[
"company_rate",
_("%(rate_currency_name)s per 1 %(company_currency_name)s", **names),
self.env._(
"%(rate_currency_name)s per 1 %(company_currency_name)s", **names
),
],
[
"inverse_company_rate",
_("%(company_currency_name)s per 1 %(rate_currency_name)s", **names),
self.env._(
"%(company_currency_name)s per 1 %(rate_currency_name)s", **names
),
],
]

@api.onchange("manual_currency", "type_currency", "currency_id", "date_order")
def _onchange_currency_change_rate(self):
today = fields.Date.today()
company_currency = self.env.company.currency_id
amount_currency = company_currency._get_conversion_rate(
company_currency,
self.currency_id,
self.company_id,
self.date_order or today,
)
if self.type_currency == "inverse_company_rate":
amount_currency = 1.0 / amount_currency
self.manual_currency_rate = amount_currency
@api.depends(
"order_line.price_subtotal", "company_id", "currency_id", "currency_rate"
)
def _amount_all(self):
"""Add currency_rate dependency to trigger recompute"""
return super()._amount_all()

@api.depends("currency_id")
def _compute_currency(self):
for rec in self:
rec.is_manual = rec.currency_id != rec.company_id.currency_id
@api.depends(
"currency_id", "date_order", "company_id", "manual_currency", "type_currency"
)
def _compute_currency_rate(self):
res = super()._compute_currency_rate()
for order in self:
if order.manual_currency and order.type_currency == "inverse_company_rate":
order.currency_rate = 1.0 / order.currency_rate
return res

def action_refresh_currency(self):
self.ensure_one()
if self.state != "draft":
raise ValidationError(_("Rate currency can refresh state draft only."))
self._onchange_currency_change_rate()
return True

@api.model
def get_view(self, view_id=None, view_type="form", **options):
"""Change string name to company currency"""
result = super().get_view(view_id=view_id, view_type=view_type, **options)
if view_type == "form":
company_currency_name = (
self.env["res.company"].browse(self._context.get("company_id"))
or self.env.company
).currency_id.name
doc = etree.XML(result["arch"])
# Total company currency
node = doc.xpath("//field[@name='total_company_currency']")
if node:
node[0].set("string", f"Total ({company_currency_name})")
# Subtotal company currency
node = doc.xpath("//field[@name='subtotal_company_currency']")
if node:
node[0].set("string", f"Subtotal ({company_currency_name})")
result["arch"] = etree.tostring(doc, encoding="unicode")
return result
raise ValidationError(
self.env._("Rate currency can refresh state draft only.")
)
return self._compute_currency_rate()

def action_view_invoice(self, invoices=False):
result = super().action_view_invoice(invoices)
Expand All @@ -141,11 +78,13 @@ def action_view_invoice(self, invoices=False):
purchases = inv.invoice_line_ids.mapped("purchase_order_id")
if len(set(purchases.mapped("manual_currency"))) != 1:
raise UserError(
_("In invoice cannot have a mixture of different manual currency.")
self.env._(
"In invoice cannot have a mixture of different manual currency."
)
)
elif len(set(purchases.mapped("manual_currency_rate"))) != 1:
elif len(set(purchases.mapped("currency_rate"))) != 1:
raise UserError(
_(
self.env._(
"In invoice cannot have a mixture of different "
"manual currency rates in purchases."
)
Expand All @@ -159,7 +98,7 @@ def action_view_invoice(self, invoices=False):
{
"manual_currency": purchases[0].manual_currency,
"type_currency": purchases[0].type_currency,
"manual_currency_rate": purchases[0].manual_currency_rate,
"manual_currency_rate": purchases[0].currency_rate,
}
)
return result
52 changes: 31 additions & 21 deletions purchase_manual_currency/models/purchase_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class PurchaseOrderLine(models.Model):
company_currency_id = fields.Many2one(
comodel_name="res.currency",
related="order_id.company_currency_id",
store=True,
string="Company Currency",
)
subtotal_company_currency = fields.Monetary(
Expand All @@ -21,28 +22,37 @@ class PurchaseOrderLine(models.Model):

@api.depends(
"price_subtotal",
"order_id.manual_currency_rate",
"order_id.currency_rate",
"order_id.type_currency",
"order_id.manual_currency",
)
def _compute_amount_company_currency(self):
for rec in self:
rec.subtotal_company_currency = rec.price_subtotal
# check multi-currency
if rec.company_currency_id != rec.currency_id:
# check manual currency
if rec.order_id.manual_currency:
rate = (
rec.order_id.manual_currency_rate
if rec.order_id.type_currency == "inverse_company_rate"
else (1.0 / rec.order_id.manual_currency_rate)
)
rec.subtotal_company_currency = rec.price_subtotal * rate
# default rate currency
else:
rec.subtotal_company_currency = rec.currency_id._convert(
rec.price_subtotal,
rec.company_currency_id,
rec.company_id,
fields.Date.today(),
)
for line in self:
line.subtotal_company_currency = line.price_subtotal
if line.company_currency_id != line.currency_id:
order = line.order_id
rate = (
order.currency_rate
if order.type_currency == "inverse_company_rate"
else (1.0 / order.currency_rate)
)
line.subtotal_company_currency = line.price_subtotal * rate

def _prepare_base_line_for_taxes_computation(self):
self.ensure_one()
order = self.order_id
if order.manual_currency and order.type_currency == "inverse_company_rate":
# Convert Rate back to company_rate
rate = 1
if order.currency_rate:
rate = 1 / order.currency_rate
return self.env["account.tax"]._prepare_base_line_for_taxes_computation(
self,
tax_ids=self.taxes_id,
quantity=self.product_qty,
partner_id=self.order_id.partner_id,
currency_id=self.order_id.currency_id
or self.order_id.company_id.currency_id,
rate=rate,
)
return super()._prepare_base_line_for_taxes_computation()
26 changes: 10 additions & 16 deletions purchase_manual_currency/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<title>Purchase - Manual Currency</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,21 +360,16 @@
</style>
</head>
<body>
<div class="document">
<div class="document" id="purchase-manual-currency">
<h1 class="title">Purchase - Manual Currency</h1>


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="purchase-manual-currency">
<h1>Purchase - Manual Currency</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:9923d9a009abd9bd6327ed125e7c569e16a3d9018dc02f92767694feb2603c31
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/purchase-workflow/tree/18.0/purchase_manual_currency"><img alt="OCA/purchase-workflow" src="https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/purchase-workflow-18-0/purchase-workflow-18-0-purchase_manual_currency"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/purchase-workflow&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/purchase-workflow/tree/18.0/purchase_manual_currency"><img alt="OCA/purchase-workflow" src="https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/purchase-workflow-18-0/purchase-workflow-18-0-purchase_manual_currency"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/purchase-workflow&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows users to update the currency manual of Purchase Order
in draft state.</p>
<p>Example, The company will make a deal with vendor before buy product
Expand All @@ -395,31 +390,31 @@ <h1>Purchase - Manual Currency</h1>
</ul>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>To use this module, the company have to access rights Multi Currencies.</p>
<p>Step to used manual currency: #. Go to Invoicing &gt; Configuration &gt;
Settings &gt; Currencies &gt; Multi-Currencies #. Go to Purchase &gt; Create new
purchase. #. Change to other currency (not company currency) #. Check
Manual Currency and specify your currency rate</p>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/purchase-workflow/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/purchase-workflow/issues/new?body=module:%20purchase_manual_currency%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>Ecosoft</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li>Saran Lim. &lt;<a class="reference external" href="mailto:saranl&#64;ecosoft.co.th">saranl&#64;ecosoft.co.th</a>&gt;</li>
<li><a class="reference external" href="https://www.prothaitechnology.com:">Prothai</a>:<ul>
Expand All @@ -430,7 +425,7 @@ <h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -443,6 +438,5 @@ <h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
</div>
</div>
</div>
</div>
</body>
</html>
Loading
Loading