This commit is contained in:
+14
@@ -28,3 +28,17 @@ Configure this repository secret:
|
|||||||
The module assigns fiscal numbers transactionally from ``ir.sequence.strict``.
|
The module assigns fiscal numbers transactionally from ``ir.sequence.strict``.
|
||||||
It does not sign or transmit e-CF documents; transmission remains the explicit
|
It does not sign or transmit e-CF documents; transmission remains the explicit
|
||||||
responsibility of an electronic invoicing connector.
|
responsibility of an electronic invoicing connector.
|
||||||
|
|
||||||
|
Authorization workflow
|
||||||
|
**********************
|
||||||
|
|
||||||
|
NCF authorizations follow the protected workflow ``Draft → Active →
|
||||||
|
Finished``. Create the authorization and its fiscal lines in draft, then use
|
||||||
|
the **Activate** button. A line can select an existing strict sequence or ask
|
||||||
|
the module to create one on activation from its authorized range. Activation
|
||||||
|
checks company, fiscal type, prefix, padding, validity, availability and range
|
||||||
|
overlap. The last issued number automatically finishes the line. Finishing
|
||||||
|
an authorization closes every remaining active line without reusing numbers.
|
||||||
|
|
||||||
|
Electronic fiscal numbers are only reserved by this module. Activating an
|
||||||
|
E-series authorization never signs or transmits an e-CF to DGII.
|
||||||
|
|||||||
+11
-1
@@ -1628,7 +1628,17 @@ class Invoice(metaclass=PoolMeta):
|
|||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'ncf_manager.msg_ncf_sequence_expired'))
|
'ncf_manager.msg_ncf_sequence_expired'))
|
||||||
|
|
||||||
return strict_sequence.get(), strict_sequence.id
|
number = strict_sequence.get()
|
||||||
|
StrictSequence = Pool().get('ir.sequence.strict')
|
||||||
|
ManagerSequence = Pool().get('ncf.manager.sequence')
|
||||||
|
strict_sequence = StrictSequence(strict_sequence.id)
|
||||||
|
if (strict_sequence.until_number is not None
|
||||||
|
and strict_sequence.number_next is not None
|
||||||
|
and strict_sequence.number_next
|
||||||
|
> strict_sequence.until_number
|
||||||
|
and ncf_sequence.state == 'active'):
|
||||||
|
ManagerSequence.complete([ncf_sequence])
|
||||||
|
return number, strict_sequence.id
|
||||||
|
|
||||||
|
|
||||||
def check_credit_note(self):
|
def check_credit_note(self):
|
||||||
|
|||||||
@@ -513,3 +513,95 @@ msgstr "Borrador"
|
|||||||
msgctxt "selection:ncf.manager.sequence,state:finished"
|
msgctxt "selection:ncf.manager.sequence,state:finished"
|
||||||
msgid "Finished"
|
msgid "Finished"
|
||||||
msgstr "Finalizado"
|
msgstr "Finalizado"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,activated_by:"
|
||||||
|
msgid "Activated By"
|
||||||
|
msgstr "Activada por"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,activated_at:"
|
||||||
|
msgid "Activated At"
|
||||||
|
msgstr "Fecha de activación"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,finished_by:"
|
||||||
|
msgid "Finished By"
|
||||||
|
msgstr "Finalizada por"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,finished_at:"
|
||||||
|
msgid "Finished At"
|
||||||
|
msgstr "Fecha de finalización"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,sequence_mode:"
|
||||||
|
msgid "Sequence Configuration"
|
||||||
|
msgstr "Configuración de secuencia"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,ncf_sequences:"
|
||||||
|
msgid "Fiscal Sequence"
|
||||||
|
msgstr "Secuencia fiscal"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,expected_prefix:"
|
||||||
|
msgid "Expected Prefix"
|
||||||
|
msgstr "Prefijo esperado"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,expected_padding:"
|
||||||
|
msgid "Expected Padding"
|
||||||
|
msgstr "Longitud del consecutivo"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,from_number:"
|
||||||
|
msgid "Authorized From"
|
||||||
|
msgstr "Desde autorizado"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,until_number:"
|
||||||
|
msgid "Authorized Until"
|
||||||
|
msgstr "Hasta autorizado"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,number_next:"
|
||||||
|
msgid "First Number to Issue"
|
||||||
|
msgstr "Primer número por emitir"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,expiration_date:"
|
||||||
|
msgid "Expiration Date"
|
||||||
|
msgstr "Fecha de vencimiento"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,availability:"
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Disponibilidad"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,sequence_mode:existing"
|
||||||
|
msgid "Select Existing Sequence"
|
||||||
|
msgstr "Seleccionar secuencia existente"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,sequence_mode:create"
|
||||||
|
msgid "Create Sequence on Activation"
|
||||||
|
msgstr "Crear secuencia al activar"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:available"
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Disponible"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:low"
|
||||||
|
msgid "Running Low"
|
||||||
|
msgstr "Próxima a agotarse"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:expired"
|
||||||
|
msgid "Expired"
|
||||||
|
msgstr "Vencida"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:exhausted"
|
||||||
|
msgid "Exhausted"
|
||||||
|
msgstr "Agotada"
|
||||||
|
|
||||||
|
msgctxt "model:ir.model.button,string:ncf_manager_activate_button"
|
||||||
|
msgid "Activate"
|
||||||
|
msgstr "Activar"
|
||||||
|
|
||||||
|
msgctxt "model:ir.model.button,string:ncf_manager_finish_button"
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Finalizar"
|
||||||
|
|
||||||
|
msgctxt "model:ir.model.button,string:ncf_sequence_finish_button"
|
||||||
|
msgid "Finish Sequence"
|
||||||
|
msgstr "Finalizar secuencia"
|
||||||
|
|
||||||
|
msgctxt "view:ncf.manager:"
|
||||||
|
msgid "Authorized Fiscal Sequences"
|
||||||
|
msgstr "Secuencias fiscales autorizadas"
|
||||||
|
|||||||
+100
-1
@@ -1,6 +1,13 @@
|
|||||||
msgctxt "selection:account.invoice,isr_withholding_type:"
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Project-Id-Version: ncf_manager 8.0\n"
|
||||||
|
"PO-Revision-Date: 2026-08-16 00:00-0400\n"
|
||||||
|
"Last-Translator: Fundación Un País Mejor\n"
|
||||||
|
"Language-Team: Spanish (Latin America)\n"
|
||||||
|
"Language: es_419\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgctxt "field:account.configuration,default_customer_ncf_type:"
|
msgctxt "field:account.configuration,default_customer_ncf_type:"
|
||||||
msgid "Default Customer NCF Type"
|
msgid "Default Customer NCF Type"
|
||||||
@@ -1021,3 +1028,95 @@ msgstr "Ingreso"
|
|||||||
msgctxt "view:account.invoice:"
|
msgctxt "view:account.invoice:"
|
||||||
msgid "Tax Information"
|
msgid "Tax Information"
|
||||||
msgstr "Información Fiscal"
|
msgstr "Información Fiscal"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,activated_by:"
|
||||||
|
msgid "Activated By"
|
||||||
|
msgstr "Activada por"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,activated_at:"
|
||||||
|
msgid "Activated At"
|
||||||
|
msgstr "Fecha de activación"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,finished_by:"
|
||||||
|
msgid "Finished By"
|
||||||
|
msgstr "Finalizada por"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager,finished_at:"
|
||||||
|
msgid "Finished At"
|
||||||
|
msgstr "Fecha de finalización"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,sequence_mode:"
|
||||||
|
msgid "Sequence Configuration"
|
||||||
|
msgstr "Configuración de secuencia"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,ncf_sequences:"
|
||||||
|
msgid "Fiscal Sequence"
|
||||||
|
msgstr "Secuencia fiscal"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,expected_prefix:"
|
||||||
|
msgid "Expected Prefix"
|
||||||
|
msgstr "Prefijo esperado"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,expected_padding:"
|
||||||
|
msgid "Expected Padding"
|
||||||
|
msgstr "Longitud del consecutivo"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,from_number:"
|
||||||
|
msgid "Authorized From"
|
||||||
|
msgstr "Desde autorizado"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,until_number:"
|
||||||
|
msgid "Authorized Until"
|
||||||
|
msgstr "Hasta autorizado"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,number_next:"
|
||||||
|
msgid "First Number to Issue"
|
||||||
|
msgstr "Primer número por emitir"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,expiration_date:"
|
||||||
|
msgid "Expiration Date"
|
||||||
|
msgstr "Fecha de vencimiento"
|
||||||
|
|
||||||
|
msgctxt "field:ncf.manager.sequence,availability:"
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Disponibilidad"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,sequence_mode:existing"
|
||||||
|
msgid "Select Existing Sequence"
|
||||||
|
msgstr "Seleccionar secuencia existente"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,sequence_mode:create"
|
||||||
|
msgid "Create Sequence on Activation"
|
||||||
|
msgstr "Crear secuencia al activar"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:available"
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Disponible"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:low"
|
||||||
|
msgid "Running Low"
|
||||||
|
msgstr "Próxima a agotarse"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:expired"
|
||||||
|
msgid "Expired"
|
||||||
|
msgstr "Vencida"
|
||||||
|
|
||||||
|
msgctxt "selection:ncf.manager.sequence,availability:exhausted"
|
||||||
|
msgid "Exhausted"
|
||||||
|
msgstr "Agotada"
|
||||||
|
|
||||||
|
msgctxt "model:ir.model.button,string:ncf_manager_activate_button"
|
||||||
|
msgid "Activate"
|
||||||
|
msgstr "Activar"
|
||||||
|
|
||||||
|
msgctxt "model:ir.model.button,string:ncf_manager_finish_button"
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Finalizar"
|
||||||
|
|
||||||
|
msgctxt "model:ir.model.button,string:ncf_sequence_finish_button"
|
||||||
|
msgid "Finish Sequence"
|
||||||
|
msgstr "Finalizar secuencia"
|
||||||
|
|
||||||
|
msgctxt "view:ncf.manager:"
|
||||||
|
msgid "Authorized Fiscal Sequences"
|
||||||
|
msgstr "Secuencias fiscales autorizadas"
|
||||||
|
|||||||
+33
@@ -126,5 +126,38 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<record model="ir.message" id="msg_ncf_next_before_range">
|
<record model="ir.message" id="msg_ncf_next_before_range">
|
||||||
<field name="text">The next fiscal number cannot be before the authorized range.</field>
|
<field name="text">The next fiscal number cannot be before the authorized range.</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_manager_without_sequences">
|
||||||
|
<field name="text">Add at least one fiscal sequence before activating the authorization.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_missing_authorization">
|
||||||
|
<field name="text">Application and authorization numbers are required before activation.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_authorization_used">
|
||||||
|
<field name="text">This authorization cannot be deleted because one of its fiscal sequences has been used on an invoice.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_sequence_used">
|
||||||
|
<field name="text">This fiscal sequence cannot be deleted because it has been used on an invoice.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_sequence_type_mismatch">
|
||||||
|
<field name="text">The selected sequence type does not correspond to the selected NCF type.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_non_fiscal_sequence">
|
||||||
|
<field name="text">The “Invoice without NCF” classification does not create a fiscal sequence.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_assisted_values_required">
|
||||||
|
<field name="text">Starting, ending and first issue numbers are required to create the fiscal sequence.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_sequence_overlap">
|
||||||
|
<field name="text">The authorized range overlaps an active range for the same company and NCF type.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_sequence_already_assigned">
|
||||||
|
<field name="text">The fiscal sequence is already assigned to another authorization line.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_sequence_delete_not_draft">
|
||||||
|
<field name="text">Only draft fiscal sequence lines can be deleted.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_ncf_authorization_delete_not_draft">
|
||||||
|
<field name="text">Only draft fiscal authorizations can be deleted.</field>
|
||||||
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</tryton>
|
</tryton>
|
||||||
|
|||||||
+379
-15
@@ -1,8 +1,12 @@
|
|||||||
# This file is part of ncf_manager module for Tryton. The COPYRIGHT file at the top level of
|
import datetime
|
||||||
# this repository contains the full copyright notices and license terms.
|
|
||||||
|
# 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.
|
||||||
from trytond.exceptions import UserError
|
from trytond.exceptions import UserError
|
||||||
from trytond.i18n import gettext
|
from trytond.i18n import gettext
|
||||||
from trytond.model import ModelView, ModelSQL, fields
|
|
||||||
|
from trytond.model import ModelView, ModelSQL, Workflow, fields, Unique
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
from trytond.pyson import Eval
|
from trytond.pyson import Eval
|
||||||
from trytond.transaction import Transaction
|
from trytond.transaction import Transaction
|
||||||
@@ -34,8 +38,48 @@ NCF_TYPE = [
|
|||||||
('99', 'Factura sin NCF'),
|
('99', 'Factura sin NCF'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ELECTRONIC_NCF_TYPES = frozenset({
|
||||||
|
'31', '32', '33', '34', '41', '43', '44', '45', '46', '47'})
|
||||||
|
|
||||||
class NCFManager(ModelSQL, ModelView):
|
NCF_SEQUENCE_TYPE_IDS = {
|
||||||
|
'1': 'sequence_type_out_invoice_01',
|
||||||
|
'2': 'sequence_type_out_invoice_02',
|
||||||
|
'3': 'sequence_type_out_invoice_03',
|
||||||
|
'4': 'sequence_type_out_credit_note_04',
|
||||||
|
'11': 'sequence_type_out_invoice_11',
|
||||||
|
'12': 'sequence_type_out_invoice_12',
|
||||||
|
'13': 'sequence_type_out_invoice_13',
|
||||||
|
'14': 'sequence_type_out_invoice_14',
|
||||||
|
'15': 'sequence_type_out_invoice_15',
|
||||||
|
'16': 'sequence_type_out_invoice_16',
|
||||||
|
'17': 'sequence_type_out_invoice_17',
|
||||||
|
'31': 'sequence_type_out_invoice_31',
|
||||||
|
'32': 'sequence_type_out_invoice_32',
|
||||||
|
'33': 'sequence_type_out_invoice_33',
|
||||||
|
'34': 'sequence_type_out_credit_note_34',
|
||||||
|
'41': 'sequence_type_out_invoice_41',
|
||||||
|
'43': 'sequence_type_out_invoice_43',
|
||||||
|
'44': 'sequence_type_out_invoice_44',
|
||||||
|
'45': 'sequence_type_out_invoice_45',
|
||||||
|
'46': 'sequence_type_out_invoice_46',
|
||||||
|
'47': 'sequence_type_out_invoice_47',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ncf_prefix(ncf_type):
|
||||||
|
if not ncf_type or ncf_type == '99':
|
||||||
|
return None
|
||||||
|
series = 'E' if ncf_type in ELECTRONIC_NCF_TYPES else 'B'
|
||||||
|
return f'{series}{ncf_type.zfill(2)}'
|
||||||
|
|
||||||
|
|
||||||
|
def ncf_padding(ncf_type):
|
||||||
|
if not ncf_type or ncf_type == '99':
|
||||||
|
return 0
|
||||||
|
return 10 if ncf_type in ELECTRONIC_NCF_TYPES else 8
|
||||||
|
|
||||||
|
|
||||||
|
class NCFManager(Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
'NCF Manager'
|
'NCF Manager'
|
||||||
__name__ = 'ncf.manager'
|
__name__ = 'ncf.manager'
|
||||||
@@ -66,10 +110,17 @@ class NCFManager(ModelSQL, ModelView):
|
|||||||
('draft', 'Draft'),
|
('draft', 'Draft'),
|
||||||
('active', 'Active'),
|
('active', 'Active'),
|
||||||
('finished', 'Finished'),
|
('finished', 'Finished'),
|
||||||
], "State", sort=False)
|
], "State", readonly=True, required=True, sort=False)
|
||||||
|
|
||||||
state_string = state.translated('state')
|
state_string = state.translated('state')
|
||||||
|
|
||||||
|
activated_by = fields.Many2One(
|
||||||
|
'res.user', 'Activated By', readonly=True)
|
||||||
|
activated_at = fields.DateTime('Activated At', readonly=True)
|
||||||
|
finished_by = fields.Many2One(
|
||||||
|
'res.user', 'Finished By', readonly=True)
|
||||||
|
finished_at = fields.DateTime('Finished At', readonly=True)
|
||||||
|
|
||||||
del _states, _depends
|
del _states, _depends
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -79,6 +130,20 @@ class NCFManager(ModelSQL, ModelView):
|
|||||||
('app_date', 'DESC'),
|
('app_date', 'DESC'),
|
||||||
('id', 'DESC'),
|
('id', 'DESC'),
|
||||||
]
|
]
|
||||||
|
cls._transitions |= {
|
||||||
|
('draft', 'active'),
|
||||||
|
('active', 'finished'),
|
||||||
|
}
|
||||||
|
cls._buttons.update({
|
||||||
|
'activate': {
|
||||||
|
'invisible': Eval('state') != 'draft',
|
||||||
|
'depends': ['state'],
|
||||||
|
},
|
||||||
|
'finish': {
|
||||||
|
'invisible': Eval('state') != 'active',
|
||||||
|
'depends': ['state'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
def get_rec_name(self, name):
|
def get_rec_name(self, name):
|
||||||
parts = []
|
parts = []
|
||||||
@@ -96,8 +161,79 @@ class NCFManager(ModelSQL, ModelView):
|
|||||||
def default_state():
|
def default_state():
|
||||||
return 'draft'
|
return 'draft'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@ModelView.button
|
||||||
|
@Workflow.transition('active')
|
||||||
|
def activate(cls, managers):
|
||||||
|
Line = Pool().get('ncf.manager.sequence')
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
user = Transaction().user
|
||||||
|
lines = []
|
||||||
|
for manager in managers:
|
||||||
|
if not manager.ncf_sequences:
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_manager_without_sequences'))
|
||||||
|
manager._validate_authorization()
|
||||||
|
for line in manager.ncf_sequences:
|
||||||
|
line.prepare_sequence()
|
||||||
|
line._validate_fiscal_sequence()
|
||||||
|
line._validate_no_overlap()
|
||||||
|
lines.append(line)
|
||||||
|
if lines:
|
||||||
|
Line.save(lines)
|
||||||
|
Line.activate(lines)
|
||||||
|
cls.write(managers, {
|
||||||
|
'activated_by': user,
|
||||||
|
'activated_at': now,
|
||||||
|
})
|
||||||
|
|
||||||
class NCFManagerSequences(ModelSQL, ModelView):
|
def _validate_authorization(self):
|
||||||
|
if not self.app_number.strip() or not self.auth_number.strip():
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_missing_authorization'))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@ModelView.button
|
||||||
|
@Workflow.transition('finished')
|
||||||
|
def finish(cls, managers):
|
||||||
|
Line = Pool().get('ncf.manager.sequence')
|
||||||
|
lines = [line for manager in managers
|
||||||
|
for line in manager.ncf_sequences if line.state == 'active']
|
||||||
|
if lines:
|
||||||
|
Line.complete(lines)
|
||||||
|
cls.write(managers, {
|
||||||
|
'finished_by': Transaction().user,
|
||||||
|
'finished_at': datetime.datetime.now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, managers):
|
||||||
|
if any(manager.state != 'draft' for manager in managers):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_authorization_delete_not_draft'))
|
||||||
|
Invoice = Pool().get('account.invoice')
|
||||||
|
if Invoice.search([
|
||||||
|
('ncf_sequence.pos', 'in', [m.id for m in managers]),
|
||||||
|
], limit=1):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_authorization_used'))
|
||||||
|
super().delete(managers)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def copy(cls, managers, default=None):
|
||||||
|
default = dict(default or {})
|
||||||
|
default.update({
|
||||||
|
'state': 'draft',
|
||||||
|
'ncf_sequences': None,
|
||||||
|
'activated_by': None,
|
||||||
|
'activated_at': None,
|
||||||
|
'finished_by': None,
|
||||||
|
'finished_at': None,
|
||||||
|
})
|
||||||
|
return super().copy(managers, default=default)
|
||||||
|
|
||||||
|
|
||||||
|
class NCFManagerSequences(Workflow, ModelSQL, ModelView):
|
||||||
|
|
||||||
'NCF Manager Sequences'
|
'NCF Manager Sequences'
|
||||||
__name__ = 'ncf.manager.sequence'
|
__name__ = 'ncf.manager.sequence'
|
||||||
@@ -118,11 +254,60 @@ class NCFManagerSequences(ModelSQL, ModelView):
|
|||||||
|
|
||||||
ncf_type_string = ncf_type.translated('ncf_type')
|
ncf_type_string = ncf_type.translated('ncf_type')
|
||||||
|
|
||||||
ncf_sequences = fields.Many2One('ir.sequence.strict', 'NCF Sequences',
|
company = fields.Function(
|
||||||
domain=[
|
fields.Many2One('company.company', 'Company'),
|
||||||
('company', '=', Eval(
|
'on_change_with_company')
|
||||||
'context', {}).get('company', -1)),
|
|
||||||
], states=_states, depends=_depends)
|
expected_prefix = fields.Function(
|
||||||
|
fields.Char('Expected Prefix'), 'on_change_with_expected_prefix')
|
||||||
|
expected_padding = fields.Function(
|
||||||
|
fields.Integer('Expected Padding'),
|
||||||
|
'on_change_with_expected_padding')
|
||||||
|
|
||||||
|
sequence_mode = fields.Selection([
|
||||||
|
('existing', 'Select Existing Sequence'),
|
||||||
|
('create', 'Create Sequence on Activation'),
|
||||||
|
], 'Sequence Configuration', required=True, states=_states,
|
||||||
|
depends=_depends, sort=False)
|
||||||
|
|
||||||
|
ncf_sequences = fields.Many2One('ir.sequence.strict', 'Fiscal Sequence',
|
||||||
|
domain=[
|
||||||
|
('company', '=', Eval('company', -1)),
|
||||||
|
('prefix', '=', Eval('expected_prefix')),
|
||||||
|
('active', '=', True),
|
||||||
|
],
|
||||||
|
states={
|
||||||
|
**_states,
|
||||||
|
'required': Eval('sequence_mode') == 'existing',
|
||||||
|
'invisible': Eval('sequence_mode') == 'create',
|
||||||
|
},
|
||||||
|
depends=_depends + [
|
||||||
|
'company',
|
||||||
|
'sequence_mode', 'expected_prefix', 'expected_padding'])
|
||||||
|
|
||||||
|
from_number = fields.Integer(
|
||||||
|
'Authorized From', states={
|
||||||
|
**_states,
|
||||||
|
'required': Eval('sequence_mode') == 'create',
|
||||||
|
'invisible': Eval('sequence_mode') != 'create',
|
||||||
|
}, depends=_depends + ['sequence_mode'])
|
||||||
|
until_number = fields.Integer(
|
||||||
|
'Authorized Until', states={
|
||||||
|
**_states,
|
||||||
|
'required': Eval('sequence_mode') == 'create',
|
||||||
|
'invisible': Eval('sequence_mode') != 'create',
|
||||||
|
}, depends=_depends + ['sequence_mode'])
|
||||||
|
number_next = fields.Integer(
|
||||||
|
'First Number to Issue', states={
|
||||||
|
**_states,
|
||||||
|
'required': Eval('sequence_mode') == 'create',
|
||||||
|
'invisible': Eval('sequence_mode') != 'create',
|
||||||
|
}, depends=_depends + ['sequence_mode'])
|
||||||
|
expiration_date = fields.Date(
|
||||||
|
'Expiration Date', states={
|
||||||
|
**_states,
|
||||||
|
'invisible': Eval('sequence_mode') != 'create',
|
||||||
|
}, depends=_depends + ['sequence_mode'])
|
||||||
|
|
||||||
invoice_type = fields.Selection([
|
invoice_type = fields.Selection([
|
||||||
('invoice_customer', 'Customer Invoice'),
|
('invoice_customer', 'Customer Invoice'),
|
||||||
@@ -134,16 +319,51 @@ class NCFManagerSequences(ModelSQL, ModelView):
|
|||||||
('draft', 'Draft'),
|
('draft', 'Draft'),
|
||||||
('active', 'Active'),
|
('active', 'Active'),
|
||||||
('finished', 'Finished'),
|
('finished', 'Finished'),
|
||||||
], "State", sort=False)
|
], "State", readonly=True, required=True, sort=False)
|
||||||
|
|
||||||
state_string = state.translated('state')
|
state_string = state.translated('state')
|
||||||
|
|
||||||
|
availability = fields.Function(fields.Selection([
|
||||||
|
('available', 'Available'),
|
||||||
|
('low', 'Running Low'),
|
||||||
|
('expired', 'Expired'),
|
||||||
|
('exhausted', 'Exhausted'),
|
||||||
|
], 'Availability', sort=False), 'get_availability')
|
||||||
|
availability_string = availability.translated('availability')
|
||||||
|
finished_by = fields.Many2One(
|
||||||
|
'res.user', 'Finished By', readonly=True)
|
||||||
|
finished_at = fields.DateTime('Finished At', readonly=True)
|
||||||
|
|
||||||
del _states, _depends
|
del _states, _depends
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_state():
|
def default_state():
|
||||||
return 'draft'
|
return 'draft'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_sequence_mode():
|
||||||
|
return 'existing'
|
||||||
|
|
||||||
|
@fields.depends('pos', '_parent_pos.company')
|
||||||
|
def on_change_with_company(self, name=None):
|
||||||
|
return self.pos.company.id if self.pos and self.pos.company else None
|
||||||
|
|
||||||
|
@fields.depends('ncf_type')
|
||||||
|
def on_change_with_expected_prefix(self, name=None):
|
||||||
|
return ncf_prefix(self.ncf_type)
|
||||||
|
|
||||||
|
@fields.depends('ncf_type')
|
||||||
|
def on_change_with_expected_padding(self, name=None):
|
||||||
|
return ncf_padding(self.ncf_type)
|
||||||
|
|
||||||
|
@fields.depends(
|
||||||
|
'sequence_mode', 'ncf_type', 'ncf_sequences',
|
||||||
|
'from_number', 'until_number', 'number_next', 'expiration_date')
|
||||||
|
def on_change_ncf_type(self):
|
||||||
|
if (self.ncf_sequences
|
||||||
|
and self.ncf_sequences.prefix != ncf_prefix(self.ncf_type)):
|
||||||
|
self.ncf_sequences = None
|
||||||
|
|
||||||
def get_rec_name(self, name):
|
def get_rec_name(self, name):
|
||||||
if not self.ncf_type_string:
|
if not self.ncf_type_string:
|
||||||
return ''
|
return ''
|
||||||
@@ -160,13 +380,157 @@ class NCFManagerSequences(ModelSQL, ModelView):
|
|||||||
sequence = self.ncf_sequences
|
sequence = self.ncf_sequences
|
||||||
if not sequence:
|
if not sequence:
|
||||||
return
|
return
|
||||||
|
if self.ncf_type == '99':
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_non_fiscal_sequence'))
|
||||||
if sequence.company != self.pos.company:
|
if sequence.company != self.pos.company:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'ncf_manager.msg_ncf_sequence_company_mismatch'))
|
'ncf_manager.msg_ncf_sequence_company_mismatch'))
|
||||||
expected_prefix = ('E' if self.ncf_type in {
|
expected_prefix = ncf_prefix(self.ncf_type)
|
||||||
'31', '32', '33', '34', '41', '43', '44', '45', '46', '47'
|
|
||||||
} else 'B') + self.ncf_type.zfill(2)
|
|
||||||
if self.ncf_type != '99' and sequence.prefix != expected_prefix:
|
if self.ncf_type != '99' and sequence.prefix != expected_prefix:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'ncf_manager.msg_ncf_sequence_prefix_mismatch',
|
'ncf_manager.msg_ncf_sequence_prefix_mismatch',
|
||||||
prefix=expected_prefix))
|
prefix=expected_prefix))
|
||||||
|
if sequence.padding != ncf_padding(self.ncf_type):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_invalid_padding',
|
||||||
|
padding=ncf_padding(self.ncf_type)))
|
||||||
|
ModelData = Pool().get('ir.model.data')
|
||||||
|
expected_type = ModelData.get_id(
|
||||||
|
'ncf_manager', NCF_SEQUENCE_TYPE_IDS[self.ncf_type])
|
||||||
|
if sequence.sequence_type.id != expected_type:
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_sequence_type_mismatch'))
|
||||||
|
today = Transaction().context.get('date') or datetime.date.today()
|
||||||
|
if sequence.expiration_date and sequence.expiration_date < today:
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_sequence_expired'))
|
||||||
|
if (sequence.until_number is not None
|
||||||
|
and sequence.number_next is not None
|
||||||
|
and sequence.number_next > sequence.until_number):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_sequence_finished'))
|
||||||
|
|
||||||
|
def prepare_sequence(self):
|
||||||
|
if self.sequence_mode != 'create':
|
||||||
|
return
|
||||||
|
if self.ncf_type == '99':
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_non_fiscal_sequence'))
|
||||||
|
self._validate_assisted_values()
|
||||||
|
pool = Pool()
|
||||||
|
ModelData = pool.get('ir.model.data')
|
||||||
|
Sequence = pool.get('ir.sequence.strict')
|
||||||
|
sequence_type = ModelData.get_id(
|
||||||
|
'ncf_manager', NCF_SEQUENCE_TYPE_IDS[self.ncf_type])
|
||||||
|
sequence, = Sequence.create([{
|
||||||
|
'name': f'{ncf_prefix(self.ncf_type)} - {self.pos.auth_number}',
|
||||||
|
'sequence_type': sequence_type,
|
||||||
|
'company': self.pos.company.id,
|
||||||
|
'prefix': ncf_prefix(self.ncf_type),
|
||||||
|
'padding': ncf_padding(self.ncf_type),
|
||||||
|
'number_increment': 1,
|
||||||
|
'number_next': self.number_next,
|
||||||
|
'from_number': self.from_number,
|
||||||
|
'until_number': self.until_number,
|
||||||
|
'expiration_date': self.expiration_date,
|
||||||
|
}])
|
||||||
|
self.ncf_sequences = sequence
|
||||||
|
self.sequence_mode = 'existing'
|
||||||
|
|
||||||
|
def _validate_assisted_values(self):
|
||||||
|
if None in (self.from_number, self.until_number, self.number_next):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_assisted_values_required'))
|
||||||
|
if not (1 <= self.from_number <= self.number_next
|
||||||
|
<= self.until_number):
|
||||||
|
raise UserError(gettext('ncf_manager.msg_ncf_invalid_range'))
|
||||||
|
|
||||||
|
def _validate_no_overlap(self):
|
||||||
|
sequence = self.ncf_sequences
|
||||||
|
if not sequence:
|
||||||
|
return
|
||||||
|
candidates = self.__class__.search([
|
||||||
|
('id', '!=', self.id),
|
||||||
|
('pos.company', '=', self.pos.company.id),
|
||||||
|
('ncf_type', '=', self.ncf_type),
|
||||||
|
('state', '=', 'active'),
|
||||||
|
('ncf_sequences', '!=', None),
|
||||||
|
])
|
||||||
|
for candidate in candidates:
|
||||||
|
other = candidate.ncf_sequences
|
||||||
|
if None in (sequence.from_number, sequence.until_number,
|
||||||
|
other.from_number, other.until_number):
|
||||||
|
continue
|
||||||
|
if (sequence.from_number <= other.until_number
|
||||||
|
and other.from_number <= sequence.until_number):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_sequence_overlap'))
|
||||||
|
|
||||||
|
def get_availability(self, name):
|
||||||
|
sequence = self.ncf_sequences
|
||||||
|
if not sequence:
|
||||||
|
return 'available'
|
||||||
|
today = Transaction().context.get('date') or datetime.date.today()
|
||||||
|
if sequence.expiration_date and sequence.expiration_date < today:
|
||||||
|
return 'expired'
|
||||||
|
if (sequence.until_number is not None
|
||||||
|
and sequence.number_next is not None):
|
||||||
|
remaining = sequence.until_number - sequence.number_next + 1
|
||||||
|
if remaining <= 0:
|
||||||
|
return 'exhausted'
|
||||||
|
approved = ((sequence.until_number - sequence.from_number + 1)
|
||||||
|
if sequence.from_number is not None else 0)
|
||||||
|
if remaining <= max(10, int(approved * .1)):
|
||||||
|
return 'low'
|
||||||
|
return 'available'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __setup__(cls):
|
||||||
|
super().__setup__()
|
||||||
|
table = cls.__table__()
|
||||||
|
cls._sql_constraints += [
|
||||||
|
('fiscal_sequence_unique', Unique(table, table.ncf_sequences),
|
||||||
|
'ncf_manager.msg_ncf_sequence_already_assigned'),
|
||||||
|
]
|
||||||
|
cls._transitions |= {
|
||||||
|
('draft', 'active'),
|
||||||
|
('active', 'finished'),
|
||||||
|
}
|
||||||
|
cls._buttons.update({
|
||||||
|
'finish': {
|
||||||
|
'invisible': Eval('state') != 'active',
|
||||||
|
'depends': ['state'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@Workflow.transition('active')
|
||||||
|
def activate(cls, lines):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@ModelView.button
|
||||||
|
def finish(cls, lines):
|
||||||
|
cls.complete(lines)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@Workflow.transition('finished')
|
||||||
|
def complete(cls, lines):
|
||||||
|
cls.write(lines, {
|
||||||
|
'finished_by': Transaction().user,
|
||||||
|
'finished_at': datetime.datetime.now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, lines):
|
||||||
|
if any(line.state != 'draft' for line in lines):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_sequence_delete_not_draft'))
|
||||||
|
Invoice = Pool().get('account.invoice')
|
||||||
|
if Invoice.search([
|
||||||
|
('ncf_sequence', 'in', [line.id for line in lines]),
|
||||||
|
], limit=1):
|
||||||
|
raise UserError(gettext(
|
||||||
|
'ncf_manager.msg_ncf_sequence_used'))
|
||||||
|
super().delete(lines)
|
||||||
|
|||||||
@@ -57,6 +57,27 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<field name="perm_delete" eval="True"/>
|
<field name="perm_delete" eval="True"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.model.button" id="ncf_manager_activate_button">
|
||||||
|
<field name="name">activate</field>
|
||||||
|
<field name="string">Activate</field>
|
||||||
|
<field name="model">ncf.manager</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.model.button-res.group"
|
||||||
|
id="ncf_manager_activate_button_group">
|
||||||
|
<field name="button" ref="ncf_manager_activate_button"/>
|
||||||
|
<field name="group" ref="account.group_account_admin"/>
|
||||||
|
</record>
|
||||||
|
<record model="ir.model.button" id="ncf_manager_finish_button">
|
||||||
|
<field name="name">finish</field>
|
||||||
|
<field name="string">Finish</field>
|
||||||
|
<field name="model">ncf.manager</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.model.button-res.group"
|
||||||
|
id="ncf_manager_finish_button_group">
|
||||||
|
<field name="button" ref="ncf_manager_finish_button"/>
|
||||||
|
<field name="group" ref="account.group_account_admin"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="ncf_manager_sequences_view_form">
|
<record model="ir.ui.view" id="ncf_manager_sequences_view_form">
|
||||||
<field name="model">ncf.manager.sequence</field>
|
<field name="model">ncf.manager.sequence</field>
|
||||||
<field name="type">form</field>
|
<field name="type">form</field>
|
||||||
@@ -86,5 +107,16 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<field name="perm_delete" eval="True"/>
|
<field name="perm_delete" eval="True"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.model.button" id="ncf_sequence_finish_button">
|
||||||
|
<field name="name">finish</field>
|
||||||
|
<field name="string">Finish Sequence</field>
|
||||||
|
<field name="model">ncf.manager.sequence</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.model.button-res.group"
|
||||||
|
id="ncf_sequence_finish_button_group">
|
||||||
|
<field name="button" ref="ncf_sequence_finish_button"/>
|
||||||
|
<field name="group" ref="account.group_account_admin"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</tryton>
|
</tryton>
|
||||||
|
|||||||
@@ -141,3 +141,7 @@ class SequenceStrict(Sequence):
|
|||||||
_table = None # Needed to reset Sequence._table
|
_table = None # Needed to reset Sequence._table
|
||||||
_strict = True
|
_strict = True
|
||||||
|
|
||||||
|
ncf_authorization_lines = fields.One2Many(
|
||||||
|
'ncf.manager.sequence', 'ncf_sequences', 'NCF Authorizations',
|
||||||
|
readonly=True)
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,46 @@ class NcfManagerTestCase(ModuleTestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
invoice.expiration_date, datetime.date(today.year, 12, 31))
|
invoice.expiration_date, datetime.date(today.year, 12, 31))
|
||||||
|
|
||||||
|
@with_transaction()
|
||||||
|
def test_assisted_sequence_workflow(self):
|
||||||
|
pool = Pool()
|
||||||
|
Manager = pool.get('ncf.manager')
|
||||||
|
today = datetime.date.today()
|
||||||
|
company = self._create_company()
|
||||||
|
|
||||||
|
manager, = Manager.create([{
|
||||||
|
'company': company.id,
|
||||||
|
'app_date': today,
|
||||||
|
'app_number': 'APP0000001',
|
||||||
|
'auth_number': 'AUT0000001',
|
||||||
|
'ncf_sequences': [('create', [{
|
||||||
|
'invoice_type': 'invoice_customer',
|
||||||
|
'ncf_type': '1',
|
||||||
|
'sequence_mode': 'create',
|
||||||
|
'from_number': 100,
|
||||||
|
'number_next': 100,
|
||||||
|
'until_number': 109,
|
||||||
|
'expiration_date': datetime.date(today.year, 12, 31),
|
||||||
|
}])],
|
||||||
|
}])
|
||||||
|
|
||||||
|
Manager.activate([manager])
|
||||||
|
manager = Manager(manager.id)
|
||||||
|
line, = manager.ncf_sequences
|
||||||
|
self.assertEqual(manager.state, 'active')
|
||||||
|
self.assertEqual(line.state, 'active')
|
||||||
|
self.assertEqual(line.sequence_mode, 'existing')
|
||||||
|
self.assertEqual(line.ncf_sequences.prefix, 'B01')
|
||||||
|
self.assertEqual(line.ncf_sequences.padding, 8)
|
||||||
|
self.assertEqual(line.ncf_sequences.number_next, 100)
|
||||||
|
self.assertEqual(line.availability, 'low')
|
||||||
|
|
||||||
|
Manager.finish([manager])
|
||||||
|
manager = Manager(manager.id)
|
||||||
|
self.assertEqual(manager.state, 'finished')
|
||||||
|
self.assertEqual(manager.ncf_sequences[0].state, 'finished')
|
||||||
|
self.assertIsNotNone(manager.finished_at)
|
||||||
|
|
||||||
|
|
||||||
del ModuleTestCase
|
del ModuleTestCase
|
||||||
|
|
||||||
|
|||||||
+28
-10
@@ -3,15 +3,33 @@
|
|||||||
The COPYRIGHT file at the top level of this repository contains the full
|
The COPYRIGHT file at the top level of this repository contains the full
|
||||||
copyright notices and license types. -->
|
copyright notices and license types. -->
|
||||||
<form>
|
<form>
|
||||||
<label name="company"/>
|
<group id="authorization" col="4" colspan="4">
|
||||||
<field name="company"/>
|
<label name="company"/>
|
||||||
<label name="app_date"/>
|
<field name="company"/>
|
||||||
<field name="app_date"/>
|
<label name="state"/>
|
||||||
<label name="app_number"/>
|
<field name="state"/>
|
||||||
<field name="app_number"/>
|
<label name="app_date"/>
|
||||||
<label name="auth_number"/>
|
<field name="app_date"/>
|
||||||
<field name="auth_number"/>
|
<newline/>
|
||||||
|
<label name="app_number"/>
|
||||||
|
<field name="app_number"/>
|
||||||
|
<label name="auth_number"/>
|
||||||
|
<field name="auth_number"/>
|
||||||
|
</group>
|
||||||
|
<separator string="Authorized Fiscal Sequences" id="sequences" colspan="4"/>
|
||||||
<field name="ncf_sequences" colspan="4"/>
|
<field name="ncf_sequences" colspan="4"/>
|
||||||
<label name="state"/>
|
<group id="audit" col="4" colspan="4">
|
||||||
<field name="state"/>
|
<label name="activated_by"/>
|
||||||
|
<field name="activated_by"/>
|
||||||
|
<label name="activated_at"/>
|
||||||
|
<field name="activated_at"/>
|
||||||
|
<label name="finished_by"/>
|
||||||
|
<field name="finished_by"/>
|
||||||
|
<label name="finished_at"/>
|
||||||
|
<field name="finished_at"/>
|
||||||
|
</group>
|
||||||
|
<group col="2" colspan="4" id="buttons">
|
||||||
|
<button name="activate" icon="tryton-ok"/>
|
||||||
|
<button name="finish" icon="tryton-stop"/>
|
||||||
|
</group>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -6,9 +6,28 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<field name="invoice_type"/>
|
<field name="invoice_type"/>
|
||||||
<label name="ncf_type"/>
|
<label name="ncf_type"/>
|
||||||
<field name="ncf_type"/>
|
<field name="ncf_type"/>
|
||||||
|
<label name="expected_prefix"/>
|
||||||
|
<field name="expected_prefix"/>
|
||||||
|
<label name="sequence_mode"/>
|
||||||
|
<field name="sequence_mode"/>
|
||||||
<label name="ncf_sequences"/>
|
<label name="ncf_sequences"/>
|
||||||
<field name="ncf_sequences"/>
|
<field name="ncf_sequences"/>
|
||||||
<newline/>
|
<newline/>
|
||||||
|
<label name="from_number"/>
|
||||||
|
<field name="from_number"/>
|
||||||
|
<label name="until_number"/>
|
||||||
|
<field name="until_number"/>
|
||||||
|
<label name="number_next"/>
|
||||||
|
<field name="number_next"/>
|
||||||
|
<label name="expiration_date"/>
|
||||||
|
<field name="expiration_date"/>
|
||||||
<label name="state"/>
|
<label name="state"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
|
<label name="availability"/>
|
||||||
|
<field name="availability"/>
|
||||||
|
<label name="finished_by"/>
|
||||||
|
<field name="finished_by"/>
|
||||||
|
<label name="finished_at"/>
|
||||||
|
<field name="finished_at"/>
|
||||||
|
<button name="finish" icon="tryton-stop"/>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<tree>
|
<tree>
|
||||||
<field name="invoice_type"/>
|
<field name="invoice_type"/>
|
||||||
<field name="ncf_type"/>
|
<field name="ncf_type"/>
|
||||||
|
<field name="expected_prefix"/>
|
||||||
<field name="ncf_sequences"/>
|
<field name="ncf_sequences"/>
|
||||||
|
<field name="from_number" optional="1"/>
|
||||||
|
<field name="until_number" optional="1"/>
|
||||||
|
<field name="number_next" optional="1"/>
|
||||||
|
<field name="expiration_date" optional="1"/>
|
||||||
|
<field name="availability"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
|
<button name="finish"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ copyright notices and license types. -->
|
|||||||
<field name="app_number"/>
|
<field name="app_number"/>
|
||||||
<field name="auth_number"/>
|
<field name="auth_number"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
|
<button name="activate"/>
|
||||||
|
<button name="finish"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|||||||
Reference in New Issue
Block a user