[IMP] account_peppol: demo mode
This commit implements a demo mode in which a user can register to
peppol. The demo user performs no communication with the proxy server,
instead making and receiving only mocked calls and responses.
The @handle_demo decorator is added and utilised in order to avoid
adding the specific (and mostly irrelevant) branches of this of demo
functionality to the body of the code. When the edi_user has the
edi_mode 'demo' this decorator diverts the execution of the
function (excepting when running tests, whenupon it is ignored).
Specific logic is added to the res config settings in order to specify
which modes are availble to the current user based on the
"account_peppol.mode_constraint" config param, and the
"saas_trail.confirmation_token" config param. This allows us to default
users using runbot/demo-data/saas-trial databases to "demo" mode
registration.
task-id: 3497466
3497725
closes odoo/odoo#136909
Signed-off-by: Laurent Smet (las) <las@odoo.com>
This commit is contained in:
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
from . import tools
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<odoo noupdate="1">
|
||||
<record id="default_account_peppol_edi_mode" model="ir.config_parameter">
|
||||
<field name="key">account_peppol.edi.mode</field>
|
||||
<field name="value">test</field>
|
||||
<field name="value">demo</field>
|
||||
</record>
|
||||
<record id="default_account_peppol_mode_constraint" model="ir.config_parameter">
|
||||
<field name="key">account_peppol.mode_constraint</field>
|
||||
<field name="value">demo</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
import logging
|
||||
|
||||
from odoo import _, fields, models, modules, tools
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyError
|
||||
from odoo.addons.account_peppol.tools.demo_utils import handle_demo
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -20,6 +21,7 @@ class AccountEdiProxyClientUser(models.Model):
|
||||
# HELPER METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@handle_demo
|
||||
def _make_request(self, url, params=False):
|
||||
# extends account_edi_proxy_client to update peppol_proxy_state
|
||||
# of archived users
|
||||
@@ -47,22 +49,10 @@ class AccountEdiProxyClientUser(models.Model):
|
||||
urls['peppol'] = {
|
||||
'prod': 'https://peppol.api.odoo.com',
|
||||
'test': 'https://peppol.test.odoo.com',
|
||||
'demo': 'demo',
|
||||
}
|
||||
return urls
|
||||
|
||||
def _get_server_url(self, proxy_type=None, edi_mode=None):
|
||||
proxy_type = proxy_type or self.proxy_type
|
||||
if not proxy_type == 'peppol':
|
||||
return super()._get_server_url(proxy_type, edi_mode)
|
||||
|
||||
peppol_param = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'account_peppol.edi.mode', False
|
||||
)
|
||||
if peppol_param == 'test':
|
||||
edi_mode = 'test'
|
||||
|
||||
return super()._get_server_url(proxy_type, edi_mode)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# CRONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class AccountMove(models.Model):
|
||||
copy=False,
|
||||
readonly=True,
|
||||
)
|
||||
peppol_is_demo_uuid = fields.Boolean(compute="_compute_peppol_is_demo_uuid")
|
||||
|
||||
def action_cancel_peppol_documents(self):
|
||||
# if the peppol_move_state is processing/done
|
||||
@@ -28,3 +29,8 @@ class AccountMove(models.Model):
|
||||
if any(move.peppol_move_state in {'processing', 'done'} for move in self):
|
||||
raise UserError(_("Cannot cancel an entry that has already been sent to PEPPOL"))
|
||||
self.peppol_move_state = 'canceled'
|
||||
|
||||
@api.depends('peppol_message_uuid')
|
||||
def _compute_peppol_is_demo_uuid(self):
|
||||
for move in self:
|
||||
move.peppol_is_demo_uuid = (move.peppol_message_uuid or '').startswith('demo_')
|
||||
|
||||
@@ -5,6 +5,11 @@ from odoo import _, api, fields, models, modules, tools
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyError
|
||||
from odoo.addons.account_edi_ubl_cii.models.account_edi_common import EAS_MAPPING
|
||||
from odoo.addons.account_peppol.tools.demo_utils import handle_demo
|
||||
|
||||
# at the moment, only European countries are accepted
|
||||
ALLOWED_COUNTRIES = set(EAS_MAPPING.keys()) - {'AU', 'SG', 'NZ'}
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
@@ -36,6 +41,17 @@ class ResConfigSettings(models.TransientModel):
|
||||
has_peppol_participant = fields.Boolean(
|
||||
compute='_compute_has_peppol_participant'
|
||||
)
|
||||
account_peppol_edi_mode = fields.Selection(
|
||||
selection=[('demo', 'Demo'), ('test', 'Test'), ('prod', 'Live')],
|
||||
compute='_compute_account_peppol_edi_mode',
|
||||
inverse='_inverse_account_peppol_edi_mode',
|
||||
readonly=False,
|
||||
)
|
||||
account_peppol_mode_constraint = fields.Selection(
|
||||
selection=[('demo', 'Demo'), ('test', 'Test'), ('prod', 'Live')],
|
||||
compute='_compute_account_peppol_mode_constraint',
|
||||
help="Using the config params, this field specifies which edi modes may be selected from the UI"
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# HELPER METHODS
|
||||
@@ -61,6 +77,26 @@ class ResConfigSettings(models.TransientModel):
|
||||
# -------------------------------------------------------------------------
|
||||
# COMPUTE METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
@api.depends('is_account_peppol_eligible', 'account_peppol_edi_user')
|
||||
def _compute_account_peppol_mode_constraint(self):
|
||||
mode_constraint = self.env['ir.config_parameter'].sudo().get_param('account_peppol.mode_constraint')
|
||||
trial_param = self.env['ir.config_parameter'].sudo().get_param('saas_trial.confirm_token')
|
||||
self.account_peppol_mode_constraint = trial_param and 'demo' or mode_constraint or 'prod'
|
||||
|
||||
@api.depends('is_account_peppol_eligible', 'account_peppol_edi_user')
|
||||
def _compute_account_peppol_edi_mode(self):
|
||||
edi_mode = self.env['ir.config_parameter'].sudo().get_param('account_peppol.edi.mode')
|
||||
for config in self:
|
||||
if config.account_peppol_edi_user:
|
||||
config.account_peppol_edi_mode = config.account_peppol_edi_user.edi_mode
|
||||
else:
|
||||
config.account_peppol_edi_mode = edi_mode or 'prod'
|
||||
|
||||
def _inverse_account_peppol_edi_mode(self):
|
||||
for config in self:
|
||||
if not config.account_peppol_edi_user and config.account_peppol_edi_mode:
|
||||
self.env['ir.config_parameter'].sudo().set_param('account_peppol.edi.mode', config.account_peppol_edi_mode)
|
||||
return
|
||||
|
||||
@api.depends("company_id.account_edi_proxy_client_ids")
|
||||
def _compute_account_peppol_edi_user(self):
|
||||
@@ -92,6 +128,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
# BUSINESS ACTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@handle_demo
|
||||
def button_create_peppol_proxy_user(self):
|
||||
"""
|
||||
The first step of the Peppol onboarding.
|
||||
@@ -109,7 +146,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
company = self.company_id
|
||||
edi_proxy_client = self.env['account_edi_proxy_client.user']
|
||||
edi_user = edi_proxy_client.sudo()._register_proxy_user(company, 'peppol', 'prod')
|
||||
edi_user = edi_proxy_client.sudo()._register_proxy_user(company, 'peppol', self.account_peppol_edi_mode)
|
||||
self.account_peppol_proxy_state = 'not_verified'
|
||||
|
||||
# if there is an error when activating the participant below,
|
||||
@@ -144,6 +181,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
# but we need the field for future in case the user decided to migrate away from Odoo
|
||||
self.account_peppol_migration_key = False
|
||||
|
||||
@handle_demo
|
||||
def button_update_peppol_user_data(self):
|
||||
"""
|
||||
Action for the user to be able to update their contact details any time
|
||||
@@ -245,6 +283,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
self.account_peppol_proxy_state = 'not_verified'
|
||||
|
||||
@handle_demo
|
||||
def button_migrate_peppol_registration(self):
|
||||
"""
|
||||
If the user is active, they need to request a migration key, generated on the IAP server.
|
||||
@@ -261,6 +300,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
response = self._call_peppol_proxy(endpoint='/api/peppol/1/migrate_peppol_registration')
|
||||
self.account_peppol_migration_key = response['migration_key']
|
||||
|
||||
@handle_demo
|
||||
def button_deregister_peppol_participant(self):
|
||||
"""
|
||||
Deregister the edi user from Peppol network
|
||||
|
||||
@@ -7,6 +7,7 @@ from hashlib import md5
|
||||
from urllib import parse
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.addons.account_peppol.tools.demo_utils import handle_demo
|
||||
|
||||
TIMEOUT = 10
|
||||
|
||||
@@ -51,6 +52,7 @@ class ResPartner(models.Model):
|
||||
else:
|
||||
partner.account_peppol_verification_label = 'not_valid'
|
||||
|
||||
@handle_demo
|
||||
def button_account_peppol_check_partner_endpoint(self):
|
||||
""" A basic check for whether a participant is reachable at the given
|
||||
Peppol participant ID - peppol_eas:peppol_endpoint (ex: '9999:test')
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { RadioField, radioField } from "@web/views/fields/radio/radio_field";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
class PeppolRadioField extends RadioField {
|
||||
static template = "account_peppol.PeppolRadioField";
|
||||
static props = {
|
||||
...RadioField.props,
|
||||
hiddenItems: { type: String, optional: true },
|
||||
readonlyItems: { type: String, optional: true },
|
||||
};
|
||||
|
||||
setup() {
|
||||
super.setup()
|
||||
this.initialSelection = this.props.record.data[this.props.name];
|
||||
this.readonlyItems = evaluateExpr(this.props.readonlyItems || "[]", this.props.record.evalContext);
|
||||
this.hiddenItems = evaluateExpr(this.props.hiddenItems || "[]", this.props.record.evalContext).filter(item => (item != this.initialSelection));
|
||||
}
|
||||
|
||||
get items() {
|
||||
return super.items.filter(item => !(this.hiddenItems.includes(item[0])))
|
||||
}
|
||||
}
|
||||
|
||||
const peppolRadioField = {
|
||||
...radioField,
|
||||
component: PeppolRadioField,
|
||||
extractProps: ({attrs, options}, dynamicInfo) => {
|
||||
return {
|
||||
hiddenItems: attrs.hidden_items || "[]",
|
||||
readonlyItems: attrs.readonly_items || "[]",
|
||||
...radioField.extractProps({attrs, options}, dynamicInfo),
|
||||
}
|
||||
},
|
||||
}
|
||||
registry.category("fields").add("account_peppol_radio_field", peppolRadioField);
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="account_peppol.PeppolRadioField" t-inherit-mode="primary" t-inherit="web.RadioField">
|
||||
<xpath expr="//input" position="attributes">
|
||||
<attribute name="t-att-disabled" add="|| this.readonlyItems.includes(item[0])" separator=" "/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
+32
-4
@@ -31,6 +31,30 @@ class PeppolSettingsButtons extends Component {
|
||||
return this.props.record.data.account_peppol_proxy_state === "active" && Boolean(this.props.record.data.account_peppol_migration_key);
|
||||
}
|
||||
|
||||
get ediMode() {
|
||||
return this.props.record.data.account_peppol_edi_mode;
|
||||
}
|
||||
|
||||
get modeConstraint() {
|
||||
return this.props.record.data.account_peppol_mode_constraint;
|
||||
}
|
||||
|
||||
get createUserButtonLabel() {
|
||||
const modes = {
|
||||
demo: _t("Validate registration (Demo)"),
|
||||
test: _t("Validate registration (Test)"),
|
||||
prod: _t("Validate registration"),
|
||||
}
|
||||
return modes[this.ediMode] || _t("Validate registration");
|
||||
}
|
||||
|
||||
get deregisterUserButtonLabel() {
|
||||
const modes = {
|
||||
demo: _t("Switch to Live"),
|
||||
}
|
||||
return this.modeConstraint !== "demo" && modes[this.ediMode] || _t("Deregister from Peppol");
|
||||
}
|
||||
|
||||
async _callConfigMethod(methodName, save = false) {
|
||||
if (save) {
|
||||
await this._save();
|
||||
@@ -74,10 +98,14 @@ class PeppolSettingsButtons extends Component {
|
||||
}
|
||||
|
||||
deregister() {
|
||||
this.showConfirmation(
|
||||
"This will delete your Peppol registration.",
|
||||
"button_deregister_peppol_participant"
|
||||
)
|
||||
if (this.ediMode === 'demo') {
|
||||
this._callConfigMethod("button_deregister_peppol_participant");
|
||||
} else {
|
||||
this.showConfirmation(
|
||||
"This will delete your Peppol registration.",
|
||||
"button_deregister_peppol_participant"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async updateDetails() {
|
||||
|
||||
+3
-3
@@ -9,14 +9,14 @@
|
||||
class="btn btn-primary"
|
||||
t-on-click="createUser"
|
||||
t-if="proxyState === 'not_registered'">
|
||||
Validate registration
|
||||
<t t-out="createUserButtonLabel"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<button type="button"
|
||||
class="btn btn-secondary me-1"
|
||||
t-on-click="migrate"
|
||||
t-if="proxyState === 'active' and migrationPrepared === false">
|
||||
t-if="proxyState === 'active' and migrationPrepared === false and ediMode!='demo'">
|
||||
Migrate registration to another service
|
||||
</button>
|
||||
</div>
|
||||
@@ -25,7 +25,7 @@
|
||||
class="btn btn-secondary me-1"
|
||||
t-on-click="deregister"
|
||||
t-if="proxyState === 'active' and migrationPrepared === false">
|
||||
Deregister from Peppol
|
||||
<t t-out="deregisterUserButtonLabel"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
|
||||
@@ -24,6 +24,7 @@ class TestPeppolMessage(TestAccountMoveSendCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
cls.env['ir.config_parameter'].sudo().set_param('account_peppol.edi.mode', 'test')
|
||||
|
||||
cls.env.company.write({
|
||||
'country_id': cls.env.ref('base.be').id,
|
||||
|
||||
@@ -18,6 +18,11 @@ PDF_FILE_PATH = 'account_peppol/tests/assets/peppol_identification_test.pdf'
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestPeppolParticipant(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env['ir.config_parameter'].sudo().set_param('account_peppol.edi.mode', 'test')
|
||||
|
||||
def _get_participant_vals(self):
|
||||
return {
|
||||
'is_account_peppol_participant': True,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from . import demo_utils
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,169 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from base64 import b64encode
|
||||
from decorator import decorator
|
||||
import uuid
|
||||
|
||||
from odoo import _, fields, modules, tools
|
||||
from odoo.tools.misc import file_open
|
||||
|
||||
DEMO_BILL_PATH = 'account_peppol/tools/demo_bill'
|
||||
DEMO_ENC_KEY = 'account_peppol/tools/enc_key'
|
||||
DEMO_PRIVATE_KEY = 'account_peppol/tools/private_key.pem'
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# HELPERS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def get_demo_vendor_bill(user):
|
||||
return {
|
||||
'direction': 'incoming',
|
||||
'receiver': user.edi_identification,
|
||||
'uuid': f'{user.company_id.id}_demo_vendor_bill',
|
||||
'accounting_supplier_party': '0208:2718281828',
|
||||
'state': 'done',
|
||||
'filename': f'{user.company_id.id}_demo_vendor_bill',
|
||||
'enc_key': file_open(DEMO_ENC_KEY, mode='rb').read(),
|
||||
'document': file_open(DEMO_BILL_PATH, mode='rb').read(),
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# MOCKED FUNCTIONS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _mock_make_request(func, self, *args, **kwargs):
|
||||
|
||||
def _mock_get_all_documents(user, args, kwargs):
|
||||
if not user.env['account.move'].search_count([
|
||||
('peppol_message_uuid', '=', f'{user.company_id.id}_demo_vendor_bill')
|
||||
]):
|
||||
return {'messages': [get_demo_vendor_bill(user)]}
|
||||
return {'messages': []}
|
||||
|
||||
def _mock_get_document(user, args, kwargs):
|
||||
message_uuid = args[1]['message_uuids'][0]
|
||||
if message_uuid.endswith('_demo_vendor_bill'):
|
||||
return {message_uuid: get_demo_vendor_bill(user)}
|
||||
return {message_uuid: {'state': 'done'}}
|
||||
|
||||
def _mock_send_document(user, args, kwargs):
|
||||
# Trigger the reception of vendor bills
|
||||
get_messages_cron = user.env['ir.cron'].sudo().env.ref(
|
||||
'account_peppol.ir_cron_peppol_get_new_documents',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
if get_messages_cron:
|
||||
get_messages_cron._trigger()
|
||||
return {
|
||||
'messages': [{
|
||||
'message_uuid': 'demo_%s' % uuid.uuid4(),
|
||||
} for i in args[1]],
|
||||
}
|
||||
|
||||
endpoint = args[0].split('/')[-1]
|
||||
return {
|
||||
'ack': lambda _user, _args, _kwargs: {},
|
||||
'activate_participant': lambda _user, _args, _kwargs: {},
|
||||
'get_all_documents': _mock_get_all_documents,
|
||||
'get_document': _mock_get_document,
|
||||
'participant_status': lambda _user, _args, _kwargs: {'peppol_state': 'active'},
|
||||
'send_document': _mock_send_document,
|
||||
}[endpoint](self, args, kwargs)
|
||||
|
||||
def _mock_button_verify_partner_endpoint(func, self, *args, **kwargs):
|
||||
self.ensure_one()
|
||||
self.account_peppol_validity_last_check = fields.Date.today()
|
||||
self.account_peppol_is_endpoint_valid = True
|
||||
|
||||
def _mock_user_creation(func, self, *args, **kwargs):
|
||||
func(self, *args, **kwargs)
|
||||
self.write({
|
||||
'account_peppol_proxy_state': 'active',
|
||||
})
|
||||
self.account_peppol_edi_user.write({
|
||||
'private_key': b64encode(file_open(DEMO_PRIVATE_KEY, 'rb').read()),
|
||||
})
|
||||
|
||||
def _mock_deregister_participant(func, self, *args, **kwargs):
|
||||
# Set documents sent in demo to a state where they can be re-sent
|
||||
demo_moves = self.env['account.move'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('peppol_message_uuid', '=like', 'demo_%'),
|
||||
])
|
||||
demo_moves.write({
|
||||
'peppol_message_uuid': None,
|
||||
'peppol_move_state': None,
|
||||
})
|
||||
demo_moves.message_main_attachment_id.unlink()
|
||||
demo_moves.ubl_cii_xml_id.unlink()
|
||||
log_message = _('The peppol status of the documents has been reset when switching from Demo to Live.')
|
||||
demo_moves._message_log_batch(bodies=dict((move.id, log_message) for move in demo_moves))
|
||||
|
||||
# also unlink the demo vendor bill
|
||||
self.env['account.move'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('peppol_message_uuid', '=', f'{self.company_id.id}_demo_vendor_bill'),
|
||||
]).unlink()
|
||||
|
||||
mode_constraint = self.env['ir.config_parameter'].get_param('account_peppol.mode_constraint')
|
||||
self.account_peppol_edi_user.unlink()
|
||||
self.account_peppol_proxy_state = 'not_registered'
|
||||
self.account_peppol_edi_mode = mode_constraint
|
||||
|
||||
|
||||
def _mock_update_user_data(func, self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def _mock_migrate_participant(func, self, *args, **kwargs):
|
||||
self.account_peppol_migration_key = 'I9cz9yw*ruDM%4VSj94s'
|
||||
|
||||
_demo_behaviour = {
|
||||
'_make_request': _mock_make_request,
|
||||
'button_account_peppol_check_partner_endpoint': _mock_button_verify_partner_endpoint,
|
||||
'button_create_peppol_proxy_user': _mock_user_creation,
|
||||
'button_deregister_peppol_participant': _mock_deregister_participant,
|
||||
'button_migrate_peppol_registration': _mock_migrate_participant,
|
||||
'button_update_peppol_user_data': _mock_update_user_data,
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# DECORATORS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@decorator
|
||||
def handle_demo(func, self, *args, **kwargs):
|
||||
""" This decorator is used on methods that should be mocked in demo mode.
|
||||
|
||||
First handle the decision: "Are we in demo mode?", and conditionally decide which function to
|
||||
execute. Whether we are in demo mode depends on the edi_mode of the EDI user, but the EDI user
|
||||
is accessible in different ways depending on the model the function is called from and in some
|
||||
contexts it might not yet exist, in which case demo mode should instead depend on the content
|
||||
of the "account_peppol.edi.mode" config param.
|
||||
"""
|
||||
def get_demo_mode_account_edi_proxy_client_user(self, args, kwargs):
|
||||
if self.id:
|
||||
return self.edi_mode == 'demo'
|
||||
return self.env['ir.config_parameter'].get_param('account_peppol.edi.mode') == 'demo'
|
||||
|
||||
def get_demo_mode_res_config_settings(self, args, kwargs):
|
||||
if self.account_peppol_edi_user:
|
||||
return self.account_peppol_edi_user.edi_mode == 'demo'
|
||||
return self.account_peppol_edi_mode == 'demo'
|
||||
|
||||
def get_demo_mode_res_partner(self, args, kwargs):
|
||||
peppol_user = self.env.company.account_edi_proxy_client_ids.filtered(lambda user: user.proxy_type == 'peppol')
|
||||
if peppol_user:
|
||||
return peppol_user.edi_mode == 'demo'
|
||||
return self.env['ir.config_parameter'].get_param('account_peppol.edi.mode') == 'demo'
|
||||
|
||||
get_demo_mode = {
|
||||
'account_edi_proxy_client.user': get_demo_mode_account_edi_proxy_client_user,
|
||||
'res.config.settings': get_demo_mode_res_config_settings,
|
||||
'res.partner': get_demo_mode_res_partner,
|
||||
}
|
||||
demo_mode = get_demo_mode.get(self._name) and get_demo_mode[self._name](self, args, kwargs) or False
|
||||
|
||||
if not demo_mode or tools.config['test_enable'] or modules.module.current_test:
|
||||
return func(self, *args, **kwargs)
|
||||
return _demo_behaviour[func.__name__](func, self, *args, **kwargs)
|
||||
@@ -0,0 +1 @@
|
||||
HjlTVuGbHXeCBWAhlIDfR3xZcCtykbMmvRoeGLARDWN+AhOdTffFb0hR+3GkMpwCvTshTkIx4nJwdFyIx/g3OuKpGPQJLi1d/4IhwYyhhSFAuHJRWE8rm2Z++aZ4Qd69h4bQjr5ymy/icqiYZ6Atyu1EXfunZBk5LsctoTp99htyFD4EzX6IH+9sBzvb32tkdU7p4LBYalTonJr8cK7IzTHyoDhg7UIwArztHwCrnf2SMLDJB/BdiihN0alf+vNN8/FOhGmJY3qH2nqrDTKk96dgUoapNi6RRWrE6rzmcBL2Lr/dBYfDXqJKqC178+m89ezaPySSToj8MNKNpqXFUw==
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCj4tVufRBH9j1Q
|
||||
1cag4xHHgYhNOWSTPtkZbB6ZVfxng/MLGgUF+InNRn2ZWFKpTsQucSa1JKWaYWt+
|
||||
gmq+7NMUu1smgdhyZxbGXubVEm8bp1AQO7432V4pOU06NW8i6YqRb79ru+wY32RN
|
||||
yWU1GnDyoGFfndM94ewlAkP9THwg6RHl5uYPcy+4PLRixhnMi+CoPhJ53yA8stHZ
|
||||
rsTKpYXvMLgbOVjI5+7mRq4ixlbmtIwifXdv0yExcj6Q0VJ+iq8aRxcxIRlNV6/6
|
||||
ifp7+cZUNpw/KzWVOQ+DcMgzMir7nn2YatLSPTcqv7YjQqpS/D2ZUu8S/SNxmQ50
|
||||
nF4/xoknAgMBAAECggEAen9gfReqBb/+kN9ZeoR/k5o0oVRW4uDFMYVpUb+9zDoK
|
||||
fq/SNWZSykb4NpiYIMkpRnV5M0jTJ5PN31/oHhGyrPpl5WCEwu5fTaM98uG2fvsO
|
||||
kzO0uNYW1cVo/itWiMf7tT3L3OE4VlcUCDiTF6BN8G0Em43CiazG79rDqx9yYL3j
|
||||
Vnj/NLiY0OnxXL6SkjxZHvjXPl8jjTrf8oZl+twpwskiC5cqIpfsTGSsDq5WS2rh
|
||||
eYdGhH4EcUAfK5PHTTIwBGFReekrTXv1PXm3EK36T8himQVX5sSd2F79QsVXsnfM
|
||||
PR5W85W7MhEKYD4mZSQsKThj+CCE8G7U5uJ2Ah+LYQKBgQDOPNQYdfrY2Ivcy1X7
|
||||
umVniEyoJ5fs3GLRwXTH+nPNxIa5/YtzLqd17GU/Ec6nyEDGcDqEpuKfopcEEqqw
|
||||
w6Z9JPudOPJoVRugTHkpAzv/A9Sq5IC3P2SJUDV11u80x9qe3j5oKj/woEE2HsUI
|
||||
uCn8HF9NUO7r8G/eg5+8vFcHiQKBgQDLbfnt2xFRFgF06+E2GIK/GAKbkJHhYO1L
|
||||
2obZQnGj7+irMlt+uaDbHk+jFCHk+q3xd48x+o1aliNwXSDQ3QJfxCSIfcP9xG2W
|
||||
3l6kCO5irwgt589E5rEUEegQM2F6oS3OtcUr9zjeW1rDMjHHLD87Tb9rB2GAweQf
|
||||
yLqsoZovLwKBgERjX2GNHdVyWU6qDqUetimSxPityG8+1XYA1JzLrEL7fEGIlgln
|
||||
2xf7f8dePEze1rv20zDRtiyBWdp75iYfesHc1aLZE2kNb8/EDBlRfT+fIZJZm2Uo
|
||||
nEn8Uv30e/Xgn9o2kDMyb2l3eqhbo7K0fxeewOt+fvu2CyKaOwn22lUhAoGBAKQz
|
||||
YfAmykR8EbLxjnhesnJSjBBLUiTsWr3GZuBI7HdZ96Dv5cBVT0xum/NTFcTAvtRQ
|
||||
IApEZgJ/e51/3jQYoIjyRlbRxPg5rAeB+DxJZTnMdDqxiLDh0H8VsQ4amw0juljG
|
||||
iZ9iTsnUTV+PTXSp92QD7oUSkRYf6uXo3Rzo2A5LAoGAI4o8zbghCrqJvkuk0hfh
|
||||
UP5X/eu06EMYVLamZit64kJMdumKcJuVDo4jyz+DYhz6T3bMPbAzRBqsZA4M8l73
|
||||
XWp9z1bKS/AHsZSXLLRKk1C/OR7z5zLz/j8BgbqKpjNtRCp1DFu1IgPC2+3rVr89
|
||||
mQa1nuu+xYOkgxcs7F5qM7g=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -21,7 +21,9 @@
|
||||
class="d-flex"
|
||||
invisible="not peppol_move_state or state == 'draft'">
|
||||
|
||||
<field name="peppol_is_demo_uuid" invisible="1"/>
|
||||
<field name="peppol_move_state" class="oe_inline"/>
|
||||
<span class="mx-1" invisible="not peppol_is_demo_uuid"> (Demo)</span>
|
||||
<span class="text-muted mx-3"
|
||||
invisible="peppol_move_state != 'to_send'">
|
||||
The invoice will be sent automatically to PEPPOL
|
||||
|
||||
@@ -99,9 +99,12 @@
|
||||
Application status:
|
||||
<b>
|
||||
<field name="account_peppol_proxy_state"
|
||||
class="oe_inline"
|
||||
readonly="1"
|
||||
decoration-danger="account_peppol_proxy_state == 'rejected'"
|
||||
decoration-info="account_peppol_proxy_state == 'active'"/>
|
||||
<span class="text-info" invisible="not account_peppol_edi_mode == 'demo'"> (Demo)</span>
|
||||
<span class="text-info" invisible="not account_peppol_edi_mode == 'test'"> (Test)</span>
|
||||
</b>
|
||||
</div>
|
||||
<div invisible="account_peppol_proxy_state != 'rejected'">
|
||||
@@ -123,10 +126,23 @@
|
||||
<field name="account_peppol_edi_identification"
|
||||
nolabel="1"/>
|
||||
</div>
|
||||
<div class="mt-5"
|
||||
invisible="account_peppol_proxy_state != 'not_registered'">
|
||||
<div class="mb-3">
|
||||
I accept that Odoo may process my e-invoices.
|
||||
<div class="mt-4" invisible="account_peppol_proxy_state != 'not_registered'">
|
||||
<field name="account_peppol_mode_constraint" invisible="1"/>
|
||||
<field name="account_peppol_edi_mode"
|
||||
widget="account_peppol_radio_field"
|
||||
class="o_field_radio"
|
||||
readonly_items="account_peppol_mode_constraint != 'prod' and ['prod'] or []"
|
||||
hidden_items="account_peppol_mode_constraint != 'test' and ['test'] or []"
|
||||
options="{'horizontal': true }"/>
|
||||
<div class="mb-3" invisible="not account_peppol_edi_mode == 'prod'">
|
||||
By clicking the button below I accept that Odoo may process my e-invoices.
|
||||
</div>
|
||||
<div class="mb-3" invisible="not account_peppol_edi_mode == 'test'">
|
||||
Test mode allows registration of the user on the test Peppol network.
|
||||
By clicking the button below I accept that Odoo may process my e-invoices.
|
||||
</div>
|
||||
<div class="mb-3" invisible="not account_peppol_edi_mode == 'demo'">
|
||||
In demo mode sending and receiving invoices is simulated. There will be no communication with the Peppol network.
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-1 action_buttons" colspan="3">
|
||||
|
||||
@@ -21,6 +21,7 @@ class AccountMoveSend(models.Model):
|
||||
string="Warning",
|
||||
compute="_compute_peppol_warning",
|
||||
)
|
||||
account_peppol_edi_mode_info = fields.Char(compute='_compute_account_peppol_edi_mode_info')
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# COMPUTE METHODS
|
||||
@@ -71,6 +72,19 @@ class AccountMoveSend(models.Model):
|
||||
)
|
||||
)
|
||||
|
||||
@api.depends('company_id.account_edi_proxy_client_ids.edi_mode')
|
||||
def _compute_account_peppol_edi_mode_info(self):
|
||||
mode_strings = {
|
||||
'test': _('Test'),
|
||||
'demo': _('Demo'),
|
||||
}
|
||||
for wizard in self:
|
||||
edi_user = wizard.company_id.account_edi_proxy_client_ids.filtered(
|
||||
lambda usr: usr.proxy_type == 'peppol'
|
||||
)
|
||||
mode = mode_strings.get(edi_user.edi_mode)
|
||||
wizard.account_peppol_edi_mode_info = f' ({mode})' if mode else ''
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ATTACHMENTS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<b>
|
||||
<label for="checkbox_send_peppol"/>
|
||||
</b>
|
||||
<field name="account_peppol_edi_mode_info" class="oe_inline o_form_label"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user