Homologate shared fiscal behavior with Tryton 7.0
Publicar paquete PyPI / publish (push) Successful in 1m37s
Publicar paquete PyPI / publish (push) Successful in 1m37s
This commit is contained in:
@@ -29,6 +29,13 @@ The module assigns fiscal numbers transactionally from ``ir.sequence.strict``.
|
||||
It does not sign or transmit e-CF documents; transmission remains the explicit
|
||||
responsibility of an electronic invoicing connector.
|
||||
|
||||
Version compatibility
|
||||
*********************
|
||||
|
||||
The 7.0 and 8.0 series share the same fiscal models, workflows, importer,
|
||||
validations and tests. Only the module registration, packaging metadata and
|
||||
report XML required by each Tryton series remain version-specific.
|
||||
|
||||
Authorization workflow
|
||||
**********************
|
||||
|
||||
|
||||
+30
-49
@@ -277,7 +277,7 @@ class Invoice(metaclass=PoolMeta):
|
||||
'on_change_with_payment_method_barter')
|
||||
|
||||
payment_method_credit_note = fields.Function(Monetary(
|
||||
"Credit Note'", currency='currency', digits='currency'),
|
||||
"Credit Note", currency='currency', digits='currency'),
|
||||
'on_change_with_payment_method_credit_note')
|
||||
|
||||
payment_method_mixed_payment = fields.Function(Monetary(
|
||||
@@ -1030,15 +1030,30 @@ class Invoice(metaclass=PoolMeta):
|
||||
|
||||
@classmethod
|
||||
def search_amount_invoiced_services(cls, name, clause):
|
||||
return cls._search_amount_invoiced_by_product_type(
|
||||
name, clause, 'service')
|
||||
|
||||
@classmethod
|
||||
def search_amount_invoiced_goods(cls, name, clause):
|
||||
return cls._search_amount_invoiced_by_product_type(
|
||||
name, clause, 'goods')
|
||||
|
||||
@classmethod
|
||||
def _search_amount_invoiced_by_product_type(
|
||||
cls, name, clause, product_type):
|
||||
pool = Pool()
|
||||
Rule = pool.get('ir.rule')
|
||||
Line = pool.get('account.invoice.line')
|
||||
Invoice = pool.get('account.invoice')
|
||||
Currency = pool.get('currency.currency')
|
||||
type_name = cls.amount_invoiced_services._field.sql_type().base
|
||||
Product = pool.get('product.product')
|
||||
Template = pool.get('product.template')
|
||||
type_name = getattr(cls, name)._field.sql_type().base
|
||||
line = Line.__table__()
|
||||
invoice = Invoice.__table__()
|
||||
currency = Currency.__table__()
|
||||
product = Product.__table__()
|
||||
template = Template.__table__()
|
||||
|
||||
_, operator, value = clause
|
||||
if value is None:
|
||||
@@ -1049,53 +1064,19 @@ class Invoice(metaclass=PoolMeta):
|
||||
if _backend_name() == 'sqlite':
|
||||
value = float(value)
|
||||
|
||||
query = line.join(invoice,
|
||||
condition=(invoice.id == line.invoice)
|
||||
).join(currency,
|
||||
condition=(currency.id == invoice.currency)
|
||||
).select(line.invoice,
|
||||
where=line.invoice.in_(
|
||||
invoice_query),
|
||||
group_by=line.invoice,
|
||||
having=Operator(Coalesce(Sum(
|
||||
Round((line.quantity * line.unit_price).cast(
|
||||
type_name),
|
||||
currency.digits)), 0).cast(type_name),
|
||||
value))
|
||||
return [('id', 'in', query)]
|
||||
|
||||
@classmethod
|
||||
def search_amount_invoiced_goods(cls, name, clause):
|
||||
pool = Pool()
|
||||
Rule = pool.get('ir.rule')
|
||||
Line = pool.get('account.invoice.line')
|
||||
Invoice = pool.get('account.invoice')
|
||||
Currency = pool.get('currency.currency')
|
||||
type_name = cls.amount_invoiced_goods._field.sql_type().base
|
||||
line = Line.__table__()
|
||||
invoice = Invoice.__table__()
|
||||
currency = Currency.__table__()
|
||||
|
||||
_, operator, value = clause
|
||||
if value is None:
|
||||
return []
|
||||
invoice_query = Rule.query_get('account.invoice')
|
||||
Operator = fields.SQL_OPERATORS[operator]
|
||||
if _backend_name() == 'sqlite':
|
||||
value = float(value)
|
||||
|
||||
query = line.join(invoice,
|
||||
condition=(invoice.id == line.invoice)
|
||||
).join(currency,
|
||||
condition=(currency.id == invoice.currency)
|
||||
).select(line.invoice,
|
||||
where=line.invoice.in_(invoice_query),
|
||||
group_by=line.invoice,
|
||||
having=Operator(Coalesce(Sum(
|
||||
Round((line.quantity * line.unit_price).cast(
|
||||
type_name),
|
||||
currency.digits)), 0).cast(type_name),
|
||||
value))
|
||||
query = (line.join(invoice, condition=(invoice.id == line.invoice))
|
||||
.join(currency, condition=(currency.id == invoice.currency))
|
||||
.join(product, condition=(product.id == line.product))
|
||||
.join(template, condition=(template.id == product.template))
|
||||
.select(
|
||||
line.invoice,
|
||||
where=(line.invoice.in_(invoice_query)
|
||||
& (line.type == 'line')
|
||||
& (template.type == product_type)),
|
||||
group_by=line.invoice,
|
||||
having=Operator(Coalesce(Sum(
|
||||
Round((line.quantity * line.unit_price).cast(type_name),
|
||||
currency.digits)), 0).cast(type_name), value)))
|
||||
return [('id', 'in', query)]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -67,8 +67,6 @@ class Party(metaclass=PoolMeta):
|
||||
config = Configuration(1)
|
||||
return config.get_multivalue(
|
||||
'default_supplier_ncf_type', **pattern)
|
||||
|
||||
|
||||
|
||||
|
||||
class PartyNcfType(ModelSQL, ValueMixin):
|
||||
|
||||
+9
-2
@@ -1,5 +1,7 @@
|
||||
# This file is part of ncf_manager module for Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import unicodedata
|
||||
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.model import fields
|
||||
from trytond.pyson import Eval
|
||||
@@ -33,6 +35,11 @@ _NCF_TYPE_MAP = {
|
||||
NCF_PREFIXES = tuple(prefix for prefix, _padding in _NCF_TYPE_MAP.values())
|
||||
|
||||
|
||||
def _sequence_type_entry(sequence_type):
|
||||
name = unicodedata.normalize('NFC', sequence_type.name or '')
|
||||
return _NCF_TYPE_MAP.get(name)
|
||||
|
||||
|
||||
class Sequence(metaclass=PoolMeta):
|
||||
__name__ = 'ir.sequence'
|
||||
|
||||
@@ -90,14 +97,14 @@ class Sequence(metaclass=PoolMeta):
|
||||
def on_change_with_prefix(self, name=None):
|
||||
if not self.sequence_type or not self.sequence_type.name:
|
||||
return None
|
||||
entry = _NCF_TYPE_MAP.get(self.sequence_type.name)
|
||||
entry = _sequence_type_entry(self.sequence_type)
|
||||
return entry[0] if entry else None
|
||||
|
||||
@fields.depends('sequence_type')
|
||||
def on_change_with_padding(self, name=None):
|
||||
if not self.sequence_type or not self.sequence_type.name:
|
||||
return 0
|
||||
entry = _NCF_TYPE_MAP.get(self.sequence_type.name)
|
||||
entry = _sequence_type_entry(self.sequence_type)
|
||||
return entry[1] if entry else 0
|
||||
|
||||
@fields.depends('from_number', 'until_number')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from types import SimpleNamespace
|
||||
import inspect
|
||||
import unittest
|
||||
|
||||
from trytond.modules.ncf_manager.ncf_manager import (
|
||||
@@ -6,6 +7,7 @@ from trytond.modules.ncf_manager.ncf_manager import (
|
||||
from trytond.modules.ncf_manager.sequence import Sequence
|
||||
from trytond.modules.ncf_manager.importer import (
|
||||
normalize_dgii_row, parse_dgii_authorizations)
|
||||
from trytond.modules.ncf_manager.invoice import Invoice
|
||||
|
||||
|
||||
class NcfManagerUnitTestCase(unittest.TestCase):
|
||||
@@ -59,6 +61,18 @@ class NcfManagerUnitTestCase(unittest.TestCase):
|
||||
self.assertIsNone(Sequence.on_change_with_prefix(sequence))
|
||||
self.assertEqual(Sequence.on_change_with_padding(sequence), 0)
|
||||
|
||||
def test_decomposed_electronic_sequence_name_is_supported(self):
|
||||
sequence = SimpleNamespace()
|
||||
sequence.sequence_type = SimpleNamespace(name='Compras Electro\u0301nico')
|
||||
self.assertEqual(Sequence.on_change_with_prefix(sequence), 'E41')
|
||||
self.assertEqual(Sequence.on_change_with_padding(sequence), 10)
|
||||
|
||||
def test_amount_searcher_filters_the_requested_product_type(self):
|
||||
source = inspect.getsource(
|
||||
Invoice._search_amount_invoiced_by_product_type)
|
||||
self.assertIn('template.type == product_type', source)
|
||||
self.assertIn("line.type == 'line'", source)
|
||||
|
||||
def test_csv_parser_preserves_unquoted_detail(self):
|
||||
source_format, rows = parse_dgii_authorizations(
|
||||
f'{self.HEADERS}\r\n{self.ROW}\r\n'.encode())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[tox]
|
||||
envlist = {py37,py38,py39,py310}-{sqlite,postgresql}
|
||||
envlist = {py311,py312,py313,py314}-{sqlite,postgresql}
|
||||
|
||||
[testenv]
|
||||
extras = test
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[tryton]
|
||||
version=8.0.3
|
||||
version=8.0.4
|
||||
depends:
|
||||
account_do
|
||||
account_invoice_do
|
||||
|
||||
Reference in New Issue
Block a user