[ADD] Welcome l10n_pe_website_sale

New module that adds peruvian requirements to the website ecommerce

* Add fields to the checkout form
	*Identification type
	*City
	*District
* Add logic to validate the new fields
* Take into account the identification type when validating the VAT in the checkout form
* Change label name for the vat field, to show "Number" instead of "VAT number".
* Add demo data to display a peruvian website and show products with taxes
* Add module translations
* Hide default city
* Refactored code to create the function
_changeOption which combines the functionality
of _changeState and _changeCity.

closes odoo/odoo#130155

Signed-off-by: Josse Colpaert <jco@odoo.com>
Co-authored-by: Andrea Geraldo <myrna.andrea@vauxoo.com>
This commit is contained in:
desdelinux
2024-01-10 05:04:28 +00:00
committed by Luis Torres
co-authored by Andrea Geraldo
parent 10070f4cde
commit 62ead4a0ca
11 changed files with 410 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
@@ -0,0 +1,26 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
"name": "Peruvian eCommerce",
"countries": ["pe"],
"version": "0.1",
"summary": "Be able to see Identification Type in ecommerce checkout form.",
"category": "Accounting/Localizations/Website",
"author": "Vauxoo, Odoo",
"license": "LGPL-3",
"depends": [
"website_sale",
"l10n_pe",
],
"data": [
"security/ir.model.access.csv",
"data/ir_model_fields.xml",
"views/templates.xml",
],
"assets": {
"web.assets_frontend": [
"l10n_pe_website_sale/static/src/js/website_sale.js",
],
},
"installable": True,
"auto_install": True,
}
@@ -0,0 +1,2 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main
@@ -0,0 +1,86 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.http import request
from odoo.addons.website_sale.controllers.main import WebsiteSale
class L10nPEWebsiteSale(WebsiteSale):
def _get_mandatory_fields_billing(self, country_id=False):
"""Extend mandatory fields to add new identification, responsibility
city_id and district fields when company is Peru"""
res = super()._get_mandatory_fields_billing(country_id)
if request.website.sudo().company_id.country_id.code != "PE":
return res
# For Peruvian company, the VAT is required for all the partners
res.append("vat")
if country_id == request.website.sudo().company_id.country_id.id:
res += ["city_id", "l10n_pe_district", "l10n_latam_identification_type_id"]
res.remove("city")
return res
def _get_mandatory_fields_shipping(self, country_id=False):
"""Extend mandatory fields to add city_id and district fields when the selected country is Peru"""
res = super()._get_mandatory_fields_shipping(country_id)
if request.website.sudo().company_id.country_id.code != "PE":
return res
if country_id == request.website.sudo().company_id.country_id.id:
res += ["city_id", "l10n_pe_district"]
res.remove("city")
return res
def _get_country_related_render_values(self, kw, render_values):
res = super()._get_country_related_render_values(kw, render_values)
if request.website.sudo().company_id.country_id.code == "PE":
values = render_values["checkout"]
state = "state_id" in values \
and values["state_id"] != "" \
and request.env["res.country.state"].browse(int(values["state_id"]))
city = "city_id" in values \
and values["city_id"] != "" \
and request.env["res.city"].browse(int(values["city_id"]))
to_include = {
"identification": kw.get("l10n_latam_identification_type_id"),
"identification_types": request.env["l10n_latam.identification.type"].sudo().search(
["|", ("country_id", "=", False), ("country_id.code", "=", "PE")]
),
}
if state:
to_include["state"] = state
to_include["state_cities"] = request.env["res.city"].sudo().search([("state_id", "=", state.id)])
if city:
to_include["city"] = city
to_include["city_districts"] = request.env["l10n_pe.res.city.district"].sudo().search([("city_id", "=", city.id)])
res.update(to_include)
return res
def _get_vat_validation_fields(self, data):
res = super()._get_vat_validation_fields(data)
if request.website.sudo().company_id.account_fiscal_country_id.code == "PE":
res.update({
"l10n_latam_identification_type_id":
int(data["l10n_latam_identification_type_id"])
if data.get("l10n_latam_identification_type_id") else False,
"name": data.get("name", False),
})
return res
@http.route(
['/shop/state_infos/<model("res.country.state"):state>'],
type="json",
auth="public",
methods=["POST"],
website=True,
)
def state_infos(self, state, **kw):
states = request.env["res.city"].sudo().search([("state_id", "=", state.id)])
return {'cities': [(c.id, c.name, c.l10n_pe_code) for c in states]}
@http.route(
['/shop/city_infos/<model("res.city"):city>'], type="json", auth="public", methods=["POST"], website=True
)
def city_infos(self, city, **kw):
districts = request.env["l10n_pe.res.city.district"].sudo().search([("city_id", "=", city.id)])
return {'districts': [(d.id, d.name, d.code) for d in districts]}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<function model="ir.model.fields" name="formbuilder_whitelist">
<value>res.partner</value>
<value eval="[
'city_id',
'l10n_latam_identification_type_id',
'l10n_pe_district',
]"/>
</function>
</odoo>
+65
View File
@@ -0,0 +1,65 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_pe_website_sale
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-15 18:53+0000\n"
"PO-Revision-Date: 2023-11-15 18:53+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_pe_website_sale
#: model:website,contact_us_button_url:l10n_pe_website_sale.default_website_pe
msgid "/contactus"
msgstr ""
#. module: l10n_pe_website_sale
#: model_terms:ir.ui.view,arch_db:l10n_pe_website_sale.partner_info
msgid "<option value=\"\">Identification Type...</option>"
msgstr "<option value=\"\">Tipo de identificación...</option>"
#. module: l10n_pe_website_sale
#: model_terms:ir.ui.view,arch_db:l10n_pe_website_sale.partner_info
msgid ""
"Changing Identification type is not allowed once document(s) have been "
"issued for your account. Please contact us directly for this operation."
msgstr ""
"No se permite cambiar el tipo de identificación una vez que se hayan emitido los documentos "
"para su cuenta. Póngase en contacto con nosotros directamente para esta operación."
#. module: l10n_pe_website_sale
#: model_terms:ir.ui.view,arch_db:l10n_pe_website_sale.partner_address_info
msgid "City"
msgstr "Ciudad"
#. module: l10n_pe_website_sale
#: model_terms:ir.ui.view,arch_db:l10n_pe_website_sale.partner_address_info
msgid "District"
msgstr "Distrito"
#. module: l10n_pe_website_sale
#: model_terms:ir.ui.view,arch_db:l10n_pe_website_sale.address
msgid "Identification Number"
msgstr "Número de identificación"
#. module: l10n_pe_website_sale
#: model_terms:ir.ui.view,arch_db:l10n_pe_website_sale.partner_info
msgid "Identification Type"
msgstr "Tipo de Identificación"
#. module: l10n_pe_website_sale
#: model:website,prevent_zero_price_sale_text:l10n_pe_website_sale.default_website_pe
msgid "Not Available For Sale"
msgstr ""
#. module: l10n_pe_website_sale
#: model:ir.model,name:l10n_pe_website_sale.model_website
msgid "Website"
msgstr "Sitio web"
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import website
@@ -0,0 +1,12 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class Website(models.Model):
_inherit = "website"
def _display_partner_b2b_fields(self):
"""Peruvian localization must always display b2b fields"""
self.ensure_one()
return self.company_id.country_id.code == "PE" or super()._display_partner_b2b_fields()
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_city_public,res_city_group_public,base_address_extended.model_res_city,base.group_public,1,0,0,0
access_city_portal,res_city_group_portal,base_address_extended.model_res_city,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_city_public res_city_group_public base_address_extended.model_res_city base.group_public 1 0 0 0
3 access_city_portal res_city_group_portal base_address_extended.model_res_city base.group_portal 1 0 0 0
@@ -0,0 +1,90 @@
/** @odoo-module **/
import {WebsiteSale} from "@website_sale/js/website_sale";
WebsiteSale.include({
events: Object.assign({}, WebsiteSale.prototype.events, {
"change select[name='state_id']": "_onChangeState",
"change select[name='city_id']": "_onChangeCity",
}),
start: function () {
this.elementCities = document.querySelector("select[name='city_id']");
this.elementDistricts = document.querySelector("select[name='l10n_pe_district']");
this.cityBlock = document.querySelector(".div_city");
this.autoFormat = document.querySelector(".checkout_autoformat");
this.elementState = document.querySelector("select[name='state_id']");
this.elemenCountry = document.querySelector("select[name='country_id']");
this.isPeruvianCompany = this.elemenCountry?.dataset.company_country_code === 'PE';
return this._super.apply(this, arguments);
},
_changeOption: function (selectCheck, rpcRoute, place, selectElement) {
if (!selectCheck) {
return;
}
return this.rpc(rpcRoute, {
}).then((data) => {
if (this.isPeruvianCompany) {
if (data[place]?.length) {
selectElement.innerHTML = "";
data[place].forEach((item) => {
let opt = document.createElement("option");
opt.textContent = item[1];
opt.value = item[0];
opt.setAttribute("data-code", item[2]);
selectElement.appendChild(opt);
});
selectElement.parentElement.style.display = "block";
} else {
selectElement.value = "";
selectElement.parentElement.style.display = "none";
}
}
});
},
_onChangeState: function () {
if (this.isPeruvianCompany) {
if (this.elementState.value === "" && this.elemenCountry.value !== '') {
this.elementState.options[1].selected = true;
}
const state = this.elementState.value;
const rpcRoute = `/shop/state_infos/${state}`;
return this.autoFormat.length
? this._changeOption(state, rpcRoute, "cities", this.elementCities).then(() => this._onChangeCity())
: undefined;
}
},
_onChangeCity: function () {
if (this.isPeruvianCompany) {
const city = this.elementCities.value;
const rpcRoute = `/shop/city_infos/${city}`;
return this.autoFormat.length
? this._changeOption(city, rpcRoute, "districts", this.elementDistricts)
: undefined;
}
},
_onChangeCountry: function (ev) {
return this._super.apply(this, arguments).then(() => {
if (this.isPeruvianCompany) {
let selectedCountry = ev.currentTarget.options[ev.currentTarget.selectedIndex].getAttribute("code");
let cityInput = document.querySelector(".form-control[name='city']");
if (selectedCountry == "PE") {
if (cityInput.value) {
cityInput.value = "";
}
this.cityBlock.classList.add("d-none");
return this._onChangeState().then(() => {
this._onChangeCity();
});
} else {
this.cityBlock.querySelectorAll("input").forEach((input) => {
input.value = "";
});
this.cityBlock.classList.remove("d-none");
this.elementCities.value = "";
this.elementCities.parentElement.style.display = "none";
this.elementDistricts.value = "";
this.elementDistricts.parentElement.style.display = "none";
}
}
});
},
});
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="partner_info" name="Peruvian partner">
<!-- show identification type -->
<div t-attf-class="mb-3 #{error.get('l10n_latam_identification_type_id') and 'o_has_error' or ''} col-xl-6">
<label class="col-form-label" for="l10n_latam_identification_type_id">Identification Type</label>
<t t-if="can_edit_vat">
<select
name="l10n_latam_identification_type_id"
t-attf-class="form-select #{error.get('l10n_latam_identification_type_id') and 'is-invalid' or ''}">
<option value="">Identification Type...</option>
<t t-foreach="identification_types or []" t-as="id_type">
<option
t-att-value="id_type.id"
t-att-selected="id_type.id == int(identification) if identification else id_type.id == partner.l10n_latam_identification_type_id.id">
<t t-out="id_type.name" />
</option>
</t>
</select>
</t>
<t t-else="">
<p class="form-control"
t-out="partner.l10n_latam_identification_type_id.name"
readonly="1"
title="Changing Identification type is not allowed once document(s) have been issued for your account. Please contact us directly for this operation."/>
<input name="l10n_latam_identification_type_id"
class="form-control"
t-att-value="partner.l10n_latam_identification_type_id.id"
type='hidden'/>
</t>
</div>
</template>
<template id="partner_address_info" name="Peruvian partner address">
<!-- show city -->
<div t-attf-class="mb-3 #{error.get('city_id') and 'o_has_error' or ''} col-lg-6 div_city_id"
t-attf-style="#{(country and country.code != 'PE') and 'd-none' or ''}">
<label class="col-form-label" for="city_id">City</label>
<select id="city_id"
name="city_id"
t-attf-class="form-select #{error.get('city_id') and 'is-invalid' or ''}"
data-init="1">
<t t-foreach="state_cities" t-as="city">
<option t-att-value="city.id"
t-att-selected="city.id == ('city_id' in checkout and checkout['city_id'] != '' and int(checkout['city_id']))">
<t t-out="city.name" />
</option>
</t>
</select>
</div>
<!-- show district -->
<div t-attf-class="mb-3 #{error.get('l10n_pe_district') and 'o_has_error' or ''} col-lg-6 div_district"
t-att-style="not city and 'd-none'">
<label class="col-form-label" for="l10n_pe_district">District</label>
<select id="l10n_pe_district"
name="l10n_pe_district"
t-attf-class="form-select #{error.get('l10n_pe_district') and 'is-invalid' or ''}"
data-init="1">
<t t-foreach="city_districts" t-as="district">
<option t-att-value="district.id"
t-att-selected="district.id == ('l10n_pe_district' in checkout and checkout['l10n_pe_district'] != '' and int(checkout['l10n_pe_district']))">
<t t-out="district.name" />
</option>
</t>
</select>
</div>
</template>
<template id="address" inherit_id="website_sale.address">
<xpath expr="//input[@name='vat']/.." position="before">
<t t-if="mode[1] == 'billing'" position="inside">
<t t-if="res_company.country_id.code == 'PE'">
<t t-set="partner" t-value="website_sale_order.partner_id" />
<div class="w-100" />
<t t-call="l10n_pe_website_sale.partner_info" />
</t>
</t>
</xpath>
<label for="vat" position="replace">
<t t-if="res_company.country_id.code != 'PE'">$0</t>
<t t-else="">
<label class="col-form-label label-optional" for="vat">
Identification Number
</label>
</t>
</label>
<xpath expr="//select[@name='country_id']" position="attributes">
<attribute name="t-att-data-company_country_code">res_company.country_id.code</attribute>
</xpath>
<xpath expr="//select[@name='state_id']/.." position="after">
<t t-if="res_company.country_id.code == 'PE'">
<t t-call="l10n_pe_website_sale.partner_address_info" />
</t>
</xpath>
<!-- Sets the country code for every country option -->
<xpath expr="//t[@t-foreach='countries']//option" position="attributes">
<attribute name="t-att-code">c.code</attribute>
</xpath>
</template>
</odoo>