diff --git a/l10n_mx_hr_payroll_edi/README.rst b/l10n_mx_hr_payroll_edi/README.rst new file mode 100644 index 0000000..489a8b9 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/README.rst @@ -0,0 +1,6 @@ +EDI for Mexican Payroll Localization +==================================== + +Allow the user to generate the EDI document for Mexican invoicing. + +This module allows the creation of the EDI documents and the communication with the Mexican certification providers (PACs) to sign/cancel them. diff --git a/l10n_mx_hr_payroll_edi/__init__.py b/l10n_mx_hr_payroll_edi/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_mx_hr_payroll_edi/__manifest__.py b/l10n_mx_hr_payroll_edi/__manifest__.py new file mode 100644 index 0000000..d44e951 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/__manifest__.py @@ -0,0 +1,31 @@ +{ + "name": "Payroll EDI for Mexico", + "icon": "/l10n_mx/static/description/icon.png", + "summary": "Mexican Localization for Payroll EDI documents", + "author": "e-maanu, " + "Open Source Integrators, " + "Asociacion Mexicana de Odoo (AMOdoo), " + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-mexico", + "category": "Human Resources/Payroll", + "version": "17.0.1.0.0", + "depends": [ + "l10n_mx_edi_40", + "l10n_mx_hr_payroll", + ], + "external_dependencies": { + "python": ["pyOpenSSL", "zeep"], + }, + "data": [ + "data/1.2/cfdi.xml", + "security/ir.model.access.csv", + "views/res_config_settings.xml", + "views/hr_payslip.xml", + "data/account_edi_data.xml", + ], + "demo": [], + "installable": True, + "application": False, + "license": "LGPL-3", + "development_status": "Beta", +} diff --git a/l10n_mx_hr_payroll_edi/data/1.2/cadenaoriginal.xslt b/l10n_mx_hr_payroll_edi/data/1.2/cadenaoriginal.xslt new file mode 100644 index 0000000..f43972c --- /dev/null +++ b/l10n_mx_hr_payroll_edi/data/1.2/cadenaoriginal.xslt @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ||| + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_mx_hr_payroll_edi/data/1.2/cadenaoriginal_TFD_1_1.xslt b/l10n_mx_hr_payroll_edi/data/1.2/cadenaoriginal_TFD_1_1.xslt new file mode 100644 index 0000000..6938956 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/data/1.2/cadenaoriginal_TFD_1_1.xslt @@ -0,0 +1,46 @@ + + + + + + | + + + + + + | + + + + + + + + ||| + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_mx_hr_payroll_edi/data/1.2/cfdi.xml b/l10n_mx_hr_payroll_edi/data/1.2/cfdi.xml new file mode 100644 index 0000000..d498f93 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/data/1.2/cfdi.xml @@ -0,0 +1,146 @@ + + + + diff --git a/l10n_mx_hr_payroll_edi/data/1.2/nomina12.xsd b/l10n_mx_hr_payroll_edi/data/1.2/nomina12.xsd new file mode 100644 index 0000000..7de7ba0 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/data/1.2/nomina12.xsd @@ -0,0 +1,703 @@ + + + + + + + + Complemento para incorporar al Comprobante Fiscal Digital por Internet (CFDI) la información que ampara conceptos de ingresos por salarios, la prestación de un servicio personal subordinado o conceptos asimilados a salarios (Nómina). + + + + + + Nodo condicional para expresar la información del contribuyente emisor del comprobante de nómina. + + + + + + Nodo condicional para que las entidades adheridas al Sistema Nacional de Coordinación Fiscal realicen la identificación del origen de los recursos utilizados en el pago de nómina del personal que presta o desempeña un servicio personal subordinado en las dependencias de la entidad federativa, del municipio o demarcación territorial de la Ciudad de México, así como en sus respectivos organismos autónomos y entidades paraestatales y paramunicipales + + + + + Atributo requerido para identificar el origen del recurso utilizado para el pago de nómina del personal que presta o desempeña un servicio personal subordinado o asimilado a salarios en las dependencias. + + + + + Atributo condicional para expresar el monto del recurso pagado con cargo a sus participaciones u otros ingresos locales (importe bruto de los ingresos propios, es decir total de gravados y exentos), cuando el origen es mixto. + + + + + + + + Atributo condicional para expresar la CURP del emisor del comprobante de nómina cuando es una persona física. + + + + + Atributo condicional para expresar el registro patronal, clave de ramo - pagaduría o la que le asigne la institución de seguridad social al patrón, a 20 posiciones máximo. Se debe ingresar cuando se cuente con él, o se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + + + + + + + + + Atributo opcional para expresar el RFC de la persona que fungió como patrón cuando el pago al trabajador se realice a través de un tercero como vehículo o herramienta de pago. + + + + + + + Nodo requerido para precisar la información del contribuyente receptor del comprobante de nómina. + + + + + + Nodo condicional para expresar la lista de las personas que los subcontrataron. + + + + + Atributo requerido para expresar el RFC de la persona que subcontrata. + + + + + Atributo requerido para expresar el porcentaje del tiempo que prestó sus servicios con el RFC que lo subcontrata. + + + + + + + + + + + + + + + + Atributo requerido para expresar la CURP del receptor del comprobante de nómina. + + + + + Atributo condicional para expresar el número de seguridad social del trabajador. Se debe ingresar cuando se cuente con él, o se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + + + + + + + + + Atributo condicional para expresar la fecha de inicio de la relación laboral entre el empleador y el empleado. Se expresa en la forma AAAA-MM-DD, de acuerdo con la especificación ISO 8601. Se debe ingresar cuando se cuente con él, o se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + Atributo condicional para expresar el número de semanas o el periodo de años, meses y días que el empleado ha mantenido relación laboral con el empleador. Se debe ingresar cuando se cuente con él, o se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + + + + + + + Atributo requerido para expresar el tipo de contrato que tiene el trabajador. + + + + + Atributo opcional para indicar si el trabajador está asociado a un sindicato. Si se omite se asume que no está asociado a algún sindicato. + + + + + + + + + + + + Atributo condicional para expresar el tipo de jornada que cubre el trabajador. Se debe ingresar cuando se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + Atributo requerido para la expresión de la clave del régimen por el cual se tiene contratado al trabajador. + + + + + Atributo requerido para expresar el número de empleado de 1 a 15 posiciones. + + + + + + + + + + + + + Atributo opcional para la expresión del departamento o área a la que pertenece el trabajador. + + + + + + + + + + + + + Atributo opcional para la expresión del puesto asignado al empleado o actividad que realiza. + + + + + + + + + + + + + Atributo opcional para expresar la clave conforme a la Clase en que deben inscribirse los patrones, de acuerdo con las actividades que desempeñan sus trabajadores, según lo previsto en el artículo 196 del Reglamento en Materia de Afiliación Clasificación de Empresas, Recaudación y Fiscalización, o conforme con la normatividad del Instituto de Seguridad Social del trabajador. Se debe ingresar cuando se cuente con él, o se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + Atributo requerido para la forma en que se establece el pago del salario. + + + + + Atributo condicional para la expresión de la clave del Banco conforme al catálogo, donde se realiza el depósito de nómina. + + + + + Atributo condicional para la expresión de la cuenta bancaria a 11 posiciones o número de teléfono celular a 10 posiciones o número de tarjeta de crédito, débito o servicios a 15 ó 16 posiciones o la CLABE a 18 posiciones o número de monedero electrónico, donde se realiza el depósito de nómina. + + + + + Atributo opcional para expresar la retribución otorgada al trabajador, que se integra por los pagos hechos en efectivo por cuota diaria, gratificaciones, percepciones, alimentación, habitación, primas, comisiones, prestaciones en especie y cualquiera otra cantidad o prestación que se entregue al trabajador por su trabajo, sin considerar los conceptos que se excluyen de conformidad con el Artículo 27 de la Ley del Seguro Social, o la integración de los pagos conforme la normatividad del Instituto de Seguridad Social del trabajador. (Se emplea para pagar las cuotas y aportaciones de Seguridad Social). Se debe ingresar cuando se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + Atributo opcional para expresar el salario que se integra con los pagos hechos en efectivo por cuota diaria, gratificaciones, percepciones, habitación, primas, comisiones, prestaciones en especie y cualquier otra cantidad o prestación que se entregue al trabajador por su trabajo, de conformidad con el Art. 84 de la Ley Federal del Trabajo. (Se utiliza para el cálculo de las indemnizaciones). Se debe ingresar cuando se esté obligado conforme a otras disposiciones distintas a las fiscales. + + + + + Atributo requerido para expresar la clave de la entidad federativa en donde el receptor del recibo prestó el servicio. + + + + + + + Nodo condicional para expresar las percepciones aplicables. + + + + + + Nodo requerido para expresar la información detallada de una percepción + + + + + + Nodo condicional para expresar ingresos por acciones o títulos valor que representan bienes. Se vuelve requerido cuando existan ingresos por sueldos derivados de adquisición de acciones o títulos (Art. 94, fracción VII LISR). + + + + + Atributo requerido para expresar el valor de mercado de las Acciones o Títulos valor al ejercer la opción. + + + + + + + + + + + + Atributo requerido para expresar el precio establecido al otorgarse la opción de ingresos en acciones o títulos valor. + + + + + + + + + + + + + + Nodo condicional para expresar las horas extra aplicables. + + + + + Atributo requerido para expresar el número de días en que el trabajador realizó horas extra en el periodo. + + + + + + + + + + + Atributo requerido para expresar el tipo de pago de las horas extra. + + + + + Atributo requerido para expresar el número de horas extra trabajadas en el periodo. + + + + + + + + + + + Atributo requerido para expresar el importe pagado por las horas extra. + + + + + + + + Atributo requerido para expresar la Clave agrupadora bajo la cual se clasifica la percepción. + + + + + Atributo requerido para expresar la clave de percepción de nómina propia de la contabilidad de cada patrón, puede conformarse desde 3 hasta 15 caracteres. + + + + + + + + + + + + + Atributo requerido para la descripción del concepto de percepción + + + + + + + + + + + + + Atributo requerido, representa el importe gravado de un concepto de percepción. + + + + + Atributo requerido, representa el importe exento de un concepto de percepción. + + + + + + + Nodo condicional para expresar la información detallada de pagos por jubilación, pensiones o haberes de retiro. + + + + + Atributo condicional que indica el monto total del pago cuando se realiza en una sola exhibición. + + + + + Atributo condicional para expresar los ingresos totales por pago cuando se hace en parcialidades. + + + + + Atributo condicional para expresar el monto diario percibido por jubilación, pensiones o haberes de retiro cuando se realiza en parcialidades. + + + + + Atributo requerido para expresar los ingresos acumulables. + + + + + Atributo requerido para expresar los ingresos no acumulables. + + + + + + + Nodo condicional para expresar la información detallada de otros pagos por separación. + + + + + Atributo requerido que indica el monto total del pago. + + + + + Atributo requerido para expresar el número de años de servicio del trabajador. Se redondea al entero superior si la cifra contiene años y meses y hay más de 6 meses. + + + + + + + + + + + + Atributo requerido que indica el último sueldo mensual ordinario. + + + + + Atributo requerido para expresar los ingresos acumulables. + + + + + Atributo requerido que indica los ingresos no acumulables. + + + + + + + + Atributo condicional para expresar el total de percepciones brutas (gravadas y exentas) por sueldos y salarios y conceptos asimilados a salarios. + + + + + Atributo condicional para expresar el importe exento y gravado de las claves tipo percepción 022 Prima por Antigüedad, 023 Pagos por separación y 025 Indemnizaciones. + + + + + Atributo condicional para expresar el importe exento y gravado de las claves tipo percepción 039 Jubilaciones, pensiones o haberes de retiro en una exhibición y 044 Jubilaciones, pensiones o haberes de retiro en parcialidades. + + + + + Atributo requerido para expresar el total de percepciones gravadas que se relacionan en el comprobante. + + + + + Atributo requerido para expresar el total de percepciones exentas que se relacionan en el comprobante. + + + + + + + Nodo opcional para expresar las deducciones aplicables. + + + + + + Nodo requerido para expresar la información detallada de una deducción. + + + + + Atributo requerido para registrar la clave agrupadora que clasifica la deducción. + + + + + Atributo requerido para la clave de deducción de nómina propia de la contabilidad de cada patrón, puede conformarse desde 3 hasta 15 caracteres. + + + + + + + + + + + + Atributo requerido para la descripción del concepto de deducción. + + + + + + + + + + + + Atributo requerido para registrar el importe del concepto de deducción. + + + + + + + + Atributo condicional para expresar el total de deducciones que se relacionan en el comprobante, donde la clave de tipo de deducción sea distinta a la 002 correspondiente a ISR. + + + + + Atributo condicional para expresar el total de los impuestos federales retenidos, es decir, donde la clave de tipo de deducción sea 002 correspondiente a ISR. + + + + + + + Nodo condicional para expresar otros pagos aplicables. + + + + + + Nodo requerido para expresar la información detallada del otro pago. + + + + + + Nodo condicional para expresar la información referente al subsidio al empleo del trabajador. + + + + + Atributo requerido para expresar el subsidio causado conforme a la tabla del subsidio para el empleo publicada en el Anexo 8 de la RMF vigente. + + + + + + + Nodo condicional para expresar la información referente a la compensación de saldos a favor de un trabajador. + + + + + Atributo requerido para expresar el saldo a favor determinado por el patrón al trabajador en periodos o ejercicios anteriores. + + + + + Atributo requerido para expresar el año en que se determinó el saldo a favor del trabajador por el patrón que se incluye en el campo “RemanenteSalFav”. + + + + + + + + + + + Atributo requerido para expresar el remanente del saldo a favor del trabajador. + + + + + + + + Atributo requerido para expresar la clave agrupadora bajo la cual se clasifica el otro pago. + + + + + Atributo requerido, representa la clave de otro pago de nómina propia de la contabilidad de cada patrón, puede conformarse desde 3 hasta 15 caracteres. + + + + + + + + + + + + + Atributo requerido para la descripción del concepto de otro pago. + + + + + + + + + + + + + Atributo requerido para expresar el importe del concepto de otro pago. + + + + + + + + + + Nodo condicional para expresar información de las incapacidades. + + + + + + Nodo requerido para expresar información de las incapacidades. + + + + + Atributo requerido para expresar el número de días enteros que el trabajador se incapacitó en el periodo. + + + + + + + + + + + Atributo requerido para expresar la razón de la incapacidad. + + + + + Atributo condicional para expresar el monto del importe monetario de la incapacidad. + + + + + + + + + + + Atributo requerido para la expresión de la versión del complemento. + + + + + Atributo requerido para indicar el tipo de nómina, puede ser O= Nómina ordinaria o E= Nómina extraordinaria. + + + + + Atributo requerido para la expresión de la fecha efectiva de erogación del gasto. Se expresa en la forma AAAA-MM-DD, de acuerdo con la especificación ISO 8601. + + + + + Atributo requerido para la expresión de la fecha inicial del período de pago. Se expresa en la forma AAAA-MM-DD, de acuerdo con la especificación ISO 8601. + + + + + Atributo requerido para la expresión de la fecha final del período de pago. Se expresa en la forma AAAA-MM-DD, de acuerdo con la especificación ISO 8601. + + + + + Atributo requerido para la expresión del número o la fracción de días pagados. + + + + + + + + + + + + + + Atributo condicional para representar la suma de las percepciones. + + + + + Atributo condicional para representar la suma de las deducciones aplicables. + + + + + Atributo condicional para representar la suma de otros pagos. + + + + + diff --git a/l10n_mx_hr_payroll_edi/data/1.2/nomina12.xslt b/l10n_mx_hr_payroll_edi/data/1.2/nomina12.xslt new file mode 100644 index 0000000..9b13f1b --- /dev/null +++ b/l10n_mx_hr_payroll_edi/data/1.2/nomina12.xslt @@ -0,0 +1,412 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_mx_hr_payroll_edi/data/1.2/tdCFDI.xsd b/l10n_mx_hr_payroll_edi/data/1.2/tdCFDI.xsd new file mode 100644 index 0000000..32713bf --- /dev/null +++ b/l10n_mx_hr_payroll_edi/data/1.2/tdCFDI.xsd @@ -0,0 +1,157 @@ + + + + + Tipo definido para expresar la Clave Única de Registro de Población (CURP) + + + + + + + + + + Tipo definido para expresar importes numéricos con fracción hasta seis decimales. El valor se redondea de acuerdo con el número de decimales que soporta la moneda. No se permiten valores negativos. + + + + + + + + + + + Tipo definido para la expresión de la fecha. Se expresa en la forma AAAA-MM-DD. + + + + + + + + + Tipo definido para expresar importes monetarios en moneda nacional MXN con fracción hasta dos decimales. No se permiten valores negativos. + + + + + + + + + + + Tipo definido para expresar la cuenta bancarizada. + + + + + + + + + Tipo definido para expresar claves del Registro Federal de Contribuyentes + + + + + + + + + + + Tipo definido para la expresión de un Registro Federal de Contribuyentes de persona moral. + + + + + + + + + + Tipo definido para la expresión de un Registro Federal de Contribuyentes de persona física. + + + + + + + + + + Tipo definido para la expresión de la fecha y hora. Se expresa en la forma AAAA-MM-DDThh:mm:ss + + + + + + + + + Tipo definido para la expresión de la fecha y hora. Se expresa en la forma AAAA-MM-DDThh:mm:ss + + + + + + + + + Tipo definido para expresar la calle en que está ubicado el domicilio del emisor del comprobante o del destinatario de la mercancía. + + + + + + + + + + + Tipo definido para expresar el número interior o el número exterior en donde se ubica el domicilio del emisor del comprobante o del destinatario de la mercancía. + + + + + + + + + + + Tipo definido para expresar la referencia geográfica adicional que permita una fácil o precisa ubicación del domicilio del emisor del comprobante o del destinatario de la mercancía, por ejemplo las coordenadas GPS. + + + + + + + + + + + Tipo definido para expresar la colonia, localidad o municipio en que está ubicado el domicilio del emisor del comprobante o del destinatario de la mercancía. + + + + + + + + + + + Tipo definido para expresar el tipo de cambio. No se permiten valores negativos. + + + + + + + + + diff --git a/l10n_mx_hr_payroll_edi/data/account_edi_data.xml b/l10n_mx_hr_payroll_edi/data/account_edi_data.xml new file mode 100644 index 0000000..81d7839 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/data/account_edi_data.xml @@ -0,0 +1,6 @@ + + + CFDI Payroll (1.2) + cfdi_1_2 + + diff --git a/l10n_mx_hr_payroll_edi/i18n/es_MX.po b/l10n_mx_hr_payroll_edi/i18n/es_MX.po new file mode 100644 index 0000000..daa80d8 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/i18n/es_MX.po @@ -0,0 +1,1005 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_mx_hr_payroll_edi +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0+e-20230816\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-04 15:43+0000\n" +"PO-Revision-Date: 2024-07-04 15:43+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_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid " Electronic Payslip error(s)" +msgstr "Error(es) de recibo de nómina electrónico" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid " Electronic Payslip info(s)" +msgstr "Información de recibo de nómina electrónico" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid " Electronic Payslip warning(s)" +msgstr "Advertencia(s) de recibo de nómina electrónic" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "MX Certificates for payroll" +msgstr "Certificados MX para nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "MX PAC for payroll" +msgstr "PAC MX para nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Payroll Rules" +msgstr "Reglas de nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Analyse Payslip" +msgstr "Analizar Recibo de Nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Extra Time" +msgstr "Tiempo extra" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "A delay of 2 hours has to be respected before to cancel" +msgstr "Se debe respetar un retraso de 2 horas antes de cancelar." + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Allowances and Deductions" +msgstr "Bonificaciones y Deducciones" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Analyse Payslip" +msgstr "Analizar Recibo de Nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__attachment_id +msgid "Attachment" +msgstr "Adjunto" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_start +msgid "Available date" +msgstr "Fecha disponible" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_origin +msgid "CFDI Origin" +msgstr "Origen CFDI" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "CFDI information" +msgstr "Información CFDI" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid "CFDI stamped before - payslip number: %(msg)s" +msgstr "CFDI sellado anteriormente: número de recibo de nómina %(msg)s" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__cancelled +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__cancelled +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__cancelled +msgid "Cancelled" +msgstr "Cancelado" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Cancelling got an error" +msgstr "Hubo un error en la cancelación" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__content +msgid "Certificate" +msgstr "Certificado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__key +msgid "Certificate Key" +msgstr "Clave del Certificado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__key +msgid "Certificate Key in der format" +msgstr "Clave de certificado en formato der" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__password +msgid "Certificate Password" +msgstr "Contraseña del certificado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__content +msgid "Certificate in der format" +msgstr "Certificado en formato der" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Certificates" +msgstr "Certificados" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_certificate_ids +msgid "Certificates (MX) for payroll" +msgstr "Certificados (MX) para nómina" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Code : %s" +msgstr "Código : %s" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_res_company +msgid "Companies" +msgstr "Empresas" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de configuración" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Configure Payroll Rules" +msgstr "Configurar Reglas de Nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Configure the Authorized Certification Provider (PAC)." +msgstr "Configure el Proveedor Autorizado de Certificación (PAC)" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Configure your SAT certificates." +msgstr "Configurar tus certificados SAT" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__create_uid +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__create_date +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__create_date +msgid "Created on" +msgstr "Creado en" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_customer_rfc +msgid "Customer RFC" +msgstr "RFC del cliente" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Discounts" +msgstr "Descuentos" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__display_name +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__display_name +msgid "Display Name" +msgstr "Mostrar nombre" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Download" +msgstr "Descargar" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_stamp_status__draft +msgid "Draft" +msgstr "Borrador" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_account_edi_format +msgid "EDI format" +msgstr "Formato EDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__edi_content +msgid "Edi Content" +msgstr "Contenido EDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_document_ids +msgid "Edi Document" +msgstr "Documento EDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__edi_format_id +msgid "Edi Format" +msgstr "Formato EDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_blocking_level +msgid "Edi Payroll Blocking Level" +msgstr "Nivel de bloqueo de nómina EDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_error_count +msgid "Edi Payroll Error Count" +msgstr "Recuento de errores de nómina de EDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_error_message +msgid "Edi Payroll Error Message" +msgstr "Mensaje de error de nómina EDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_show_abandon_cancel_button +msgid "Edi Payroll Show Abandon Cancel Button" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_show_cancel_button +msgid "Edi Payroll Show Cancel Button" +msgstr "Botón Mostrar Cancelar Nómina Edi" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_web_services_to_process +msgid "Edi Payroll Web Services To Process" +msgstr "Servicios web de nómina EDI para procesar" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_payslip_edi_document +msgid "Electronic Document for an hr.payslip" +msgstr "Documento Electrónico de nómina para un hr.payslip" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_state +msgid "Electronic Payroll" +msgstr "Nómina Electrónica" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_test_env +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_test_env +msgid "Enable the usage of test credentials" +msgstr "Habilitar el uso de credenciales de prueba" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__error +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_blocking_level__error +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_stamp_status__error +msgid "Error" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_end +msgid "Expiration date" +msgstr "Fecha de expiración" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Extratimes" +msgstr "Tiempos extras" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Failure during the generation of the CFDI:" +msgstr "Fallo durante la generación del CFDI:" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid "Failure during update of the SAT status: %(msg)s" +msgstr "Fallo durante la actualización del estado del SAT: %(msg)s" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__solfact +msgid "Solución Factible" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__finkok +msgid "Finkok" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_uuid +msgid "Fiscal Folio" +msgstr "Folio Fiscal" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_request +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_state_pac +msgid "Flag indicating a CFDI should be generated for this journal entry." +msgstr "" +"Indicador que señala que se debe generar un CFDI para esta entrada de " +"registro." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_uuid +msgid "Folio in electronic invoice, is returned by SAT when send to stamp." +msgstr "" +"El folio en la factura electrónica es devuelto por el SAT al momento de " +"enviarla para timbrado." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__edi_format_name +msgid "Format Name" +msgstr "Nombre del formato" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_error_count +msgid "How many EDIs are in error for this payslip ?" +msgstr "¿Cuántos EDIs están en error para este recibo de nómina?" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__id +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__id +msgid "ID" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_origin +msgid "" +"In some cases like payments, credit notes, debit notes, invoices re-signed or invoices that are redone due to payment in advance will need this field filled, the format is:\n" +"Origin Type|UUID1, UUID2, ...., UUIDn.\n" +"Where the origin type could be:\n" +"- 01: Credit Note\n" +"- 02: Debit note for related documents\n" +"- 03: Return of merchandise on previous invoices or shipments\n" +"- 04: Replacement of previous CFDIs\n" +"- 05: Previously invoiced merchandise shipments\n" +"- 06: Invoice generated for previous shipments\n" +"- 07: CFDI for advance application" +msgstr "" +"- 01: Nota de crédito\n" +"- 02: Nota de débito de los documentos relacionados\n" +"- 03: Devolución de mercancía sobre facturas o traslados previos\n" +"- 04: Sustitución de los CFDI previos\n" +"- 05: Traslados de mercancias facturados previamente\n" +"- 06: Factura generada por los traslados previos\n" +"- 07: CFDI por aplicación de anticipo" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_payment_method_id +msgid "" +"Indicates the way the invoice was/will be paid, where the options could be: " +"Cash, Nominal Check, Credit Card, etc. Leave empty if unkown and the XML " +"will show 'Unidentified'." +msgstr "" +"Indica la forma en que se pagó o se pagará la factura, donde las opciones " +"pueden ser: Efectivo, Cheque de nómina, Tarjeta de crédito, etc. Dejar vacío" +" si es desconocido y el XML mostrará 'No identificado'." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_blocking_level__info +msgid "Info" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Invalid configuration:" +msgstr "Configuración inválida" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid "" +"Invalid payslip configuration:\n" +"\n" +"%s" +msgstr "" +"Configuración de recibo de nómina inválida:\n" +"\n" +"%s" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__facturalo +msgid "Factúralo" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_post_time +msgid "Keep empty to use the current México central time" +msgstr "Mantener vacío para usar la hora central de México actual" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate____last_update +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__write_uid +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__write_date +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__write_date +msgid "Last Updated on" +msgstr "Ultima actualización en" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_certificate_ids +msgid "MX Certificates* for payroll" +msgstr "Certificados* MX para nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_password +msgid "MX PAC password* for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_test_env +msgid "MX PAC test environment* for Payroll" +msgstr "Entorno* de prueba MX PAC para Nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_username +msgid "MX PAC username* for Patroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac +msgid "MX PAC* for Payroll" +msgstr "MX PAC* para Nómina" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Message : %s" +msgstr "Mensaje : %s" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Mexican EDI Payroll" +msgstr "Nómina EDI mexicana" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Mexican payslip CFDI generated for the %s document." +msgstr "CFDI de nómina mexicano generado para el documento %s." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__name +msgid "Name" +msgstr "Nombre" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "No PAC credentials specified." +msgstr "No se han especificado credenciales de PAC." + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "No PAC specified." +msgstr "No se ha especificado ningún PAC." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_state_pac__no_signed +msgid "No Signed" +msgstr "Sin firmar" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "No valid certificate found" +msgstr "No se encontró ningún certificado válido" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__not_found +msgid "Not Found" +msgstr "No encontrado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__undefined +msgid "Not Synced Yet" +msgstr "No sincronizado todavía" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_cfdi_request__on_refund +msgid "On Credit Note" +msgstr "En Nota de Crédito" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_cfdi_request__on_payment +msgid "On Payment" +msgstr "En el pago" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_cfdi_request__on_payslip +msgid "On payslip" +msgstr "En el recibo" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.constraint,message:l10n_mx_hr_payroll_edi.constraint_payslip_edi_document_unique_edi_document_by_payslip_by_format +msgid "Only one edi document by payslip by format" +msgstr "Solo un documento EDI por nómina por formato" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "PAC" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_state_pac +msgid "PAC Status" +msgstr "Estado PAC" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "PAC authentification error:" +msgstr "Error de autenticación de PAC:" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "PAC failed to sign the CFDI:" +msgstr "El PAC falló en firmar el CFDI" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac +msgid "PAC for payroll" +msgstr "PAC para nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "PAC password" +msgstr "Contraseña PAC" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_password +msgid "PAC password for payroll" +msgstr "Contraseña PAC para nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_test_env +msgid "PAC test environment for payroll" +msgstr "Entorno de prueba PAC para nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "PAC username" +msgstr "Nombre de usuario PAC" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_username +msgid "PAC username for payroll" +msgstr "Nombre de usuario de PAC para nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_payment_policy__ppd +msgid "PPD" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_payment_policy__pue +msgid "PUE" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__password +msgid "Password for the Certificate Key" +msgstr "Contraseña para la clave del certificado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_hr_payslip +msgid "Pay Slip" +msgstr "Recibo de nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_payment_policy +msgid "Payment Policy" +msgstr "Política de pago" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_payment_method_id +msgid "Payment Way" +msgstr "Forma de pago" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_mode2work +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_mode2work +msgid "Payroll Work Method" +msgstr "Método de trabajo de nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__payslip_id +msgid "Payslip" +msgstr "Recibo de nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Payslip Informations" +msgstr "Información del recibo de nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx +msgid "Payslip Line" +msgstr "Línea de Recibo de Nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__uso_cfdi__p01 +msgid "Por definir" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_post_time +msgid "Posted Time" +msgstr "Tiempo publicado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_sat_status +msgid "Refers to the status of the journal entry inside the SAT system." +msgstr "" +"Se refiere al estado de la entrada del registro dentro del sistema SAT." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_request +msgid "Request a CFDI" +msgstr "Solicitar un CFDI" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_2 +msgid "Retry" +msgstr "Intentar de nuevo" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_l10n_mx_payroll_edi_certificate +msgid "SAT Digital Sail" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_sat_status +msgid "SAT status" +msgstr "Estado del SAT" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__sw +msgid "SW sapien-SmarterWEB" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_add_buttons_inherit_payroll_mx +msgid "Send by email" +msgstr "Enviar por email" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__sent +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__sent +msgid "Sent" +msgstr "Enviado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__serial_number +msgid "Serial number" +msgstr "Número de serie" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_state_pac__signed +msgid "Signed" +msgstr "Firmado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_mode2work__stamp +msgid "Stamp First and then create entries account" +msgstr "Timbrar primero y luego crear las entradas contables." + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_2 +msgid "Stamp Payslip" +msgstr "Timbrar Recibo de Nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_mode2work__same_time +msgid "Stamp and create entries account at the same time" +msgstr "Timbrar y crear las entradas contables al mismo tiempo" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_stamp_status +msgid "Stamp status" +msgstr "Estado de timbrado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_stamp_status__stamped +msgid "Stamped" +msgstr "Timbrado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__state +msgid "State" +msgstr "Estado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__none +msgid "State not defined" +msgstr "Estado no definido" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cancel_payslip_id +msgid "Substituted By" +msgstr "Sustituido por" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_supplier_rfc +msgid "Supplier RFC" +msgstr "RFC de proveedor" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "The Finkok service failed to cancel with the following error: %s" +msgstr "El servicio Finkok no se pudo cancelar con el siguiente error: %s" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "The Finkok service failed to sign with the following error: %s" +msgstr "El servicio Finkok no se pudo firmar con el siguiente error: %s" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac +msgid "The PAC that will sign/cancel the invoices" +msgstr "El PAC que firmará/cancelará las facturas" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The SAT does not provide information for the currency %s.\n" +"You must get manually a key from the PAC to confirm the currency rate is accurate enough." +msgstr "" +"El SAT no proporciona información para la moneda %s.\n" +"Debes obtener manualmente una clave del PAC para confirmar que la tasa de cambio de la moneda sea suficientemente precisa." + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The Solucion Factible service failed to cancel with the following error: %s" +msgstr "" +"El servicio Solución Factible no se pudo cancelar con el siguiente error: %s" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The Solucion Factible service failed to sign with the following error: %s" +msgstr "" +"El servicio Solución Factible no se pudo firmar con el siguiente error: %s" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_state +msgid "The aggregated state of all the EDIs with web-service of this payslip" +msgstr "" +"El estado agregado de todos los EDI con servicio web de este recibo de " +"nómina" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "The certificate content is invalid %s." +msgstr "El contenido del certificado no es válido %s." + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "The certificate is expired since %s" +msgstr "El certificado está expirado desde %s" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "The certificate key and/or password is/are invalid." +msgstr "La clave del certificado y/o la contraseña no son válidas." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_customer_rfc +msgid "The customer tax identification number." +msgstr "El número de identificación fiscal del cliente." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_end +msgid "The date on which the certificate expires" +msgstr "La fecha en la que expira el certificado." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_start +msgid "The date on which the certificate starts to be valid" +msgstr "La fecha en la que el certificado comienza a ser válido." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_payslip_edi_document__attachment_id +msgid "" +"The file generated by edi_format_id when the invoice is posted (and this " +"document is processed)." +msgstr "" +"El archivo generado por edi_format_id cuando la factura se contabiliza (y este " +"documento se procesa)." + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "The parameters to configure the MX PAC. for Payroll" +msgstr "" +"El archivo generado por edi_format_id cuando la factura se contabiliza (y " +"este documento se procesa)." + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "The parameters to configure the MX certificate for Payroll" +msgstr "Los parámetros para configurar el certificado MX para Nómina" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_password +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_password +msgid "The password used to request the seal from the PAC" +msgstr "La contraseña utilizada para solicitar el sello al PAC" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__serial_number +msgid "The serial number to add to electronic documents" +msgstr "El número de serie para agregar a los documentos electrónicos." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_supplier_rfc +msgid "The supplier tax identification number." +msgstr "El número de identificación fiscal del proveedor." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_payslip_edi_document__error +msgid "" +"The text of the last error that happened during Electronic Invoice " +"operation." +msgstr "" +"El texto del último error que ocurrió durante la operación de Factura " +"Electrónica." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_amount +msgid "The total amount reported on the cfdi." +msgstr "El monto total reportado en el cfdi." + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "The username and/or password are missing." +msgstr "Falta el nombre de usuario y/o contraseña." + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_username +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_username +msgid "The username used to request the seal from the PAC" +msgstr "El nombre de usuario utilizado para solicitar el sello al PAC" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__to_cancel +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__to_cancel +msgid "To Cancel" +msgstr "Cancelar" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__to_send +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__to_send +msgid "To Send" +msgstr "Mandar" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_amount +msgid "Total Amount" +msgstr "Cantidad total" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__uso_cfdi +msgid "Uso CFDI (Employee)" +msgstr "Uso CFDI (Empleado)" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__valid +msgid "Valid" +msgstr "Válido" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Validity" +msgstr "Validez" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_blocking_level__warning +msgid "Warning" +msgstr "Advertencia" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "" +"You cannot remove a certificate if at least one invoice has been signed. " +"Expired certificates will not be used as Odoo uses the latest valid " +"certificate. To not use it, you can unlink it from the current company " +"certificates." +msgstr "" +"No se puede eliminar un certificado si al menos una factura ha sido firmada." +" Los certificados vencidos no serán utilizados ya que Odoo utiliza el " +"certificado válido más reciente. Para no usarlo, puedes desvincularlo de los" +" certificados actuales de la empresa." + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "certificate Form" +msgstr "Formulario de certificado" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__emaanu +msgid "e-Maanu (Multipac)" +msgstr "" diff --git a/l10n_mx_hr_payroll_edi/i18n/l10n_mx_hr_payroll_edi.pot b/l10n_mx_hr_payroll_edi/i18n/l10n_mx_hr_payroll_edi.pot new file mode 100644 index 0000000..b81a672 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/i18n/l10n_mx_hr_payroll_edi.pot @@ -0,0 +1,971 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_mx_hr_payroll_edi +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0+e-20230816\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-07-04 15:43+0000\n" +"PO-Revision-Date: 2024-07-04 15:43+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_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid " Electronic Payslip error(s)" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid " Electronic Payslip info(s)" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid " Electronic Payslip warning(s)" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "MX Certificates for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "MX PAC for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Payroll Rules" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Analyse Payslip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Extra Time" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "A delay of 2 hours has to be respected before to cancel" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Allowances and Deductions" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Analyse Payslip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__attachment_id +msgid "Attachment" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_start +msgid "Available date" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_origin +msgid "CFDI Origin" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "CFDI information" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid "CFDI stamped before - payslip number: %(msg)s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__cancelled +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__cancelled +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__cancelled +msgid "Cancelled" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Cancelling got an error" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__content +msgid "Certificate" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__key +msgid "Certificate Key" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__key +msgid "Certificate Key in der format" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__password +msgid "Certificate Password" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__content +msgid "Certificate in der format" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Certificates" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_certificate_ids +msgid "Certificates (MX) for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Code : %s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Configure Payroll Rules" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Configure the Authorized Certification Provider (PAC)." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Configure your SAT certificates." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__create_uid +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__create_date +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_customer_rfc +msgid "Customer RFC" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Discounts" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__display_name +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Download" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_stamp_status__draft +msgid "Draft" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_account_edi_format +msgid "EDI format" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__edi_content +msgid "Edi Content" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_document_ids +msgid "Edi Document" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__edi_format_id +msgid "Edi Format" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_blocking_level +msgid "Edi Payroll Blocking Level" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_error_count +msgid "Edi Payroll Error Count" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_error_message +msgid "Edi Payroll Error Message" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_show_abandon_cancel_button +msgid "Edi Payroll Show Abandon Cancel Button" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_show_cancel_button +msgid "Edi Payroll Show Cancel Button" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_web_services_to_process +msgid "Edi Payroll Web Services To Process" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_payslip_edi_document +msgid "Electronic Document for an hr.payslip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_state +msgid "Electronic Payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_test_env +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_test_env +msgid "Enable the usage of test credentials" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__error +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_blocking_level__error +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_stamp_status__error +msgid "Error" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_end +msgid "Expiration date" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Extratimes" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__facturalo +msgid "Factúralo" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Failure during the generation of the CFDI:" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid "Failure during update of the SAT status: %(msg)s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__finkok +msgid "Finkok" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_uuid +msgid "Fiscal Folio" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_request +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_state_pac +msgid "Flag indicating a CFDI should be generated for this journal entry." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_uuid +msgid "Folio in electronic invoice, is returned by SAT when send to stamp." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__edi_format_name +msgid "Format Name" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_error_count +msgid "How many EDIs are in error for this payslip ?" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__id +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__id +msgid "ID" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_origin +msgid "" +"In some cases like payments, credit notes, debit notes, invoices re-signed or invoices that are redone due to payment in advance will need this field filled, the format is:\n" +"Origin Type|UUID1, UUID2, ...., UUIDn.\n" +"Where the origin type could be:\n" +"- 01: Credit Note\n" +"- 02: Debit note for related documents\n" +"- 03: Return of merchandise on previous invoices or shipments\n" +"- 04: Replacement of previous CFDIs\n" +"- 05: Previously invoiced merchandise shipments\n" +"- 06: Invoice generated for previous shipments\n" +"- 07: CFDI for advance application" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_payment_method_id +msgid "" +"Indicates the way the invoice was/will be paid, where the options could be: " +"Cash, Nominal Check, Credit Card, etc. Leave empty if unkown and the XML " +"will show 'Unidentified'." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_blocking_level__info +msgid "Info" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Invalid configuration:" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/hr_payslip.py:0 +#, python-format +msgid "" +"Invalid payslip configuration:\n" +"\n" +"%s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_post_time +msgid "Keep empty to use the current México central time" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate____last_update +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document____last_update +msgid "Last Modified on" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__write_uid +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__write_date +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_certificate_ids +msgid "MX Certificates* for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_password +msgid "MX PAC password* for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_test_env +msgid "MX PAC test environment* for Payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_username +msgid "MX PAC username* for Patroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac +msgid "MX PAC* for Payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Message : %s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Mexican EDI Payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "Mexican payslip CFDI generated for the %s document." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__name +msgid "Name" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "No PAC credentials specified." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "No PAC specified." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_state_pac__no_signed +msgid "No Signed" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "No valid certificate found" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__not_found +msgid "Not Found" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__undefined +msgid "Not Synced Yet" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_cfdi_request__on_refund +msgid "On Credit Note" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_cfdi_request__on_payment +msgid "On Payment" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_cfdi_request__on_payslip +msgid "On payslip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.constraint,message:l10n_mx_hr_payroll_edi.constraint_payslip_edi_document_unique_edi_document_by_payslip_by_format +msgid "Only one edi document by payslip by format" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "PAC" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_state_pac +msgid "PAC Status" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "PAC authentification error:" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "PAC failed to sign the CFDI:" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac +msgid "PAC for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "PAC password" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_password +msgid "PAC password for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_test_env +msgid "PAC test environment for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "PAC username" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_username +msgid "PAC username for payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_payment_policy__ppd +msgid "PPD" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_payment_policy__pue +msgid "PUE" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__password +msgid "Password for the Certificate Key" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_hr_payslip +msgid "Pay Slip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_payment_policy +msgid "Payment Policy" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_payment_method_id +msgid "Payment Way" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_mode2work +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_mode2work +msgid "Payroll Work Method" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__payslip_id +msgid "Payslip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_3 +msgid "Payslip Informations" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx +msgid "Payslip Line" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__uso_cfdi__p01 +msgid "Por definir" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_post_time +msgid "Posted Time" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_sat_status +msgid "Refers to the status of the journal entry inside the SAT system." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_request +msgid "Request a CFDI" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_2 +msgid "Retry" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model,name:l10n_mx_hr_payroll_edi.model_l10n_mx_payroll_edi_certificate +msgid "SAT Digital Sail" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_sat_status +msgid "SAT status" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__sw +msgid "SW sapien-SmarterWEB" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_add_buttons_inherit_payroll_mx +msgid "Send by email" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__sent +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__sent +msgid "Sent" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__serial_number +msgid "Serial number" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_state_pac__signed +msgid "Signed" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__solfact +msgid "Solución Factible" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_mode2work__stamp +msgid "Stamp First and then create entries account" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.hr_payslip_form_inherit_payroll_mx_2 +msgid "Stamp Payslip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_mode2work__same_time +msgid "Stamp and create entries account at the same time" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_stamp_status +msgid "Stamp status" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_stamp_status__stamped +msgid "Stamped" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_payslip_edi_document__state +msgid "State" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__none +msgid "State not defined" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cancel_payslip_id +msgid "Substituted By" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_supplier_rfc +msgid "Supplier RFC" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "The Finkok service failed to cancel with the following error: %s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "The Finkok service failed to sign with the following error: %s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac +msgid "The PAC that will sign/cancel the invoices" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The SAT does not provide information for the currency %s.\n" +"You must get manually a key from the PAC to confirm the currency rate is accurate enough." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The Solucion Factible service failed to cancel with the following error: %s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "" +"The Solucion Factible service failed to sign with the following error: %s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__edi_payroll_state +msgid "The aggregated state of all the EDIs with web-service of this payslip" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "The certificate content is invalid %s." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "The certificate is expired since %s" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "The certificate key and/or password is/are invalid." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_customer_rfc +msgid "The customer tax identification number." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_end +msgid "The date on which the certificate expires" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__date_start +msgid "The date on which the certificate starts to be valid" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_payslip_edi_document__attachment_id +msgid "" +"The file generated by edi_format_id when the invoice is posted (and this " +"document is processed)." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "The parameters to configure the MX PAC. for Payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "The parameters to configure the MX certificate for Payroll" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_password +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_password +msgid "The password used to request the seal from the PAC" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_l10n_mx_payroll_edi_certificate__serial_number +msgid "The serial number to add to electronic documents" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_supplier_rfc +msgid "The supplier tax identification number." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_payslip_edi_document__error +msgid "" +"The text of the last error that happened during Electronic Invoice " +"operation." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_amount +msgid "The total amount reported on the cfdi." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#: code:addons/l10n_mx_hr_payroll_edi/models/account_edi_format.py:0 +#, python-format +msgid "The username and/or password are missing." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_company__l10n_mx_hr_payroll_edi_pac_username +#: model:ir.model.fields,help:l10n_mx_hr_payroll_edi.field_res_config_settings__l10n_mx_hr_payroll_edi_pac_username +msgid "The username used to request the seal from the PAC" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__to_cancel +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__to_cancel +msgid "To Cancel" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_state__to_send +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__payslip_edi_document__state__to_send +msgid "To Send" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__l10n_mx_edi_cfdi_amount +msgid "Total Amount" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields,field_description:l10n_mx_hr_payroll_edi.field_hr_payslip__uso_cfdi +msgid "Uso CFDI (Employee)" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__l10n_mx_edi_sat_status__valid +msgid "Valid" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "Validity" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__hr_payslip__edi_payroll_blocking_level__warning +msgid "Warning" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#. odoo-python +#: code:addons/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py:0 +#, python-format +msgid "" +"You cannot remove a certificate if at least one invoice has been signed. " +"Expired certificates will not be used as Odoo uses the latest valid " +"certificate. To not use it, you can unlink it from the current company " +"certificates." +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model_terms:ir.ui.view,arch_db:l10n_mx_hr_payroll_edi.res_config_settings_view_form +msgid "certificate Form" +msgstr "" + +#. module: l10n_mx_hr_payroll_edi +#: model:ir.model.fields.selection,name:l10n_mx_hr_payroll_edi.selection__res_company__l10n_mx_hr_payroll_edi_pac__emaanu +msgid "e-Maanu (Multipac)" +msgstr "" diff --git a/l10n_mx_hr_payroll_edi/models/__init__.py b/l10n_mx_hr_payroll_edi/models/__init__.py new file mode 100644 index 0000000..7b1124b --- /dev/null +++ b/l10n_mx_hr_payroll_edi/models/__init__.py @@ -0,0 +1,6 @@ +from . import res_company +from . import res_config_setting +from . import l10n_mx_hr_payroll_certificate +from . import hr_payslip +from . import account_edi_format +from . import payslip_edi_document diff --git a/l10n_mx_hr_payroll_edi/models/account_edi_format.py b/l10n_mx_hr_payroll_edi/models/account_edi_format.py new file mode 100644 index 0000000..ccda8d9 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/models/account_edi_format.py @@ -0,0 +1,971 @@ +import base64 +import json +import logging +import random +import string +from datetime import datetime +from json.decoder import JSONDecodeError + +import requests +from lxml import etree +from zeep import Client +from zeep.transports import Transport + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_is_zero + +_logger = logging.getLogger(__name__) + + +class AccountEdiFormat(models.Model): + _inherit = "account.edi.format" + + # ------------------------------------------------------------------------- + # CFDI: Helpers + # ------------------------------------------------------------------------- + + @api.model + def _l10n_mx_hr_payroll_edi_get_serie_and_folio(self, payslip): + serie_number = "SLIP" + folio_number = payslip.number + return { + "serie_number": serie_number, + "folio_number": folio_number.replace("/", ""), + } + + @api.model + def _l10n_mx_hr_payroll_edi_check_configuration(self, payslip): + company = payslip.company_id + pac_name = company.l10n_mx_hr_payroll_edi_pac + + errors = [] + + # == Check the certificate == + certificate = company.l10n_mx_hr_payroll_edi_certificate_ids.sudo().get_valid_certificate() + if not certificate: + errors.append(_("No valid certificate found")) + + # == Check the credentials to call the PAC web-service == + if pac_name: + pac_test_env = company.l10n_mx_hr_payroll_edi_pac_test_env + pac_password = company.l10n_mx_hr_payroll_edi_pac_password + if not pac_test_env and not pac_password: + errors.append(_("No PAC credentials specified.")) + else: + errors.append(_("No PAC specified.")) + + # == Check the 'l10n_mx_edi_decimal_places' field set on the currency == + currency_precision = payslip.currency_id.l10n_mx_edi_decimal_places + if currency_precision is False: + errors.append( + _( + "The SAT does not provide information for the currency %s.\n" + "You must get manually a key from the PAC to confirm the " + "currency rate is accurate enough." + ) + % payslip.currency_id + ) + + return errors + + # ------------------------------------------------------------------------- + # BUSINESS FLOW: EDI + # ------------------------------------------------------------------------- + + def _check_move_configuration(self, payslip): + if self.code != "cfdi_1_2": + return super()._check_move_configuration(payslip) + return self._l10n_mx_hr_payroll_edi_check_configuration(payslip) + + def _get_payslip_edi_content(self, payslip): + if self.code != "cfdi_1_2": + return super()._get_payslip_edi_content(payslip) + return self._l10n_mx_hr_payroll_edi_export_payslip_cfdi(payslip).get("cfdi_str") + + def _needs_web_services(self): + # OVERRIDE + return self.code == "cfdi_1_2" or super()._needs_web_services() + + def _is_compatible_with_journal(self, journal): + # OVERRIDE + self.ensure_one() + if self.code != "cfdi_1_2": + return super()._is_compatible_with_journal(journal) + return ( + journal.type == "general" + and journal.country_code == "MX" + and journal.company_id.currency_id.name == "MXN" + ) + + @api.model + def _l10n_mx_hr_payroll_edi_format_error_message(self, error_title, errors): + bullet_list_msg = "".join("
  • %s
  • " % msg for msg in errors) + return "%s" % (error_title, bullet_list_msg) + + # ------------------------------------------------------------------------- + # CFDI Generation: Generic + # ---------------------------------------- + + def _l10n_mx_hr_payroll_edi_get_common_cfdi_values(self, payslip): + """Generic values to generate a cfdi for a journal entry. + :param move: The account.move record to which generate the CFDI. + :return: A python dictionary. + """ + + def _format_string_cfdi(text, size=100): + """Replace from text received the characters that are not found in the + regex. This regex is taken from SAT documentation + https://goo.gl/C9sKH6 + text: Text to remove extra characters + size: Cut the string in size len + Ex. 'Product ABC (small size)' - 'Product ABC small size'""" + if not text: + return None + text = text.replace("|", " ") + return text.strip()[:size] + + def _format_float_cfdi(amount, precision): + if amount is None or amount is False: + return None + return "%.*f" % ( + precision, + amount + if not float_is_zero(amount, precision_digits=precision) + else 0.0, + ) + + company = payslip.company_id + certificate = company.l10n_mx_hr_payroll_edi_certificate_ids.sudo().get_valid_certificate() + currency_precision = payslip.currency_id.l10n_mx_edi_decimal_places + + customer = payslip.contract_id.employee_id.address_home_id + employee = payslip.employee_id + contract = payslip.contract_id + supplier = payslip.company_id.partner_id.commercial_partner_id + + if not customer: + customer_rfc = False + elif customer.country_id and customer.country_id.code != "MX": + customer_rfc = "XEXX010101000" + elif customer.vat: + customer_rfc = customer.vat.strip() + elif customer.country_id.code in (False, "MX"): + customer_rfc = "XAXX010101000" + else: + customer_rfc = "XEXX010101000" + if payslip.l10n_mx_edi_origin: + origin_type, origin_uuids = payslip._l10n_mx_edi_read_cfdi_origin( + payslip.l10n_mx_edi_origin + ) + else: + origin_type = None + origin_uuids = [] + + return { + **self._l10n_mx_hr_payroll_edi_get_serie_and_folio(payslip), + "certificate": certificate, + "certificate_number": certificate.serial_number, + "certificate_key": certificate.sudo()._get_data()[0].decode("utf-8"), + "record": payslip, + "supplier": supplier, + "customer": customer, + "employee": employee, + "contract": contract, + "customer_rfc": customer_rfc, + "issued_address": payslip._get_l10n_mx_hr_payroll_edi_issued_address(), + "currency_precision": currency_precision, + "origin_type": origin_type, + "origin_uuids": origin_uuids, + "format_string": _format_string_cfdi, + "format_float": _format_float_cfdi, + } + + # ------------------------------------------------------------------------- + # CFDI Generation: Payslip + # ------------------------------------------------------------------------- + + def _l10n_mx_hr_payroll_edi_get_payslip_line_cfdi_values(self, payslip, line): + cfdi_values = {"line": line} + + return cfdi_values + + def _l10n_mx_hr_payroll_edi_get_payslip_cfdi_values(self, payslip): + cfdi_date = datetime.combine( + fields.Datetime.from_string(payslip.payslip_date), + payslip.l10n_mx_edi_post_time.time(), + ).strftime("%Y-%m-%dT%H:%M:%S") + + cfdi_values = { + **self._l10n_mx_hr_payroll_edi_get_common_cfdi_values(payslip), + "currency_name": payslip.currency_id.name, + "employee": payslip.employee_id, + "numdiaspagados": (payslip.date_to - payslip.date_from).days + 1, + "antiguedad": payslip._l10n_mx_payroll_antiguedad(), + "tiponomina": "O" + if payslip.contract_id.period_cfdi.type_id.payroll_type == "O" + else "E", + "banco": payslip.employee_id.bank_account_id.bank_id.l10n_mx_edi_code + or False, + "payment_method_code": ( + payslip.l10n_mx_edi_payment_method_id.code or "" + ).replace("NA", "99"), + "cfdi_date": cfdi_date, + } + + # ==== Payslip Values ==== + + payslip_lines = payslip.line_ids.filtered( + lambda payslip: payslip.appears_on_payslip + ) + + if payslip.currency_id.name == "MXN": + cfdi_values["currency_conversion_rate"] = None + else: + # assumes that invoice.company_id.country_id.code == 'MX' + # as checked in '_is_required_for_invoice' + cfdi_values["currency_conversion_rate"] = ( + abs(payslip.amount_total_signed) / abs(payslip.amount_total) + if payslip.amount_total + else 1 + ) + + if cfdi_values["customer"].country_id.l10n_mx_edi_code != "MEX" and cfdi_values[ + "customer_rfc" + ] not in ("XEXX010101000", "XAXX010101000"): + cfdi_values["customer_fiscal_residence"] = cfdi_values[ + "customer" + ].country_id.l10n_mx_edi_code + else: + cfdi_values["customer_fiscal_residence"] = None + + # ==== payslip lines ==== + + cfdi_values["payslip_line_allowance_values"] = [] + cfdi_values["payslip_line_deduction_values"] = [] + cfdi_values["payslip_line_ImpuestosRetenidos_values"] = [] + cfdi_values["payslip_line_others_values"] = [] + + for line in payslip_lines: + if line.category_id.code == "ALW": + cfdi_values["payslip_line_allowance_values"].append( + self._l10n_mx_hr_payroll_edi_get_payslip_line_cfdi_values( + payslip, line + ) + ) + if line.category_id.code == "DED" and line.total != 0.00: + cfdi_values["payslip_line_deduction_values"].append( + self._l10n_mx_hr_payroll_edi_get_payslip_line_cfdi_values( + payslip, line + ) + ) + if ( + line.category_id.code == "DED" + and line.total != 0.00 + and line.code in ("D045") + ): + cfdi_values["payslip_line_ImpuestosRetenidos_values"].append( + self._l10n_mx_hr_payroll_edi_get_payslip_line_cfdi_values( + payslip, line + ) + ) + if line.category_id.code == "OTPAY": + cfdi_values["payslip_line_others_values"].append( + self._l10n_mx_hr_payroll_edi_get_payslip_line_cfdi_values( + payslip, line + ) + ) + + # ==== Totals ==== + + cfdi_values["total_allowance_amount"] = sum( + vals["line"].total for vals in cfdi_values["payslip_line_allowance_values"] + ) + cfdi_values["total_deduction_amount"] = sum( + vals["line"].total for vals in cfdi_values["payslip_line_deduction_values"] + ) + cfdi_values["TotalImpuestosRetenidos"] = sum( + vals["line"].total + for vals in cfdi_values["payslip_line_ImpuestosRetenidos_values"] + ) + cfdi_values["TotalOtrasDeducciones"] = ( + cfdi_values["total_deduction_amount"] + - cfdi_values["TotalImpuestosRetenidos"] + ) + cfdi_values["total_others_amount"] = sum( + vals["line"].total for vals in cfdi_values["payslip_line_others_values"] + ) + cfdi_values["ValorUnitario_total"] = cfdi_values["total_allowance_amount"] + cfdi_values["total_cfdi"] = ( + cfdi_values["total_allowance_amount"] + - (cfdi_values["total_deduction_amount"]) + ) + + return cfdi_values + + def _l10n_mx_hr_payroll_edi_get_templates(self): + return "l10n_mx_hr_payroll_edi.cfdiv12", "nomina12.xsd" + + def _l10n_mx_hr_payroll_edi_export_payslip_cfdi(self, payslip): + # == CFDI values == + cfdi_values = self._l10n_mx_hr_payroll_edi_get_payslip_cfdi_values(payslip) + ( + qweb_template, + xsd_attachment_name, + ) = self._l10n_mx_hr_payroll_edi_get_templates() + + # == Generate the CFDI == + cfdi = self.env["ir.qweb"]._render(qweb_template, cfdi_values) + decoded_cfdi_values = payslip._l10n_mx_hr_payroll_edi_decode_cfdi( + cfdi_data=cfdi + ) + cfdi_cadena_crypted = ( + cfdi_values["certificate"] + .sudo() + ._get_encrypted_cadena(decoded_cfdi_values["cadena"]) + ) + decoded_cfdi_values["cfdi_node"].attrib["Sello"] = cfdi_cadena_crypted + + res = { + "cfdi_str": etree.tostring( + decoded_cfdi_values["cfdi_node"], + pretty_print=True, + xml_declaration=True, + encoding="UTF-8", + ), + } + + try: + ( + self.env["ir.attachment"].l10n_mx_edi_validate_xml_from_attachment( + decoded_cfdi_values["cfdi_node"], xsd_attachment_name + ) + ) + except UserError as error: + res["errors"] = str(error).split("\\n") + + return res + + # -------------------------------------------------------------------------- + # CFDI: PACs + # -------------------------------------------------------------------------- + + def _l10n_mx_hr_payroll_edi_get_finkok_credentials(self, payslip): + return self._l10n_mx_edi_get_finkok_credentials_company(payslip.company_id) + + def _l10n_mx_hr_payroll_edi_get_finkok_credentials_company(self, company): + """Return the company credentials for PAC: finkok. Does not depend on a recordset""" + if company.l10n_mx_hr_payroll_edi_pac_test_env: + return { + "username": "cfdi@vauxoo.com", + "password": "vAux00__", + "sign_url": "http://demo-facturacion.finkok.com/servicios/soap/stamp.wsdl", + "cancel_url": "http://demo-facturacion.finkok.com/servicios/soap/cancel.wsdl", + } + else: + if ( + not company.l10n_mx_hr_payroll_edi_pac_username + or not company.l10n_mx_hr_payroll_edi_pac_password + ): + return {"errors": [_("The username and/or password are missing.")]} + + return { + "username": company.l10n_mx_hr_payroll_edi_pac_username, + "password": company.l10n_mx_hr_payroll_edi_pac_password, + "sign_url": "http://facturacion.finkok.com/servicios/soap/stamp.wsdl", + "cancel_url": "http://facturacion.finkok.com/servicios/soap/cancel.wsdl", + } + + def _l10n_mx_hr_payroll_edi_finkok_sign(self, move, credentials, cfdi): + return self._l10n_mx_hr_payroll_edi_finkok_sign_service(credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_finkok_sign_service(self, credentials, cfdi): + """Send the CFDI XML document to Finkok for signature. Does not depend on a recordset""" + try: + transport = Transport(timeout=20) + client = Client(credentials["sign_url"], transport=transport) + response = client.service.stamp( + cfdi, credentials["username"], credentials["password"] + ) + except Exception as e: + return { + "errors": [ + _( + "The Finkok service failed to sign with the following error: %s", + str(e), + ) + ], + } + + if response.Incidencias and not response.xml: + code = getattr(response.Incidencias.Incidencia[0], "CodigoError", None) + msg = getattr(response.Incidencias.Incidencia[0], "MensajeIncidencia", None) + errors = [] + if code: + errors.append(_("Code : %s") % code) + if msg: + errors.append(_("Message : %s") % msg) + return {"errors": errors} + + cfdi_signed = getattr(response, "xml", None) + if cfdi_signed: + cfdi_signed = cfdi_signed.encode("utf-8") + + return { + "cfdi_signed": cfdi_signed, + "cfdi_encoding": "str", + } + + def _l10n_mx_hr_payroll_edi_finkok_cancel(self, payslip, credentials, cfdi): + uuid_replace = payslip.l10n_mx_edi_cancel_invoice_id.l10n_mx_edi_cfdi_uuid + return self._l10n_mx_hr_payroll_edi_finkok_cancel_service( + payslip.l10n_mx_edi_cfdi_uuid, + payslip.company_id, + credentials, + uuid_replace=uuid_replace, + ) + + def _l10n_mx_hr_payroll_edi_finkok_cancel_service( + self, uuid, company, credentials, uuid_replace=None + ): + """Cancel the CFDI document with PAC: finkok. Does not depend on a recordset""" + certificates = company.l10n_mx_hr_payroll_edi_certificate_ids + certificate = certificates.sudo().get_valid_certificate() + cer_pem = certificate.get_pem_cer(certificate.content) + key_pem = certificate.get_pem_key(certificate.key, certificate.password) + try: + transport = Transport(timeout=20) + client = Client(credentials["cancel_url"], transport=transport) + factory = client.type_factory("apps.services.soap.core.views") + uuid_type = factory.UUID() + uuid_type.UUID = uuid + uuid_type.Motivo = "01" if uuid_replace else "02" + if uuid_replace: + uuid_type.FolioSustitucion = uuid_replace + docs_list = factory.UUIDArray(uuid_type) + response = client.service.cancel( + docs_list, + credentials["username"], + credentials["password"], + company.vat, + cer_pem, + key_pem, + ) + except Exception as e: + return { + "errors": [ + _( + "The Finkok service failed to cancel with the following error: %s", + str(e), + ) + ], + } + + if not getattr(response, "Folios", None): + code = getattr(response, "CodEstatus", None) + msg = ( + _("Cancelling got an error") + if code + else _("A delay of 2 hours has to be respected before to cancel") + ) + else: + code = getattr(response.Folios.Folio[0], "EstatusUUID", None) + cancelled = code in ("201", "202") # cancelled or previously cancelled + # no show code and response message if cancel was success + code = "" if cancelled else code + msg = "" if cancelled else _("Cancelling got an error") + + errors = [] + if code: + errors.append(_("Code : %s") % code) + if msg: + errors.append(_("Message : %s") % msg) + if errors: + return {"errors": errors} + + return {"success": True} + + def _l10n_mx_hr_payroll_edi_finkok_sign_payslip(self, payslip, credentials, cfdi): + return self._l10n_mx_hr_payroll_edi_finkok_sign(payslip, credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_finkok_cancel_payslip(self, payslip, credentials, cfdi): + return self._l10n_mx_hr_payroll_edi_finkok_cancel(payslip, credentials, cfdi) + + # Solucion Factible + def _l10n_mx_hr_payroll_edi_get_solfact_credentials(self, payslip): + return self._l10n_mx_hr_payroll_edi_get_solfact_credentials_company( + payslip.company_id + ) + + def _l10n_mx_hr_payroll_edi_get_solfact_credentials_company(self, company): + """Return the company credentials for PAC: solucion factible. + Does not depend on a recordset""" + if company.l10n_mx_hr_payroll_edi_pac_test_env: + return { + "username": "testing@solucionfactible.com", + "password": "timbrado.SF.16672", + "url": "https://testing.solucionfactible.com/ws/services/Timbrado?wsdl", + } + else: + if ( + not company.l10n_mx_hr_payroll_edi_pac_username + or not company.l10n_mx_hr_payroll_edi_pac_password + ): + return {"errors": [_("The username and/or password are missing.")]} + + return { + "username": company.l10n_mx_hr_payroll_edi_pac_username, + "password": company.l10n_mx_hr_payroll_edi_pac_password, + "url": "https://solucionfactible.com/ws/services/Timbrado?wsdl", + } + + def _l10n_mx_hr_payroll_edi_solfact_sign(self, payslip, credentials, cfdi): + return self._l10n_mx_hr_payroll_edi_solfact_sign_service(credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_solfact_sign_service(self, credentials, cfdi): + """Send the CFDI XML document to Solucion Factible for signature. + Does not depend on a recordset""" + try: + transport = Transport(timeout=20) + client = Client(credentials["url"], transport=transport) + response = client.service.timbrar( + credentials["username"], credentials["password"], cfdi, False + ) + except Exception as e: + return { + "errors": [ + _( + "The Solucion Factible service failed to sign with the " + "following error: %s", + str(e), + ) + ], + } + + if response.status != 200: + # ws-timbrado-timbrar - status 200: + # CFDI correctamente validado y timbrado. + return { + "errors": [ + _( + "The Solucion Factible service failed to sign with " + "the following error: %s", + response.mensaje, + ) + ], + } + + res = response.resultados + cfdi_signed = getattr(res[0] if res else response, "cfdiTimbrado", None) + + if cfdi_signed: + return { + "cfdi_signed": cfdi_signed, + "cfdi_encoding": "str", + } + + msg = getattr(res[0] if res else response, "mensaje", None) + code = getattr(res[0] if res else response, "status", None) + errors = [] + if code: + errors.append(_("Code : %s") % code) + if msg: + errors.append(_("Message : %s") % msg) + return {"errors": errors} + + def _l10n_mx_hr_payroll_edi_solfact_cancel(self, payslip, credentials, cfdi): + uuid_replace = payslip.l10n_mx_edi_cancel_payslip_id.l10n_mx_edi_cfdi_uuid + return self._l10n_mx_edi_solfact_cancel_service( + payslip.l10n_mx_edi_cfdi_uuid, + payslip.company_id, + credentials, + uuid_replace=uuid_replace, + ) + + def _l10n_mx_hr_payroll_edi_solfact_cancel_service( + self, uuid, company, credentials, uuid_replace=None + ): + """calls the Solucion Factible web service to cancel the document based on the UUID. + Method does not depend on a recordset + """ + motivo = "01" if uuid_replace else "02" + uuid = uuid + "|" + motivo + "|" + if uuid_replace: + uuid = uuid + uuid_replace + certificates = company.l10n_mx_hr_payroll_edi_certificate_ids + certificate = certificates.sudo().get_valid_certificate() + cer_pem = certificate.get_pem_cer(certificate.content) + key_pem = certificate.get_pem_key(certificate.key, certificate.password) + key_password = certificate.password + + try: + transport = Transport(timeout=20) + client = Client(credentials["url"], transport=transport) + response = client.service.cancelar( + credentials["username"], + credentials["password"], + uuid, + cer_pem, + key_pem, + key_password, + ) + except Exception as e: + return { + "errors": [ + _( + "The Solucion Factible service failed to cancel with " + "the following error: %s", + str(e), + ) + ], + } + + if response.status not in (200, 201): + # ws-timbrado-cancelar - status 200: + # El proceso de cancelación se ha completado correctamente. + # ws-timbrado-cancelar - status 201: + # El folio se ha cancelado con éxito. + return { + "errors": [ + _( + "The Solucion Factible service failed to cancel with " + "the following error: %s", + response.mensaje, + ) + ], + } + + res = response.resultados + code = ( + getattr(res[0], "statusUUID", None) + if res + else getattr(response, "status", None) + ) + cancelled = code in ("201", "202") # cancelled or previously cancelled + # no show code and response message if cancel was success + msg = "" if cancelled else getattr(res[0] if res else response, "mensaje", None) + code = "" if cancelled else code + + errors = [] + if code: + errors.append(_("Code : %s") % code) + if msg: + errors.append(_("Message : %s") % msg) + if errors: + return {"errors": errors} + + return {"success": True} + + def _l10n_mx_hr_payroll_edi_solfact_sign_payslip(self, payslip, credentials, cfdi): + return self._l10n_mx_hr_payroll_edi_solfact_sign(payslip, credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_solfact_cancel_payslip( + self, payslip, credentials, cfdi + ): + return self._l10n_mx_hr_payroll_edi_solfact_cancel(payslip, credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_get_sw_token(self, credentials): + if credentials["password"] and not credentials["username"]: + # token is configured directly instead of user/password + return { + "token": credentials["password"].strip(), + } + + try: + headers = { + "user": credentials["username"], + "password": credentials["password"], + "Cache-Control": "no-cache", + } + response = requests.post( + credentials["login_url"], headers=headers, timeout=3 + ) + response.raise_for_status() + response_json = response.json() + return { + "token": response_json["data"]["token"], + } + except (requests.exceptions.RequestException, KeyError, TypeError) as req_e: + return { + "errors": [str(req_e)], + } + + def _l10n_mx_hr_payroll_edi_get_sw_credentials(self, payslip): + return self._l10n_mx_hr_payroll_edi_get_sw_credentials_company( + payslip.company_id + ) + + def _l10n_mx_hr_payroll_edi_get_sw_credentials_company(self, company): + """Get the company credentials for PAC: SW. + Does not depend on a recordset""" + if ( + not company.l10n_mx_hr_payroll_edi_pac_username + or not company.l10n_mx_hr_payroll_edi_pac_password + ): + return {"errors": [_("The username and/or password are missing.")]} + + credentials = { + "username": company.l10n_mx_hr_payroll_edi_pac_username, + "password": company.l10n_mx_hr_payroll_edi_pac_password, + } + + if company.l10n_mx_hr_payroll_edi_pac_test_env: + credentials.update( + { + "login_url": "https://services.test.sw.com.mx/security/authenticate", + "sign_url": "https://services.test.sw.com.mx/cfdi33/stamp/v3/b64", + "cancel_url": "https://services.test.sw.com.mx/cfdi33/cancel/csd", + } + ) + else: + credentials.update( + { + "login_url": "https://services.sw.com.mx/security/authenticate", + "sign_url": "https://services.sw.com.mx/cfdi33/stamp/v3/b64", + "cancel_url": "https://services.sw.com.mx/cfdi33/cancel/csd", + } + ) + + # Retrieve a valid token. + credentials.update(self._l10n_mx_hr_payroll_edi_get_sw_token(credentials)) + + return credentials + + def _l10n_mx_hr_payroll_edi_sw_call(self, url, headers, payload=None): + try: + response = requests.post( + url, data=payload, headers=headers, verify=True, timeout=20 + ) + except requests.exceptions.RequestException as req_e: + return {"status": "error", "message": str(req_e)} + msg = "" + try: + response.raise_for_status() + except requests.exceptions.HTTPError as res_e: + msg = str(res_e) + try: + response_json = response.json() + except JSONDecodeError: + # If it is not possible get json then + # use response exception message + return {"status": "error", "message": msg} + if response_json["status"] == "error" and response_json["message"].startswith( + "307" + ): + # XML signed previously + cfdi = base64.encodebytes(response_json["messageDetail"].encode("UTF-8")) + cfdi = cfdi.decode("UTF-8") + response_json["data"] = {"cfdi": cfdi} + # We do not need an error message if XML signed was + # retrieved then cleaning them + response_json.update( + { + "message": None, + "messageDetail": None, + "status": "success", + } + ) + return response_json + + def _l10n_mx_hr_payroll_edi_sw_sign(self, payslip, credentials, cfdi): + return self._l10n_mx_edi_sw_sign_service(credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_sw_sign_service(self, credentials, cfdi): + """calls the SW web service to send and sign the CFDI XML. + Method does not depend on a recordset + """ + cfdi_b64 = base64.encodebytes(cfdi).decode("UTF-8") + random_values = [ + random.choice(string.ascii_letters + string.digits) for n in range(30) + ] + boundary = "".join(random_values) + payload = """--%(boundary)s +Content-Type: text/xml +Content-Transfer-Encoding: binary +Content-Disposition: form-data; name="xml"; filename="xml" +%(cfdi_b64)s +--%(boundary)s-- +""" % { + "boundary": boundary, + "cfdi_b64": cfdi_b64, + } + payload = payload.replace("\n", "\r\n").encode("UTF-8") + + headers = { + "Authorization": "bearer " + credentials["token"], + "Content-Type": ("multipart/form-data; " 'boundary="%s"') % boundary, + } + + response_json = self._l10n_mx_hr_payroll_edi_sw_call( + credentials["sign_url"], headers, payload=payload + ) + + try: + cfdi_signed = response_json["data"]["cfdi"] + except (KeyError, TypeError): + cfdi_signed = None + + if cfdi_signed: + return { + "cfdi_signed": cfdi_signed.encode("UTF-8"), + "cfdi_encoding": "base64", + } + else: + code = response_json.get("message") + msg = response_json.get("messageDetail") + errors = [] + if code: + errors.append(_("Code : %s") % code) + if msg: + errors.append(_("Message : %s") % msg) + return {"errors": errors} + + def _l10n_mx_hr_payroll_edi_sw_cancel(self, payslip, credentials, cfdi): + uuid_replace = payslip.l10n_mx_edi_cancel_invoice_id.l10n_mx_edi_cfdi_uuid + return self._l10n_mx_hr_payroll_edi_sw_cancel_service( + payslip.l10n_mx_edi_cfdi_uuid, + payslip.company_id, + credentials, + uuid_replace=uuid_replace, + ) + + def _l10n_mx_hr_payroll_edi_sw_cancel_service( + self, uuid, company, credentials, uuid_replace=None + ): + """Calls the SW web service to cancel the document based on the UUID. + Method does not depend on a recordset + """ + headers = { + "Authorization": "bearer " + credentials["token"], + "Content-Type": "application/json", + } + certificates = company.l10n_mx_edi_certificate_ids + certificate = certificates.sudo().get_valid_certificate() + payload_dict = { + "rfc": company.vat, + "b64Cer": certificate.content.decode("UTF-8"), + "b64Key": certificate.key.decode("UTF-8"), + "password": certificate.password, + "uuid": uuid, + "motivo": "01" if uuid_replace else "02", + } + if uuid_replace: + payload_dict["folioSustitucion"] = uuid_replace + payload = json.dumps(payload_dict) + + response_json = self._l10n_mx_hr_payroll_edi_sw_call( + credentials["cancel_url"], headers, payload=payload.encode("UTF-8") + ) + + cancelled = response_json["status"] == "success" + if cancelled: + return {"success": cancelled} + + code = response_json.get("message") + msg = response_json.get("messageDetail") + errors = [] + if code: + errors.append(_("Code : %s") % code) + if msg: + errors.append(_("Message : %s") % msg) + return {"errors": errors} + + def _l10n_mx_hr_payroll_edi_sw_sign_payslip(self, payslip, credentials, cfdi): + return self._l10n_mx_edi_sw_sign(payslip, credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_sw_cancel_payslip(self, payslip, credentials, cfdi): + return self._l10n_mx_edi_sw_cancel(payslip, credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_sw_sign_payment(self, move, credentials, cfdi): + return self._l10n_mx_edi_sw_sign(move, credentials, cfdi) + + def _l10n_mx_hr_payroll_edi_sw_cancel_payment(self, move, credentials, cfdi): + return self._l10n_mx_edi_sw_cancel(move, credentials, cfdi) + + # ------------------------------------------------------------------------- + # BUSINESS FLOW: EDI + # ------------------------------------------------------------------------- + + def _post_payslip_edi(self, payslips, test_mode=False): + edi_result = {} + for payslip in payslips: + # == Check the configuration == + errors = self._l10n_mx_hr_payroll_edi_check_configuration(payslip) + if errors: + edi_result[payslip] = { + "error": self._l10n_mx_hr_payroll_edi_format_error_message( + _("Invalid configuration:"), errors + ), + } + continue + + # == Generate the CFDI == + res = self._l10n_mx_hr_payroll_edi_export_payslip_cfdi(payslip) + if res.get("errors"): + edi_result[payslip] = { + "error": self._l10n_mx_hr_payroll_edi_format_error_message( + _("Failure during the generation of the CFDI:"), res["errors"] + ), + } + continue + + # == Call the web-service == + pac_name = payslip.company_id.l10n_mx_hr_payroll_edi_pac + + credentials = getattr( + self, "_l10n_mx_hr_payroll_edi_get_%s_credentials" % pac_name + )(payslip) + if credentials.get("errors"): + edi_result[payslip] = { + "error": self._l10n_mx_hr_payroll_edi_format_error_message( + _("PAC authentification error:"), credentials["errors"] + ), + } + continue + + res = getattr(self, "_l10n_mx_hr_payroll_edi_%s_sign_payslip" % pac_name)( + payslip, credentials, res["cfdi_str"] + ) + if res.get("errors"): + edi_result[payslip] = { + "error": self._l10n_mx_edi_format_error_message( + _("PAC failed to sign the CFDI:"), res["errors"] + ), + } + continue + + if res["cfdi_encoding"] == "str": + res.update( + { + "cfdi_signed": base64.encodebytes(res["cfdi_signed"]), + "cfdi_encoding": "base64", + } + ) + + # == Create the attachment == + cfdi_filename = ("%s-MX-payslip-1.2.xml" % (payslip.number)).replace( + "/", "" + ) + cfdi_attachment = self.env["ir.attachment"].create( + { + "name": cfdi_filename, + "res_id": payslip.id, + "res_model": payslip._name, + "type": "binary", + "datas": res["cfdi_signed"], + "mimetype": "application/xml", + "description": _( + "Mexican payslip CFDI generated for the %s document." + ) + % payslip.name, + } + ) + + edi_result[payslip] = {"attachment": cfdi_attachment} + # # == Chatter == + # payslip.with_context(no_new_payslip=True).message_post( + # body=_("The CFDI document was successfully created and + # signed by the government."), + # attachment_ids=cfdi_attachment.ids, + # ) + return edi_result diff --git a/l10n_mx_hr_payroll_edi/models/hr_payslip.py b/l10n_mx_hr_payroll_edi/models/hr_payslip.py new file mode 100644 index 0000000..5d2ed13 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/models/hr_payslip.py @@ -0,0 +1,816 @@ +import base64 +import logging +from datetime import datetime + +from lxml import etree +from lxml.objectify import fromstring +from pytz import timezone + +from odoo import _, api, fields, models, tools +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_repr + +_logger = logging.getLogger(__name__) +CFDI_XSLT_CADENA = "l10n_mx_edi_40/data/4.0/cadenaoriginal_4_0.xslt" +CFDI_XSLT_CADENA_TFD = "l10n_mx_edi_40/data/4.0/cadenaoriginal_TFD_1_1.xslt" + + +class HrPayslip(models.Model): + _inherit = "hr.payslip" + + edi_document_ids = fields.One2many( + comodel_name="payslip.edi.document", inverse_name="payslip_id" + ) + edi_payroll_state = fields.Selection( + selection=[ + ("to_send", "To Send"), + ("sent", "Sent"), + ("to_cancel", "To Cancel"), + ("cancelled", "Cancelled"), + ], + string="Electronic Payroll", + store=True, + compute="_compute_edi_state", + help="The aggregated state of all the EDIs with web-service of this payslip", + ) + edi_payroll_error_count = fields.Integer( + compute="_compute_edi_payroll_error_count", + help="How many EDIs are in error for this payslip ?", + ) + edi_payroll_blocking_level = fields.Selection( + selection=[("info", "Info"), ("warning", "Warning"), ("error", "Error")], + compute="_compute_edi_payroll_error_message", + ) + edi_payroll_error_message = fields.Html( + compute="_compute_edi_payroll_error_message" + ) + edi_payroll_web_services_to_process = fields.Text( + compute="_compute_edi_payroll_web_services_to_process" + ) + edi_payroll_show_cancel_button = fields.Boolean( + compute="_compute_edi_payroll_show_cancel_button" + ) + edi_payroll_show_abandon_cancel_button = fields.Boolean( + compute="_compute_edi_show_abandon_cancel_button" + ) + + # ==== CFDI flow fields ==== + + l10n_mx_edi_state_pac = fields.Selection( + selection=[ + ("no_signed", "No Signed"), + ("signed", "Signed"), + ], + string="PAC Status", + store=False, + compute="_compute_cfdi_values", + help="Flag indicating a CFDI should be generated for this journal entry.", + ) + + l10n_mx_edi_cfdi_request = fields.Selection( + selection=[ + ("on_payslip", "On payslip"), + ("on_refund", "On Credit Note"), + ("on_payment", "On Payment"), + ], + string="Request a CFDI", + store=True, + compute="_compute_l10n_mx_edi_cfdi_request", + help="Flag indicating a CFDI should be generated for this journal entry.", + ) + + l10n_mx_edi_stamp_status = fields.Selection( + selection=[ + ("draft", "Draft"), + ("error", "Error"), + ("stamped", "Stamped"), + ], + string="Stamp status", + readonly=True, + copy=False, + required=True, + default="draft", + help="", + ) + + l10n_mx_edi_sat_status = fields.Selection( + selection=[ + ("none", "State not defined"), + ("undefined", "Not Synced Yet"), + ("not_found", "Not Found"), + ("cancelled", "Cancelled"), + ("valid", "Valid"), + ], + string="SAT status", + readonly=True, + copy=False, + required=True, + tracking=True, + default="undefined", + help="Refers to the status of the journal entry inside the SAT system.", + ) + + l10n_mx_edi_post_time = fields.Datetime( + string="Posted Time", + readonly=True, + copy=False, + help="Keep empty to use the current México central time", + ) + + l10n_mx_edi_origin = fields.Char( + string="CFDI Origin", + copy=False, + help="In some cases like payments, credit notes, debit notes, invoices " + "re-signed or invoices that are redone due to payment in advance " + "will need this field filled, the format is:\n" + "Origin Type|UUID1, UUID2, ...., UUIDn.\n" + "Where the origin type could be:\n" + "- 01: Credit Note\n" + "- 02: Debit note for related documents\n" + "- 03: Return of merchandise on previous invoices or shipments\n" + "- 04: Replacement of previous CFDIs\n" + "- 05: Previously invoiced merchandise shipments\n" + "- 06: Invoice generated for previous shipments\n" + "- 07: CFDI for advance application", + ) + + l10n_mx_edi_cancel_payslip_id = fields.Many2one( + comodel_name="hr.payslip", + string="Substituted By", + compute="_compute_l10n_mx_edi_cancel", + readonly=True, + ) + l10n_mx_edi_cfdi_uuid = fields.Char( + string="Fiscal Folio", + copy=False, + readonly=True, + help="Folio in electronic invoice, is returned by SAT when send to stamp.", + compute="_compute_cfdi_values", + ) + l10n_mx_edi_cfdi_supplier_rfc = fields.Char( + string="Supplier RFC", + copy=False, + readonly=True, + help="The supplier tax identification number.", + compute="_compute_cfdi_values", + ) + l10n_mx_edi_cfdi_customer_rfc = fields.Char( + string="Customer RFC", + copy=False, + readonly=True, + help="The customer tax identification number.", + compute="_compute_cfdi_values", + ) + l10n_mx_edi_cfdi_amount = fields.Monetary( + string="Total Amount", + copy=False, + readonly=True, + help="The total amount reported on the cfdi.", + compute="_compute_cfdi_values", + ) + + # ==== Other fields ==== + + l10n_mx_edi_payment_method_id = fields.Many2one( + "l10n_mx_edi.payment.method", + string="Payment Way", + help="Indicates the way the invoice was/will be paid, " + "where the options could be: " + "Cash, Nominal Check, Credit Card, etc. " + "Leave empty if unkown and the XML will show 'Unidentified'.", + default=lambda self: self.env.ref( + "l10n_mx_edi.payment_method_otros", raise_if_not_found=False + ), + ) + + l10n_mx_edi_payment_policy = fields.Selection( + string="Payment Policy", + selection=[("PPD", "PPD"), ("PUE", "PUE")], + compute="_compute_l10n_mx_edi_payment_policy", + ) + + uso_cfdi = fields.Selection( + selection=[("P01", "Por definir")], + string="Uso CFDI (Employee)", + default="P01", + ) + + @api.depends("edi_document_ids.state") + def _compute_edi_state(self): + for payslip in self: + all_states = set( + payslip.edi_document_ids.filtered( + lambda d: d.edi_format_id._needs_web_services() + ).mapped("state") + ) + if all_states == {"sent"}: + payslip.edi_payroll_state = "sent" + elif all_states == {"cancelled"}: + payslip.edi_payroll_state = "cancelled" + elif "to_send" in all_states: + payslip.edi_payroll_state = "to_send" + elif "to_cancel" in all_states: + payslip.edi_payroll_state = "to_cancel" + else: + payslip.edi_payroll_state = False + + @api.depends("edi_document_ids.error") + def _compute_edi_error_count(self): + for payslip in self: + payslip.edi_payroll_error_count = len( + payslip.edi_document_ids.filtered(lambda d: d.error) + ) + + @api.depends( + "edi_payroll_error_count", + "edi_document_ids.error", + "edi_payroll_blocking_level", + ) + def _compute_edi_payroll_error_message(self): + for payslip in self: + if payslip.edi_payroll_error_count == 0: + payslip.edi_payroll_error_message = None + payslip.edi_payroll_blocking_level = None + elif payslip.edi_payroll_error_count == 1: + error_doc = payslip.edi_payroll_document_ids.filtered(lambda d: d.error) + payslip.edi_payroll_error_message = error_doc.error + payslip.edi_payroll_blocking_level = ( + error_doc.edi_payroll_blocking_level + ) + else: + error_levels = { + doc.edi_payroll_blocking_level for doc in payslip.edi_document_ids + } + if "error" in error_levels: + payslip.edi_payroll_error_message = str( + payslip.edi_payroll_error_count + ) + _(" Electronic Payslip error(s)") + payslip.edi_payroll_blocking_level = "error" + elif "warning" in error_levels: + payslip.edi_payroll_error_message = str( + payslip.edi_payroll_error_count + ) + _(" Electronic Payslip warning(s)") + payslip.edi_payroll_blocking_level = "warning" + else: + payslip.edi_payroll_error_message = str( + payslip.edi_payroll_error_count + ) + _(" Electronic Payslip info(s)") + payslip.edi_payroll_blocking_level = "info" + + @api.depends( + "edi_document_ids", + "edi_document_ids.state", + "edi_document_ids.edi_payroll_blocking_level", + "edi_document_ids.edi_format_id", + "edi_document_ids.edi_format_id.name", + ) + def _compute_edi_web_services_to_process(self): + for payslip in self: + to_process = payslip.edi_document_ids.filtered( + lambda d: d.state in ["to_send", "to_cancel"] + and d.blocking_level != "error" + ) + format_web_services = to_process.edi_format_id.filtered( + lambda f: f._needs_web_services() + ) + payslip.edi_web_services_to_process = ", ".join( + f.name for f in format_web_services + ) + + # ------------------------------------------------------------------------- + # HELPERS + # ------------------------------------------------------------------------- + + def _l10n_mx_edi_get_cadena_xslts(self): + return CFDI_XSLT_CADENA_TFD, CFDI_XSLT_CADENA + + # -------------------------------------------------------------------------- + # Payroll slip buttons + # -------------------------------------------------------------------------- + + def action_stamp_payroll_pac(self): + if self.journal_id.edi_format_ids.code != "cfdi_1_2": + return super().action_payslip_done() + certificate_date = ( + self.env["l10n_mx_edi.certificate"].sudo().get_mx_current_datetime() + ) + + edi_document_vals_list = [] + + for payslip in self: + _logger.info("action_stamp_payroll_pac --->>> " + str(payslip.number)) + if payslip.l10n_mx_edi_state_pac == "no_signed": + issued_address = payslip._get_l10n_mx_hr_payroll_edi_issued_address() + tz = self._l10n_mx_edi_get_cfdi_partner_timezone(issued_address) + tz_force = ( + self.env["ir.config_parameter"] + .sudo() + .get_param( + "l10n_mx_edi_tz_%s" % payslip.journal_id.id, default=None + ) + ) + if tz_force: + tz = timezone(tz_force) + payslip.l10n_mx_edi_post_time = fields.Datetime.to_string( + datetime.now(tz) + ) + + if payslip.l10n_mx_edi_cfdi_request in ("on_payslip"): + # Assign time and date coming from a certificate. + if not payslip.payslip_date: + payslip.payslip_date = certificate_date.date() + # payslip.with_context(check_move_validity=False)._onchange_payslip_date() + edi_result = self.env["account.edi.format"]._post_payslip_edi(payslip) + if edi_result[payslip].get("error", False): + raise UserError( + _("Invalid payslip configuration:\n\n%s") + % (edi_result[payslip].get("error", False)) + ) + payslip.l10n_mx_edi_stamp_status = "stamped" + for edi_format in payslip.journal_id.edi_format_ids: + is_edi_needed = True + + if is_edi_needed: + errors = edi_format._check_move_configuration(payslip) + if errors: + raise UserError( + _("Invalid payslip configuration:\n\n%s") + % "\n".join(errors) + ) + + existing_edi_document = payslip.edi_document_ids.filtered( + lambda x, y=edi_format: x.edi_format_id == y + ) + if existing_edi_document: + _logger.warning("existing_edi_document") + attachment_xml = self.env["ir.attachment"].search( + [ + ("name", "like", "-MX-payslip-1.2.xml"), + ("res_id", "=", payslip.id), + ] + ) + if existing_edi_document: + existing_edi_document.write( + { + "state": "to_send", + "attachment_id": attachment_xml.id + if attachment_xml + else False, + } + ) + else: + payslip_id = payslip.id + edi_document_vals_list = { + "edi_format_id": edi_format.id, + "payslip_id": payslip_id, + "state": "to_send", + "attachment_id": attachment_xml.id + if attachment_xml + else False, + } + self.env["payslip.edi.document"].create( + edi_document_vals_list + ) + + # Validate work entries for regular payslips + # (exclude end of year bonus, ...) + else: + _logger.info("CFDI stamped before --->>> " + str(payslip.number)) + payslip.message_post( + body=_( + "CFDI stamped before - payslip number: %(msg)s", + msg=str(payslip.number), + ) + ) + + return + + def _get_l10n_mx_hr_payroll_edi_issued_address(self): + self.ensure_one() + return self.company_id.partner_id.commercial_partner_id + + @api.model + def _l10n_mx_edi_get_cfdi_partner_timezone(self, partner): + code = partner.state_id.code + + # northwest area + if code == "BCN": + return timezone("America/Tijuana") + # Southeast area + elif code == "ROO": + return timezone("America/Cancun") + # Pacific area + elif code in ("BCS", "CHH", "SIN", "NAY"): + return timezone("America/Chihuahua") + # Sonora + elif code == "SON": + return timezone("America/Hermosillo") + # By default, takes the central area timezone + return timezone("America/Mexico_City") + + def _compute_l10n_mx_edi_cancel(self): + for move in self: + if move.l10n_mx_edi_cfdi_uuid: + replaced_move = move.search( + [ + ("l10n_mx_edi_origin", "like", "04|%"), + ( + "l10n_mx_edi_origin", + "like", + "%" + move.l10n_mx_edi_cfdi_uuid + "%", + ), + ("company_id", "=", move.company_id.id), + ], + limit=1, + ) + move.l10n_mx_edi_cancel_payslip_id = replaced_move + else: + move.l10n_mx_edi_cancel_payslip_id = None + + # ------------------------------------------------------------------------- + # SAT + # ------------------------------------------------------------------------- + + def l10n_mx_edi_update_sat_status(self): + """Synchronize both systems: Odoo & SAT to make sure the invoice is + valid.""" + for payslip in self: + supplier_rfc = payslip.l10n_mx_edi_cfdi_supplier_rfc + customer_rfc = payslip.l10n_mx_edi_cfdi_customer_rfc + total = float_repr( + payslip.l10n_mx_edi_cfdi_amount, + precision_digits=payslip.currency_id.decimal_places, + ) + uuid = payslip.l10n_mx_edi_cfdi_uuid + try: + status = self.env["account.edi.format"]._l10n_mx_edi_get_sat_status( + supplier_rfc, customer_rfc, total, uuid + ) + except Exception as e: + payslip.message_post( + body=_( + "Failure during update of the SAT status: %(msg)s", msg=str(e) + ) + ) + continue + + if status == "Vigente": + payslip.l10n_mx_edi_sat_status = "valid" + elif status == "Cancelado": + payslip.l10n_mx_edi_sat_status = "cancelled" + elif status == "No Encontrado": + payslip.l10n_mx_edi_sat_status = "not_found" + else: + payslip.l10n_mx_edi_sat_status = "none" + + @api.model + def _l10n_mx_payroll_edi_cron_update_sat_status(self): + """Call the SAT to know if the invoice is available government-side or + if the invoice has been cancelled. + In the second case, the cancellation could be done Odoo-side and then + we need to check if the SAT is up-to-date, or could be done manually + government-side forcing Odoo to update the invoice's state. + """ + + # Update the 'l10n_mx_payroll_edi_sat_status' field. + cfdi_edi_format = self.env.ref("l10n_mx_hr_payroll.edi_cfdi_payroll_1_2") + to_process = self.env["payslip.edi.document"].search( + [ + ("edi_format_id", "=", cfdi_edi_format.id), + ("state", "in", ("sent", "cancelled")), + ( + "payslip_id.l10n_mx_edi_sat_status", + "in", + ("undefined", "not_found", "none"), + ), + ] + ) + to_process.payslip_id.l10n_mx_edi_update_sat_status() + + # Handle the case when the invoice has been cancelled manually + # government-side. + to_process.filtered( + lambda doc: doc.state == "sent" + and doc.payslip_id.l10n_mx_edi_sat_status == "cancelled" + ).payslip_id.action_payslip_cancel() + + # ------------------------------------------------------------------------- + # COMPUTE METHODS + # ------------------------------------------------------------------------- + + @api.depends("company_id", "state") + def _compute_l10n_mx_edi_cfdi_request(self): + for payslip in self: + payslip.l10n_mx_edi_state_pac = "no_signed" + if payslip.country_code != "MX": + payslip.l10n_mx_edi_cfdi_request = False + else: + payslip.l10n_mx_edi_cfdi_request = "on_payslip" + + @api.depends("payslip_date") + def _compute_l10n_mx_edi_payment_policy(self): + for payslip in self: + payslip.l10n_mx_edi_payment_policy = False + + def compute_sheet(self): + if self.journal_id.edi_format_ids.code != "cfdi_1_2": + return super().action_payslip_done() + payslips = self.filtered(lambda slip: slip.state in ["draft", "verify"]) + + # delete old payslip lines + payslips.line_ids.unlink() + + # Check if we have movements, loans and alimony + for payslip in payslips: + number = payslip.number or self.env["ir.sequence"].next_by_code( + "salary.slip" + ) + # payslip.calculate_imss() + hr_payroll_movement_inputs = self.env["hr.payroll.movement.line"].search( + [ + ("date_start", "<=", payslip.date_from), + ("employee_id", "=", payslip.employee_id.id), + ("state", "=", "approved"), + ] + ) + + hr_payroll_movement_loans = self.env["hr.payroll.loan"].search( + [ + ("date_start", "<=", payslip.date_from), + ("employee_id", "=", payslip.employee_id.id), + ("state", "=", "approved"), + ] + ) + + hr_payroll_movement_alimony = self.env["hr.payroll.alimony"].search( + [ + ("date_start", "<=", payslip.date_from), + ("employee_id", "=", payslip.employee_id.id), + ("state", "=", "approved"), + ] + ) + + hr_payroll_movement_extratime = self.env[ + "hr.payroll.extratime.line" + ].search( + [ + ("date", ">=", payslip.date_from), + ("date", "<=", payslip.date_to), + ("employee_id", "=", payslip.employee_id.id), + ("state", "=", "approved"), + ] + ) + + # Input Payroll Movements + payslip.input_line_ids.unlink() + for movement_input in hr_payroll_movement_inputs: + payslip.input_line_ids.create( + { + "payslip_id": payslip.id, + "input_type_id": movement_input.payslip_input_id.id, + "amount": movement_input.amount, + } + ) + + # Input Payroll loans + for loan in hr_payroll_movement_loans: + payslip.input_line_ids.create( + { + "payslip_id": payslip.id, + "input_type_id": loan.loan_type_mx.id, + "amount": loan.amount, + } + ) + + # Input Payroll Alimony + for alimony in hr_payroll_movement_alimony: + payslip.input_line_ids.create( + { + "payslip_id": payslip.id, + "input_type_id": self.env.ref( + "l10n_mx_hr_payroll.input_alimony" + ).id, + "amount": alimony.amount, + } + ) + + # Input Payroll Extratime + extratime_double = extratime_triple = 0 + for extra in hr_payroll_movement_extratime: + if extra.type_hour == "double": + extratime_double += extra.hours + if extra.type_hour == "triple": + extratime_triple += extra.hours + + if extratime_double > 0: + payslip.input_line_ids.create( + { + "payslip_id": payslip.id, + "input_type_id": self.env.ref( + "l10n_mx_hr_payroll.input_extratime_double" + ).id, + "amount": extratime_double, + } + ) + + if extratime_triple > 0: + payslip.input_line_ids.create( + { + "payslip_id": payslip.id, + "input_type_id": self.env.ref( + "l10n_mx_hr_payroll.input_extratime_triple" + ).id, + "amount": extratime_triple, + } + ) + + lines = [(0, 0, line) for line in payslip._get_payslip_lines()] + + payslip.write( + { + "line_ids": lines, + "number": number, + "state": "verify", + "compute_date": fields.Date.today(), + } + ) + return True + + # ------------------------------------------------------------------------- + # HELPERS + # ------------------------------------------------------------------------- + + def _get_l10n_mx_hr_payroll_edi_signed_edi_document(self): + self.ensure_one() + cfdi_1_2_edi = self.env.ref("l10n_mx_hr_payroll_edi.edi_cfdi_payroll_1_2") + return self.edi_document_ids.filtered( + lambda document: document.edi_format_id == cfdi_1_2_edi + and document.attachment_id + ) + + def _l10n_mx_hr_payroll_edi_decode_cfdi(self, cfdi_data=None): + self.ensure_one() + + # Find a signed cfdi. + if not cfdi_data: + signed_edi = self._get_l10n_mx_hr_payroll_edi_signed_edi_document() + if signed_edi: + cfdi_data = base64.decodebytes( + signed_edi.attachment_id.with_context(bin_size=False).datas + ) + + # Nothing to decode. + if not cfdi_data: + return {} + + try: + cfdi_node = fromstring(cfdi_data) + except etree.XMLSyntaxError: + # Not an xml + return {} + + return self._l10n_mx_edi_hr_payroll_decode_cfdi_etree(cfdi_node) + + def _l10n_mx_edi_hr_payroll_decode_cfdi_etree(self, cfdi_node): + """Helper to extract relevant data from the CFDI etree object, + does not require a move record. + :param cfdi_node: The cfdi etree object. + :return: A python dictionary. + """ + + def get_node(cfdi_node, attribute, namespaces): + if hasattr(cfdi_node, "Complemento"): + node = cfdi_node.Complemento.xpath(attribute, namespaces=namespaces) + return node[0] if node else None + else: + return None + + def get_cadena(cfdi_node, template): + if cfdi_node is None: + return None + cadena_root = etree.parse(tools.file_open(template)) + return str(etree.XSLT(cadena_root)(cfdi_node)) + + tfd_node = get_node( + cfdi_node, + "tfd:TimbreFiscalDigital[1]", + {"tfd": "http://www.sat.gob.mx/TimbreFiscalDigital"}, + ) + + return { + "uuid": ({} if tfd_node is None else tfd_node).get("UUID"), + "supplier_rfc": cfdi_node.Emisor.get("Rfc", cfdi_node.Emisor.get("rfc")), + "customer_rfc": cfdi_node.Receptor.get( + "Rfc", cfdi_node.Receptor.get("rfc") + ), + "amount_total": cfdi_node.get("Total", cfdi_node.get("total")), + "cfdi_node": cfdi_node, + "usage": cfdi_node.Receptor.get("UsoCFDI"), + "payment_method": cfdi_node.get("formaDePago", cfdi_node.get("MetodoPago")), + "bank_account": cfdi_node.get("NumCtaPago"), + "sello": cfdi_node.get("sello", cfdi_node.get("Sello", "No identificado")), + "sello_sat": tfd_node is not None + and tfd_node.get("selloSAT", tfd_node.get("SelloSAT", "No identificado")), + "cadena": tfd_node is not None + and get_cadena(tfd_node, self._l10n_mx_edi_get_cadena_xslts()[0]) + or get_cadena(cfdi_node, self._l10n_mx_edi_get_cadena_xslts()[1]), + "certificate_number": cfdi_node.get( + "noCertificado", cfdi_node.get("NoCertificado") + ), + "certificate_sat_number": tfd_node is not None + and tfd_node.get("NoCertificadoSAT"), + "expedition": cfdi_node.get("LugarExpedicion"), + "fiscal_regime": cfdi_node.Emisor.get("RegimenFiscal", ""), + "emission_date_str": cfdi_node.get( + "fecha", cfdi_node.get("Fecha", "") + ).replace("T", " "), + "stamp_date": tfd_node is not None + and tfd_node.get("FechaTimbrado", "").replace("T", " "), + } + + @api.model + def _l10n_mx_edi_cfdi_amount_to_text(self): + """Method to transform a float amount to text words + E.g. 100 - ONE HUNDRED + :returns: Amount transformed to words mexican format for invoices + :rtype: str + """ + self.ensure_one() + + currency_name = self.currency_id.name.upper() + + # M.N. = Moneda Nacional (National Currency) + # M.E. = Moneda Extranjera (Foreign Currency) + currency_type = "M.N" if currency_name == "MXN" else "M.E." + + # Split integer and decimal part + amount_i, amount_d = divmod(self.amount_total, 1) + amount_d = round(amount_d, 2) + amount_d = int(round(amount_d * 100, 2)) + + words = ( + self.currency_id.with_context( + lang=self.employee_id.address_home_id.lang or "es_ES" + ) + .amount_to_text(amount_i) + .upper() + ) + return "%(words)s con %(amount_d)02d/100 %(currency_type)s" % { + "words": words, + "amount_d": amount_d, + "currency_type": currency_type, + } + + @api.model + def _l10n_mx_edi_write_cfdi_origin(self, code, uuids): + """Format the code and uuids passed as parameter in order to fill the + l10n_mx_edi_origin field. + The code corresponds to the following types: + - 01: Nota de crédito + - 02: Nota de débito de los documentos relacionados + - 03: Devolución de mercancía sobre facturas o traslados previos + - 04: Sustitución de los CFDI previos + - 05: Traslados de mercancias facturados previamente + - 06: Factura generada por los traslados previos + - 07: CFDI por aplicación de anticipo + The generated string must match the following template: + |,,..., + :param code: A valid code as a string between 01 and 07. + :param uuids: A list of uuids returned by the government. + :return: A valid string to be put inside the l10n_mx_edi_origin field. + """ + return "%s|%s" % (code, ",".join(uuids)) + + @api.model + def _l10n_mx_edi_read_cfdi_origin(self, cfdi_origin): + splitted = cfdi_origin.split("|") + if len(splitted) != 2: + return False + + try: + code = int(splitted[0]) + except ValueError: + return False + + if code < 1 or code > 7: + return False + return splitted[0], [uuid.strip() for uuid in splitted[1].split(",")] + + # ------------------------------------------------------------------------- + # COMPUTE METHODS + # ------------------------------------------------------------------------- + + @api.depends("edi_document_ids") + def _compute_cfdi_values(self): + """Fill the invoice fields from the cfdi values.""" + for payslip in self: + cfdi_infos = payslip._l10n_mx_hr_payroll_edi_decode_cfdi() + _logger.info("_compute_cfdi_values --->> " + str(cfdi_infos)) + if cfdi_infos.get("uuid"): + payslip.l10n_mx_edi_state_pac = "signed" + else: + payslip.l10n_mx_edi_state_pac = "no_signed" + payslip.l10n_mx_edi_cfdi_uuid = cfdi_infos.get("uuid") + payslip.l10n_mx_edi_cfdi_supplier_rfc = cfdi_infos.get("supplier_rfc") + payslip.l10n_mx_edi_cfdi_customer_rfc = cfdi_infos.get("customer_rfc") + payslip.l10n_mx_edi_cfdi_amount = cfdi_infos.get("amount_total") diff --git a/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py b/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py new file mode 100644 index 0000000..732c9fe --- /dev/null +++ b/l10n_mx_hr_payroll_edi/models/l10n_mx_hr_payroll_certificate.py @@ -0,0 +1,245 @@ +import base64 +import logging +import ssl +import subprocess +import tempfile +from datetime import datetime + +from lxml import etree, objectify +from pytz import timezone + +from odoo import _, api, fields, models, tools +from odoo.exceptions import UserError, ValidationError +from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT + +_logger = logging.getLogger(__name__) +_tzmx = timezone("America/Mexico_City") + +try: + from OpenSSL import crypto +except ImportError: + _logger.warning( + "OpenSSL library not found. If you plan to use l10n_mx_edi, please " + "install the library from https://pypi.python.org/pypi/pyOpenSSL" + ) + + +KEY_TO_PEM_CMD = "openssl pkcs8 -in %s -inform der -outform pem -out %s -passin file:%s" + + +def convert_key_cer_to_pem(key, password): + # TODO compute it from a python way + with tempfile.NamedTemporaryFile( + "wb", suffix=".key", prefix="edi.mx.tmp." + ) as key_file, tempfile.NamedTemporaryFile( + "wb", suffix=".txt", prefix="edi.mx.tmp." + ) as pwd_file, tempfile.NamedTemporaryFile( + "rb", suffix=".key", prefix="edi.mx.tmp." + ) as keypem_file: + key_file.write(key) + key_file.flush() + pwd_file.write(password) + pwd_file.flush() + subprocess.call( + (KEY_TO_PEM_CMD % (key_file.name, keypem_file.name, pwd_file.name)).split() + ) + key_pem = keypem_file.read() + return key_pem + + +def str_to_datetime(dt_str, tz=_tzmx): + return tz.localize(fields.Datetime.from_string(dt_str)) + + +class CertificatePayroll(models.Model): + _name = "l10n_mx_payroll_edi.certificate" + _description = "SAT Digital Sail" + _order = "date_start desc, id desc" + + content = fields.Binary( + string="Certificate", + help="Certificate in der format", + required=True, + attachment=False, + ) + key = fields.Binary( + string="Certificate Key", + help="Certificate Key in der format", + required=True, + attachment=False, + ) + password = fields.Char( + string="Certificate Password", + help="Password for the Certificate Key", + required=True, + ) + serial_number = fields.Char( + string="Serial number", + help="The serial number to add to electronic documents", + readonly=True, + index=True, + ) + date_start = fields.Datetime( + string="Available date", + help="The date on which the certificate starts to be valid", + readonly=True, + ) + date_end = fields.Datetime( + string="Expiration date", + help="The date on which the certificate expires", + readonly=True, + ) + + @tools.ormcache("content") + def _get_pem_cer(self, content): + """Get the current content in PEM format""" + self.ensure_one() + return ssl.DER_cert_to_PEM_cert(base64.decodebytes(content)).encode("UTF-8") + + @tools.ormcache("key", "password") + def _get_pem_key(self, key, password): + """Get the current key in PEM format""" + self.ensure_one() + return convert_key_cer_to_pem(base64.decodebytes(key), password.encode("UTF-8")) + + def _get_data(self): + """Return the content (b64 encoded) and the certificate decrypted""" + self.ensure_one() + cer_pem = self._get_pem_cer(self.content) + certificate = crypto.load_certificate(crypto.FILETYPE_PEM, cer_pem) + for to_del in ["\n", ssl.PEM_HEADER, ssl.PEM_FOOTER]: + cer_pem = cer_pem.replace(to_del.encode("UTF-8"), b"") + return cer_pem, certificate + + def get_mx_current_datetime(self): + """Get the current datetime with the Mexican timezone.""" + return fields.Datetime.context_timestamp( + self.with_context(tz="America/Mexico_City"), fields.Datetime.now() + ) + + def get_valid_certificate(self): + """Search for a valid certificate that is available and not expired.""" + mexican_dt = self.get_mx_current_datetime() + for record in self: + date_start = str_to_datetime(record.date_start) + date_end = str_to_datetime(record.date_end) + if date_start <= mexican_dt <= date_end: + return record + return None + + def _get_encrypted_cadena(self, cadena): + """Encrypt the cadena using the private key.""" + self.ensure_one() + key_pem = self._get_pem_key(self.key, self.password) + private_key = crypto.load_privatekey(crypto.FILETYPE_PEM, bytes(key_pem)) + encrypt = "sha256WithRSAEncryption" + cadena_crypted = crypto.sign(private_key, bytes(cadena.encode()), encrypt) + return base64.b64encode(cadena_crypted) + + @api.model + def _get_cadena_chain(self, xml_tree, xslt_path): + """Use the provided XSLT document to generate a pipe-delimited string + :param xml_tree: the source lxml document + :param xslt_path: Path to the XSLT document + :return: string + """ + cadena_transformer = etree.parse(tools.file_open(xslt_path)) + return str(etree.XSLT(cadena_transformer)(xml_tree)) + + def _certify_and_stamp( + self, xml_content_str, xslt_path, no_cert_attrib_name="NoCertificado" + ): + """Appends the seal stamp, certificate, and serial number to CFDI + documents + :param xml_content_str: The XML document string to certify and stamp + :param xslt_path: Path to the XSLT used to generate the cadena chain + (pipe delimited string of important values) + :param no_cert_attrib_name: string of the NoCertificado (default) + attribute which can be replaced with noCertificado + :return: A string of the XML with appended attributes: NoCertificado, + Certificado, Sello + """ + + self.ensure_one() + if not xml_content_str: + return None + tree = objectify.fromstring(xml_content_str) + tree.attrib[no_cert_attrib_name] = self.serial_number + tree.attrib["Certificado"] = self._get_data()[0] + cadena_chain = self._get_cadena_chain(tree, xslt_path) + sello = self._get_encrypted_cadena(cadena_chain) + tree.attrib["Sello"] = sello + return etree.tostring( + tree, pretty_print=True, xml_declaration=True, encoding="UTF-8" + ) + + @api.constrains("content", "key", "password") + def _check_credentials(self): + """Check the validity of content/key/password and fill the fields + with the certificate values. + """ + mexican_tz = timezone("America/Mexico_City") + mexican_dt = self.get_mx_current_datetime() + date_format = "%Y%m%d%H%M%SZ" + for record in self: + # Try to decrypt the certificate + try: + certificate = record._get_data()[1] + before = mexican_tz.localize( + datetime.strptime( + certificate.get_notBefore().decode("utf-8"), date_format + ) + ) + after = mexican_tz.localize( + datetime.strptime( + certificate.get_notAfter().decode("utf-8"), date_format + ) + ) + serial_number = certificate.get_serial_number() + except UserError as exc_orm: # ;-) + raise exc_orm + except Exception as e: + raise ValidationError( + _("The certificate content is invalid " "%s.", e) + ) from e + # Assign extracted values from the certificate + record.serial_number = ("%x" % serial_number)[1::2] + record.date_start = before.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + record.date_end = after.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + if mexican_dt > after: + raise ValidationError( + _("The certificate is expired since %s", record.date_end) + ) + # Check the pair key/password + try: + key_pem = self._get_pem_key(self.key, self.password) + crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem) + except Exception: + raise ValidationError( + _("The certificate key and/or password is/are invalid.") + ) from None + + @api.ondelete(at_uninstall=True) + def _unlink_except_invoices(self): + mx_edi = self.env.ref("l10n_mx_edi.edi_cfdi_3_3") + if ( + self.env["account.edi.document"] + .sudo() + .search( + [ + ("edi_format_id", "=", mx_edi.id), + ("state", "=", "sent"), + ], + limit=1, + ) + ): + raise UserError( + _( + "You cannot remove a certificate if at least one invoice " + "has been signed. " + "Expired certificates will not be used as Odoo uses the " + "latest valid certificate. " + "To not use it, you can unlink it from the current company " + "certificates." + ) + ) diff --git a/l10n_mx_hr_payroll_edi/models/payslip_edi_document.py b/l10n_mx_hr_payroll_edi/models/payslip_edi_document.py new file mode 100644 index 0000000..093650c --- /dev/null +++ b/l10n_mx_hr_payroll_edi/models/payslip_edi_document.py @@ -0,0 +1,69 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import base64 +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class PayslipEdiDocument(models.Model): + _name = "payslip.edi.document" + _description = "Electronic Document for an hr.payslip" + + # == Stored fields == + payslip_id = fields.Many2one("hr.payslip", required=True, ondelete="cascade") + edi_format_id = fields.Many2one("account.edi.format", required=True) + attachment_id = fields.Many2one( + "ir.attachment", + help="The file generated by edi_format_id when the invoice is posted " + "(and this document is processed).", + ) + state = fields.Selection( + [ + ("to_send", "To Send"), + ("sent", "Sent"), + ("to_cancel", "To Cancel"), + ("cancelled", "Cancelled"), + ] + ) + error = fields.Html( + help="The text of the last error that happened during Electronic Invoice operation." + ) + + # == Not stored fields == + name = fields.Char(related="attachment_id.name") + edi_format_name = fields.Char(string="Format Name", related="edi_format_id.name") + edi_content = fields.Binary(compute="_compute_edi_content", compute_sudo=True) + + _sql_constraints = [ + ( + "unique_edi_document_by_payslip_by_format", + "UNIQUE(edi_format_id, payslip_id)", + "Only one edi document by payslip by format", + ), + ] + + @api.depends("payslip_id", "error", "state") + def _compute_edi_content(self): + for doc in self: + res = b"" + if doc.state in ("to_send", "to_cancel"): + payslip = doc.payslip_id + config_errors = doc.edi_format_id._check_move_configuration(payslip) + if config_errors: + res = base64.b64encode("\n".join(config_errors).encode("UTF-8")) + else: + res = base64.b64encode( + doc.edi_format_id._get_payslip_edi_content(doc.payslip_id) + ) + _logger.info("_compute_edi_content", res) + doc.edi_content = res + + def action_export_xml(self): + self.ensure_one() + return { + "type": "ir.actions.act_url", + "url": "/web/content/account.edi.document/%s/edi_content" % self.id, + } diff --git a/l10n_mx_hr_payroll_edi/models/res_company.py b/l10n_mx_hr_payroll_edi/models/res_company.py new file mode 100644 index 0000000..361791a --- /dev/null +++ b/l10n_mx_hr_payroll_edi/models/res_company.py @@ -0,0 +1,51 @@ +import logging + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class ResCompany(models.Model): + _inherit = "res.company" + + # == PAC web-services == + l10n_mx_hr_payroll_edi_pac = fields.Selection( + selection=[ + ("solfact", "Solución Factible"), + ("facturalo", "Factúralo"), + ("finkok", "Finkok"), + ("sw", "SW sapien-SmarterWEB"), + ("emaanu", "e-Maanu (Multipac)"), + ], + string="PAC for payroll", + help="The PAC that will sign/cancel the invoices", + default="emaanu", + ) + l10n_mx_hr_payroll_edi_pac_test_env = fields.Boolean( + string="PAC test environment for payroll", + help="Enable the usage of test credentials", + default=False, + ) + l10n_mx_hr_payroll_edi_pac_username = fields.Char( + string="PAC username for payroll", + help="The username used to request the seal from the PAC", + groups="base.group_system", + ) + l10n_mx_hr_payroll_edi_pac_password = fields.Char( + string="PAC password for payroll", + help="The password used to request the seal from the PAC", + groups="base.group_system,hr_payroll.group_hr_payroll_manager", + ) + l10n_mx_hr_payroll_edi_certificate_ids = fields.Many2many( + "l10n_mx_payroll_edi.certificate", string="Certificates (MX) for payroll" + ) + + l10n_mx_hr_payroll_mode2work = fields.Selection( + selection=[ + ("stamp", "Stamp First and then create entries account"), + ("same_time", "Stamp and create entries account at the same time"), + ], + string="Payroll Work Method", + help="", + default="stamp", + ) diff --git a/l10n_mx_hr_payroll_edi/models/res_config_setting.py b/l10n_mx_hr_payroll_edi/models/res_config_setting.py new file mode 100644 index 0000000..b067e0d --- /dev/null +++ b/l10n_mx_hr_payroll_edi/models/res_config_setting.py @@ -0,0 +1,38 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + # == PAC web-services == + l10n_mx_hr_payroll_edi_pac = fields.Selection( + related="company_id.l10n_mx_hr_payroll_edi_pac", + readonly=False, + string="MX PAC* for Payroll", + ) + l10n_mx_hr_payroll_edi_pac_test_env = fields.Boolean( + related="company_id.l10n_mx_hr_payroll_edi_pac_test_env", + readonly=False, + string="MX PAC test environment* for Payroll", + ) + l10n_mx_hr_payroll_edi_pac_username = fields.Char( + related="company_id.l10n_mx_hr_payroll_edi_pac_username", + readonly=False, + string="MX PAC username* for Patroll", + ) + l10n_mx_hr_payroll_edi_pac_password = fields.Char( + related="company_id.l10n_mx_hr_payroll_edi_pac_password", + readonly=False, + string="MX PAC password* for payroll", + ) + l10n_mx_hr_payroll_edi_certificate_ids = fields.Many2many( + related="company_id.l10n_mx_hr_payroll_edi_certificate_ids", + readonly=False, + string="MX Certificates* for payroll", + ) + + l10n_mx_hr_payroll_mode2work = fields.Selection( + related="company_id.l10n_mx_hr_payroll_mode2work", + readonly=False, + string="Payroll Work Method", + ) diff --git a/l10n_mx_hr_payroll_edi/pyproject.toml b/l10n_mx_hr_payroll_edi/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/l10n_mx_hr_payroll_edi/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/l10n_mx_hr_payroll_edi/security/ir.model.access.csv b/l10n_mx_hr_payroll_edi/security/ir.model.access.csv new file mode 100644 index 0000000..ff296e4 --- /dev/null +++ b/l10n_mx_hr_payroll_edi/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_l10n_mx_payroll_edi_certificate,l10n_mx_payroll_edi_certificate,model_l10n_mx_payroll_edi_certificate,hr_payroll.group_hr_payroll_user,1,1,1,1 +access_payslip_edi_document,payslip_edi_document,model_payslip_edi_document,hr_payroll.group_hr_payroll_user,1,1,1,1 diff --git a/l10n_mx_hr_payroll_edi/static/description/icon.png b/l10n_mx_hr_payroll_edi/static/description/icon.png new file mode 100755 index 0000000..4da9a11 Binary files /dev/null and b/l10n_mx_hr_payroll_edi/static/description/icon.png differ diff --git a/l10n_mx_hr_payroll_edi/static/src/scss/report.scss b/l10n_mx_hr_payroll_edi/static/src/scss/report.scss new file mode 100644 index 0000000..d08db8b --- /dev/null +++ b/l10n_mx_hr_payroll_edi/static/src/scss/report.scss @@ -0,0 +1,23 @@ +#complement { + page-break-before: auto; + page-break-inside: avoid; + div { + padding-top: 0.3em; + padding-bottom: 0.3em; + } + .barcode { + float: none; + } + .complement-details { + font-size: 8px; + } + div.digital-stamp { + background-color: grey; + color: white; + text-align: center; + font-weight: bold; + } + div.digital-stamp-content { + width: 93%; + } +} diff --git a/l10n_mx_hr_payroll_edi/views/hr_payslip.xml b/l10n_mx_hr_payroll_edi/views/hr_payslip.xml new file mode 100644 index 0000000..ca9663d --- /dev/null +++ b/l10n_mx_hr_payroll_edi/views/hr_payslip.xml @@ -0,0 +1,287 @@ + + + + hr.payslip.form.inherit_page_payroll_mx + hr.payslip + + 100 + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    + + + hr.payslip.buttons.inherit_page_payroll_mx + hr.payslip + + + + + + + + + + +

    + + + +
    + + +

    + + +
    + +
    +
    + + + + + + + + + + + + + +