[IMP] base: Currencies configuration

Improves list; kanban and form view for currencies and currency rates.
Add checks to prevent changing/adding rates when accounting entries exists
on dates concerned by those rates.

Task: 2450747
This commit is contained in:
jbw
2021-03-31 14:15:54 +00:00
parent 3ed1890c7a
commit 32ada4a415
7 changed files with 361 additions and 45 deletions
+1
View File
@@ -12,6 +12,7 @@ The kernel of Odoo, needed for all installation.
""",
'depends': [],
'data': [
'data/res_bank.xml',
'data/res.lang.csv',
'data/res_lang_data.xml',
'data/res_partner_data.xml',
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="res_bank_1" model="res.bank">
<field name="name">Reserve</field>
<field name="active" eval="False"/>
</record>
</data>
</odoo>
File diff suppressed because it is too large Load Diff
@@ -11,7 +11,7 @@
<record id="rateUSDbis" model="res.currency.rate">
<field name="rate">1.5289</field>
<field name="currency_id" ref="USD"/>
<field eval="time.strftime('%Y-06-06')" name="name"/>
<field eval="time.strftime('%Y-01-01')" name="name"/>
</record>
<record forcecreate="0" id="rateVEF" model="res.currency.rate">
-1
View File
@@ -10,7 +10,6 @@ import re
from odoo import api, fields, models, tools, _, Command
from odoo.exceptions import ValidationError, UserError
from odoo.modules.module import get_resource_path
from random import randrange
from PIL import Image
+129 -18
View File
@@ -5,8 +5,8 @@ import logging
import math
import re
import time
import traceback
from lxml import etree
from odoo import api, fields, models, tools, _
_logger = logging.getLogger(__name__)
@@ -27,18 +27,25 @@ class Currency(models.Model):
# Note: 'code' column was removed as of v6.0, the 'name' should now hold the ISO code.
name = fields.Char(string='Currency', size=3, required=True, help="Currency Code (ISO 4217)")
full_name = fields.Char(string='Name')
symbol = fields.Char(help="Currency sign, to be used when printing amounts.", required=True)
rate = fields.Float(compute='_compute_current_rate', string='Current Rate', digits=0,
help='The rate of the currency to the currency of rate 1.')
inverse_rate = fields.Float(compute='_compute_current_rate', digits=0, readonly=True,
help='The currency of rate 1 to the rate of the currency.')
rate_string = fields.Char(compute='_compute_current_rate')
rate_ids = fields.One2many('res.currency.rate', 'currency_id', string='Rates')
rounding = fields.Float(string='Rounding Factor', digits=(12, 6), default=0.01)
decimal_places = fields.Integer(compute='_compute_decimal_places', store=True)
rounding = fields.Float(string='Rounding Factor', digits=(12, 6), default=0.01,
help='Amounts in this currency are rounded off to the nearest multiple of the rounding factor.')
decimal_places = fields.Integer(compute='_compute_decimal_places', store=True,
help='Decimal places taken into account for operations on amounts in this currency. It is determined by the rounding factor.')
active = fields.Boolean(default=True)
position = fields.Selection([('after', 'After Amount'), ('before', 'Before Amount')], default='after',
string='Symbol Position', help="Determines where the currency symbol should be placed after or before the amount.")
date = fields.Date(compute='_compute_date')
currency_unit_label = fields.Char(string="Currency Unit", help="Currency Unit Name")
currency_subunit_label = fields.Char(string="Currency Subunit", help="Currency Subunit Name")
is_current_company_currency = fields.Boolean(compute='_compute_is_current_company_currency')
_sql_constraints = [
('unique_name', 'unique (name)', 'The currency code must be unique!'),
@@ -61,14 +68,25 @@ class Currency(models.Model):
currency_rates = dict(self._cr.fetchall())
return currency_rates
@api.depends_context('company')
def _compute_is_current_company_currency(self):
for currency in self:
currency.is_current_company_currency = self.env.company.currency_id == currency
@api.depends('rate_ids.rate')
def _compute_current_rate(self):
date = self._context.get('date') or fields.Date.today()
company = self.env['res.company'].browse(self._context.get('company_id')) or self.env.company
# the subquery selects the last rate before 'date' for the given currency/company
currency_rates = self._get_rates(company, date)
last_rate = self.env['res.currency.rate']._get_last_rates_for_companies(company)
for currency in self:
currency.rate = currency_rates.get(currency.id) or 1.0
currency.rate = (currency_rates.get(currency.id) or 1.0) / last_rate[company]
currency.inverse_rate = 1 / currency.rate
if currency != company.currency_id:
currency.rate_string = '1 %s = %s %s' % (company.currency_id.name, currency.rate, currency.name)
else:
currency.rate_string = ''
@api.depends('rounding')
def _compute_decimal_places(self):
@@ -229,6 +247,20 @@ class Currency(models.Model):
JOIN res_company c ON (r.company_id is null or r.company_id = c.id)
"""
@api.model
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
result = super(Currency, self)._fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
if view_type in ('tree', 'form'):
currency_name = (self.env['res.company'].browse(self._context.get('company_id')) or self.env.company).currency_id.name
doc = etree.XML(result['arch'])
for field in [['company_rate', _('Unit per %s', currency_name)],
['inverse_company_rate', _('%s per Unit', currency_name)]]:
node = doc.xpath("//tree//field[@name='%s']" % field[0])
if node:
node[0].set('string', field[1])
result['arch'] = etree.tostring(doc, encoding='unicode')
return result
class CurrencyRate(models.Model):
_name = "res.currency.rate"
@@ -237,8 +269,26 @@ class CurrencyRate(models.Model):
name = fields.Date(string='Date', required=True, index=True,
default=lambda self: fields.Date.today())
rate = fields.Float(digits=0, compute="_compute_rate", store=True, readonly=False, group_operator="avg",
help='The rate of the currency to the currency of rate 1')
rate = fields.Float(
digits=0,
group_operator="avg",
help='The rate of the currency to the currency of rate 1',
string='Technical Rate'
)
company_rate = fields.Float(
digits=0,
compute="_compute_company_rate",
inverse="_inverse_company_rate",
group_operator="avg",
help="The currency of rate 1 to the rate of the currency.",
)
inverse_company_rate = fields.Float(
digits=0,
compute="_compute_inverse_company_rate",
inverse="_inverse_inverse_company_rate",
group_operator="avg",
help="The currency of rate 1 to the rate of the currency.",
)
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, required=True, ondelete="cascade")
company_id = fields.Many2one('res.company', string='Company',
default=lambda self: self.env.company)
@@ -248,26 +298,70 @@ class CurrencyRate(models.Model):
('currency_rate_check', 'CHECK (rate>0)', 'The currency rate must be strictly positive.'),
]
def _sanitize_vals(self, vals):
if 'inverse_company_rate' in vals and ('company_rate' in vals or 'rate' in vals):
del vals['inverse_company_rate']
if 'company_rate' in vals and 'rate' in vals:
del vals['company_rate']
return vals
def write(self, vals):
return super().write(self._sanitize_vals(vals))
@api.model_create_multi
def create(self, vals_list):
return super().create([self._sanitize_vals(vals) for vals in vals_list])
def _get_latest_rate(self):
return self.search([
('currency_id', '=', self.currency_id.id),
('company_id', '=', self.company_id.id or self.env.company.id),
('name', '<=', self.name or fields.Date.today()),
], order="name desc", limit=1)
return self.currency_id.rate_ids.filtered(lambda x: (
x.rate
and x.company_id == (self.company_id or self.env.company)
and x.name < (self.name or fields.Date.today())
)).sorted('name')[-1:]
def _get_last_rates_for_companies(self, companies):
return {
company: company.currency_id.rate_ids.filtered(lambda x: (
x.rate
and x.company_id == company or not x.company_id
)).sorted('name')[-1:].rate or 1
for company in companies
}
@api.depends('currency_id', 'company_id', 'name')
def _compute_rate(self):
for currency_rate in self:
latest_rate = currency_rate._get_latest_rate()
currency_rate.rate = latest_rate.rate if latest_rate else 1.0
currency_rate.rate = currency_rate.rate or self._get_latest_rate().rate or 1.0
@api.onchange('rate')
@api.depends('rate', 'name', 'currency_id', 'company_id', 'currency_id.rate_ids.rate')
@api.depends_context('company')
def _compute_company_rate(self):
last_rate = self.env['res.currency.rate']._get_last_rates_for_companies(self.company_id | self.env.company)
for currency_rate in self:
company = currency_rate.company_id or self.env.company
currency_rate.company_rate = (currency_rate.rate or self._get_latest_rate().rate or 1.0) / last_rate[company]
@api.onchange('company_rate')
def _inverse_company_rate(self):
last_rate = self.env['res.currency.rate']._get_last_rates_for_companies(self.company_id | self.env.company)
for currency_rate in self:
company = currency_rate.company_id or self.env.company
currency_rate.rate = currency_rate.company_rate * last_rate[company]
@api.depends('company_rate')
def _compute_inverse_company_rate(self):
for currency_rate in self:
currency_rate.inverse_company_rate = 1.0 / currency_rate.company_rate
@api.onchange('inverse_company_rate')
def _inverse_inverse_company_rate(self):
for currency_rate in self:
currency_rate.company_rate = 1.0 / currency_rate.inverse_company_rate
@api.onchange('company_rate')
def _onchange_rate_warning(self):
if not self.currency_id.id:
return
latest_rate = self._get_latest_rate()
if latest_rate and latest_rate.rate:
if latest_rate:
diff = (latest_rate.rate - self.rate) / latest_rate.rate
if abs(diff) > 0.2:
return {
@@ -298,3 +392,20 @@ class CurrencyRate(models.Model):
name = ''
operator = 'ilike'
return super(CurrencyRate, self)._name_search(name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid)
@api.model
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
result = super(CurrencyRate, self)._fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
if view_type in ('tree'):
names = {
'company_currency_name': (self.env['res.company'].browse(self._context.get('company_id')) or self.env.company).currency_id.name,
'rate_currency_name': self.env['res.currency'].browse(self._context.get('active_id')).name or 'Unit',
}
doc = etree.XML(result['arch'])
for field in [['company_rate', _('%(rate_currency_name)s per %(company_currency_name)s', **names)],
['inverse_company_rate', _('%(company_currency_name)s per %(rate_currency_name)s', **names)]]:
node = doc.xpath("//tree//field[@name='%s']" % field[0])
if node:
node[0].set('string', field[1])
result['arch'] = etree.tostring(doc, encoding='unicode')
return result
+48 -20
View File
@@ -17,8 +17,11 @@
<field name="arch" type="xml">
<tree string="Currency Rates" editable="bottom">
<field name="name"/>
<field name="rate" digits="[12,12]"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="company_rate" digits="[12,12]"/>
<field name="inverse_company_rate" digits="[12,12]"/>
<field name="rate" digits="[12,12]" optional="hide"/>
<field name="write_date" optional="hide"/>
</tree>
</field>
</record>
@@ -32,13 +35,14 @@
<group>
<group>
<field name="name"/>
<field name="rate" digits="[12,12]"/>
<field name="rate" digits="[12,12]" groups="base.group_no_one"/>
<field name="company_rate" digits="[12,12]"/>
<field name="inverse_company_rate" digits="[12,12]"/>
</group>
<group>
<field name="currency_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
</sheet>
</form>
@@ -46,12 +50,15 @@
</record>
<record id="act_view_currency_rates" model="ir.actions.act_window">
<field name="name">Currency Rates</field>
<field name="name">Show Currency Rates</field>
<field name="res_model">res.currency.rate</field>
<field name='view_mode'>tree,form</field>
<field name='view_id' ref='view_currency_rate_tree'/>
<field name="domain">[('currency_id','=', active_id)]</field>
<field name="context">{'default_currency_id': active_id}</field>
<!-- <field name="target">fullscreen</field> -->
<field name="binding_model_id" ref="model_res_currency"/>
<field name="binding_view_types">form</field>
</record>
<record id="view_currency_search" model="ir.ui.view">
@@ -73,9 +80,10 @@
<tree string="Currencies" decoration-muted="(not active)">
<field name="name"/>
<field name="symbol"/>
<field name="rate_ids" invisible="1"/>
<field name="date"/>
<field name="full_name" string="Name" optional="show"/>
<field name="date" string="Last Update"/>
<field name="rate" digits="[12,6]"/>
<field name="inverse_rate" digits="[12,6]" optional="hide"/>
<field name="active" widget="boolean_toggle"/>
</tree>
</field>
@@ -88,21 +96,26 @@
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="symbol"/>
<field name="full_name"/>
<field name="active"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="row mb4">
<div class="col-6">
<strong><span><t t-esc="record.name.value"/></span></strong>
<div class="col-2">
<h3><t t-esc="record.name.value"/></h3>
</div>
<div class="col-6 text-right">
<div class="col-5">
<span class="badge badge-pill"><t t-esc="record.symbol.value"/></span>
</div>
<div class="col-5 text-right">
<t t-if="! record.active.raw_value"><span class="badge badge-pill bg-light border">inactive</span></t>
</div>
</div>
<div class="row">
<div class="col-12">
<strong><span>Current Rate: </span></strong>
<field name="rate" digits="[12,6]"/>
<div><field name="rate_string"/></div>
<t t-if="record.date.raw_value"><div>Last update: <field name="date"/></div></t>
</div>
</div>
</div>
@@ -117,15 +130,19 @@
<field name="model">res.currency</field>
<field name="arch" type="xml">
<form string="Currency">
<field name="is_current_company_currency" invisible="1"/>
<div class="oe_edit_only alert alert-info text-center" role="alert" groups="base.group_no_one">
You cannot reduce the number of decimal places of a currency already used on an accounting entry.
</div>
<div class="alert alert-info text-center" role="alert" attrs="{'invisible': [('is_current_company_currency','=',False)]}">
This is your company's currency.
</div>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" string="Rates" type="action" name="%(act_view_currency_rates)d"
icon="fa-money" widget="statinfo" attrs="{'invisible': [('active', '=', False)]}"/>
</div>
<group>
<group>
<field name="name"/>
<field name="rate" digits="[12,12]"/>
<field name="full_name" string="Name"/>
<field name="active" widget="boolean_toggle"/>
</group>
<group>
@@ -134,10 +151,6 @@
</group>
</group>
<div class="oe_edit_only alert alert-info" role="alert">
You won't be able to reduce the number of decimal places of a currency which has already been used to make accounting entries.
</div>
<group groups="base.group_no_one">
<group string="Price Accuracy">
<field name="rounding"/>
@@ -149,10 +162,25 @@
<field name="position"/>
</group>
</group>
<notebook class="o_currency_rate_list" attrs="{'invisible': [('is_current_company_currency','=',True)]}">
<page string="Rates" name="rates">
<field name="rate_ids" widget="one2many">
<tree string="Rates" editable="top" limit="25">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="company_rate" digits="[12,12]"/>
<field name="inverse_company_rate" digits="[12,12]"/>
<field name="rate" digits="[12,12]" optional="hide"/>
<field name="write_date" optional="hide"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_currency_form" model="ir.actions.act_window">
<field name="name">Currencies</field>
<field name="res_model">res.currency</field>