[PATCH] Upstream patch - 19032022

This commit is contained in:
Parthiv Patel committed 2022-03-19 08:34:49 +00:00
1 parent f88c9b2a46
commit f56ba0f62f
11 files changed
+365 -44

No files matched your search

@@ -162,26 +162,17 @@
<t t-set="line_counter" t-value="line_counter + 1"/>
<t t-call="l10n_it_edi.account_invoice_line_it_FatturaPA"/>
</t>
<t t-foreach="record.line_ids.filtered(lambda line: line.tax_line_id)" t-as="tax_line">
<t t-foreach="tax_details['tax_details']" t-as="tax_name">
<t t-set="tax_dict" t-value="tax_details['tax_details'][tax_name]"/>
<DatiRiepilogo>
<!--2.2.2-->
<AliquotaIVA t-esc="format_numbers(tax_line.tax_line_id.amount)"/>
<Natura t-if="tax_line.tax_line_id.l10n_it_has_exoneration" t-esc="tax_line.tax_line_id.l10n_it_kind_exoneration"/>
<ImponibileImporto t-esc="format_monetary(tax_line.tax_base_amount, currency)"/>
<Imposta t-esc="format_monetary(tax_line.price_unit, currency)"/>
<EsigibilitaIVA t-if="not tax_line.tax_line_id.l10n_it_has_exoneration or tax_line.tax_line_id.l10n_it_kind_exoneration=='N6'" t-esc="tax_line.tax_line_id.l10n_it_vat_due_date"/>
<RiferimentoNormativo t-if="tax_line.tax_line_id.l10n_it_has_exoneration" t-esc="tax_line.tax_line_id.l10n_it_law_reference"/>
</DatiRiepilogo>
</t>
<!-- 0% tax lines -->
<t t-foreach="tax_map" t-as="tax">
<DatiRiepilogo>
<AliquotaIVA t-esc="format_numbers(tax.amount)"/>
<Natura t-if="tax.l10n_it_has_exoneration" t-esc="tax.l10n_it_kind_exoneration"/>
<ImponibileImporto t-esc="format_monetary(tax_map[tax], currency)"/>
<Imposta t-esc="format_monetary(0.00, currency)"/>
<EsigibilitaIVA t-if="not tax.l10n_it_has_exoneration or tax.l10n_it_kind_exoneration=='N6'" t-esc="tax.l10n_it_vat_due_date"/>
<RiferimentoNormativo t-if="tax.l10n_it_has_exoneration" t-esc="tax.l10n_it_law_reference"/>
<AliquotaIVA t-esc="format_numbers(tax_dict['tax'].amount)"/>
<Natura t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="tax_dict['tax'].l10n_it_kind_exoneration"/>
<Arrotondamento t-if="tax_dict.get('rounding')" t-esc="format_numbers(tax_dict['rounding'])"/>
<ImponibileImporto t-esc="format_monetary(abs(tax_dict['base_amount']), currency)"/>
<Imposta t-esc="format_monetary(abs(tax_dict['tax_amount']), currency)"/>
<EsigibilitaIVA t-if="not tax_dict['tax'].l10n_it_has_exoneration or tax_dict['tax'].l10n_it_kind_exoneration=='N6'" t-esc="tax_dict['tax'].l10n_it_vat_due_date"/>
<RiferimentoNormativo t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="tax_dict['tax'].l10n_it_law_reference"/>
</DatiRiepilogo>
</t>
</DatiBeniServizi>
+11 -10
View File
@@ -418,10 +418,6 @@ class AccountEdiFormat(models.Model):
invoice_line_form.sequence = int(line_elements[0].text)
# Product.
line_elements = element.xpath('.//Descrizione')
if line_elements:
invoice_line_form.name = " ".join(line_elements[0].text.split())
elements_code = element.xpath('.//CodiceArticolo')
if elements_code:
for element_code in elements_code:
@@ -433,18 +429,23 @@ class AccountEdiFormat(models.Model):
invoice_line_form.product_id = product
break
if partner:
product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)], limit=1)
if product_supplier and product_supplier.product_id:
product_supplier = self.env['product.supplierinfo'].search([('name', '=', partner.id), ('product_code', '=', code.text)], limit=2)
if product_supplier and len(product_supplier) == 1 and product_supplier.product_id:
invoice_line_form.product_id = product_supplier.product_id
break
if not invoice_line_form.product_id:
for element_code in elements_code:
code = element_code.xpath('.//CodiceValore')[0]
product = self.env['product.product'].search([('default_code', '=', code.text)], limit=1)
if product:
product = self.env['product.product'].search([('default_code', '=', code.text)], limit=2)
if product and len(product) == 1:
invoice_line_form.product_id = product
break
# Label.
line_elements = element.xpath('.//Descrizione')
if line_elements:
invoice_line_form.name = " ".join(line_elements[0].text.split())
# Price Unit.
line_elements = element.xpath('.//PrezzoUnitario')
if line_elements:
@@ -502,7 +503,7 @@ class AccountEdiFormat(models.Model):
# Special case of only 1 percentage discount
if discount_percentage and len(discount_elements) == 1:
discount_type = discount_element.xpath('.//Tipo')
discount_sign = 1
discount_sign = 1
if discount_type and discount_type[0].text == 'MG':
discount_sign = -1
invoice_line_form.discount = discount_sign * float(discount_percentage[0].text)
@@ -520,7 +521,7 @@ class AccountEdiFormat(models.Model):
discounted_amount = taxable_amount
for discount_element in discount_elements:
discount_type = discount_element.xpath('.//Tipo')
discount_sign = 1
discount_sign = 1
if discount_type and discount_type[0].text == 'MG':
discount_sign = -1
discount_amount = discount_element.xpath('.//Importo')
+16 -1
View File
@@ -11,7 +11,7 @@ from datetime import date, datetime
from lxml import etree
from flectra import api, fields, models, _
from flectra.tools import float_repr
from flectra.tools import float_repr, float_compare
from flectra.exceptions import UserError, ValidationError
from flectra.addons.base.models.ir_mail_server import MailDeliveryException
from flectra.tests.common import Form
@@ -162,6 +162,20 @@ class AccountMove(models.Model):
if tax.amount == 0.0:
tax_map[tax] = tax_map.get(tax, 0.0) + line.price_subtotal
# Constraints within the edi make local rounding on price included taxes a problem.
# To solve this there is a <Arrotondamento> or 'rounding' field, such that:
# taxable base = sum(taxable base for each unit) + Arrotondamento
tax_details = self._prepare_edi_tax_details()
for _tax_name, tax_dict in tax_details['tax_details'].items():
base_amount = tax_dict['base_amount']
tax_amount = tax_dict['tax_amount']
tax_rate = tax_dict['tax'].amount
if tax_dict['tax'].price_include and tax_dict['tax'].amount_type == 'percent':
expected_base_amount = tax_amount * 100 / tax_rate if tax_rate else False
if expected_base_amount and float_compare(base_amount, expected_base_amount, 2):
tax_dict['rounding'] = base_amount - (tax_amount * 100 / tax_rate)
tax_dict['base_amount'] = base_amount - tax_dict['rounding']
# Create file content.
template_values = {
'record': self,
@@ -180,6 +194,7 @@ class AccountMove(models.Model):
'pdf': pdf,
'pdf_name': pdf_name,
'tax_map': tax_map,
'tax_details': tax_details,
}
return template_values
@@ -11,5 +11,10 @@
<field name="registration_view_template_id" ref="ogone_s2s_form"/>
</record>
<record model="ir.config_parameter" id="payment_ogone_hash_function" forcecreate="False">
<field name="key">payment_ogone.hash_function</field>
<field name="value">sha1</field>
</record>
</data>
</flectra>
+11 -5
View File
@@ -3,7 +3,7 @@ import base64
import datetime
import logging
import time
from hashlib import sha1
from hashlib import new as hashnew
from pprint import pformat
from unicodedata import normalize
@@ -31,8 +31,8 @@ class PaymentAcquirerOgone(models.Model):
ogone_pspid = fields.Char('PSPID', required_if_provider='ogone', groups='base.group_user')
ogone_userid = fields.Char('API User ID', required_if_provider='ogone', groups='base.group_user')
ogone_password = fields.Char('API User Password', required_if_provider='ogone', groups='base.group_user')
ogone_shakey_in = fields.Char('SHA Key IN', size=32, required_if_provider='ogone', groups='base.group_user')
ogone_shakey_out = fields.Char('SHA Key OUT', size=32, required_if_provider='ogone', groups='base.group_user')
ogone_shakey_in = fields.Char('SHA Key IN', required_if_provider='ogone', groups='base.group_user')
ogone_shakey_out = fields.Char('SHA Key OUT', required_if_provider='ogone', groups='base.group_user')
ogone_alias_usage = fields.Char('Alias Usage', default="Allow saving my payment data",
help="If you want to use Ogone Aliases, this default "
"Alias Usage will be presented to the customer as the "
@@ -154,8 +154,14 @@ class PaymentAcquirerOgone(models.Model):
items = sorted((k.upper(), v) for k, v in values.items())
sign = ''.join('%s=%s%s' % (k, v, key) for k, v in items if v and filter_key(k))
sign = sign.encode("utf-8")
shasign = sha1(sign).hexdigest()
return shasign
hash_function = self.env['ir.config_parameter'].sudo().get_param('payment_ogone.hash_function')
if not hash_function or hash_function.lower() not in ['sha1', 'sha256', 'sha512']:
hash_function = 'sha1'
shasign = hashnew(hash_function)
shasign.update(sign)
return shasign.hexdigest()
def ogone_form_generate_values(self, values):
base_url = self.get_base_url()
+2 -6
View File
@@ -494,11 +494,7 @@ class PosSession(models.Model):
invoice_receivables[key] = self._update_amounts(invoice_receivables[key], {'amount': order.amount_total}, order.date_order)
# side loop to gather receivable lines by account for reconciliation
for move_line in order.account_move.line_ids.filtered(lambda aml: aml.account_id.internal_type == 'receivable' and not aml.reconciled):
# NOTE: Negative (Positive) amount in the order's invoice's receivable line, will be paired to
# the respective positive (negative) balance in session's invoice receivable lines. That's why the comparator to
# calculate `key`s for `invoice_receivable_lines` in `_create_invoice_receivable_lines` is inverted.
# The key ensures we don't reconcile invoice receivable lines with opposite sign together
key = (order.partner_id.commercial_partner_id.id, order.amount_total < 0, move_line.account_id.id)
key = (order.partner_id.commercial_partner_id.id, move_line.account_id.id)
order_account_move_receivable_lines[key] |= move_line
else:
order_taxes = defaultdict(tax_amounts)
@@ -695,7 +691,7 @@ class PosSession(models.Model):
receivable_lines = MoveLine.create(vals)
for receivable_line in receivable_lines:
if (not receivable_line.reconciled):
key = (commercial_partner.id, receivable_line.balance > 0, account_id)
key = (commercial_partner.id, account_id)
if key not in invoice_receivable_lines:
invoice_receivable_lines[key] = receivable_line
else:
@@ -660,3 +660,272 @@ class TestPoSBasicConfig(TestPoSCommon):
open_and_check(pos01_data)
open_and_check(pos02_data)
def test_refund_customer_reconcile(self):
""" Test return invoiced order
2 orders
- 2nd order is returned
Orders
======
+------------------+----------+-----------+----------+-----+-------+
| order | payments | invoiced? | product | qty | total |
+------------------+----------+-----------+----------+-----+-------+
| order 1 | bank | yes | product1 | 3 | 30 |
| | | | product2 | 2 | 40 |
| | | | product3 | 1 | 30 |
+------------------+----------+-----------+----------+-----+-------+
| order 2 | bank | yes | product1 | 3 | 30 |
| | | | product2 | 2 | 40 |
| | | | product3 | 1 | 30 |
+------------------+----------+-----------+----------+-----+-------+
| order 3 (return) | bank | yes | product1 | -3 | -30 |
| | | | product2 | -2 | -40 |
| | | | product3 | -1 | -30 |
+------------------+----------+-----------+----------+-----+-------+
Expected Result
===============
+---------------------+---------+
| account | balance |
+---------------------+---------+
| receivable | -100 |
| pos receivable bank | 100 |
+---------------------+---------+
| Total balance | 0.0 |
+---------------------+---------+
"""
start_qty_available = {
self.product1: self.product1.qty_available,
self.product2: self.product2.qty_available,
self.product3: self.product3.qty_available,
}
self.open_new_session()
# create orders
orders = []
orders.append(self.create_ui_order_data(
[(self.product1, 3), (self.product2, 2), (self.product3, 1)],
payments=[(self.bank_pm, 100)],
is_invoiced=True,
uid='12346-123-1234',
customer=self.customer
))
orders.append(self.create_ui_order_data(
[(self.product1, 3), (self.product2, 2), (self.product3, 1)],
payments=[(self.bank_pm, 100)],
uid='12345-123-1234',
is_invoiced=True,
customer=self.customer
))
# sync orders
order = self.env['pos.order'].create_from_ui(orders)
# check values before closing the session
self.assertEqual(2, self.pos_session.order_count)
orders_total = sum(order.amount_total for order in self.pos_session.order_ids)
self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount,
msg='Total order amount should be equal to the total payment amount.')
# return order
order_to_return = self.pos_session.order_ids.filtered(
lambda order: '12345-123-1234' in order.pos_reference)
order_to_return.refund()
refund_order = self.pos_session.order_ids.filtered(
lambda order: order.state == 'draft')
# check if amount to pay
self.assertAlmostEqual(refund_order.amount_total - refund_order.amount_paid,
-100)
# pay the refund
context_make_payment = {"active_ids": [refund_order.id],
"active_id": refund_order.id}
make_payment = self.env['pos.make.payment'].with_context(
context_make_payment).create({
'payment_method_id': self.bank_pm.id,
'amount': -100,
})
make_payment.check()
self.assertEqual(refund_order.state, 'paid',
'Payment is registered, order should be paid.')
self.assertAlmostEqual(refund_order.amount_paid, -100.0,
msg='Amount paid for return order should be negative.')
refund_order.action_pos_order_invoice()
self.assertTrue(refund_order.account_move)
# check product qty_available after syncing the order
self.assertEqual(
self.product1.qty_available + 3,
start_qty_available[self.product1],
)
self.assertEqual(
self.product2.qty_available + 2,
start_qty_available[self.product2],
)
self.assertEqual(
self.product3.qty_available + 1,
start_qty_available[self.product3],
)
# picking and stock moves should be in done state
# no exception of return orders
for order in self.pos_session.order_ids:
self.assertEqual(
order.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
move_lines = order.picking_ids[0].move_lines
self.assertEqual(
move_lines.mapped('state'),
['done'] * len(move_lines),
'Move Lines should be in done state.'
)
# close the session
self.pos_session.action_pos_session_validate()
# check values after the session is closed
session_move = self.pos_session.move_id
sale_lines = session_move.line_ids.filtered(
lambda line: line.account_id == self.sale_account)
self.assertEqual(len(sale_lines), 0,
msg='There should be no lines as all is invoiced.')
self.assertEqual(
sum(session_move.line_ids.filtered(
lambda line: line.partner_id == self.customer
).mapped("balance")),
-100
)
receivable_line_bank = session_move.line_ids.filtered(
lambda line: self.bank_pm.name in line.name)
self.assertAlmostEqual(receivable_line_bank.balance, 100.0)
for order in self.pos_session.order_ids:
self.assertEqual(0, order.account_move.amount_residual)
def test_multiple_customers_reconcile(self):
""" Test return invoiced order from another customer
2 actions
- 1st order from one customer
- 2nd order is a refund from another customer
Orders
======
+------------------+----------+-----------+----------+-----+-------+
| order | payments | invoiced? | product | qty | total |
+------------------+----------+-----------+----------+-----+-------+
| order 1 | bank | yes | product1 | 3 | 30 |
| | | customer | product2 | 2 | 40 |
| | | | product3 | 1 | 30 |
+------------------+----------+-----------+----------+-----+-------+
| order 2 (return) | bank | yes | product1 | -3 | -30 |
| | | other | product2 | -2 | -40 |
| | | customer | product3 | -1 | -30 |
+------------------+----------+-----------+----------+-----+-------+
Expected Result
===============
+---------------------------+---------+
| account | balance |
+---------------------------+---------+
| receivable customer | -100 |
| receivable other customer | 100 |
| pos receivable bank | 0 |
+---------------------------+---------+
| Total balance | 0.0 |
+---------------------------+---------+
"""
start_qty_available = {
self.product1: self.product1.qty_available,
self.product2: self.product2.qty_available,
self.product3: self.product3.qty_available,
}
self.open_new_session()
# create orders
orders = []
orders.append(self.create_ui_order_data(
[(self.product1, 3), (self.product2, 2), (self.product3, 1)],
payments=[(self.bank_pm, 100)],
is_invoiced=True,
uid='12346-123-1234',
customer=self.customer
))
orders.append(self.create_ui_order_data(
[(self.product1, -3), (self.product2, -2), (self.product3, -1)],
payments=[(self.bank_pm, -100)],
uid='12345-123-1234',
is_invoiced=True,
customer=self.other_customer
))
# sync orders
order = self.env['pos.order'].create_from_ui(orders)
# check values before closing the session
self.assertEqual(2, self.pos_session.order_count)
orders_total = sum(order.amount_total for order in self.pos_session.order_ids)
self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount,
msg='Total order amount should be equal to the total payment amount.')
self.assertEqual(
self.product1.qty_available,
start_qty_available[self.product1],
)
self.assertEqual(
self.product2.qty_available,
start_qty_available[self.product2],
)
self.assertEqual(
self.product3.qty_available,
start_qty_available[self.product3],
)
# picking and stock moves should be in done state
# no exception of return orders
for order in self.pos_session.order_ids:
self.assertEqual(
order.picking_ids[0].state,
'done',
'Picking should be in done state.'
)
move_lines = order.picking_ids[0].move_lines
self.assertEqual(
move_lines.mapped('state'),
['done'] * len(move_lines),
'Move Lines should be in done state.'
)
# close the session
self.pos_session.action_pos_session_validate()
# check values after the session is closed
session_move = self.pos_session.move_id
sale_lines = session_move.line_ids.filtered(
lambda line: line.account_id == self.sale_account)
self.assertEqual(len(sale_lines), 0,
msg='There should be no lines as all is invoiced.')
self.assertEqual(
session_move.line_ids.filtered(
lambda line: line.partner_id == self.customer
).balance,
-100
)
self.assertEqual(
session_move.line_ids.filtered(
lambda line: line.partner_id == self.other_customer
).balance,
100
)
receivable_line_bank = session_move.line_ids.filtered(
lambda line: self.bank_pm.name in line.name)
self.assertAlmostEqual(receivable_line_bank.balance, 0.0)
for order in self.pos_session.order_ids:
self.assertEqual(0, order.account_move.amount_residual)
+2 -2
View File
@@ -1727,7 +1727,7 @@ class SaleOrderLine(models.Model):
'sale',
fiscal_position=self.order_id.fiscal_position_id,
product_price_unit=self._get_display_price(product),
product_currency=self.currency_id
product_currency=self.order_id.currency_id
)
self.update(vals)
@@ -1768,7 +1768,7 @@ class SaleOrderLine(models.Model):
'sale',
fiscal_position=self.order_id.fiscal_position_id,
product_price_unit=self._get_display_price(product),
product_currency=self.currency_id
product_currency=self.order_id.currency_id
)
def name_get(self):
+9 -1
View File
@@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta
from flectra import api, fields, models, _
from flectra.exceptions import UserError
from flectra.tools import float_compare
from flectra.tools.misc import get_lang
class SaleOrder(models.Model):
@@ -236,13 +237,20 @@ class SaleOrderLine(models.Model):
# compute unit price
price_unit = 0.0
product_ctx = {
'lang': get_lang(self.env, purchase_order.partner_id.lang).code,
'company_id': purchase_order.company_id,
}
if supplierinfo:
price_unit = self.env['account.tax'].sudo()._fix_tax_included_price_company(supplierinfo.price, self.product_id.supplier_taxes_id, taxes, self.company_id)
if purchase_order.currency_id and supplierinfo.currency_id != purchase_order.currency_id:
price_unit = supplierinfo.currency_id._convert(price_unit, purchase_order.currency_id, purchase_order.company_id, fields.datetime.today())
product_ctx.update({'seller_id': supplierinfo.id})
else:
product_ctx.update({'partner_id': purchase_order.partner_id.id})
return {
'name': '[%s] %s' % (self.product_id.default_code, self.name) if self.product_id.default_code else self.name,
'name': self.product_id.with_context(**product_ctx).display_name,
'product_qty': purchase_qty_uom,
'product_id': self.product_id.id,
'product_uom': self.product_id.uom_po_id.id,
@@ -243,3 +243,33 @@ class TestSalePurchase(TestCommonSalePurchaseNoChart):
self.assertEqual(len(purchase_order2.order_line), 1, "The 2nd PO has only one line")
self.assertEqual(purchase_line2.sale_line_id, self.sol1_service_purchase_1, "The 2nd PO line came from the SO line sol1_service_purchase_1")
self.assertEqual(purchase_line2.product_qty, delta, "The quantity of the new PO line is the quantity added on the Sale Line, after first PO confirmation")
def test_pol_description(self):
service = self.env['product.product'].create({
'name': 'Super Product',
'type': 'service',
'service_to_purchase': True,
'seller_ids': [(0, 0, {
'name': self.partner_vendor_service.id,
'min_qty': 1,
'price': 10,
'product_code': 'C01',
'product_name': 'Name01',
'sequence': 1,
})]
})
so = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
(0, 0, {
'name': service.name,
'product_id': service.id,
'product_uom_qty': 1,
})
],
})
so.action_confirm()
po = self.env['purchase.order'].search([('partner_id', '=', self.partner_vendor_service.id)], order='id desc', limit=1)
self.assertEqual(po.order_line.name, "[C01] Name01")
+1 -1
View File
@@ -527,7 +527,7 @@ class Web_Editor(http.Controller):
"""
svg = None
if module == 'illustration':
attachment = request.env['ir.attachment'].sudo().search([('url', '=like', request.httprequest.path), ('public', '=', True)], limit=1)
attachment = request.env['ir.attachment'].sudo().search([('url', '=', request.httprequest.path), ('public', '=', True)], limit=1)
if not attachment:
raise werkzeug.exceptions.NotFound()
svg = b64decode(attachment.datas).decode('utf-8')