diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 740db8b1d060a7679504b532b81e72b421132f43..d2eb54a281f0e49890598bda0d9b87c96bc63a22 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,35 +1,86 @@ variables: - BUILDOUT: tools/buildout.cfg + BUILDOUT: buildout.cfg + DB_NAME: $CI_PROJECT_NAME-$CI_JOB_ID + DB_USER: openerp + NAME_PRO: $CI_PROJECT_NAME cache: - key: "$CI_BUILD_NAME" + key: one-key-to-rule-them-all paths: - buildout-cache/ -before_script: - - git clone --depth=1 https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.openpyme.mx/pyerp/test.git tools - - virtualenv --quiet --python=python2.7 . - - source bin/activate - - pip install -r tools/requirements.txt - stages: - build - - test - - deploy + - unit test + - code style + +make: + tags: + - base + stage: build + script: + - git clone --depth=1 --branch feat_bench_odoo_15 http://gitlab.openpyme.mx/pyerp/bench.git + - cd bench + - cp -f $CI_PROJECT_DIR/buildout.cfg buildout.cfg + - ln -s profiles/ci.cfg local.cfg + - virtualenv . --python=python3.10 + - source bin/activate + - bin/pip3.10 install -r requirements.txt + - bin/buildout + - cp -LR $CI_PROJECT_DIR/$NAME_PRO local_modules/ + artifacts: + paths: + - bench + expire_in: 1 week + +test: + tags: + - base + stage: unit test + script: + - cd bench + - source bin/activate + - createdb $DB_NAME + - start_openerp -d $DB_NAME -i $NAME_PRO --stop-after-init + - start_openerp -d $DB_NAME -i l10n_generic_coa --stop-after-init + - start_openerp -d $DB_NAME -i account_payment --stop-after-init + - start_openerp -d $DB_NAME -i $NAME_PRO --test-enable --stop-after-init + dependencies: + - make code-analysis: - stage: test + tags: + - base + stage: code style + allow_failure: true script: - - buildout -qc $BUILDOUT buildout:directory=$CI_PROJECT_DIR install node code-analysis + - cd bench + - source bin/activate - code-analysis + dependencies: + - make lint-analysis: - stage: test + tags: + - base + stage: code style + allow_failure: true script: - - buildout -qc $BUILDOUT buildout:directory=$CI_PROJECT_DIR install pylint-bin pylint + - cd bench + - source bin/activate + - python-pylint scripts/run_pylint --path local_modules/ -c config/pylint.cfg + + dependencies: + - make mccabe: - stage: test + tags: + - base + stage: code style + allow_failure: true script: - - buildout -qc $BUILDOUT buildout:directory=$CI_PROJECT_DIR install xenon - - xenon -bC -mB -aB -i bin,eggs,old-eggs,downloads,lib . + - cd bench + - source bin/activate + - xenon -bC -mB -aB -i bin,eggs,downloads,lib,parts . + dependencies: + - make diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 579857ee15feb39a8acec2199aa5505cb99e79b6..e3344df941991b59bc926f4cf8373a61ef9f084e 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -2,3 +2,4 @@ * Moisés López * Federico Cruz <federico.cruz@openpyme.mx> * AgustÃn Cruz <agustin.cruz@openpyme.mx> +* Noé Fernando <noe.izquierdo@openpyme.mx> \ No newline at end of file diff --git a/buildout.cfg b/buildout.cfg new file mode 100644 index 0000000000000000000000000000000000000000..d190c7defea46de47e9ee6a693eeda0d92965532 --- /dev/null +++ b/buildout.cfg @@ -0,0 +1,27 @@ +[buildout] +extends = + local.cfg + config/account.cfg + config/mexico.cfg + +# The project name, base for paths +site = pyerp +domain = subdomain.openpyme.mx + +# Adjust to directory-setup of server. (Usually don't needed) +# Relative paths: +filestore = ${buildout:directory}/data_dir + +# The PyERP options +[options] +admin_passwd = 11235813 +xmlrpc_port = 8069 +longpolling_port = 8072 +db_maxconn = 64 +dbfilter = .* +workers = 3 +limit_time_cpu = 86400 +limit_time_real = 86400 +limit_memory_soft = 2684354560 +limit_memory_hard = 3221225472 +sentry_dns = http://a49ee0cdb261441aa28990a5b909fc55:c079095e7aa84bebbd2027157de826a6@sentry.openpyme.mx/7 diff --git a/l10n_mx_facturae/__init__.py b/l10n_mx_facturae/__init__.py index 35e7c9600c556ac0a37452da226bd559cf1e5a03..084335270d09e1c36da8db121e7fde6f39222269 100644 --- a/l10n_mx_facturae/__init__.py +++ b/l10n_mx_facturae/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- from . import models -from . import wizard +from . import report +#from . import wizard diff --git a/l10n_mx_facturae/__manifest__.py b/l10n_mx_facturae/__manifest__.py new file mode 100644 index 0000000000000000000000000000000000000000..040c9ffb87c5a22357f130bf6d331ddd9e8d358a --- /dev/null +++ b/l10n_mx_facturae/__manifest__.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +{ + "name": "Factura Electronica CFDI", + "version": "15.0.3.7.0", + "author": "OpenPyme", + "category": "Localization/Mexico", + "website": "http://www.openpyme.mx/", + "license": "AGPL-3", + "depends": [ + "account_global_discount", + "account_invoice_change_currency", + "l10n_mx_base", + ], + "data": [ + # Security + "security/res_groups.xml", + + # Datas + "data/account_move.xml", + "data/account_payment.xml", + #"data/email_template.xml", + "data/facturae_data.xml", + "data/ir_attachment_facturae_config.xml", + "data/res_partner.xml", + #"data/ir_cron.xml", + + # Views + "views/account_move.xml", + "views/account_payment.xml", + "views/res_company.xml", + "views/res_partner.xml", + + # templates + "templates/account_move.xml", + "templates/account_payment.xml", + + #Views Wizards + #"wizard/account_invoice_refund.xml", + ], + "demo": [ + "demo/demo_res_partner.xml", + "demo/demo_product.xml", + ], + "installable": True, +} diff --git a/l10n_mx_facturae/__openerp__.py b/l10n_mx_facturae/__openerp__.py deleted file mode 100644 index ab41196dd2c3f36b72f74a9de5b013232425023a..0000000000000000000000000000000000000000 --- a/l10n_mx_facturae/__openerp__.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- - -{ - "name": "Factura Electronica CFDI", - "version": "8.0.3.7.0", - "author": "OpenPyme", - "category": "Localization/Mexico", - "website": "http://www.openpyme.mx/", - "license": "AGPL-3", - "depends": [ - "account_cancel", - "account_invoice_discount", - "base_vat", - "base_iso3166", - "l10n_mx_account_tax_category", - "l10n_mx_ir_attachment_facturae", - "l10n_mx_res_partner_bank", - ], - "demo": [], - "data": [ - "security/res_groups.xml", - "views/account_invoice.xml", - "views/account_voucher.xml", - "views/res_partner.xml", - "wizard/account_invoice_refund.xml", - "data/account_invoice.xml", - "data/account_voucher.xml", - "data/ir_cron.xml", - "data/email_template.xml", - "data/ir_attachment_facturae_config.xml", - "data/facturae_data.xml", - ], - "installable": True, -} diff --git a/l10n_mx_facturae/data/account_invoice.xml b/l10n_mx_facturae/data/account_invoice.xml deleted file mode 100644 index d59cdf073035f071afaeb3a09e5b20519e35e311..0000000000000000000000000000000000000000 --- a/l10n_mx_facturae/data/account_invoice.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<openerp> -<data> - - <!-- XML invoice report definition --> - <record id="report_templates_aeroo_account_invoice_cfdi" - model="report.templates.aeroo"> - <field name="name">Account Invoice CFDI XML</field> - <field name="model">account.invoice</field> - <field name="report_name">account.invoice.cfdi.xml</field> - <field - name="report_rml">l10n_mx_facturae/templates/account_invoice.txt</field> - </record> - - <record id="ir_actions_report_xml_account_invioice_cfdi" - model="ir.actions.report.xml"> - <field name="name">Account Invoice CFDI XML</field> - <field name="type">ir.actions.report.xml</field> - <field name="model">account.invoice</field> - <field name="report_name">account.invoice.cfdi.xml</field> - <field name="report_type">aeroo</field> - <field name="in_format">genshi-raw</field> - <field name="out_format" ref="report_aeroo.report_mimetypes_raw" /> - <field name="aeroo_templates_ids" - eval="[(6, 0, [report_templates_aeroo_account_invoice_cfdi])]" /> - <field name="parser_state">default</field> - </record> - -</data> - -<data noupdate="1"> - - <!-- PDF invoice report definition --> - <record id="report_templates_aeroo_pdf_account_invoice_cfdi" - model="report.templates.aeroo"> - <field name="name">Default Invoice Report PDF</field> - <field name="model">account.invoice</field> - <field name="report_name">invoice.report.aaero.pdf</field> - <field name="report_rml">l10n_mx_facturae/report/account_invoice.odt</field> - </record> - - <record id="ir_actions_report_pdf_account_invioice_cfdi" - model="ir.actions.report.xml"> - <field name="name">Account Invoice CFDI PDF</field> - <field name="type">ir.actions.report.xml</field> - <field name="model">account.invoice</field> - <field name="report_name">invoice.report.aaero.pdf</field> - <field name="report_type">aeroo</field> - <field name="in_format">oo-odt</field> - <field name="out_format" ref="report_aeroo.report_mimetypes_pdf_odt" /> - <field name="aeroo_templates_ids" - eval="[(6, 0, [report_templates_aeroo_pdf_account_invoice_cfdi])]" /> - <field name="parser_loc">l10n_mx_ir_attachment_facturae/report/generate_qr.py</field> - <field name="parser_state">loc</field> - </record> - -</data> -</openerp> diff --git a/l10n_mx_facturae/data/account_move.xml b/l10n_mx_facturae/data/account_move.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b2fd495bf7b1fb8392416cc66e77c84e4c80107 --- /dev/null +++ b/l10n_mx_facturae/data/account_move.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <!-- XML move report definition --> + <record id="ir_actions_report_xml_account_move_cfdi" model="ir.actions.report"> + <field name="name">Account Move CFDI XML</field> + <field name="model">account.move</field> + <field name="report_type">qweb-xml</field> + <field name="report_name">l10n_mx_facturae.account_move</field> + <field name="xml_declaration">true</field> + <field name="xsd_schema">PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjx4czpzY2hlbWEgeG1sbnM6Y2ZkaT0iaHR0cDovL3d3dy5zYXQuZ29iLm14L2NmZC80IiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOmNhdENGREk9Imh0dHA6Ly93d3cuc2F0LmdvYi5teC9zaXRpb19pbnRlcm5ldC9jZmQvY2F0YWxvZ29zIiB4bWxuczp0ZENGREk9Imh0dHA6Ly93d3cuc2F0LmdvYi5teC9zaXRpb19pbnRlcm5ldC9jZmQvdGlwb0RhdG9zL3RkQ0ZESSIgdGFyZ2V0TmFtZXNwYWNlPSJodHRwOi8vd3d3LnNhdC5nb2IubXgvY2ZkLzQiIGVsZW1lbnRGb3JtRGVmYXVsdD0icXVhbGlmaWVkIiBhdHRyaWJ1dGVGb3JtRGVmYXVsdD0idW5xdWFsaWZpZWQiPg0KCTx4czppbXBvcnQgbmFtZXNwYWNlPSJodHRwOi8vd3d3LnNhdC5nb2IubXgvc2l0aW9faW50ZXJuZXQvY2ZkL2NhdGFsb2dvcyIgc2NoZW1hTG9jYXRpb249Imh0dHA6Ly93d3cuc2F0LmdvYi5teC9zaXRpb19pbnRlcm5ldC9jZmQvY2F0YWxvZ29zL2NhdENGREkueHNkIi8+DQoJPHhzOmltcG9ydCBuYW1lc3BhY2U9Imh0dHA6Ly93d3cuc2F0LmdvYi5teC9zaXRpb19pbnRlcm5ldC9jZmQvdGlwb0RhdG9zL3RkQ0ZESSIgc2NoZW1hTG9jYXRpb249Imh0dHA6Ly93d3cuc2F0LmdvYi5teC9zaXRpb19pbnRlcm5ldC9jZmQvdGlwb0RhdG9zL3RkQ0ZESS90ZENGREkueHNkIi8+DQoJPHhzOmVsZW1lbnQgbmFtZT0iQ29tcHJvYmFudGUiPg0KCQk8eHM6YW5ub3RhdGlvbj4NCgkJCTx4czpkb2N1bWVudGF0aW9uPkVzdMOhbmRhciBkZSBDb21wcm9iYW50ZSBGaXNjYWwgRGlnaXRhbCBwb3IgSW50ZXJuZXQuPC94czpkb2N1bWVudGF0aW9uPg0KCQk8L3hzOmFubm90YXRpb24+DQoJCTx4czpjb21wbGV4VHlwZT4NCgkJCTx4czpzZXF1ZW5jZT4NCgkJCQk8eHM6ZWxlbWVudCBuYW1lPSJJbmZvcm1hY2lvbkdsb2JhbCIgbWluT2NjdXJzPSIwIj4NCgkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIGNvbmRpY2lvbmFsIHBhcmEgcHJlY2lzYXIgbGEgaW5mb3JtYWNpw7NuIHJlbGFjaW9uYWRhIGNvbiBlbCBjb21wcm9iYW50ZSBnbG9iYWwuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iUGVyaW9kaWNpZGFkIiB0eXBlPSJjYXRDRkRJOmNfUGVyaW9kaWNpZGFkIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgZXhwcmVzYXIgZWwgcGVyw61vZG8gYWwgcXVlIGNvcnJlc3BvbmRlIGxhIGluZm9ybWFjacOzbiBkZWwgY29tcHJvYmFudGUgZ2xvYmFsLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iTWVzZXMiIHR5cGU9ImNhdENGREk6Y19NZXNlcyIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGV4cHJlc2FyIGVsIG1lcyBvIGxvcyBtZXNlcyBhbCBxdWUgY29ycmVzcG9uZGUgbGEgaW5mb3JtYWNpw7NuIGRlbCBjb21wcm9iYW50ZSBnbG9iYWwuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJBw7FvIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgZXhwcmVzYXIgZWwgYcOxbyBhbCBxdWUgY29ycmVzcG9uZGUgbGEgaW5mb3JtYWNpw7NuIGRlbCBjb21wcm9iYW50ZSBnbG9iYWwuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOnNob3J0Ij4NCgkJCQkJCQkJCTx4czptaW5JbmNsdXNpdmUgdmFsdWU9IjIwMjEiLz4NCgkJCQkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCTwveHM6ZWxlbWVudD4NCgkJCQk8eHM6ZWxlbWVudCBuYW1lPSJDZmRpUmVsYWNpb25hZG9zIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIj4NCgkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIG9wY2lvbmFsIHBhcmEgcHJlY2lzYXIgbGEgaW5mb3JtYWNpw7NuIGRlIGxvcyBjb21wcm9iYW50ZXMgcmVsYWNpb25hZG9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQk8eHM6Y29tcGxleFR5cGU+DQoJCQkJCQk8eHM6c2VxdWVuY2U+DQoJCQkJCQkJPHhzOmVsZW1lbnQgbmFtZT0iQ2ZkaVJlbGFjaW9uYWRvIiBtYXhPY2N1cnM9InVuYm91bmRlZCI+DQoJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+Tm9kbyByZXF1ZXJpZG8gcGFyYSBwcmVjaXNhciBsYSBpbmZvcm1hY2nDs24gZGUgbG9zIGNvbXByb2JhbnRlcyByZWxhY2lvbmFkb3MuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iVVVJRCIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHJlZ2lzdHJhciBlbCBmb2xpbyBmaXNjYWwgKFVVSUQpIGRlIHVuIENGREkgcmVsYWNpb25hZG8gY29uIGVsIHByZXNlbnRlIGNvbXByb2JhbnRlLCBwb3IgZWplbXBsbzogU2kgZWwgQ0ZESSByZWxhY2lvbmFkbyBlcyB1biBjb21wcm9iYW50ZSBkZSB0cmFzbGFkbyBxdWUgc2lydmUgcGFyYSByZWdpc3RyYXIgZWwgbW92aW1pZW50byBkZSBsYSBtZXJjYW5jw61hLiBTaSBlc3RlIGNvbXByb2JhbnRlIHNlIHVzYSBjb21vIG5vdGEgZGUgY3LDqWRpdG8gbyBub3RhIGRlIGTDqWJpdG8gZGVsIGNvbXByb2JhbnRlIHJlbGFjaW9uYWRvLiBTaSBlc3RlIGNvbXByb2JhbnRlIGVzIHVuYSBkZXZvbHVjacOzbiBzb2JyZSBlbCBjb21wcm9iYW50ZSByZWxhY2lvbmFkby4gU2kgw6lzdGUgc3VzdGl0dXllIGEgdW5hIGZhY3R1cmEgY2FuY2VsYWRhLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJCQkJCQkJPHhzOmxlbmd0aCB2YWx1ZT0iMzYiLz4NCgkJCQkJCQkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQkJCQkJCQkJPHhzOnBhdHRlcm4gdmFsdWU9IlthLWYwLTlBLUZdezh9LVthLWYwLTlBLUZdezR9LVthLWYwLTlBLUZdezR9LVthLWYwLTlBLUZdezR9LVthLWYwLTlBLUZdezEyfSIvPg0KCQkJCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCTwveHM6ZWxlbWVudD4NCgkJCQkJCTwveHM6c2VxdWVuY2U+DQoJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlRpcG9SZWxhY2lvbiIgdHlwZT0iY2F0Q0ZESTpjX1RpcG9SZWxhY2lvbiIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGluZGljYXIgbGEgY2xhdmUgZGUgbGEgcmVsYWNpw7NuIHF1ZSBleGlzdGUgZW50cmUgw6lzdGUgcXVlIHNlIGVzdMOhIGdlbmVyYW5kbyB5IGVsIG8gbG9zIENGREkgcHJldmlvcy48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCTwveHM6Y29tcGxleFR5cGU+DQoJCQkJPC94czplbGVtZW50Pg0KCQkJCTx4czplbGVtZW50IG5hbWU9IkVtaXNvciI+DQoJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+Tm9kbyByZXF1ZXJpZG8gcGFyYSBleHByZXNhciBsYSBpbmZvcm1hY2nDs24gZGVsIGNvbnRyaWJ1eWVudGUgZW1pc29yIGRlbCBjb21wcm9iYW50ZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJSZmMiIHR5cGU9InRkQ0ZESTp0X1JGQyIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHJlZ2lzdHJhciBsYSBDbGF2ZSBkZWwgUmVnaXN0cm8gRmVkZXJhbCBkZSBDb250cmlidXllbnRlcyBjb3JyZXNwb25kaWVudGUgYWwgY29udHJpYnV5ZW50ZSBlbWlzb3IgZGVsIGNvbXByb2JhbnRlLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iTm9tYnJlIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgcmVnaXN0cmFyIGVsIG5vbWJyZSwgZGVub21pbmFjacOzbiBvIHJhesOzbiBzb2NpYWwgZGVsIGNvbnRyaWJ1eWVudGUgaW5zY3JpdG8gZW4gZWwgUkZDLCBkZWwgZW1pc29yIGRlbCBjb21wcm9iYW50ZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjMwMCIvPg0KCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQk8eHM6cGF0dGVybiB2YWx1ZT0iW158XXsxLDMwMH0iLz4NCgkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iUmVnaW1lbkZpc2NhbCIgdHlwZT0iY2F0Q0ZESTpjX1JlZ2ltZW5GaXNjYWwiIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBpbmNvcnBvcmFyIGxhIGNsYXZlIGRlbCByw6lnaW1lbiBkZWwgY29udHJpYnV5ZW50ZSBlbWlzb3IgYWwgcXVlIGFwbGljYXLDoSBlbCBlZmVjdG8gZmlzY2FsIGRlIGVzdGUgY29tcHJvYmFudGUuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJGYWNBdHJBZHF1aXJlbnRlIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSBleHByZXNhciBlbCBuw7ptZXJvIGRlIG9wZXJhY2nDs24gcHJvcG9yY2lvbmFkbyBwb3IgZWwgU0FUIGN1YW5kbyBzZSB0cmF0ZSBkZSB1biBjb21wcm9iYW50ZSBhIHRyYXbDqXMgZGUgdW4gUENFQ0ZESSBvIHVuIFBDR0NGRElTUC48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQkJCQkJPHhzOmxlbmd0aCB2YWx1ZT0iMTAiLz4NCgkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbMC05XXsxMH0iLz4NCgkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJPHhzOmVsZW1lbnQgbmFtZT0iUmVjZXB0b3IiPg0KCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gcmVxdWVyaWRvIHBhcmEgcHJlY2lzYXIgbGEgaW5mb3JtYWNpw7NuIGRlbCBjb250cmlidXllbnRlIHJlY2VwdG9yIGRlbCBjb21wcm9iYW50ZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJSZmMiIHR5cGU9InRkQ0ZESTp0X1JGQyIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHJlZ2lzdHJhciBsYSBDbGF2ZSBkZWwgUmVnaXN0cm8gRmVkZXJhbCBkZSBDb250cmlidXllbnRlcyBjb3JyZXNwb25kaWVudGUgYWwgY29udHJpYnV5ZW50ZSByZWNlcHRvciBkZWwgY29tcHJvYmFudGUuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJOb21icmUiIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSByZWdpc3RyYXIgZWwgbm9tYnJlKHMpLCBwcmltZXIgYXBlbGxpZG8sIHNlZ3VuZG8gYXBlbGxpZG8sIHNlZ8O6biBjb3JyZXNwb25kYSwgZGVub21pbmFjacOzbiBvIHJhesOzbiBzb2NpYWwgZGVsIGNvbnRyaWJ1eWVudGUsIGluc2NyaXRvIGVuIGVsIFJGQywgZGVsIHJlY2VwdG9yIGRlbCBjb21wcm9iYW50ZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjMwMCIvPg0KCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQk8eHM6cGF0dGVybiB2YWx1ZT0iW158XXsxLDMwMH0iLz4NCgkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iRG9taWNpbGlvRmlzY2FsUmVjZXB0b3IiIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSByZWdpc3RyYXIgZWwgY8OzZGlnbyBwb3N0YWwgZGVsIGRvbWljaWxpbyBmaXNjYWwgZGVsIHJlY2VwdG9yIGRlbCBjb21wcm9iYW50ZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCTx4czpsZW5ndGggdmFsdWU9IjUiLz4NCgkJCQkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQkJCQkJPHhzOnBhdHRlcm4gdmFsdWU9IlswLTldezV9Ii8+DQoJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlJlc2lkZW5jaWFGaXNjYWwiIHR5cGU9ImNhdENGREk6Y19QYWlzIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSByZWdpc3RyYXIgbGEgY2xhdmUgZGVsIHBhw61zIGRlIHJlc2lkZW5jaWEgcGFyYSBlZmVjdG9zIGZpc2NhbGVzIGRlbCByZWNlcHRvciBkZWwgY29tcHJvYmFudGUsIGN1YW5kbyBzZSB0cmF0ZSBkZSB1biBleHRyYW5qZXJvLCB5IHF1ZSBlcyBjb25mb3JtZSBjb24gbGEgZXNwZWNpZmljYWNpw7NuIElTTyAzMTY2LTEgYWxwaGEtMy4gRXMgcmVxdWVyaWRvIGN1YW5kbyBzZSBpbmNsdXlhIGVsIGNvbXBsZW1lbnRvIGRlIGNvbWVyY2lvIGV4dGVyaW9yIG8gc2UgcmVnaXN0cmUgZWwgYXRyaWJ1dG8gTnVtUmVnSWRUcmliLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iTnVtUmVnSWRUcmliIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSBleHByZXNhciBlbCBuw7ptZXJvIGRlIHJlZ2lzdHJvIGRlIGlkZW50aWRhZCBmaXNjYWwgZGVsIHJlY2VwdG9yIGN1YW5kbyBzZWEgcmVzaWRlbnRlIGVuIGVsIGV4dHJhbmplcm8uIEVzIHJlcXVlcmlkbyBjdWFuZG8gc2UgaW5jbHV5YSBlbCBjb21wbGVtZW50byBkZSBjb21lcmNpbyBleHRlcmlvci48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjQwIi8+DQoJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iUmVnaW1lbkZpc2NhbFJlY2VwdG9yIiB0eXBlPSJjYXRDRkRJOmNfUmVnaW1lbkZpc2NhbCIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGluY29ycG9yYXIgbGEgY2xhdmUgZGVsIHLDqWdpbWVuIGZpc2NhbCBkZWwgY29udHJpYnV5ZW50ZSByZWNlcHRvciBhbCBxdWUgYXBsaWNhcsOhIGVsIGVmZWN0byBmaXNjYWwgZGUgZXN0ZSBjb21wcm9iYW50ZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlVzb0NGREkiIHR5cGU9ImNhdENGREk6Y19Vc29DRkRJIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgZXhwcmVzYXIgbGEgY2xhdmUgZGVsIHVzbyBxdWUgZGFyw6EgYSBlc3RhIGZhY3R1cmEgZWwgcmVjZXB0b3IgZGVsIENGREkuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCTwveHM6ZWxlbWVudD4NCgkJCQk8eHM6ZWxlbWVudCBuYW1lPSJDb25jZXB0b3MiPg0KCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gcmVxdWVyaWRvIHBhcmEgbGlzdGFyIGxvcyBjb25jZXB0b3MgY3ViaWVydG9zIHBvciBlbCBjb21wcm9iYW50ZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJPHhzOnNlcXVlbmNlPg0KCQkJCQkJCTx4czplbGVtZW50IG5hbWU9IkNvbmNlcHRvIiBtYXhPY2N1cnM9InVuYm91bmRlZCI+DQoJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+Tm9kbyByZXF1ZXJpZG8gcGFyYSByZWdpc3RyYXIgbGEgaW5mb3JtYWNpw7NuIGRldGFsbGFkYSBkZSB1biBiaWVuIG8gc2VydmljaW8gYW1wYXJhZG8gZW4gZWwgY29tcHJvYmFudGUuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCTx4czpzZXF1ZW5jZT4NCgkJCQkJCQkJCQk8eHM6ZWxlbWVudCBuYW1lPSJJbXB1ZXN0b3MiIG1pbk9jY3Vycz0iMCI+DQoJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+Tm9kbyBjb25kaWNpb25hbCBwYXJhIGNhcHR1cmFyIGxvcyBpbXB1ZXN0b3MgYXBsaWNhYmxlcyBhbCBwcmVzZW50ZSBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJPHhzOnNlcXVlbmNlPg0KCQkJCQkJCQkJCQkJCTx4czplbGVtZW50IG5hbWU9IlRyYXNsYWRvcyIgbWluT2NjdXJzPSIwIj4NCgkJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIG9wY2lvbmFsIHBhcmEgYXNlbnRhciBsb3MgaW1wdWVzdG9zIHRyYXNsYWRhZG9zIGFwbGljYWJsZXMgYWwgcHJlc2VudGUgY29uY2VwdG8uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQkJCQkJCTx4czpzZXF1ZW5jZT4NCgkJCQkJCQkJCQkJCQkJCQk8eHM6ZWxlbWVudCBuYW1lPSJUcmFzbGFkbyIgbWF4T2NjdXJzPSJ1bmJvdW5kZWQiPg0KCQkJCQkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gcmVxdWVyaWRvIHBhcmEgYXNlbnRhciBsYSBpbmZvcm1hY2nDs24gZGV0YWxsYWRhIGRlIHVuIHRyYXNsYWRvIGRlIGltcHVlc3RvcyBhcGxpY2FibGUgYWwgcHJlc2VudGUgY29uY2VwdG8uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iQmFzZSIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHNlw7FhbGFyIGxhIGJhc2UgcGFyYSBlbCBjw6FsY3VsbyBkZWwgaW1wdWVzdG8sIGxhIGRldGVybWluYWNpw7NuIGRlIGxhIGJhc2Ugc2UgcmVhbGl6YSBkZSBhY3VlcmRvIGNvbiBsYXMgZGlzcG9zaWNpb25lcyBmaXNjYWxlcyB2aWdlbnRlcy4gTm8gc2UgcGVybWl0ZW4gdmFsb3JlcyBuZWdhdGl2b3MuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOmRlY2ltYWwiPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmZyYWN0aW9uRGlnaXRzIHZhbHVlPSI2Ii8+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6bWluSW5jbHVzaXZlIHZhbHVlPSIwLjAwMDAwMSIvPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkltcHVlc3RvIiB0eXBlPSJjYXRDRkRJOmNfSW1wdWVzdG8iIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBzZcOxYWxhciBsYSBjbGF2ZSBkZWwgdGlwbyBkZSBpbXB1ZXN0byB0cmFzbGFkYWRvIGFwbGljYWJsZSBhbCBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlRpcG9GYWN0b3IiIHR5cGU9ImNhdENGREk6Y19UaXBvRmFjdG9yIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgc2XDsWFsYXIgbGEgY2xhdmUgZGVsIHRpcG8gZGUgZmFjdG9yIHF1ZSBzZSBhcGxpY2EgYSBsYSBiYXNlIGRlbCBpbXB1ZXN0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlRhc2FPQ3VvdGEiIHVzZT0ib3B0aW9uYWwiPg0KCQkJCQkJCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBjb25kaWNpb25hbCBwYXJhIHNlw7FhbGFyIGVsIHZhbG9yIGRlIGxhIHRhc2EgbyBjdW90YSBkZWwgaW1wdWVzdG8gcXVlIHNlIHRyYXNsYWRhIHBhcmEgZWwgcHJlc2VudGUgY29uY2VwdG8uIEVzIHJlcXVlcmlkbyBjdWFuZG8gZWwgYXRyaWJ1dG8gVGlwb0ZhY3RvciB0ZW5nYSB1bmEgY2xhdmUgcXVlIGNvcnJlc3BvbmRhIGEgVGFzYSBvIEN1b3RhLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpkZWNpbWFsIj4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpmcmFjdGlvbkRpZ2l0cyB2YWx1ZT0iNiIvPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOm1pbkluY2x1c2l2ZSB2YWx1ZT0iMC4wMDAwMDAiLz4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJJbXBvcnRlIiB0eXBlPSJ0ZENGREk6dF9JbXBvcnRlIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSBzZcOxYWxhciBlbCBpbXBvcnRlIGRlbCBpbXB1ZXN0byB0cmFzbGFkYWRvIHF1ZSBhcGxpY2EgYWwgY29uY2VwdG8uIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLiBFcyByZXF1ZXJpZG8gY3VhbmRvIFRpcG9GYWN0b3Igc2VhIFRhc2EgbyBDdW90YS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQkJCQkJCTwveHM6Y29tcGxleFR5cGU+DQoJCQkJCQkJCQkJCQkJCQkJPC94czplbGVtZW50Pg0KCQkJCQkJCQkJCQkJCQkJPC94czpzZXF1ZW5jZT4NCgkJCQkJCQkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJCQkJCQkJPHhzOmVsZW1lbnQgbmFtZT0iUmV0ZW5jaW9uZXMiIG1pbk9jY3Vycz0iMCI+DQoJCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+Tm9kbyBvcGNpb25hbCBwYXJhIGFzZW50YXIgbG9zIGltcHVlc3RvcyByZXRlbmlkb3MgYXBsaWNhYmxlcyBhbCBwcmVzZW50ZSBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJCQkJPHhzOnNlcXVlbmNlPg0KCQkJCQkJCQkJCQkJCQkJCTx4czplbGVtZW50IG5hbWU9IlJldGVuY2lvbiIgbWF4T2NjdXJzPSJ1bmJvdW5kZWQiPg0KCQkJCQkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gcmVxdWVyaWRvIHBhcmEgYXNlbnRhciBsYSBpbmZvcm1hY2nDs24gZGV0YWxsYWRhIGRlIHVuYSByZXRlbmNpw7NuIGRlIGltcHVlc3RvcyBhcGxpY2FibGUgYWwgcHJlc2VudGUgY29uY2VwdG8uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iQmFzZSIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHNlw7FhbGFyIGxhIGJhc2UgcGFyYSBlbCBjw6FsY3VsbyBkZSBsYSByZXRlbmNpw7NuLCBsYSBkZXRlcm1pbmFjacOzbiBkZSBsYSBiYXNlIHNlIHJlYWxpemEgZGUgYWN1ZXJkbyBjb24gbGFzIGRpc3Bvc2ljaW9uZXMgZmlzY2FsZXMgdmlnZW50ZXMuIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpkZWNpbWFsIj4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpmcmFjdGlvbkRpZ2l0cyB2YWx1ZT0iNiIvPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOm1pbkluY2x1c2l2ZSB2YWx1ZT0iMC4wMDAwMDEiLz4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJJbXB1ZXN0byIgdHlwZT0iY2F0Q0ZESTpjX0ltcHVlc3RvIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgc2XDsWFsYXIgbGEgY2xhdmUgZGVsIHRpcG8gZGUgaW1wdWVzdG8gcmV0ZW5pZG8gYXBsaWNhYmxlIGFsIGNvbmNlcHRvLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iVGlwb0ZhY3RvciIgdHlwZT0iY2F0Q0ZESTpjX1RpcG9GYWN0b3IiIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBzZcOxYWxhciBsYSBjbGF2ZSBkZWwgdGlwbyBkZSBmYWN0b3IgcXVlIHNlIGFwbGljYSBhIGxhIGJhc2UgZGVsIGltcHVlc3RvLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iVGFzYU9DdW90YSIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHNlw7FhbGFyIGxhIHRhc2EgbyBjdW90YSBkZWwgaW1wdWVzdG8gcXVlIHNlIHJldGllbmUgcGFyYSBlbCBwcmVzZW50ZSBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6ZGVjaW1hbCI+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czptaW5JbmNsdXNpdmUgdmFsdWU9IjAuMDAwMDAwIi8+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCQk8eHM6ZnJhY3Rpb25EaWdpdHMgdmFsdWU9IjYiLz4NCgkJCQkJCQkJCQkJCQkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iSW1wb3J0ZSIgdHlwZT0idGRDRkRJOnRfSW1wb3J0ZSIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHNlw7FhbGFyIGVsIGltcG9ydGUgZGVsIGltcHVlc3RvIHJldGVuaWRvIHF1ZSBhcGxpY2EgYWwgY29uY2VwdG8uIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJCQkJCQkJCQk8L3hzOnNlcXVlbmNlPg0KCQkJCQkJCQkJCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJCTwveHM6ZWxlbWVudD4NCgkJCQkJCQkJCQkJCTwveHM6c2VxdWVuY2U+DQoJCQkJCQkJCQkJCTwveHM6Y29tcGxleFR5cGU+DQoJCQkJCQkJCQkJPC94czplbGVtZW50Pg0KCQkJCQkJCQkJCTx4czplbGVtZW50IG5hbWU9IkFDdWVudGFUZXJjZXJvcyIgbWluT2NjdXJzPSIwIj4NCgkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIG9wY2lvbmFsIHBhcmEgcmVnaXN0cmFyIGluZm9ybWFjacOzbiBkZWwgY29udHJpYnV5ZW50ZSBUZXJjZXJvLCBhIGN1ZW50YSBkZWwgcXVlIHNlIHJlYWxpemEgbGEgb3BlcmFjacOzbi48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJSZmNBQ3VlbnRhVGVyY2Vyb3MiIHR5cGU9InRkQ0ZESTp0X1JGQyIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHJlZ2lzdHJhciBsYSBDbGF2ZSBkZWwgUmVnaXN0cm8gRmVkZXJhbCBkZSBDb250cmlidXllbnRlcyBkZWwgY29udHJpYnV5ZW50ZSBUZXJjZXJvLCBhIGN1ZW50YSBkZWwgcXVlIHNlIHJlYWxpemEgbGEgb3BlcmFjacOzbi48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9Ik5vbWJyZUFDdWVudGFUZXJjZXJvcyIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHJlZ2lzdHJhciBlbCBub21icmUsIGRlbm9taW5hY2nDs24gbyByYXrDs24gc29jaWFsIGRlbCBjb250cmlidXllbnRlIFRlcmNlcm8gY29ycmVzcG9uZGllbnRlIGNvbiBlbCBSZmMsIGEgY3VlbnRhIGRlbCBxdWUgc2UgcmVhbGl6YSBsYSBvcGVyYWNpw7NuLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJCQkJCQkJCQkJPHhzOm1pbkxlbmd0aCB2YWx1ZT0iMSIvPg0KCQkJCQkJCQkJCQkJCQkJPHhzOm1heExlbmd0aCB2YWx1ZT0iMzAwIi8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbXnxdezEsMzAwfSIvPg0KCQkJCQkJCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJSZWdpbWVuRmlzY2FsQUN1ZW50YVRlcmNlcm9zIiB0eXBlPSJjYXRDRkRJOmNfUmVnaW1lbkZpc2NhbCIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGluY29ycG9yYXIgbGEgY2xhdmUgZGVsIHLDqWdpbWVuIGRlbCBjb250cmlidXllbnRlIFRlcmNlcm8sIGEgY3VlbnRhIGRlbCBxdWUgc2UgcmVhbGl6YSBsYSBvcGVyYWNpw7NuLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iRG9taWNpbGlvRmlzY2FsQUN1ZW50YVRlcmNlcm9zIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgaW5jb3Jwb3JhciBlbCBjw7NkaWdvIHBvc3RhbCBkZWwgZG9taWNpbGlvIGZpc2NhbCBkZWwgVGVyY2VybywgYSBjdWVudGEgZGVsIHF1ZSBzZSByZWFsaXphIGxhIG9wZXJhY2nDs24uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOnN0cmluZyI+DQoJCQkJCQkJCQkJCQkJCQk8eHM6bGVuZ3RoIHZhbHVlPSI1Ii8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbMC05XXs1fSIvPg0KCQkJCQkJCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCTwveHM6ZWxlbWVudD4NCgkJCQkJCQkJCQk8eHM6ZWxlbWVudCBuYW1lPSJJbmZvcm1hY2lvbkFkdWFuZXJhIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIj4NCgkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIG9wY2lvbmFsIHBhcmEgaW50cm9kdWNpciBsYSBpbmZvcm1hY2nDs24gYWR1YW5lcmEgYXBsaWNhYmxlIGN1YW5kbyBzZSB0cmF0ZSBkZSB2ZW50YXMgZGUgcHJpbWVyYSBtYW5vIGRlIG1lcmNhbmPDrWFzIGltcG9ydGFkYXMgbyBzZSB0cmF0ZSBkZSBvcGVyYWNpb25lcyBkZSBjb21lcmNpbyBleHRlcmlvciBjb24gYmllbmVzIG8gc2VydmljaW9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQk8eHM6Y29tcGxleFR5cGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9Ik51bWVyb1BlZGltZW50byIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGV4cHJlc2FyIGVsIG7Dum1lcm8gZGVsIHBlZGltZW50byBxdWUgYW1wYXJhIGxhIGltcG9ydGFjacOzbiBkZWwgYmllbiBxdWUgc2UgZXhwcmVzYSBlbiBlbCBzaWd1aWVudGUgZm9ybWF0bzogw7psdGltb3MgMiBkw61naXRvcyBkZWwgYcOxbyBkZSB2YWxpZGFjacOzbiBzZWd1aWRvcyBwb3IgZG9zIGVzcGFjaW9zLCAyIGTDrWdpdG9zIGRlIGxhIGFkdWFuYSBkZSBkZXNwYWNobyBzZWd1aWRvcyBwb3IgZG9zIGVzcGFjaW9zLCA0IGTDrWdpdG9zIGRlbCBuw7ptZXJvIGRlIGxhIHBhdGVudGUgc2VndWlkb3MgcG9yIGRvcyBlc3BhY2lvcywgMSBkw61naXRvIHF1ZSBjb3JyZXNwb25kZSBhbCDDumx0aW1vIGTDrWdpdG8gZGVsIGHDsW8gZW4gY3Vyc28sIHNhbHZvIHF1ZSBzZSB0cmF0ZSBkZSB1biBwZWRpbWVudG8gY29uc29saWRhZG8gaW5pY2lhZG8gZW4gZWwgYcOxbyBpbm1lZGlhdG8gYW50ZXJpb3IgbyBkZWwgcGVkaW1lbnRvIG9yaWdpbmFsIGRlIHVuYSByZWN0aWZpY2FjacOzbiwgc2VndWlkbyBkZSA2IGTDrWdpdG9zIGRlIGxhIG51bWVyYWNpw7NuIHByb2dyZXNpdmEgcG9yIGFkdWFuYS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCQkJCQkJCTx4czpsZW5ndGggdmFsdWU9IjIxIi8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6cGF0dGVybiB2YWx1ZT0iWzAtOV17Mn0gIFswLTldezJ9ICBbMC05XXs0fSAgWzAtOV17N30iLz4NCgkJCQkJCQkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJCQkJPHhzOmVsZW1lbnQgbmFtZT0iQ3VlbnRhUHJlZGlhbCIgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCI+DQoJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+Tm9kbyBvcGNpb25hbCBwYXJhIGFzZW50YXIgZWwgbsO6bWVybyBkZSBjdWVudGEgcHJlZGlhbCBjb24gZWwgcXVlIGZ1ZSByZWdpc3RyYWRvIGVsIGlubXVlYmxlLCBlbiBlbCBzaXN0ZW1hIGNhdGFzdHJhbCBkZSBsYSBlbnRpZGFkIGZlZGVyYXRpdmEgZGUgcXVlIHRyYXRlLCBvIGJpZW4gcGFyYSBpbmNvcnBvcmFyIGxvcyBkYXRvcyBkZSBpZGVudGlmaWNhY2nDs24gZGVsIGNlcnRpZmljYWRvIGRlIHBhcnRpY2lwYWNpw7NuIGlubW9iaWxpYXJpYSBubyBhbW9ydGl6YWJsZS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJOdW1lcm8iIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBwcmVjaXNhciBlbCBuw7ptZXJvIGRlIGxhIGN1ZW50YSBwcmVkaWFsIGRlbCBpbm11ZWJsZSBjdWJpZXJ0byBwb3IgZWwgcHJlc2VudGUgY29uY2VwdG8sIG8gYmllbiBwYXJhIGluY29ycG9yYXIgbG9zIGRhdG9zIGRlIGlkZW50aWZpY2FjacOzbiBkZWwgY2VydGlmaWNhZG8gZGUgcGFydGljaXBhY2nDs24gaW5tb2JpbGlhcmlhIG5vIGFtb3J0aXphYmxlLCB0cmF0w6FuZG9zZSBkZSBhcnJlbmRhbWllbnRvLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJCQkJCQkJCQkJPHhzOm1pbkxlbmd0aCB2YWx1ZT0iMSIvPg0KCQkJCQkJCQkJCQkJCQkJPHhzOm1heExlbmd0aCB2YWx1ZT0iMTUwIi8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbMC05YS16QS1aXXsxLDE1MH0iLz4NCgkJCQkJCQkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJCQkJPHhzOmVsZW1lbnQgbmFtZT0iQ29tcGxlbWVudG9Db25jZXB0byIgbWluT2NjdXJzPSIwIj4NCgkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIG9wY2lvbmFsIGRvbmRlIHNlIGluY2x1eWVuIGxvcyBub2RvcyBjb21wbGVtZW50YXJpb3MgZGUgZXh0ZW5zacOzbiBhbCBjb25jZXB0byBkZWZpbmlkb3MgcG9yIGVsIFNBVCwgZGUgYWN1ZXJkbyBjb24gbGFzIGRpc3Bvc2ljaW9uZXMgcGFydGljdWxhcmVzIHBhcmEgdW4gc2VjdG9yIG8gYWN0aXZpZGFkIGVzcGVjw61maWNhLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQk8eHM6Y29tcGxleFR5cGU+DQoJCQkJCQkJCQkJCQk8eHM6c2VxdWVuY2U+DQoJCQkJCQkJCQkJCQkJPHhzOmFueSBtYXhPY2N1cnM9InVuYm91bmRlZCIvPg0KCQkJCQkJCQkJCQkJPC94czpzZXF1ZW5jZT4NCgkJCQkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJCQkJPHhzOmVsZW1lbnQgbmFtZT0iUGFydGUiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJvdW5kZWQiPg0KCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gb3BjaW9uYWwgcGFyYSBleHByZXNhciBsYXMgcGFydGVzIG8gY29tcG9uZW50ZXMgcXVlIGludGVncmFuIGxhIHRvdGFsaWRhZCBkZWwgY29uY2VwdG8gZXhwcmVzYWRvIGVuIGVsIGNvbXByb2JhbnRlIGZpc2NhbCBkaWdpdGFsIHBvciBJbnRlcm5ldC48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJPHhzOnNlcXVlbmNlPg0KCQkJCQkJCQkJCQkJCTx4czplbGVtZW50IG5hbWU9IkluZm9ybWFjaW9uQWR1YW5lcmEiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJvdW5kZWQiPg0KCQkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gb3BjaW9uYWwgcGFyYSBpbnRyb2R1Y2lyIGxhIGluZm9ybWFjacOzbiBhZHVhbmVyYSBhcGxpY2FibGUgY3VhbmRvIHNlIHRyYXRlIGRlIHZlbnRhcyBkZSBwcmltZXJhIG1hbm8gZGUgbWVyY2FuY8OtYXMgaW1wb3J0YWRhcyBvIHNlIHRyYXRlIGRlIG9wZXJhY2lvbmVzIGRlIGNvbWVyY2lvIGV4dGVyaW9yIGNvbiBiaWVuZXMgbyBzZXJ2aWNpb3MuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iTnVtZXJvUGVkaW1lbnRvIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgZXhwcmVzYXIgZWwgbsO6bWVybyBkZWwgcGVkaW1lbnRvIHF1ZSBhbXBhcmEgbGEgaW1wb3J0YWNpw7NuIGRlbCBiaWVuIHF1ZSBzZSBleHByZXNhIGVuIGVsIHNpZ3VpZW50ZSBmb3JtYXRvOiDDumx0aW1vcyAyIGTDrWdpdG9zIGRlbCBhw7FvIGRlIHZhbGlkYWNpw7NuIHNlZ3VpZG9zIHBvciBkb3MgZXNwYWNpb3MsIDIgZMOtZ2l0b3MgZGUgbGEgYWR1YW5hIGRlIGRlc3BhY2hvIHNlZ3VpZG9zIHBvciBkb3MgZXNwYWNpb3MsIDQgZMOtZ2l0b3MgZGVsIG7Dum1lcm8gZGUgbGEgcGF0ZW50ZSBzZWd1aWRvcyBwb3IgZG9zIGVzcGFjaW9zLCAxIGTDrWdpdG8gcXVlIGNvcnJlc3BvbmRlIGFsIMO6bHRpbW8gZMOtZ2l0byBkZWwgYcOxbyBlbiBjdXJzbywgc2Fsdm8gcXVlIHNlIHRyYXRlIGRlIHVuIHBlZGltZW50byBjb25zb2xpZGFkbyBpbmljaWFkbyBlbiBlbCBhw7FvIGlubWVkaWF0byBhbnRlcmlvciBvIGRlbCBwZWRpbWVudG8gb3JpZ2luYWwgZGUgdW5hIHJlY3RpZmljYWNpw7NuLCBzZWd1aWRvIGRlIDYgZMOtZ2l0b3MgZGUgbGEgbnVtZXJhY2nDs24gcHJvZ3Jlc2l2YSBwb3IgYWR1YW5hLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJCQkJCQkJCQkJCQkJPHhzOmxlbmd0aCB2YWx1ZT0iMjEiLz4NCgkJCQkJCQkJCQkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbMC05XXsyfSAgWzAtOV17Mn0gIFswLTldezR9ICBbMC05XXs3fSIvPg0KCQkJCQkJCQkJCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCQkJCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJCTwveHM6ZWxlbWVudD4NCgkJCQkJCQkJCQkJCTwveHM6c2VxdWVuY2U+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkNsYXZlUHJvZFNlcnYiIHR5cGU9ImNhdENGREk6Y19DbGF2ZVByb2RTZXJ2IiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgZXhwcmVzYXIgbGEgY2xhdmUgZGVsIHByb2R1Y3RvIG8gZGVsIHNlcnZpY2lvIGFtcGFyYWRvIHBvciBsYSBwcmVzZW50ZSBwYXJ0ZS4gRXMgcmVxdWVyaWRvIHkgZGViZW4gdXRpbGl6YXIgbGFzIGNsYXZlcyBkZWwgY2F0w6Fsb2dvIGRlIHByb2R1Y3RvcyB5IHNlcnZpY2lvcywgY3VhbmRvIGxvcyBjb25jZXB0b3MgcXVlIHJlZ2lzdHJlbiBwb3Igc3VzIGFjdGl2aWRhZGVzIGNvcnJlc3BvbmRhbiBjb24gZGljaG9zIGNvbmNlcHRvcy48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9Ik5vSWRlbnRpZmljYWNpb24iIHVzZT0ib3B0aW9uYWwiPg0KCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBvcGNpb25hbCBwYXJhIGV4cHJlc2FyIGVsIG7Dum1lcm8gZGUgc2VyaWUsIG7Dum1lcm8gZGUgcGFydGUgZGVsIGJpZW4gbyBpZGVudGlmaWNhZG9yIGRlbCBwcm9kdWN0byBvIGRlbCBzZXJ2aWNpbyBhbXBhcmFkbyBwb3IgbGEgcHJlc2VudGUgcGFydGUuIE9wY2lvbmFsbWVudGUgc2UgcHVlZGUgdXRpbGl6YXIgY2xhdmVzIGRlbCBlc3TDoW5kYXIgR1RJTi48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCQkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCQkJCQkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjEwMCIvPg0KCQkJCQkJCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6cGF0dGVybiB2YWx1ZT0iW158XXsxLDEwMH0iLz4NCgkJCQkJCQkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iQ2FudGlkYWQiIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBwcmVjaXNhciBsYSBjYW50aWRhZCBkZSBiaWVuZXMgbyBzZXJ2aWNpb3MgZGVsIHRpcG8gcGFydGljdWxhciBkZWZpbmlkbyBwb3IgbGEgcHJlc2VudGUgcGFydGUuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOmRlY2ltYWwiPg0KCQkJCQkJCQkJCQkJCQkJPHhzOmZyYWN0aW9uRGlnaXRzIHZhbHVlPSI2Ii8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6bWluSW5jbHVzaXZlIHZhbHVlPSIwLjAwMDAwMSIvPg0KCQkJCQkJCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlVuaWRhZCIgdXNlPSJvcHRpb25hbCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIG9wY2lvbmFsIHBhcmEgcHJlY2lzYXIgbGEgdW5pZGFkIGRlIG1lZGlkYSBwcm9waWEgZGUgbGEgb3BlcmFjacOzbiBkZWwgZW1pc29yLCBhcGxpY2FibGUgcGFyYSBsYSBjYW50aWRhZCBleHByZXNhZGEgZW4gbGEgcGFydGUuIExhIHVuaWRhZCBkZWJlIGNvcnJlc3BvbmRlciBjb24gbGEgZGVzY3JpcGNpw7NuIGRlIGxhIHBhcnRlLiA8L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCQkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCQkJCQkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjIwIi8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbXnxdezEsMjB9Ii8+DQoJCQkJCQkJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkRlc2NyaXBjaW9uIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgcHJlY2lzYXIgbGEgZGVzY3JpcGNpw7NuIGRlbCBiaWVuIG8gc2VydmljaW8gY3ViaWVydG8gcG9yIGxhIHByZXNlbnRlIHBhcnRlLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJCQkJCQkJCQkJPHhzOm1pbkxlbmd0aCB2YWx1ZT0iMSIvPg0KCQkJCQkJCQkJCQkJCQkJPHhzOm1heExlbmd0aCB2YWx1ZT0iMTAwMCIvPg0KCQkJCQkJCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6cGF0dGVybiB2YWx1ZT0iW158XXsxLDEwMDB9Ii8+DQoJCQkJCQkJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlZhbG9yVW5pdGFyaW8iIHR5cGU9InRkQ0ZESTp0X0ltcG9ydGUiIHVzZT0ib3B0aW9uYWwiPg0KCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBvcGNpb25hbCBwYXJhIHByZWNpc2FyIGVsIHZhbG9yIG8gcHJlY2lvIHVuaXRhcmlvIGRlbCBiaWVuIG8gc2VydmljaW8gY3ViaWVydG8gcG9yIGxhIHByZXNlbnRlIHBhcnRlLiBObyBzZSBwZXJtaXRlbiB2YWxvcmVzIG5lZ2F0aXZvcy48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkltcG9ydGUiIHR5cGU9InRkQ0ZESTp0X0ltcG9ydGUiIHVzZT0ib3B0aW9uYWwiPg0KCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBvcGNpb25hbCBwYXJhIHByZWNpc2FyIGVsIGltcG9ydGUgdG90YWwgZGUgbG9zIGJpZW5lcyBvIHNlcnZpY2lvcyBkZSBsYSBwcmVzZW50ZSBwYXJ0ZS4gRGViZSBzZXIgZXF1aXZhbGVudGUgYWwgcmVzdWx0YWRvIGRlIG11bHRpcGxpY2FyIGxhIGNhbnRpZGFkIHBvciBlbCB2YWxvciB1bml0YXJpbyBleHByZXNhZG8gZW4gbGEgcGFydGUuIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJCQk8L3hzOnNlcXVlbmNlPg0KCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJDbGF2ZVByb2RTZXJ2IiB0eXBlPSJjYXRDRkRJOmNfQ2xhdmVQcm9kU2VydiIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGV4cHJlc2FyIGxhIGNsYXZlIGRlbCBwcm9kdWN0byBvIGRlbCBzZXJ2aWNpbyBhbXBhcmFkbyBwb3IgZWwgcHJlc2VudGUgY29uY2VwdG8uIEVzIHJlcXVlcmlkbyB5IGRlYmVuIHV0aWxpemFyIGxhcyBjbGF2ZXMgZGVsIGNhdMOhbG9nbyBkZSBwcm9kdWN0b3MgeSBzZXJ2aWNpb3MsIGN1YW5kbyBsb3MgY29uY2VwdG9zIHF1ZSByZWdpc3RyZW4gcG9yIHN1cyBhY3RpdmlkYWRlcyBjb3JyZXNwb25kYW4gY29uIGRpY2hvcyBjb25jZXB0b3MuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJOb0lkZW50aWZpY2FjaW9uIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gb3BjaW9uYWwgcGFyYSBleHByZXNhciBlbCBuw7ptZXJvIGRlIHBhcnRlLCBpZGVudGlmaWNhZG9yIGRlbCBwcm9kdWN0byBvIGRlbCBzZXJ2aWNpbywgbGEgY2xhdmUgZGUgcHJvZHVjdG8gbyBzZXJ2aWNpbywgU0tVIG8gZXF1aXZhbGVudGUsIHByb3BpYSBkZSBsYSBvcGVyYWNpw7NuIGRlbCBlbWlzb3IsIGFtcGFyYWRvIHBvciBlbCBwcmVzZW50ZSBjb25jZXB0by4gT3BjaW9uYWxtZW50ZSBzZSBwdWVkZSB1dGlsaXphciBjbGF2ZXMgZGVsIGVzdMOhbmRhciBHVElOLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQkJCQk8eHM6bWluTGVuZ3RoIHZhbHVlPSIxIi8+DQoJCQkJCQkJCQkJCQk8eHM6bWF4TGVuZ3RoIHZhbHVlPSIxMDAiLz4NCgkJCQkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbXnxdezEsMTAwfSIvPg0KCQkJCQkJCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCQkJCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJDYW50aWRhZCIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHByZWNpc2FyIGxhIGNhbnRpZGFkIGRlIGJpZW5lcyBvIHNlcnZpY2lvcyBkZWwgdGlwbyBwYXJ0aWN1bGFyIGRlZmluaWRvIHBvciBlbCBwcmVzZW50ZSBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6ZGVjaW1hbCI+DQoJCQkJCQkJCQkJCQk8eHM6ZnJhY3Rpb25EaWdpdHMgdmFsdWU9IjYiLz4NCgkJCQkJCQkJCQkJCTx4czptaW5JbmNsdXNpdmUgdmFsdWU9IjAuMDAwMDAxIi8+DQoJCQkJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQkJCQkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iQ2xhdmVVbmlkYWQiIHR5cGU9ImNhdENGREk6Y19DbGF2ZVVuaWRhZCIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHByZWNpc2FyIGxhIGNsYXZlIGRlIHVuaWRhZCBkZSBtZWRpZGEgZXN0YW5kYXJpemFkYSBhcGxpY2FibGUgcGFyYSBsYSBjYW50aWRhZCBleHByZXNhZGEgZW4gZWwgY29uY2VwdG8uIExhIHVuaWRhZCBkZWJlIGNvcnJlc3BvbmRlciBjb24gbGEgZGVzY3JpcGNpw7NuIGRlbCBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlVuaWRhZCIgdXNlPSJvcHRpb25hbCI+DQoJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIG9wY2lvbmFsIHBhcmEgcHJlY2lzYXIgbGEgdW5pZGFkIGRlIG1lZGlkYSBwcm9waWEgZGUgbGEgb3BlcmFjacOzbiBkZWwgZW1pc29yLCBhcGxpY2FibGUgcGFyYSBsYSBjYW50aWRhZCBleHByZXNhZGEgZW4gZWwgY29uY2VwdG8uIExhIHVuaWRhZCBkZWJlIGNvcnJlc3BvbmRlciBjb24gbGEgZGVzY3JpcGNpw7NuIGRlbCBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCQkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCQkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjIwIi8+DQoJCQkJCQkJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCQkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbXnxdezEsMjB9Ii8+DQoJCQkJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkRlc2NyaXBjaW9uIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgcHJlY2lzYXIgbGEgZGVzY3JpcGNpw7NuIGRlbCBiaWVuIG8gc2VydmljaW8gY3ViaWVydG8gcG9yIGVsIHByZXNlbnRlIGNvbmNlcHRvLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCQkJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJCQkJCQkJPHhzOm1pbkxlbmd0aCB2YWx1ZT0iMSIvPg0KCQkJCQkJCQkJCQkJPHhzOm1heExlbmd0aCB2YWx1ZT0iMTAwMCIvPg0KCQkJCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQkJCQk8eHM6cGF0dGVybiB2YWx1ZT0iW158XXsxLDEwMDB9Ii8+DQoJCQkJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlZhbG9yVW5pdGFyaW8iIHR5cGU9InRkQ0ZESTp0X0ltcG9ydGUiIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBwcmVjaXNhciBlbCB2YWxvciBvIHByZWNpbyB1bml0YXJpbyBkZWwgYmllbiBvIHNlcnZpY2lvIGN1YmllcnRvIHBvciBlbCBwcmVzZW50ZSBjb25jZXB0by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkltcG9ydGUiIHR5cGU9InRkQ0ZESTp0X0ltcG9ydGUiIHVzZT0icmVxdWlyZWQiPg0KCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBwcmVjaXNhciBlbCBpbXBvcnRlIHRvdGFsIGRlIGxvcyBiaWVuZXMgbyBzZXJ2aWNpb3MgZGVsIHByZXNlbnRlIGNvbmNlcHRvLiBEZWJlIHNlciBlcXVpdmFsZW50ZSBhbCByZXN1bHRhZG8gZGUgbXVsdGlwbGljYXIgbGEgY2FudGlkYWQgcG9yIGVsIHZhbG9yIHVuaXRhcmlvIGV4cHJlc2FkbyBlbiBlbCBjb25jZXB0by4gTm8gc2UgcGVybWl0ZW4gdmFsb3JlcyBuZWdhdGl2b3MuIDwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iRGVzY3VlbnRvIiB0eXBlPSJ0ZENGREk6dF9JbXBvcnRlIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gb3BjaW9uYWwgcGFyYSByZXByZXNlbnRhciBlbCBpbXBvcnRlIGRlIGxvcyBkZXNjdWVudG9zIGFwbGljYWJsZXMgYWwgY29uY2VwdG8uIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iT2JqZXRvSW1wIiB0eXBlPSJjYXRDRkRJOmNfT2JqZXRvSW1wIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgZXhwcmVzYXIgc2kgbGEgb3BlcmFjacOzbiBjb21lcmNpYWwgZXMgb2JqZXRvIG8gbm8gZGUgaW1wdWVzdG8uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCTwveHM6ZWxlbWVudD4NCgkJCQkJCTwveHM6c2VxdWVuY2U+DQoJCQkJCTwveHM6Y29tcGxleFR5cGU+DQoJCQkJPC94czplbGVtZW50Pg0KCQkJCTx4czplbGVtZW50IG5hbWU9IkltcHVlc3RvcyIgbWluT2NjdXJzPSIwIj4NCgkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIGNvbmRpY2lvbmFsIHBhcmEgZXhwcmVzYXIgZWwgcmVzdW1lbiBkZSBsb3MgaW1wdWVzdG9zIGFwbGljYWJsZXMuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCTx4czpzZXF1ZW5jZT4NCgkJCQkJCQk8eHM6ZWxlbWVudCBuYW1lPSJSZXRlbmNpb25lcyIgbWluT2NjdXJzPSIwIj4NCgkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIGNvbmRpY2lvbmFsIHBhcmEgY2FwdHVyYXIgbG9zIGltcHVlc3RvcyByZXRlbmlkb3MgYXBsaWNhYmxlcy4gRXMgcmVxdWVyaWRvIGN1YW5kbyBlbiBsb3MgY29uY2VwdG9zIHNlIHJlZ2lzdHJlIGFsZ8O6biBpbXB1ZXN0byByZXRlbmlkby48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJPHhzOnNlcXVlbmNlPg0KCQkJCQkJCQkJCTx4czplbGVtZW50IG5hbWU9IlJldGVuY2lvbiIgbWF4T2NjdXJzPSJ1bmJvdW5kZWQiPg0KCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gcmVxdWVyaWRvIHBhcmEgbGEgaW5mb3JtYWNpw7NuIGRldGFsbGFkYSBkZSB1bmEgcmV0ZW5jacOzbiBkZSBpbXB1ZXN0byBlc3BlY8OtZmljby48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJJbXB1ZXN0byIgdHlwZT0iY2F0Q0ZESTpjX0ltcHVlc3RvIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgc2XDsWFsYXIgbGEgY2xhdmUgZGVsIHRpcG8gZGUgaW1wdWVzdG8gcmV0ZW5pZG8uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJJbXBvcnRlIiB0eXBlPSJ0ZENGREk6dF9JbXBvcnRlIiB1c2U9InJlcXVpcmVkIj4NCgkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgc2XDsWFsYXIgZWwgbW9udG8gZGVsIGltcHVlc3RvIHJldGVuaWRvLiBObyBzZSBwZXJtaXRlbiB2YWxvcmVzIG5lZ2F0aXZvcy48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCQkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCTwveHM6Y29tcGxleFR5cGU+DQoJCQkJCQkJCQkJPC94czplbGVtZW50Pg0KCQkJCQkJCQkJPC94czpzZXF1ZW5jZT4NCgkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJPHhzOmVsZW1lbnQgbmFtZT0iVHJhc2xhZG9zIiBtaW5PY2N1cnM9IjAiPg0KCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPk5vZG8gY29uZGljaW9uYWwgcGFyYSBjYXB0dXJhciBsb3MgaW1wdWVzdG9zIHRyYXNsYWRhZG9zIGFwbGljYWJsZXMuIEVzIHJlcXVlcmlkbyBjdWFuZG8gZW4gbG9zIGNvbmNlcHRvcyBzZSByZWdpc3RyZSB1biBpbXB1ZXN0byB0cmFzbGFkYWRvLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQkJCQk8eHM6Y29tcGxleFR5cGU+DQoJCQkJCQkJCQk8eHM6c2VxdWVuY2U+DQoJCQkJCQkJCQkJPHhzOmVsZW1lbnQgbmFtZT0iVHJhc2xhZG8iIG1heE9jY3Vycz0idW5ib3VuZGVkIj4NCgkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIHJlcXVlcmlkbyBwYXJhIGxhIGluZm9ybWFjacOzbiBkZXRhbGxhZGEgZGUgdW4gdHJhc2xhZG8gZGUgaW1wdWVzdG8gZXNwZWPDrWZpY28uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCTx4czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iQmFzZSIgdHlwZT0idGRDRkRJOnRfSW1wb3J0ZSIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHNlw7FhbGFyIGxhIHN1bWEgZGUgbG9zIGF0cmlidXRvcyBCYXNlIGRlIGxvcyBjb25jZXB0b3MgZGVsIGltcHVlc3RvIHRyYXNsYWRhZG8uIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iSW1wdWVzdG8iIHR5cGU9ImNhdENGREk6Y19JbXB1ZXN0byIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHNlw7FhbGFyIGxhIGNsYXZlIGRlbCB0aXBvIGRlIGltcHVlc3RvIHRyYXNsYWRhZG8uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJUaXBvRmFjdG9yIiB0eXBlPSJjYXRDRkRJOmNfVGlwb0ZhY3RvciIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJCQkJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIHNlw7FhbGFyIGxhIGNsYXZlIGRlbCB0aXBvIGRlIGZhY3RvciBxdWUgc2UgYXBsaWNhIGEgbGEgYmFzZSBkZWwgaW1wdWVzdG8uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQkJCQkJCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJUYXNhT0N1b3RhIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSBzZcOxYWxhciBlbCB2YWxvciBkZSBsYSB0YXNhIG8gY3VvdGEgZGVsIGltcHVlc3RvIHF1ZSBzZSB0cmFzbGFkYSBwb3IgbG9zIGNvbmNlcHRvcyBhbXBhcmFkb3MgZW4gZWwgY29tcHJvYmFudGUuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJCQkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJCQkJCQkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOmRlY2ltYWwiPg0KCQkJCQkJCQkJCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQkJCQkJCQkJCQk8eHM6bWluSW5jbHVzaXZlIHZhbHVlPSIwLjAwMDAwMCIvPg0KCQkJCQkJCQkJCQkJCQkJPHhzOmZyYWN0aW9uRGlnaXRzIHZhbHVlPSI2Ii8+DQoJCQkJCQkJCQkJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJCQkJCQkJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJCQkJCQkJCQkJPC94czphdHRyaWJ1dGU+DQoJCQkJCQkJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkltcG9ydGUiIHR5cGU9InRkQ0ZESTp0X0ltcG9ydGUiIHVzZT0ib3B0aW9uYWwiPg0KCQkJCQkJCQkJCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQkJCQkJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBjb25kaWNpb25hbCBwYXJhIHNlw7FhbGFyIGxhIHN1bWEgZGVsIGltcG9ydGUgZGVsIGltcHVlc3RvIHRyYXNsYWRhZG8sIGFncnVwYWRvIHBvciBpbXB1ZXN0bywgVGlwb0ZhY3RvciB5IFRhc2FPQ3VvdGEuIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQkJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCQkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQkJCQkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJCQkJCQk8L3hzOnNlcXVlbmNlPg0KCQkJCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCQkJCTwveHM6ZWxlbWVudD4NCgkJCQkJCTwveHM6c2VxdWVuY2U+DQoJCQkJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlRvdGFsSW1wdWVzdG9zUmV0ZW5pZG9zIiB0eXBlPSJ0ZENGREk6dF9JbXBvcnRlIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSBleHByZXNhciBlbCB0b3RhbCBkZSBsb3MgaW1wdWVzdG9zIHJldGVuaWRvcyBxdWUgc2UgZGVzcHJlbmRlbiBkZSBsb3MgY29uY2VwdG9zIGV4cHJlc2Fkb3MgZW4gZWwgY29tcHJvYmFudGUgZmlzY2FsIGRpZ2l0YWwgcG9yIEludGVybmV0LiBObyBzZSBwZXJtaXRlbiB2YWxvcmVzIG5lZ2F0aXZvcy4gRXMgcmVxdWVyaWRvIGN1YW5kbyBlbiBsb3MgY29uY2VwdG9zIHNlIHJlZ2lzdHJlbiBpbXB1ZXN0b3MgcmV0ZW5pZG9zLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCQkJCTx4czphdHRyaWJ1dGUgbmFtZT0iVG90YWxJbXB1ZXN0b3NUcmFzbGFkYWRvcyIgdHlwZT0idGRDRkRJOnRfSW1wb3J0ZSIgdXNlPSJvcHRpb25hbCI+DQoJCQkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIGNvbmRpY2lvbmFsIHBhcmEgZXhwcmVzYXIgZWwgdG90YWwgZGUgbG9zIGltcHVlc3RvcyB0cmFzbGFkYWRvcyBxdWUgc2UgZGVzcHJlbmRlbiBkZSBsb3MgY29uY2VwdG9zIGV4cHJlc2Fkb3MgZW4gZWwgY29tcHJvYmFudGUgZmlzY2FsIGRpZ2l0YWwgcG9yIEludGVybmV0LiBObyBzZSBwZXJtaXRlbiB2YWxvcmVzIG5lZ2F0aXZvcy4gRXMgcmVxdWVyaWRvIGN1YW5kbyBlbiBsb3MgY29uY2VwdG9zIHNlIHJlZ2lzdHJlbiBpbXB1ZXN0b3MgdHJhc2xhZGFkb3MuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJCTwveHM6YXR0cmlidXRlPg0KCQkJCQk8L3hzOmNvbXBsZXhUeXBlPg0KCQkJCTwveHM6ZWxlbWVudD4NCgkJCQk8eHM6ZWxlbWVudCBuYW1lPSJDb21wbGVtZW50byIgbWluT2NjdXJzPSIwIj4NCgkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIG9wY2lvbmFsIGRvbmRlIHNlIGluY2x1eWUgZWwgY29tcGxlbWVudG8gVGltYnJlIEZpc2NhbCBEaWdpdGFsIGRlIG1hbmVyYSBvYmxpZ2F0b3JpYSB5IGxvcyBub2RvcyBjb21wbGVtZW50YXJpb3MgZGV0ZXJtaW5hZG9zIHBvciBlbCBTQVQsIGRlIGFjdWVyZG8gY29uIGxhcyBkaXNwb3NpY2lvbmVzIHBhcnRpY3VsYXJlcyBwYXJhIHVuIHNlY3RvciBvIGFjdGl2aWRhZCBlc3BlY8OtZmljYS48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmNvbXBsZXhUeXBlPg0KCQkJCQkJPHhzOnNlcXVlbmNlPg0KCQkJCQkJCTx4czphbnkgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIvPg0KCQkJCQkJPC94czpzZXF1ZW5jZT4NCgkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQk8L3hzOmVsZW1lbnQ+DQoJCQkJPHhzOmVsZW1lbnQgbmFtZT0iQWRkZW5kYSIgbWluT2NjdXJzPSIwIj4NCgkJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5Ob2RvIG9wY2lvbmFsIHBhcmEgcmVjaWJpciBsYXMgZXh0ZW5zaW9uZXMgYWwgcHJlc2VudGUgZm9ybWF0byBxdWUgc2VhbiBkZSB1dGlsaWRhZCBhbCBjb250cmlidXllbnRlLiBQYXJhIGxhcyByZWdsYXMgZGUgdXNvIGRlbCBtaXNtbywgcmVmZXJpcnNlIGFsIGZvcm1hdG8gb3JpZ2VuLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCQk8eHM6Y29tcGxleFR5cGU+DQoJCQkJCQk8eHM6c2VxdWVuY2U+DQoJCQkJCQkJPHhzOmFueSBtYXhPY2N1cnM9InVuYm91bmRlZCIvPg0KCQkJCQkJPC94czpzZXF1ZW5jZT4NCgkJCQkJPC94czpjb21wbGV4VHlwZT4NCgkJCQk8L3hzOmVsZW1lbnQ+DQoJCQk8L3hzOnNlcXVlbmNlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJWZXJzaW9uIiB1c2U9InJlcXVpcmVkIiBmaXhlZD0iNC4wIj4NCgkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIGNvbiB2YWxvciBwcmVmaWphZG8gYSA0LjAgcXVlIGluZGljYSBsYSB2ZXJzacOzbiBkZWwgZXN0w6FuZGFyIGJham8gZWwgcXVlIHNlIGVuY3VlbnRyYSBleHByZXNhZG8gZWwgY29tcHJvYmFudGUuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOnN0cmluZyI+DQoJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCTx4czphdHRyaWJ1dGUgbmFtZT0iU2VyaWUiIHVzZT0ib3B0aW9uYWwiPg0KCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBvcGNpb25hbCBwYXJhIHByZWNpc2FyIGxhIHNlcmllIHBhcmEgY29udHJvbCBpbnRlcm5vIGRlbCBjb250cmlidXllbnRlLiBFc3RlIGF0cmlidXRvIGFjZXB0YSB1bmEgY2FkZW5hIGRlIGNhcmFjdGVyZXMuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOnN0cmluZyI+DQoJCQkJCQk8eHM6bWluTGVuZ3RoIHZhbHVlPSIxIi8+DQoJCQkJCQk8eHM6bWF4TGVuZ3RoIHZhbHVlPSIyNSIvPg0KCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCQk8eHM6cGF0dGVybiB2YWx1ZT0iW158XXsxLDI1fSIvPg0KCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCTwveHM6YXR0cmlidXRlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJGb2xpbyIgdXNlPSJvcHRpb25hbCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIG9wY2lvbmFsIHBhcmEgY29udHJvbCBpbnRlcm5vIGRlbCBjb250cmlidXllbnRlIHF1ZSBleHByZXNhIGVsIGZvbGlvIGRlbCBjb21wcm9iYW50ZSwgYWNlcHRhIHVuYSBjYWRlbmEgZGUgY2FyYWN0ZXJlcy48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjQwIi8+DQoJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbXnxdezEsNDB9Ii8+DQoJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJPC94czphdHRyaWJ1dGU+DQoJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkZlY2hhIiB0eXBlPSJ0ZENGREk6dF9GZWNoYUgiIHVzZT0icmVxdWlyZWQiPg0KCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSBsYSBleHByZXNpw7NuIGRlIGxhIGZlY2hhIHkgaG9yYSBkZSBleHBlZGljacOzbiBkZWwgQ29tcHJvYmFudGUgRmlzY2FsIERpZ2l0YWwgcG9yIEludGVybmV0LiBTZSBleHByZXNhIGVuIGxhIGZvcm1hIEFBQUEtTU0tRERUaGg6bW06c3MgeSBkZWJlIGNvcnJlc3BvbmRlciBjb24gbGEgaG9yYSBsb2NhbCBkb25kZSBzZSBleHBpZGUgZWwgY29tcHJvYmFudGUuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCTwveHM6YXR0cmlidXRlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJTZWxsbyIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGNvbnRlbmVyIGVsIHNlbGxvIGRpZ2l0YWwgZGVsIGNvbXByb2JhbnRlIGZpc2NhbCwgYWwgcXVlIGhhY2VuIHJlZmVyZW5jaWEgbGFzIHJlZ2xhcyBkZSByZXNvbHVjacOzbiBtaXNjZWzDoW5lYSB2aWdlbnRlLiBFbCBzZWxsbyBkZWJlIHNlciBleHByZXNhZG8gY29tbyB1bmEgY2FkZW5hIGRlIHRleHRvIGVuIGZvcm1hdG8gQmFzZSA2NC48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCTwveHM6YXR0cmlidXRlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJGb3JtYVBhZ28iIHR5cGU9ImNhdENGREk6Y19Gb3JtYVBhZ28iIHVzZT0ib3B0aW9uYWwiPg0KCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBjb25kaWNpb25hbCBwYXJhIGV4cHJlc2FyIGxhIGNsYXZlIGRlIGxhIGZvcm1hIGRlIHBhZ28gZGUgbG9zIGJpZW5lcyBvIHNlcnZpY2lvcyBhbXBhcmFkb3MgcG9yIGVsIGNvbXByb2JhbnRlLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQk8L3hzOmFubm90YXRpb24+DQoJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCTx4czphdHRyaWJ1dGUgbmFtZT0iTm9DZXJ0aWZpY2FkbyIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGV4cHJlc2FyIGVsIG7Dum1lcm8gZGUgc2VyaWUgZGVsIGNlcnRpZmljYWRvIGRlIHNlbGxvIGRpZ2l0YWwgcXVlIGFtcGFyYSBhbCBjb21wcm9iYW50ZSwgZGUgYWN1ZXJkbyBjb24gZWwgYWN1c2UgY29ycmVzcG9uZGllbnRlIGEgMjAgcG9zaWNpb25lcyBvdG9yZ2FkbyBwb3IgZWwgc2lzdGVtYSBkZWwgU0FULjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQk8L3hzOmFubm90YXRpb24+DQoJCQkJPHhzOnNpbXBsZVR5cGU+DQoJCQkJCTx4czpyZXN0cmljdGlvbiBiYXNlPSJ4czpzdHJpbmciPg0KCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJPC94czphdHRyaWJ1dGU+DQoJCQk8eHM6YXR0cmlidXRlIG5hbWU9IkNlcnRpZmljYWRvIiB1c2U9InJlcXVpcmVkIj4NCgkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHF1ZSBzaXJ2ZSBwYXJhIGluY29ycG9yYXIgZWwgY2VydGlmaWNhZG8gZGUgc2VsbG8gZGlnaXRhbCBxdWUgYW1wYXJhIGFsIGNvbXByb2JhbnRlLCBjb21vIHRleHRvIGVuIGZvcm1hdG8gYmFzZSA2NC48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQk8L3hzOnJlc3RyaWN0aW9uPg0KCQkJCTwveHM6c2ltcGxlVHlwZT4NCgkJCTwveHM6YXR0cmlidXRlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJDb25kaWNpb25lc0RlUGFnbyIgdXNlPSJvcHRpb25hbCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIGNvbmRpY2lvbmFsIHBhcmEgZXhwcmVzYXIgbGFzIGNvbmRpY2lvbmVzIGNvbWVyY2lhbGVzIGFwbGljYWJsZXMgcGFyYSBlbCBwYWdvIGRlbCBjb21wcm9iYW50ZSBmaXNjYWwgZGlnaXRhbCBwb3IgSW50ZXJuZXQuIEVzdGUgYXRyaWJ1dG8gcHVlZGUgc2VyIGNvbmRpY2lvbmFkbyBtZWRpYW50ZSBhdHJpYnV0b3MgbyBjb21wbGVtZW50b3MuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOnN0cmluZyI+DQoJCQkJCQk8eHM6d2hpdGVTcGFjZSB2YWx1ZT0iY29sbGFwc2UiLz4NCgkJCQkJCTx4czptaW5MZW5ndGggdmFsdWU9IjEiLz4NCgkJCQkJCTx4czptYXhMZW5ndGggdmFsdWU9IjEwMDAiLz4NCgkJCQkJCTx4czpwYXR0ZXJuIHZhbHVlPSJbXnxdezEsMTAwMH0iLz4NCgkJCQkJPC94czpyZXN0cmljdGlvbj4NCgkJCQk8L3hzOnNpbXBsZVR5cGU+DQoJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCTx4czphdHRyaWJ1dGUgbmFtZT0iU3ViVG90YWwiIHR5cGU9InRkQ0ZESTp0X0ltcG9ydGUiIHVzZT0icmVxdWlyZWQiPg0KCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byByZXF1ZXJpZG8gcGFyYSByZXByZXNlbnRhciBsYSBzdW1hIGRlIGxvcyBpbXBvcnRlcyBkZSBsb3MgY29uY2VwdG9zIGFudGVzIGRlIGRlc2N1ZW50b3MgZSBpbXB1ZXN0by4gTm8gc2UgcGVybWl0ZW4gdmFsb3JlcyBuZWdhdGl2b3MuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCTwveHM6YXR0cmlidXRlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJEZXNjdWVudG8iIHR5cGU9InRkQ0ZESTp0X0ltcG9ydGUiIHVzZT0ib3B0aW9uYWwiPg0KCQkJCTx4czphbm5vdGF0aW9uPg0KCQkJCQk8eHM6ZG9jdW1lbnRhdGlvbj5BdHJpYnV0byBjb25kaWNpb25hbCBwYXJhIHJlcHJlc2VudGFyIGVsIGltcG9ydGUgdG90YWwgZGUgbG9zIGRlc2N1ZW50b3MgYXBsaWNhYmxlcyBhbnRlcyBkZSBpbXB1ZXN0b3MuIE5vIHNlIHBlcm1pdGVuIHZhbG9yZXMgbmVnYXRpdm9zLiBTZSBkZWJlIHJlZ2lzdHJhciBjdWFuZG8gZXhpc3RhbiBjb25jZXB0b3MgY29uIGRlc2N1ZW50by48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJPC94czphdHRyaWJ1dGU+DQoJCQk8eHM6YXR0cmlidXRlIG5hbWU9Ik1vbmVkYSIgdHlwZT0iY2F0Q0ZESTpjX01vbmVkYSIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGlkZW50aWZpY2FyIGxhIGNsYXZlIGRlIGxhIG1vbmVkYSB1dGlsaXphZGEgcGFyYSBleHByZXNhciBsb3MgbW9udG9zLCBjdWFuZG8gc2UgdXNhIG1vbmVkYSBuYWNpb25hbCBzZSByZWdpc3RyYSBNWE4uIENvbmZvcm1lIGNvbiBsYSBlc3BlY2lmaWNhY2nDs24gSVNPIDQyMTcuPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCTwveHM6YXR0cmlidXRlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJUaXBvQ2FtYmlvIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSByZXByZXNlbnRhciBlbCB0aXBvIGRlIGNhbWJpbyBGSVggY29uZm9ybWUgY29uIGxhIG1vbmVkYSB1c2FkYS4gRXMgcmVxdWVyaWRvIGN1YW5kbyBsYSBjbGF2ZSBkZSBtb25lZGEgZXMgZGlzdGludGEgZGUgTVhOIHkgZGUgWFhYLiBFbCB2YWxvciBkZWJlIHJlZmxlamFyIGVsIG7Dum1lcm8gZGUgcGVzb3MgbWV4aWNhbm9zIHF1ZSBlcXVpdmFsZW4gYSB1bmEgdW5pZGFkIGRlIGxhIGRpdmlzYSBzZcOxYWxhZGEgZW4gZWwgYXRyaWJ1dG8gbW9uZWRhLiBTaSBlbCB2YWxvciBlc3TDoSBmdWVyYSBkZWwgcG9yY2VudGFqZSBhcGxpY2FibGUgYSBsYSBtb25lZGEgdG9tYWRvIGRlbCBjYXTDoWxvZ28gY19Nb25lZGEsIGVsIGVtaXNvciBkZWJlIG9idGVuZXIgZGVsIFBBQyBxdWUgdmF5YSBhIHRpbWJyYXIgZWwgQ0ZESSwgZGUgbWFuZXJhIG5vIGF1dG9tw6F0aWNhLCB1bmEgY2xhdmUgZGUgY29uZmlybWFjacOzbiBwYXJhIHJhdGlmaWNhciBxdWUgZWwgdmFsb3IgZXMgY29ycmVjdG8gZSBpbnRlZ3JhciBkaWNoYSBjbGF2ZSBlbiBlbCBhdHJpYnV0byBDb25maXJtYWNpb24uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCQk8eHM6c2ltcGxlVHlwZT4NCgkJCQkJPHhzOnJlc3RyaWN0aW9uIGJhc2U9InhzOmRlY2ltYWwiPg0KCQkJCQkJPHhzOmZyYWN0aW9uRGlnaXRzIHZhbHVlPSI2Ii8+DQoJCQkJCQk8eHM6bWluSW5jbHVzaXZlIHZhbHVlPSIwLjAwMDAwMSIvPg0KCQkJCQkJPHhzOndoaXRlU3BhY2UgdmFsdWU9ImNvbGxhcHNlIi8+DQoJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJPC94czphdHRyaWJ1dGU+DQoJCQk8eHM6YXR0cmlidXRlIG5hbWU9IlRvdGFsIiB0eXBlPSJ0ZENGREk6dF9JbXBvcnRlIiB1c2U9InJlcXVpcmVkIj4NCgkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgcmVwcmVzZW50YXIgbGEgc3VtYSBkZWwgc3VidG90YWwsIG1lbm9zIGxvcyBkZXNjdWVudG9zIGFwbGljYWJsZXMsIG3DoXMgbGFzIGNvbnRyaWJ1Y2lvbmVzIHJlY2liaWRhcyAoaW1wdWVzdG9zIHRyYXNsYWRhZG9zIC0gZmVkZXJhbGVzIHkvbyBsb2NhbGVzLCBkZXJlY2hvcywgcHJvZHVjdG9zLCBhcHJvdmVjaGFtaWVudG9zLCBhcG9ydGFjaW9uZXMgZGUgc2VndXJpZGFkIHNvY2lhbCwgY29udHJpYnVjaW9uZXMgZGUgbWVqb3JhcykgbWVub3MgbG9zIGltcHVlc3RvcyByZXRlbmlkb3MgZmVkZXJhbGVzIHkvbyBsb2NhbGVzLiBTaSBlbCB2YWxvciBlcyBzdXBlcmlvciBhbCBsw61taXRlIHF1ZSBlc3RhYmxlemNhIGVsIFNBVCBlbiBsYSBSZXNvbHVjacOzbiBNaXNjZWzDoW5lYSBGaXNjYWwgdmlnZW50ZSwgZWwgZW1pc29yIGRlYmUgb2J0ZW5lciBkZWwgUEFDIHF1ZSB2YXlhIGEgdGltYnJhciBlbCBDRkRJLCBkZSBtYW5lcmEgbm8gYXV0b23DoXRpY2EsIHVuYSBjbGF2ZSBkZSBjb25maXJtYWNpw7NuIHBhcmEgcmF0aWZpY2FyIHF1ZSBlbCB2YWxvciBlcyBjb3JyZWN0byBlIGludGVncmFyIGRpY2hhIGNsYXZlIGVuIGVsIGF0cmlidXRvIENvbmZpcm1hY2lvbi4gTm8gc2UgcGVybWl0ZW4gdmFsb3JlcyBuZWdhdGl2b3MuIDwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQk8L3hzOmFubm90YXRpb24+DQoJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCTx4czphdHRyaWJ1dGUgbmFtZT0iVGlwb0RlQ29tcHJvYmFudGUiIHR5cGU9ImNhdENGREk6Y19UaXBvRGVDb21wcm9iYW50ZSIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGV4cHJlc2FyIGxhIGNsYXZlIGRlbCBlZmVjdG8gZGVsIGNvbXByb2JhbnRlIGZpc2NhbCBwYXJhIGVsIGNvbnRyaWJ1eWVudGUgZW1pc29yLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQk8L3hzOmFubm90YXRpb24+DQoJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCTx4czphdHRyaWJ1dGUgbmFtZT0iRXhwb3J0YWNpb24iIHR5cGU9ImNhdENGREk6Y19FeHBvcnRhY2lvbiIgdXNlPSJyZXF1aXJlZCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIHJlcXVlcmlkbyBwYXJhIGV4cHJlc2FyIHNpIGVsIGNvbXByb2JhbnRlIGFtcGFyYSB1bmEgb3BlcmFjacOzbiBkZSBleHBvcnRhY2nDs24uPC94czpkb2N1bWVudGF0aW9uPg0KCQkJCTwveHM6YW5ub3RhdGlvbj4NCgkJCTwveHM6YXR0cmlidXRlPg0KCQkJPHhzOmF0dHJpYnV0ZSBuYW1lPSJNZXRvZG9QYWdvIiB0eXBlPSJjYXRDRkRJOmNfTWV0b2RvUGFnbyIgdXNlPSJvcHRpb25hbCI+DQoJCQkJPHhzOmFubm90YXRpb24+DQoJCQkJCTx4czpkb2N1bWVudGF0aW9uPkF0cmlidXRvIGNvbmRpY2lvbmFsIHBhcmEgcHJlY2lzYXIgbGEgY2xhdmUgZGVsIG3DqXRvZG8gZGUgcGFnbyBxdWUgYXBsaWNhIHBhcmEgZXN0ZSBjb21wcm9iYW50ZSBmaXNjYWwgZGlnaXRhbCBwb3IgSW50ZXJuZXQsIGNvbmZvcm1lIGFsIEFydMOtY3VsbyAyOS1BIGZyYWNjacOzbiBWSUkgaW5jaXNvcyBhIHkgYiBkZWwgQ0ZGLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQk8L3hzOmFubm90YXRpb24+DQoJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCTx4czphdHRyaWJ1dGUgbmFtZT0iTHVnYXJFeHBlZGljaW9uIiB0eXBlPSJjYXRDRkRJOmNfQ29kaWdvUG9zdGFsIiB1c2U9InJlcXVpcmVkIj4NCgkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gcmVxdWVyaWRvIHBhcmEgaW5jb3Jwb3JhciBlbCBjw7NkaWdvIHBvc3RhbCBkZWwgbHVnYXIgZGUgZXhwZWRpY2nDs24gZGVsIGNvbXByb2JhbnRlIChkb21pY2lsaW8gZGUgbGEgbWF0cml6IG8gZGUgbGEgc3VjdXJzYWwpLjwveHM6ZG9jdW1lbnRhdGlvbj4NCgkJCQk8L3hzOmFubm90YXRpb24+DQoJCQk8L3hzOmF0dHJpYnV0ZT4NCgkJCTx4czphdHRyaWJ1dGUgbmFtZT0iQ29uZmlybWFjaW9uIiB1c2U9Im9wdGlvbmFsIj4NCgkJCQk8eHM6YW5ub3RhdGlvbj4NCgkJCQkJPHhzOmRvY3VtZW50YXRpb24+QXRyaWJ1dG8gY29uZGljaW9uYWwgcGFyYSByZWdpc3RyYXIgbGEgY2xhdmUgZGUgY29uZmlybWFjacOzbiBxdWUgZW50cmVndWUgZWwgUEFDIHBhcmEgZXhwZWRpciBlbCBjb21wcm9iYW50ZSBjb24gaW1wb3J0ZXMgZ3JhbmRlcywgY29uIHVuIHRpcG8gZGUgY2FtYmlvIGZ1ZXJhIGRlbCByYW5nbyBlc3RhYmxlY2lkbyBvIGNvbiBhbWJvcyBjYXNvcy4gRXMgcmVxdWVyaWRvIGN1YW5kbyBzZSByZWdpc3RyYSB1biB0aXBvIGRlIGNhbWJpbyBvIHVuIHRvdGFsIGZ1ZXJhIGRlbCByYW5nbyBlc3RhYmxlY2lkby48L3hzOmRvY3VtZW50YXRpb24+DQoJCQkJPC94czphbm5vdGF0aW9uPg0KCQkJCTx4czpzaW1wbGVUeXBlPg0KCQkJCQk8eHM6cmVzdHJpY3Rpb24gYmFzZT0ieHM6c3RyaW5nIj4NCgkJCQkJCTx4czp3aGl0ZVNwYWNlIHZhbHVlPSJjb2xsYXBzZSIvPg0KCQkJCQkJPHhzOmxlbmd0aCB2YWx1ZT0iNSIvPg0KCQkJCQkJPHhzOnBhdHRlcm4gdmFsdWU9IlswLTlhLXpBLVpdezV9Ii8+DQoJCQkJCTwveHM6cmVzdHJpY3Rpb24+DQoJCQkJPC94czpzaW1wbGVUeXBlPg0KCQkJPC94czphdHRyaWJ1dGU+DQoJCTwveHM6Y29tcGxleFR5cGU+DQoJPC94czplbGVtZW50Pg0KPC94czpzY2hlbWE+DQo=</field> + </record> + + <!-- PDF move report definition --> + <record id="ir_actions_report_pdf_account_move_cfdi" model="ir.actions.report"> + <field name="name">Account Move CFDI PDF</field> + <field name="model">account.move</field> + <field name="report_type">aeroo</field> + <field name="report_name">l10n_mx_facturae.account_move_template_cfdi_pdf</field> + <field name="tml_source">file</field> + <field name="report_file">l10n_mx_facturae/report/account_move.odt</field> + <field name="in_format">oo-odt</field> + <field name="out_format" ref="report_aeroo.report_mimetypes_pdf_odt"/> + <field name="parser_model">report.l10n_mx_facturae.account_move</field> + <field name="styles_mode">default</field> + <field name="preload_mode">static</field> + <field name="deferred">off</field> + </record> +</odoo> diff --git a/l10n_mx_facturae/data/account_payment.xml b/l10n_mx_facturae/data/account_payment.xml new file mode 100644 index 0000000000000000000000000000000000000000..f2d4733197eb5ed90bcf2bd5c21a5c9e37d5ad60 --- /dev/null +++ b/l10n_mx_facturae/data/account_payment.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <!-- XML payment report definition --> + <record id="ir_actions_report_xml_account_payment_cfdi" model="ir.actions.report"> + <field name="name">Account Payment CFDI XML</field> + <field name="model">account.payment</field> + <field name="report_type">qweb-xml</field> + <field name="report_name">l10n_mx_facturae.account_payment</field> + <field name="xml_declaration">true</field> + <field name="xsd_schema"></field> + </record> + + <!-- PDF payment report definition --> + <record id="ir_actions_report_pdf_account_payment_cfdi" model="ir.actions.report"> + <field name="name">Account Payment CFDI PDF</field> + <field name="model">account.payment</field> + <field name="report_type">aeroo</field> + <field name="report_name">l10n_mx_facturae.account_payment_template_cfdi_pdf</field> + <field name="tml_source">file</field> + <field name="report_file">l10n_mx_facturae/report/account_payment.odt</field> + <field name="in_format">oo-odt</field> + <field name="out_format" ref="report_aeroo.report_mimetypes_pdf_odt"/> + <field name="parser_model">report.l10n_mx_facturae.account_payment</field> + <field name="styles_mode">default</field> + <field name="preload_mode">static</field> + <field name="deferred">off</field> + </record> + +</odoo> diff --git a/l10n_mx_facturae/data/account_voucher.xml b/l10n_mx_facturae/data/account_voucher.xml deleted file mode 100644 index 0ac23e61480099dbd0e104e9af5679e42f5dc6ac..0000000000000000000000000000000000000000 --- a/l10n_mx_facturae/data/account_voucher.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<openerp> -<data> - - <!-- XML voucher report definition --> - <record id="report_templates_aeroo_account_voucher_cfdi" - model="report.templates.aeroo"> - <field name="name">Account Voucher CFDI XML</field> - <field name="model">account.voucher</field> - <field name="report_name">account.voucher.cfdi.xml</field> - <field - name="report_rml">l10n_mx_facturae/templates/account_voucher.txt</field> - </record> - - <record id="ir_actions_report_xml_account_voucher_cfdi" - model="ir.actions.report.xml"> - <field name="name">Account Voucher CFDI XML</field> - <field name="type">ir.actions.report.xml</field> - <field name="model">account.voucher</field> - <field name="report_name">account.voucher.cfdi.xml</field> - <field name="report_type">aeroo</field> - <field name="in_format">genshi-raw</field> - <field name="out_format" ref="report_aeroo.report_mimetypes_raw" /> - <field name="aeroo_templates_ids" - eval="[(6, 0, [report_templates_aeroo_account_voucher_cfdi])]" /> - <field name="parser_state">default</field> - </record> - -</data> - -<data noupdate="1"> - - <!-- PDF voucher report definition --> - <record id="report_templates_aeroo_account_voucher_pdf_cfdi" - model="report.templates.aeroo"> - <field name="name">Default Voucher CFDI PDF</field> - <field name="model">account.voucher</field> - <field name="report_name">account.voucher.cfdi.pdf</field> - <field name="report_rml">l10n_mx_facturae/report/account_voucher.odt</field> - </record> - - <record id="ir_actions_report_pdf_account_voucher_cfdi" - model="ir.actions.report.xml"> - <field name="name">Account Voucher CFDI PDF</field> - <field name="type">ir.actions.report.xml</field> - <field name="model">account.voucher</field> - <field name="report_name">account.voucher.cfdi.pdf</field> - <field name="report_type">aeroo</field> - <field name="in_format">oo-odt</field> - <field name="out_format" ref="report_aeroo.report_mimetypes_pdf_odt" /> - <field name="aeroo_templates_ids" - eval="[(6, 0, [report_templates_aeroo_account_voucher_pdf_cfdi])]" /> - <field name="parser_loc">l10n_mx_ir_attachment_facturae/report/generate_qr.py</field> - <field name="parser_state">loc</field> - </record> - -</data> -</openerp> diff --git a/l10n_mx_facturae/data/email_template.xml b/l10n_mx_facturae/data/email_template.xml index 3d534e910ddc5b7ef05ffc4561afcbe3142517fe..64aad2a1ae48f7f2c2052d186df328ea1c6a7633 100644 --- a/l10n_mx_facturae/data/email_template.xml +++ b/l10n_mx_facturae/data/email_template.xml @@ -1,14 +1,14 @@ <?xml version="1.0" ?> -<openerp> +<odoo> <!-- Mail template are declared in a NOUPDATE block so users can freely customize/delete them --> <data noupdate="1"> <!--Email template --> - <record id="account_voucher_cfdi_email_template" model="email.template"> + <record id="account_payment_cfdi_email_template" model="email.template"> <field name="name">Electronic Payment Receipt</field> <field name="subject">${object.company_id.name|safe} Payment (Ref ${object.number or 'n/a'})</field> <field name="partner_to">${object.partner_id.id}</field> - <field name="model_id" ref="account_voucher.model_account_voucher"/> + <field name="model_id" ref="account_payment.model_account_payment"/> <field name="auto_delete" eval="True"/> <field name="lang">${object.partner_id.lang}</field> <field name="body_html"><![CDATA[ @@ -65,4 +65,4 @@ ]]></field> </record> </data> -</openerp> +</odoo> diff --git a/l10n_mx_facturae/data/facturae_data.xml b/l10n_mx_facturae/data/facturae_data.xml index 2f0a3e6c000656d4a45af5b1cd4445856ef04fe7..c64076bb004fde29392768f9167479bf2d6ea3b7 100644 --- a/l10n_mx_facturae/data/facturae_data.xml +++ b/l10n_mx_facturae/data/facturae_data.xml @@ -1,12 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> -<openerp> -<data noupdate="0"> +<odoo> <record id="group_cfdi_custom_number" model="res.groups"> <field name="name">CFDI Custom Number</field> <field name="category_id" ref="base.module_category_accounting_and_finance"/> </record> -</data> -</openerp> + +</odoo> diff --git a/l10n_mx_facturae/data/ir_attachment_facturae_config.xml b/l10n_mx_facturae/data/ir_attachment_facturae_config.xml index bbde0be4411965c51a65df1359f7af05e1fe3e58..45397483037e90a0ce92c1a8e8a1f0464bc9de83 100644 --- a/l10n_mx_facturae/data/ir_attachment_facturae_config.xml +++ b/l10n_mx_facturae/data/ir_attachment_facturae_config.xml @@ -1,28 +1,26 @@ <?xml version="1.0"?> -<openerp> -<data noupdate="1"> +<odoo> - <record id="ir_attachment_facturae_mx_config_account_invoice" + <record id="ir_attachment_facturae_mx_config_account_move" model="ir.attachment.facturae.mx.config"> - <field name="model">account.invoice</field> + <field name="model">account.move</field> <field name="version">4.0</field> - <field name="template_xml_sign">account.invoice.cfdi.xml</field> + <field name="template_xml_sign">l10n_mx_facturae.account_move</field> <field name="template_xml_cancel">Aun.no.hay.uno</field> - <field name="template_pdf_sign">invoice.report.aaero.pdf</field> - <field name="template_pdf_cancel">invoice.report.aaero.pdf</field> - <field name="email_template_id" ref="account.email_template_edi_invoice"/> + <field name="template_pdf_sign">l10n_mx_facturae.account_move_template_cfdi_pdf</field> + <field name="template_pdf_cancel">l10n_mx_facturae.account_move_template_cfdi_pdf</field> + <!--<field name="mail_template_id" ref="account.email_template_edi_invoice"/>--> </record> - <record id="ir_attachment_facturae_mx_config_account_voucher" + <record id="ir_attachment_facturae_mx_config_account_payment" model="ir.attachment.facturae.mx.config"> - <field name="model">account.voucher</field> + <field name="model">account.payment</field> <field name="version">4.0</field> - <field name="template_xml_sign">account.voucher.cfdi.xml</field> + <field name="template_xml_sign">l10n_mx_facturae.account_payment</field> <field name="template_xml_cancel">Aun.no.hay.uno</field> - <field name="template_pdf_sign">account.voucher.cfdi.pdf</field> - <field name="template_pdf_cancel">account.voucher.cfdi.pdf</field> - <field name="email_template_id" ref="account_voucher_cfdi_email_template"/> + <field name="template_pdf_sign">l10n_mx_facturae.account_payment_template_cfdi_pdf</field> + <field name="template_pdf_cancel"><!--l10n_mx_facturae.account_payment_template_cfdi_pdf--></field> + <!--<field name="mail_template_id" ref="account_payment_cfdi_email_template"/>--> </record> -</data> -</openerp> +</odoo> diff --git a/l10n_mx_facturae/data/ir_cron.xml b/l10n_mx_facturae/data/ir_cron.xml index 976b588bbdd2c35b877b443728b3b76f18123d19..ccda338b1d467bdc0776dc9e184895c14e71a6fe 100644 --- a/l10n_mx_facturae/data/ir_cron.xml +++ b/l10n_mx_facturae/data/ir_cron.xml @@ -1,16 +1,12 @@ <?xml version="1.0" encoding='UTF-8'?> -<openerp> - <data noupdate="1"> - <record model="ir.cron" id="ir_cron_invoices_pending_to_cancel"> - <field name="name">Invoices pending to cancel</field> - <field name="interval_number">2</field> - <field name="interval_type">hours</field> - <field name="numbercall">-1</field> - <field name="model" eval="'account.invoice'"/> - <field name="function" eval="'cron_invoices_pending_to_cancel'"/> - <field name="args" eval="'()'"/> - <field name="active" eval="True" /> - </record> - </data> -</openerp> - +<odoo noupdate="1"> + <record model="ir.cron" id="ir_cron_invoices_pending_to_cancel"> + <field name="name">Invoices pending to cancel</field> + <field name="interval_number">2</field> + <field name="interval_type">hours</field> + <field name="numbercall">-1</field> + <field name="model_name">l10n_mx_base.model_account_move</field> + <!--<field name="function" eval="cron_invoices_pending_to_cancel"/>--> + <field name="active" eval="True" /> + </record> +</odoo> diff --git a/l10n_mx_facturae/data/res_partner.xml b/l10n_mx_facturae/data/res_partner.xml new file mode 100644 index 0000000000000000000000000000000000000000..547f322a97ffdab30f8e5cb0d7aced3b79389f64 --- /dev/null +++ b/l10n_mx_facturae/data/res_partner.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="res_partner_public_general" model="res.partner"> + <field name="name">Publico en general</field> + <field name="is_company">1</field> + <field name="street">45 10 oriente</field> + <field name="city">Culiacán</field> + <field name="state_id" ref="base.state_mx_sin"/> + <field name="zip">80290</field> + <field name="vat">MXXAXX010101000</field> + <field name="phone">(870)-931-0505</field> + <field name="country_id" ref="base.mx"/> + <field name="cfdi_fiscal_regime_id" ref="l10n_mx_base.regime_fiscal_616"/> + <field name="cfdi_use_id" ref="l10n_mx_base.cfdi_use_S01"/> + <field name="payment_method_id" ref="l10n_mx_base.cfdi_payment_method_1"/> + </record> + +</odoo> diff --git a/l10n_mx_facturae/demo/demo_product.xml b/l10n_mx_facturae/demo/demo_product.xml new file mode 100644 index 0000000000000000000000000000000000000000..839b077907094b6804600303f0f048098f0aad22 --- /dev/null +++ b/l10n_mx_facturae/demo/demo_product.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> +<data> + + <record id="product.product_product_7" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_27" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_16" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_3" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_9" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_20" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_5" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_8" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.consu_delivery_03" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_13" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_10" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_6" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.consu_delivery_02" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_24" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_delivery_02" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_25" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_delivery_01" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_12" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_order_01" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.consu_delivery_01" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> + + <record id="product.product_product_22" model="product.product"> + <field name="cfdi_product_service_id" ref="l10n_mx_base.56101700"/> + </record> +</data> +</odoo> diff --git a/l10n_mx_facturae/demo/demo_res_partner.xml b/l10n_mx_facturae/demo/demo_res_partner.xml new file mode 100644 index 0000000000000000000000000000000000000000..2313354b9c8e92946527bf99f5ae98403b6b5185 --- /dev/null +++ b/l10n_mx_facturae/demo/demo_res_partner.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> +<data> + <record id="res_partner_2023" model="res.partner"> + <field name="name">Felix Manuel Andrade Ballado</field> + <field name="is_company">1</field> + <field name="street">4557 10 oriente</field> + <field name="city">Huimanguillo</field> + <field name="state_id" ref="base.state_mx_tab"/> + <field name="zip">86400</field> + <field name="vat">MXAABF800614HI0</field> + <field name="phone">(870)-931-0505</field> + <field name="country_id" ref="base.mx"/> + <field name="email">felix.andrade@example.com</field> + <field name="company_id" ref="l10n_mx.demo_company_mx"/> + <field name="cfdi_fiscal_regime_id" ref="l10n_mx_base.regime_fiscal_616"/> + <field name="cfdi_use_id" ref="l10n_mx_base.cfdi_use_S01"/> + <field name="payment_method_id" ref="l10n_mx_base.cfdi_payment_method_1"/> + </record> + + <record id="l10n_mx_ir_attachment_facturae.demo_partner_company_mx_frontier" model="res.partner"> + <field name="cfdi_fiscal_regime_id" ref="l10n_mx_base.regime_fiscal_601"/> + <field name="cfdi_use_id" ref="l10n_mx_base.cfdi_use_S01"/> + </record> + + <function name="write" model="ir.model.data"> + <function name="search" model="ir.model.data"> + <value + eval="[ + ('module', '=', 'l10n_mx'), ('name', '=', 'partner_demo_company_mx'), + ('module', '=', 'l10n_mx'), ('name', '=', 'demo_company_mx') + ]" + /> + </function> + <value eval="{'noupdate': False}" /> + </function> + + <record id="l10n_mx.partner_demo_company_mx" model="res.partner"> + <field name="cfdi_fiscal_regime_id" ref="l10n_mx_base.regime_fiscal_601"/> + <field name="cfdi_use_id" ref="l10n_mx_base.cfdi_use_S01"/> + </record> + + <record id="l10n_mx.demo_company_mx" model="res.company"> + <field name="city">Jesus Maria</field> + <field name="state_id" ref="base.state_mx_ags"/> + <field name="cfdi_fiscal_regime_id" ref="l10n_mx_base.regime_fiscal_601"/> + <field name="cfdi_use_id" ref="l10n_mx_base.cfdi_use_S01"/> + </record> + + <record id="l10n_mx_ir_attachment_facturae.res_company_mx_frontier" model="res.company"> + <field name="cfdi_fiscal_regime_id" ref="l10n_mx_base.regime_fiscal_601"/> + <field name="cfdi_use_id" ref="l10n_mx_base.cfdi_use_S01"/> + </record> + + <record id="res_partner_2024" model="res.partner"> + <field name="name">Maria Olivia Martinez Sagaz</field> + <field name="is_company">1</field> + <field name="street">45 10 oriente</field> + <field name="city">Culiacán</field> + <field name="state_id" ref="base.state_mx_sin"/> + <field name="zip">80290</field> + <field name="vat">MXMASO451221PM4</field> + <field name="phone">(870)-931-0505</field> + <field name="country_id" ref="base.mx"/> + <field name="email">maria.martinez@example.com</field> + <field name="company_id" ref="l10n_mx_ir_attachment_facturae.res_company_mx_frontier"/> + <field name="cfdi_fiscal_regime_id" ref="l10n_mx_base.regime_fiscal_616"/> + <field name="cfdi_use_id" ref="l10n_mx_base.cfdi_use_S01"/> + <field name="payment_method_id" ref="l10n_mx_base.cfdi_payment_method_1"/> + </record> +</data> +</odoo> diff --git a/l10n_mx_facturae/i18n/es_MX.po b/l10n_mx_facturae/i18n/es_MX.po new file mode 100644 index 0000000000000000000000000000000000000000..460e0b6d950aea6a7598e0716cd5d12cffb8111f --- /dev/null +++ b/l10n_mx_facturae/i18n/es_MX.po @@ -0,0 +1,628 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_mx_facturae +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-25 23:00+0000\n" +"PO-Revision-Date: 2024-04-25 18:02-0600\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es_MX\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 3.2.2\n" + +#. module: l10n_mx_facturae +#: model:ir.actions.report,name:l10n_mx_facturae.ir_actions_report_pdf_account_move_cfdi +msgid "Account Move CFDI PDF" +msgstr "Traslado de cuenta CFDI PDF" + +#. module: l10n_mx_facturae +#: model:ir.actions.report,name:l10n_mx_facturae.ir_actions_report_xml_account_move_cfdi +msgid "Account Move CFDI XML" +msgstr "Traslado de cuenta CFDI XML" + +#. module: l10n_mx_facturae +#: model:ir.actions.report,name:l10n_mx_facturae.ir_actions_report_pdf_account_payment_cfdi +msgid "Account Payment CFDI PDF" +msgstr "Pago de cuenta CFDI PDF" + +#. module: l10n_mx_facturae +#: model:ir.actions.report,name:l10n_mx_facturae.ir_actions_report_xml_account_payment_cfdi +msgid "Account Payment CFDI XML" +msgstr "Pago de cuenta CFDI XML" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.l10n_mx_facturae_res_partner_view_form +msgid "" +"Addends are used to add additional content to the invoice that the SAT.\n" +" An addendum contains information of a commercial, " +"logistic and operation,\n" +" often required by the receiving company (client).\n" +" <br/><br/>\n" +" Once you have selected the addendum, you must add the " +"necessary information\n" +" in all or some of the following sections: Customers, " +"suppliers, company,\n" +" products, services, invoices or delivery notes\n" +" <br/><br/>\n" +" Done the necessary configurations, you will be able to " +"stamp your invoices\n" +" with the selected complement." +msgstr "" +"Los sumandos se utilizan para agregar contenido adicional a la factura que " +"emite el SAT.\n" +" Una adenda contiene información de carácter comercial, " +"logÃstico y de operación,\n" +" a menudo requerido por la empresa receptora " +"(cliente).\n" +" <br/>><br/>\n" +" Una vez que hayas seleccionado la adenda, deberás " +"agregar la información necesaria\n" +" en todos o algunos de los siguientes apartados: " +"Clientes, proveedores, empresa,\n" +" productos, servicios, facturas o albaranes\n" +" <br/>><br/>\n" +" Realizadas las configuraciones necesarias, podrás " +"sellar tus facturas\n" +" con el complemento seleccionado." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_adenda_ids +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_adenda_ids +msgid "Addendum" +msgstr "Adenda" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__address_issued_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__address_issued_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__address_issued_id +msgid "Address Issued Invoice" +msgstr "Dirección Factura Emitida" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.l10n_mx_facturae_res_partner_view_form +msgid "Adendas" +msgstr "Adendas" + +#. module: l10n_mx_facturae +#: model:ir.model.fields.selection,name:l10n_mx_facturae.selection__account_move__cfdi_periodicity__03 +msgid "Biweekly" +msgstr "Cada dos semanas" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_folio_fiscal +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_folio_fiscal +msgid "CFD-I Folio Fiscal" +msgstr "CFD-I Folio Fiscal" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_id +msgid "CFDI" +msgstr "CFDI" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__cfdi_adenda_ids +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__cfdi_adenda_ids +msgid "CFDI Adendas" +msgstr "CFDI Adendas" + +#. module: l10n_mx_facturae +#: model:res.groups,name:l10n_mx_facturae.group_cfdi_custom_number +msgid "CFDI Custom Number" +msgstr "Número de Pedimento" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__cfdi_relation_type +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_relation_type +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_relation_type +msgid "CFDI Relation type" +msgstr "CFDI Tipo de relación" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__cfdi_id +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__cfdi_id +msgid "CFDI related to the selected record" +msgstr "CFDI relacionado con el registro seleccionado" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__cfdi_use +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_use +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_use +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__cfdi_use_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__cfdi_use_id +msgid "CFDI use" +msgstr "Uso de CFDI" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "" +"Can't post the entry %s, the related invoice is waiting for a request to " +"cancel." +msgstr "" +"No se puede asentar la póliza %s, porque la factura relacionada esta " +"esperando el resultado de una solicitud de cancelación." + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "Cancellation request sent" +msgstr "Solicitud de cancelación enviada" + +#. module: l10n_mx_facturae +#: model:res.groups,name:l10n_mx_facturae.cfdi_cuentapredial +msgid "Cfdi Cuenta Predial" +msgstr "Cuenta Predial" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_datetime +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_datetime +msgid "Cfdi Datetime" +msgstr "CFDI Fecha/hora" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__cfdi_periodicity +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_periodicity +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_periodicity +msgid "Cfdi Periodicity" +msgstr "CFDI Periodicidad" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_partner__cfdi_use_id +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_users__cfdi_use_id +msgid "" +"Cfdi usage that will be used by default on this customer invoices and " +"credit notes" +msgstr "" +"Uso de CFDI que se utilizará por defecto en las facturas y notas de crédito " +"de este cliente" + +#. module: l10n_mx_facturae +#: model:ir.model,name:l10n_mx_facturae.model_res_company +msgid "Companies" +msgstr "CompañÃas" + +#. module: l10n_mx_facturae +#: model:ir.model,name:l10n_mx_facturae.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "Could not check SAT invoice status due to the following error: %s." +msgstr "" +"No se ha podido comprobar el estado de la factura SAT debido al siguiente " +"error: %s." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move_line__cfdi_cuentapredial +msgid "Cuenta Predial" +msgstr "Cuenta Predial" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_partner__gln_number +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_users__gln_number +msgid "Customer or Delivery branch" +msgstr "Cliente o sucursal de entrega" + +#. module: l10n_mx_facturae +#: model:ir.model.fields.selection,name:l10n_mx_facturae.selection__account_move__cfdi_periodicity__01 +msgid "Daily" +msgstr "Diario" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__date_invoice_cancel +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__date_invoice_cancel +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__date_invoice_cancel +msgid "Date Invoice Cancelled" +msgstr "Fecha de cancelación de la factura" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__datetime +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__datetime +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__datetime +msgid "Datetime" +msgstr "Fecha y hora" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_form_customer +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_tree +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_payment_cfdi_view_form +msgid "Fiscal Number" +msgstr "Número Fiscal" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__cfdi_folio_fiscal +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__cfdi_folio_fiscal +msgid "Folio used in the electronic invoice" +msgstr "Folio utilizado en la factura electrónica" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__gln_number +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__gln_number +msgid "GLN Number" +msgstr "Número GLN" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__is_cfdi_candidate +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__is_cfdi_candidate +msgid "" +"Helper field to determine if document is CFDI candidate to show send PAC " +"button on form view." +msgstr "" +"Campo auxiliar para determinar si el documento es candidato a CFDI para " +"mostrar el botón enviar PAC en la vista del formulario." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__show_unreconcile +msgid "Helper field to hide unreconcile button" +msgstr "Campo de ayuda para ocultar el botón de irreconciliar" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move_line__cfdi_numero_identificacion +msgid "Identification Number" +msgstr "Número de identificación" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_bank_statement_line__date_invoice_cancel +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__date_invoice_cancel +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__date_invoice_cancel +msgid "If the invoice is cancelled, save the date when was cancel" +msgstr "Si la factura es cancelada, guarde la fecha cuando se canceló" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "" +"Incorrect tax sequence configuration, check this data in Account >> Tax >> " +"Sequence" +msgstr "" +"Configuración incorrecta de la secuencia de impuestos, compruebe estos " +"datos en Cuenta >> Impuestos >> Secuencia" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_search +msgid "Invoices that being substituted and must be cancelled" +msgstr "Facturas que se sustituyen y deben cancelarse" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_search +msgid "Invoices to be signed" +msgstr "Facturas por firmar" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__is_cfdi_candidate +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__is_cfdi_candidate +msgid "Is Cfdi Candidate" +msgstr "Es Candidato Cfdi" + +#. module: l10n_mx_facturae +#: model:ir.model,name:l10n_mx_facturae.model_account_move +msgid "Journal Entry" +msgstr "Asiento de diario" + +#. module: l10n_mx_facturae +#: model:ir.model,name:l10n_mx_facturae.model_account_move_line +msgid "Journal Item" +msgstr "Apunte contable" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__l10n_mx_edi_error +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__l10n_mx_edi_error +msgid "L10N Mx Edi Error" +msgstr "L10N Mx Edi Error" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__l10n_mx_edi_original_invoice +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__l10n_mx_edi_original_invoice +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__l10n_mx_edi_original_invoice +msgid "L10N Mx Edi Original Invoice" +msgstr "L10N Mx Edi Factura original" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__l10n_mx_edi_to_cancel +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__l10n_mx_edi_to_cancel +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__l10n_mx_edi_to_cancel +msgid "L10N Mx Edi To Cancel" +msgstr "L10N Mx Edi Para Cancelar" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__l10n_mx_export +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__l10n_mx_export +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__l10n_mx_export +msgid "Merchandise export" +msgstr "Exportación de mercancÃas" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "Missing SAT code for product: {product}" +msgstr "Falta el código SAT para el producto: {producto}" + +#. module: l10n_mx_facturae +#: model:ir.model.fields.selection,name:l10n_mx_facturae.selection__account_move__cfdi_periodicity__04 +msgid "Monthly" +msgstr "Mensualmente" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "No status update found on SAT" +msgstr "No se ha encontrado ninguna actualización de estado en SAT" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_partner__supplier_number +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_users__supplier_number +msgid "Number or reference that the Client assigned to our company." +msgstr "Número o referencia que el Cliente asignó a nuestra empresa." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move_line__cfdi_custom_number +msgid "N° Pediment" +msgstr "N° Pedimento" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "Operation not supported" +msgstr "Operación no admitida" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__related_cfdi_ids +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__related_cfdi_ids +msgid "Original CFDI to which this CFDI is referred to" +msgstr "CFDI original al que se refiere este CFDI" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_form_customer +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_payment_cfdi_view_form +msgid "PAC State" +msgstr "Estado PAC" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_bank_statement_line__payment_method_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__payment_method_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__payment_method_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__payment_method_id +msgid "Payment Method" +msgstr "Forma de pago" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_partner__payment_method_id +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_users__payment_method_id +msgid "" +"Payment method associated with this partner accordingto CFDI 4.0 catalog." +msgstr "Forma de pago asociada a este socio de acuerdo al catálogo CFDI 4.0." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_bank_statement_line__payment_method_id +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__payment_method_id +msgid "Payment method associated with this payment term according" +msgstr "Método de pago asociado a este plazo de pago según" + +#. module: l10n_mx_facturae +#: model:ir.model,name:l10n_mx_facturae.model_account_payment +msgid "Payments" +msgstr "Pagos" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "" +"Predial Account must be only numbers.\n" +"All letters must be replaced by '0'" +msgstr "" +"La cuenta de premarcado debe ser solo numérica.\n" +"Todas las letras deben sustituirse por \"0\"." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move_line__cfdi_cuentapredial +msgid "Predial number for real state lease invoices" +msgstr "Número predial para facturas de arrendamiento de inmuebles" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "Product {p} must have at least one tax selected." +msgstr "El producto {p} debe tener al menos un impuesto seleccionado." + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_form_customer +msgid "Related" +msgstr "Relacionado" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__related_cfdi_ids +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__related_cfdi_ids +msgid "Related Cfdi" +msgstr "CFDI relacionado" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_form_customer +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_payment_cfdi_view_form +msgid "Retry" +msgstr "Reintentar" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__cfdi_adenda_ids +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__cfdi_adenda_ids +msgid "Select addendum node to use on this CFDI." +msgstr "Seleccione el nodo de adenda a utilizar en este CFDI." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__sequence_id +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__sequence_id +msgid "Sequence" +msgstr "Secuencia" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__show_edi +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__show_edi +msgid "Show Edi" +msgstr "Mostrar edición" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__show_glnnumber +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__show_glnnumber +msgid "Show Glnnumber" +msgstr "Mostrar número de registro" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__show_suppliernumber +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__show_suppliernumber +msgid "Show Suppliernumber" +msgstr "Mostrar número de proveedor" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__show_unreconcile +msgid "Show Unreconcile" +msgstr "Mostrar irreconciliar" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_payment_cfdi_view_form +msgid "Sign" +msgstr "Firma" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_payment.py:0 +#, python-format +msgid "" +"Some of the invoices that will be paid with this record are not signed, and " +"the UUID is required to indicate the invoices that are paid with this CFDI " +msgstr "" +"Algunas de las facturas que se pagarán con este registro no están firmadas, " +"y se requiere el UUID para indicar las facturas que se pagan con este CFDI" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__cfdi_state +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__cfdi_state +msgid "State" +msgstr "Estado" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__cfdi_state +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__cfdi_state +msgid "State of attachments" +msgstr "Estado de los archivos adjuntos" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_move__state +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_account_payment__state +msgid "Status" +msgstr "Estado" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__supplier_number +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__supplier_number +msgid "Supplier Number" +msgstr "Número de proveedor" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_bank_statement_line__l10n_mx_edi_to_cancel +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__l10n_mx_edi_to_cancel +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__l10n_mx_edi_to_cancel +msgid "" +"Technical field to display a warning when an invoice must be canceled " +"because have being replaced by a new one." +msgstr "" +"Campo técnico para mostrar un aviso cuando una factura debe cancelarse " +"porque ha sido sustituida por una nueva." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_bank_statement_line__l10n_mx_edi_original_invoice +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__l10n_mx_edi_original_invoice +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__l10n_mx_edi_original_invoice +msgid "Technical field to relate origin invoice with substitute" +msgstr "Campo técnico para relacionar la factura de origen con la sustituta" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "The invoice could not be canceled" +msgstr "La factura no se ha podido cancelar" + +#. module: l10n_mx_facturae +#: model:res.groups,comment:l10n_mx_facturae.cfdi_cuentapredial +msgid "" +"The user will have access to add Cuenta Predial information to invoice " +"lines." +msgstr "" +"El usuario tendrá acceso al campo para la Cuenta Predial en las lÃneas de " +"las facturas." + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_bank_statement_line__address_issued_id +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move__address_issued_id +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_payment__address_issued_id +msgid "" +"This address will be used as address that issued for electronic invoice" +msgstr "" +"Esta dirección se utilizará como dirección emitida para la factura " +"electrónica" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_partner__cfdi_adenda_ids +#: model:ir.model.fields,help:l10n_mx_facturae.field_res_users__cfdi_adenda_ids +msgid "This field allows adding a node or addendum to the invoice" +msgstr "Este campo permite agregar un nodo o adenda a la factura" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#, python-format +msgid "" +"This invoice must be cancelled because have being replaced with invoice: " +msgstr "" +"Esta factura debe cancelarse porque ha sido reemplazada por una factura: " + +#. module: l10n_mx_facturae +#: model:ir.model.fields,help:l10n_mx_facturae.field_account_move_line__cfdi_numero_identificacion +msgid "This number is the identification number for invoice line in cfdi" +msgstr "" +"Este número es el número de identificación de la lÃnea de factura en CFDI" + +#. module: l10n_mx_facturae +#: code:addons/l10n_mx_facturae/models/account_move.py:0 +#: model:ir.model.fields.selection,name:l10n_mx_facturae.selection__account_move__state__waiting +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_search +#, python-format +msgid "To cancel" +msgstr "A cancelar" + +#. module: l10n_mx_facturae +#: model_terms:ir.ui.view,arch_db:l10n_mx_facturae.account_move_view_search +msgid "To sign" +msgstr "A firmar" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_company__cfdi_use_id +msgid "Use CFDI" +msgstr "Uso de CFDI" + +#. module: l10n_mx_facturae +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_partner__edi +#: model:ir.model.fields,field_description:l10n_mx_facturae.field_res_users__edi +msgid "User EDI" +msgstr "Usuario EDI" + +#. module: l10n_mx_facturae +#: model:ir.model.fields.selection,name:l10n_mx_facturae.selection__account_move__cfdi_periodicity__02 +msgid "Weekly" +msgstr "Semanal" + +#. module: l10n_mx_facturae +#: model:ir.model,name:l10n_mx_facturae.model_report_l10n_mx_facturae_account_move +msgid "report.l10n_mx_facturae.account_move" +msgstr "report.l10n_mx_facturae.account_move" + +#. module: l10n_mx_facturae +#: model:ir.model,name:l10n_mx_facturae.model_report_l10n_mx_facturae_account_payment +msgid "report.l10n_mx_facturae.account_payment" +msgstr "report.l10n_mx_facturae.account_payment" diff --git a/l10n_mx_facturae/models/__init__.py b/l10n_mx_facturae/models/__init__.py index cbde404e0d2ea99dc6b467faa78fd70566f1a74b..8d64c35b0c1a3a9b78dcf0f716b9296d67b58200 100644 --- a/l10n_mx_facturae/models/__init__.py +++ b/l10n_mx_facturae/models/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from . import account_invoice from . import account_move -from . import account_voucher -from . import email_template +from . import account_payment +#from . import email_template +from . import res_company from . import res_partner diff --git a/l10n_mx_facturae/models/account_invoice.py b/l10n_mx_facturae/models/account_invoice.py deleted file mode 100644 index 294f08df6aaedeba15a29d3b9d09db892c182d08..0000000000000000000000000000000000000000 --- a/l10n_mx_facturae/models/account_invoice.py +++ /dev/null @@ -1,671 +0,0 @@ -# -*- coding: utf-8 -*- - -import logging -import pytz -from datetime import datetime, timedelta - -from openerp import api, fields, models -from openerp.exceptions import ValidationError, Warning as UserError -from openerp.tools.float_utils import float_round -from openerp.tools.translate import _ - - -_logger = logging.getLogger(__name__) - - -class AccountInvoice(models.Model): - _name = "account.invoice" - _inherit = ["account.invoice", "base.cfdi"] - - @property - def formapago(self): - """Return payment type for display on CFDI""" - self.ensure_one() - try: - code = self.payment_type_ids[0].code - except IndexError: - code = "99" - return code - - @property - def descuento(self): - self.ensure_one() - discount = 0.0 - for line in self.invoice_line: - discount += line.descuento - return discount - - @property - def subtotal(self): - self.ensure_one() - subtotal = 0.0 - for line in self.invoice_line: - subtotal += line.importe - return subtotal - - @property - def total(self): - self.ensure_one() - return ( - self.subtotal - - self.descuento - + self.impuestos["total_traslados"] - + self.impuestos["total_retenciones"] - + self.impuestos["total_locales"] - ) - - @property - def impuestos(self): - """Return computed taxes for display on CFDI""" - self.ensure_one() - tax_grouped = {} - taxes = { - "traslados": [], - "retenciones": [], - "locales": [], - "total_traslados": 0.0, - "total_retenciones": 0.0, - "total_locales": 0.0, - } - - for line in self.invoice_line: - for tax in line.export_invoice_line_for_xml().taxes: - # Mimic logic from compute function in account.invoice.tax - # object to group taxes from invoice lines and be able to get - # the same result for display on CFDI - if self.type in ("out_invoice", "in_invoice"): - tax.account_id = tax.account_collected_id or line.account_id.id - tax.analytic_id = tax.account_analytic_collected_id - else: - tax.account_id = tax.account_paid_id or line.account_id.id - tax.analytic_id = tax.account_analytic_paid_id - - key = (tax.id, tax.account_id, tax.analytic_id) - if key not in tax_grouped: - tax_grouped[key] = tax - else: - tax_grouped[key].amount += tax.amount - tax_grouped[key].base += tax.base - - # Classify taxes for CFDI - for dummy, tax in tax_grouped.items(): - if tax.group in ["IVA", "IEPS", "ISR"]: - if tax.amount >= 0: - taxes["traslados"].append(tax) - taxes["total_traslados"] += tax.amount - else: - taxes["retenciones"].append(tax) - taxes["total_retenciones"] += tax.amount - else: - taxes["locales"].append(tax) - taxes["total_locales"] += tax.amount - return taxes - - # pylint: disable=W0212 - address_issued_id = fields.Many2one( - "res.partner", - "Address Issued Invoice", - readonly=True, - states={"draft": [("readonly", False)]}, - help="This address will be used as address that issued " - "for electronic invoice", - compute="_compute_address_issued", - ) - datetime = fields.Datetime(compute="_compute_datetime") - date_invoice_cancel = fields.Datetime( - "Date Invoice Cancelled", - readonly=True, - copy=False, - help="If the invoice is cancelled, save the date" " when was cancel", - ) - payment_method_id = fields.Many2one( - "cfdi.payment.method", - string="Payment Method", - readonly=True, - states={"draft": [("readonly", False)]}, - help="Payment method associated with this payment term according", - ) - cfdi_use = fields.Many2one( - "cfdi.use", "CFDI use", readonly=True, states={"draft": [("readonly", False)]} - ) - cfdi_relation_type = fields.Many2one("cfdi.relation.type", "CFDI Relation type") - state = fields.Selection(selection_add=[("waiting", _("To cancel"))]) - l10n_mx_edi_to_cancel = fields.Char( - compute="_compute_l10n_mx_edi_to_cancel", - search="_search_l10n_mx_edi_to_cancel", - help="Technical field to display a warning when an invoice must be canceled " - "because have being replaced by a new one.", - ) - l10n_mx_edi_original_invoice = fields.Many2one( - "account.invoice", - compute="_compute_l10n_mx_edi_to_cancel", - help="Technical field to relate origin invoice with substitute", - ) - cfdi_periodicity = fields.Selection( - [ - ("01", "Daily"), - ("02", "Weekly"), - ("03", "Biweekly"), - ("04", "Monthly"), - ], - default="04", - ) - - l10n_mx_export = fields.Boolean( - compute="_compute_export", - string="Merchandise export" - ) - - @api.multi - @api.depends("company_id") - def _compute_is_cfdi_candidate(self): - mexico = self.env.ref("base.mx") - for record in self: - record.is_cfdi_candidate = bool(record.company_id.country_id == mexico) - - @api.multi - def _get_cfdi_datetime(self): - """This base function inherits the creation of the cfdi datetime - what it basically does is respect the date_invoice from invoice form - """ - self.ensure_one() - date_invoice = fields.Datetime.from_string(self.date_invoice) - time_now = fields.Datetime.context_timestamp(self, datetime.now()) - # If we are singing an invoice in the past we need to move forward 1 minute the - # computed time to be in the 72 hours range defined by SAT - # On the opposite if we are singing an invoice for today we move back 1 minute - # the computed time to prevent false errors about being out of 72 hours range - if date_invoice.date() < time_now.date(): - time_now += timedelta(minutes=1) - else: - time_now -= timedelta(minutes=1) - date_create = datetime.combine(date_invoice, time_now.timetz()) - # Needed to save date into the database as until now we are using user timezone - # to express the datetime that will cause double timezone conversion if kept - date_create = date_create.astimezone(tz=pytz.utc) - return fields.Datetime.to_string(date_create) - - @api.multi - @api.depends("journal_id") - def _compute_address_issued(self): - for invoice in self: - if invoice.journal_id.address_issued_id: - invoice.address_issued_id = invoice.journal_id.address_issued_id - else: - invoice.address_issued_id = invoice.company_id.partner_id - - @api.multi - def _compute_sequence_id(self): - for record in self: - record.sequence_id = record.journal_id.sequence_id - - @api.multi - def _compute_l10n_mx_edi_to_cancel(self): - """Computes legend to display when an invoice needs to be cancelled because - have being substituted by a new one. - """ - # Get substitution relation to compare - substitution = self.env.ref("l10n_mx.cfdi_relation_type_04") - invoices_to_cancel = self.filtered( - lambda i: i.type == "out_invoice" and i.state in ("open", "paid") - ) - for inv in invoices_to_cancel: - message = "" - origin_documents = inv.refund_invoice_ids.filtered( - lambda i: - i.state in ("open", "paid") and i.type == "out_invoice" - ) - is_substitue = any( - [o.cfdi_relation_type == substitution for o in origin_documents] - ) - if origin_documents and is_substitue: - message = _("This invoice must be cancelled because have being " - "replaced with invoice: ") - inv.l10n_mx_edi_original_invoice = origin_documents[0] - - inv.l10n_mx_edi_to_cancel = message - - @api.model - def _search_l10n_mx_edi_to_cancel(self, operator, value): - """Allows to get ids for invoices that must be cancelled""" - substitution = self.env.ref("l10n_mx.cfdi_relation_type_04") - - if operator not in ["=", "!="] or not isinstance(value, bool): - raise UserError(_("Operation not supported")) - - if operator != "=": - value = not value - - self._cr.execute( - """ - SELECT id FROM account_invoice ai - WHERE EXISTS ( - SELECT * FROM account_invoice_refunds_rel airr - INNER JOIN account_invoice air on airr.refund_invoice_id = air.id - WHERE airr.original_invoice_id = ai.id - AND air.cfdi_relation_type = %s - LIMIT 1 - ) - AND ai.state IN ('open', 'paid') - """, - (substitution.id,) - ) - return [ - ("id", "in" if value else "not in", [r[0] for r in self._cr.fetchall()]) - ] - - @api.model - def cron_invoices_pending_to_cancel(self): - invoices = self.env["account.invoice"].search([("state", "in", ["waiting"])]) - for invoice in invoices: - invoice.action_consult_cancellation_status() - - @api.one - def _compute_datetime(self): - self.datetime = self.cfdi_datetime - - @api.multi - @api.onchange("partner_id") - def onchange_partner_id(self): - """Copy fields cfdi_use, payment_method_id and cfdi_adenda_id - from selected partner - """ - res = super(AccountInvoice, self).onchange_partner_id() - for invoice in self.filtered(lambda i: i.type in ("out_invoice", "out_refund")): - partner = invoice.partner_id - if partner: - if partner.cfdi_fiscal_regime_id.id: - invoice.cfdi_fiscal_regime_id = partner.cfdi_fiscal_regime_id.id - if partner.cfdi_use.id: - invoice.cfdi_use = partner.cfdi_use.id - invoice.payment_method_id = partner.payment_method_id.id - invoice.cfdi_adenda_ids = [(6, 0, partner.cfdi_adenda_ids._ids)] - return res - - @api.model - def _prepare_refund( - self, invoice, date=None, period_id=None, description=None, journal_id=None - ): - """Overrides the prepare refund function to set field UsoCFDI""" - values = super(AccountInvoice, self)._prepare_refund( - invoice, - date=date, - period_id=period_id, - description=description, - journal_id=journal_id, - ) - # We set field UsoCFDI to Descuentos y devoluciones for all refunds - usocfdi = self.env.ref("l10n_mx.cfdi_use_G02") - values["cfdi_use"] = usocfdi.id - values["cfdi_relation_type"] = self._context.get("cfdi_relation_type") - payment_type = self.env.ref("l10n_mx.pay_method_condonacion") - values["payment_type_ids"] = [(4, payment_type.id, None)] - payment_method = self.env.ref("l10n_mx.cfdi_payment_method_1") - values["payment_method_id"] = payment_method.id - - return values - - @api.multi - def action_invoice_validate(self): - res = super(AccountInvoice, self).action_invoice_validate() - for account_invoice in self: - if account_invoice.journal_id.sign_sat: - # Create new CFDI object for this invoice - account_invoice.create_cfdi() - return res - - @api.multi - def action_cancel(self): - """Extend `AccountInvoice.action_cancel()`; Cancels the CFDI related to the - invoice - """ - # Get only invoices with related cfdi to cancel cfdi before cancel invoice - cfdis = self.filtered( - lambda i: - i.journal_id.sign_sat - and i.cfdi_id - and i.cfdi_id.state not in ["draft", "cancel"] - ) - for invoice in cfdis: - # Ensure we can cancel this invoice - invoice.check_if_can_cancel() - # If l10n_mx_edi_original_invoice is set save uuid to send info to PAC - # while cancel invoice - invoice.cfdi_id.substitute_cfdi_uuid = ( - invoice.l10n_mx_edi_original_invoice.cfdi_id.uuid - ) - cancelacion = invoice.cancel_cfdi()[0] - if cancelacion: - # CFDI cancelled (cancelacion == True) must cancel invoice too - super(AccountInvoice, invoice).action_cancel() - elif cancelacion is None: - # CFDI set to approval (cancelacion == None) must set invoice - # to waiting too - invoice.write({"state": "waiting"}) - elif cancelacion is False: - # CFDI cancel denied (cancelacion == False) must get back invoice - # to open state - self.undo_waiting_state() - - # Call super only with invoices without CFDI - invoices = self - cfdis - return super(AccountInvoice, invoices).action_cancel() - - @api.multi - def undo_waiting_state(self): - """When cancel is negate revert invoice to open and post account_move""" - to_update = self.filtered(lambda i: i.state == "waiting") - to_update.write({"state": "open"}) - to_update.mapped("move_id").post() - - @api.multi - def action_consult_cancellation_status(self): - """Verify cancellation status""" - # TODO: Is this really needed? Maybe we can reuse the action_cancel - for invoice in self: - try: - with self.env.cr.savepoint(): - status_cancelacion = invoice.consult_cfdi_cancellation_status() - if status_cancelacion is None: - invoice.message_post( - body=_("No status update found on SAT") - ) - elif status_cancelacion is False: - self.undo_waiting_state() - else: - try: - invoice.action_cancel() - except Exception as e: - invoice.message_post( - body=_("The invoice could not be canceled") - ) - except Exception as e: - invoice.message_post( - body=_( - "Could not check SAT invoice status " - "due to the following error: %s." - ) % (e) - ) - - @api.multi - def _validate_cfdi_data(self): - self._validate_account_invoice_fields() - self._validate_account_invoice_partners() - - @api.multi - def _validate_account_invoice_fields(self): - """ This function is to validate that the invoice has an - issue address, partner and company""" - for record in self: - self._cfdi_validate_required_fields( - record, - [ - "cfdi_use", - "partner_id", - "cfdi_fiscal_regime_id", - "payment_type_ids", - "address_issued_id", - "journal_id", - "account_id" - ], - ) - - @api.multi - def _validate_account_invoice_partners(self): - """ This function is to validate that the vat of the - commercial partner and company""" - - required = [ - "vat", - "zip", - "cfdi_fiscal_regime_id", - ] - - for record in self: - self._cfdi_validate_required_fields( - record.address_issued_id, - required, - ) - - self._cfdi_validate_required_fields( - record.company_id.partner_id, - required, - ) - - self._cfdi_validate_required_fields( - record.commercial_partner_id, - required, - ) - - if record.partner_id.country_id.code_alpha3 == "MEX": - self._cfdi_validate_required_fields( - record.partner_id, - required, - ) - - @api.multi - def _compute_export(self): - for record in self: - if record.cfdi_adenda_ids.filtered( - lambda i: i.code == "02" - ) and record.commercial_partner_id.country_id.code_alpha3 != "MEX": - record.l10n_mx_export = True - - -class AccountInvoiceLine(models.Model): - _inherit = "account.invoice.line" - - cfdi_numero_identificacion = fields.Char( - string="Identification Number", - help="This number is the identification number for invoice line in cfdi", - compute="_compute_cfdi_ident_number", - ) - cfdi_cuentapredial = fields.Char( - string="Cuenta Predial", - help="Predial number for real state lease invoices", - ) - cfdi_custom_number = fields.Many2many( - "import.pediment.number", - "invoice_pediment_rel", - "invoice_line_id", - "cfdi_custom_number_id", - "N° Pediment", - ) - - @api.multi - @api.constrains("cfdi_cuentapredial") - def _constraint_cfdi_cuentapredial(self): - for record in self: - if record.cfdi_cuentapredial and not record.cfdi_cuentapredial.isdigit(): - raise ValidationError( - _( - "Predial Account must be only numbers.\n" - "All letters must be replaced by '0'" - ) - ) - - @api.multi - @api.depends("product_id") - def _compute_cfdi_ident_number(self): - """Update cfdi_numero_identificacion only for invoice lines that are not signed - yet and that are customer's documents. - """ - to_update = self.filtered( - lambda line: line.invoice_id.cfdi_state == "draft" - and line.invoice_id.type in ("out_invoice", "out_refund") - ) - for record in to_update: - record.cfdi_numero_identificacion = record.product_id.default_code - - @property - def cfdi_product_code(self): - """Return computed cfdi code for current line based on code selected - for product or product category. - Raise a validation error if no code found""" - self.ensure_one() - if self.product_id.cfdi_product_service_id.exists(): - return self.product_id.cfdi_product_service_id.code - # Traverse product category to find cfdi product code - category = self.product_id.categ_id - while category: - if category.cfdi_product_service_id: - return category.cfdi_product_service_id.code - else: - category = category.parent_id - # If not have return for this point raise an error - raise ValidationError( - _("Missing SAT code for product: {product}").format( - product=self.product_id.name - ) - ) - - @property - def importe(self): - """Return computed total line for display on CFDI""" - self.ensure_one() - return self.export_invoice_line_for_xml().importe - - @property - def descuento(self): - """Property that computes the discount amount in currency for CFDI XML - invoice view - """ - self.ensure_one() - return self.export_invoice_line_for_xml().descuento - - @property - def impuestos(self): - """Return computed taxes for display on CFDI""" - self.ensure_one() - taxes = {"traslados": [], "retenciones": [], "locales": []} - for tax in self.export_invoice_line_for_xml().taxes: - if tax.group in ["IVA", "IEPS", "ISR"]: - if tax.amount >= 0: - taxes["traslados"].append(tax) - else: - taxes["retenciones"].append(tax) - else: - taxes["locales"].append(tax) - return taxes - - @property - def valorunitario(self): - """Return computed price unit for display on CFDI""" - self.ensure_one() - return self.export_invoice_line_for_xml().price_unit - - def export_invoice_line_for_xml(self): - # pylint: disable=R1710 - """Computes all values needed for export account.invoice.line as CFDI""" - - class Dict2obj(object): - """Convert dictionary to object - @source http://stackoverflow.com/a/1305561/383912 - """ - - def __init__(self, d): - self.__dict__["d"] = d - - def __getattr__(self, key): - value = self.__dict__["d"][key] - return value - - def process_tax(tax): - """Helper function to populate extra values needed for display - taxes on CFDI representation from account.invoice - @param tax: tax values computed from original compute_all function - on account.invoice.tax object - @type tax: dictionary - @return: dictionary populated with all values needed for tax - excluding IEPS tax if partner is not IEPS subjected - @rtype: dictionary or None - """ - tax_record = self.env["account.tax"].browse(tax["id"]) - tax_group = tax_record.tax_category_id - # IEPS tax only must be included when partner is IEPS subjected - if tax_group.name == "IEPS" and not partner.ieps_subjected: - return - # TODO: Delete on version 3.0.0 - if "base" not in tax: - tax["base"] = currency.cfdi_round(tax["price_unit"] * self.quantity) - tax["group"] = tax_group.name - tax["type"] = tax_record.type - tax["TasaOCuota"] = abs(tax_record.amount) - return tax - - currency = self.invoice_id.currency_id - precision = self.env["decimal.precision"].precision_get("Product Price") - total_discount = 1 - self.discount / 100.0 - # Include global discount - total_discount *= 1 - self.invoice_id.global_discount / 100 - price = float_round(self.price_unit * total_discount, precision) - partner = self.invoice_id.partner_id - # Check if IEPS is on taxes, this will be used later to know if need price - # to be recalculated because IEPS must be price included as partner is not - # IEPS subjected and product include IEPS taxes - ieps_group = self.env.ref("l10n_mx.tax_category_ieps") - is_ieps_tax_subjected = any( - tax.tax_category_id == ieps_group for tax in self.invoice_line_tax_id - ) - is_price_included = any( - tax.price_include for tax in self.invoice_line_tax_id - ) - # Compute taxes using original compute_all function from - # account.invoice.tax to get same result for CFDI display - res = self.invoice_line_tax_id.compute_all( - price, - self.quantity, - product=self.product_id, - partner=partner, - currency=self.invoice_id.currency_id, - ) - # pylint: disable=C1801 - if len(res["taxes"]) == 0: - raise ValidationError( - _("Product {p} must have at least one tax selected.").format( - p=self.product_id.name - ) - ) - taxes = [] - taxes_list = iter(res["taxes"]) - tax = next(taxes_list) - # Iterate taxes and append to the new tax list as needed - while True: - tax = process_tax(tax) - if tax: - taxes.append(tax) - try: - tax = next(taxes_list) - except StopIteration: - if tax is None: - raise ValidationError( - _( - "Incorrect tax sequence configuration, check " - "this data in Account >> Tax >> Sequence" - ) - ) - break - res["price_unit"] = self.price_unit - # Recompute price_unit is needed when any tax is setup to price included or - # when product is IEPS subjected but not partner - if is_price_included or (not partner.ieps_subjected and is_ieps_tax_subjected): - # Send round=False in context to avoid rounding to wrong value when working - # with high Product Price precision (6 digits) - res["price_unit"] = self.invoice_line_tax_id.with_context( - round=False - ).compute_all( - res["price_unit"], 1.0, product=self.product_id, partner=partner - )[ - "base" - ] - # Round price_unit to Product Price precision after computing taxes - res["price_unit"] = float_round( - res["price_unit"], - self.env["decimal.precision"].precision_get("Product Price"), - ) - - res["importe"] = currency.round(res["price_unit"] * self.quantity) - res["descuento"] = currency.round(res["importe"] * (1 - total_discount)) - # Overrides original taxes with the list computed by us - res["taxes"] = [Dict2obj(t) for t in taxes] - return Dict2obj(res) diff --git a/l10n_mx_facturae/models/account_move.py b/l10n_mx_facturae/models/account_move.py index 06a0050e1ee453b3adf35fb9b9c1e33ac323a6f6..e4265bd6c7d73bf938cc00a16b498243ced01706 100644 --- a/l10n_mx_facturae/models/account_move.py +++ b/l10n_mx_facturae/models/account_move.py @@ -1,19 +1,159 @@ # -*- coding: utf-8 -*- import logging +import pytz +from datetime import datetime, timedelta -from openerp import api, models -from openerp.exceptions import Warning as UserError -from openerp.tools.translate import _ +from odoo import api, fields, models +from odoo.exceptions import ValidationError, Warning as UserError +from odoo.tools.float_utils import float_round +from odoo.tools.translate import _ _logger = logging.getLogger(__name__) class AccountMove(models.Model): - _inherit = "account.move" + _name = "account.move" + _inherit = ["account.move", "base.cfdi"] + + @property + def formapago(self): + """Return payment type for display on CFDI""" + self.ensure_one() + try: + code = self.payment_type_ids[0].code + except IndexError: + code = "99" + return code + + @property + def descuento(self): + self.ensure_one() + discount = 0.0 + for line in self.invoice_line_ids: + discount += line.descuento + return discount + + @property + def subtotal(self): + self.ensure_one() + subtotal = 0.0 + for line in self.invoice_line_ids: + subtotal += line.importe + return subtotal + + @property + def total(self): + self.ensure_one() + return ( + self.subtotal + - self.descuento + + self.impuestos["total_traslados"] + + self.impuestos["total_retenciones"] + + self.impuestos["total_locales"] + ) + + @property + def impuestos(self): + """Return computed taxes for display on CFDI""" + self.ensure_one() + tax_grouped = {} + taxes = { + "traslados": [], + "retenciones": [], + "locales": [], + "total_traslados": 0.0, + "total_retenciones": 0.0, + "total_locales": 0.0, + } + + for line in self.invoice_line_ids: + for tax in line.export_invoice_line_for_xml().taxes: + # Mimic logic from compute function in account.invoice.tax + # object to group taxes from invoice lines and be able to get + # the same result for display on CFDI + grouping_dict = self._get_tax_grouping_key_from_tax_line(line) + grouping_key = "-".join(str(v) for v in grouping_dict.values()) + if grouping_key not in tax_grouped: + tax_grouped[grouping_key] = tax + else: + tax_grouped[grouping_key].amount += tax.amount + tax_grouped[grouping_key].base += tax.base + + # Classify taxes for CFDI + for dummy, tax in tax_grouped.items(): + if tax.group in ["IVA", "IEPS", "ISR"]: + tax.xml_amount = "{0:.2f}".format(tax.amount) + if tax.amount >= 0: + taxes["traslados"].append(tax) + taxes["total_traslados"] += tax.amount + else: + taxes["retenciones"].append(tax) + taxes["total_retenciones"] += tax.amount + else: + taxes["locales"].append(tax) + taxes["total_locales"] += tax.amount + return taxes + + # pylint: disable=W0212 + address_issued_id = fields.Many2one( + "res.partner", + "Address Issued Invoice", + readonly=True, + states={"draft": [("readonly", False)]}, + help="This address will be used as address that issued " + "for electronic invoice", + compute="_compute_address_issued", + ) + datetime = fields.Datetime(compute="_compute_datetime") + date_invoice_cancel = fields.Datetime( + "Date Invoice Cancelled", + readonly=True, + copy=False, + help="If the invoice is cancelled, save the date" " when was cancel", + ) + payment_method_id = fields.Many2one( + "cfdi.payment.method", + string="Payment Method", + readonly=True, + states={"draft": [("readonly", False)]}, + help="Payment method associated with this payment term according", + ) + cfdi_use = fields.Many2one( + "cfdi.use", "CFDI use", readonly=True, states={"draft": [("readonly", False)]} + ) + cfdi_relation_type = fields.Many2one("cfdi.relation.type", "CFDI Relation type") + l10n_mx_edi_to_cancel = fields.Char( + #compute="_compute_l10n_mx_edi_to_cancel", + #search="_search_l10n_mx_edi_to_cancel", + help="Technical field to display a warning when an invoice must be canceled " + "because have being replaced by a new one.", + ) + l10n_mx_edi_original_invoice = fields.Many2one( + "account.move", + #compute="_compute_l10n_mx_edi_to_cancel", + help="Technical field to relate origin invoice with substitute", + ) + invoice_global = fields.Boolean( + compute="_compute_invoice_global", + string="Is Invoice global?", + ) + cfdi_periodicity = fields.Selection( + [ + ("01", "Daily"), + ("02", "Weekly"), + ("03", "Biweekly"), + ("04", "Monthly"), + ], + default="04", + ) + + l10n_mx_export = fields.Boolean( + compute="_compute_export", + string="Merchandise export", + ) - @api.multi def button_validate(self): """ Extend `AccountMove.button_validate`: prevents to manipulate the @@ -28,3 +168,626 @@ class AccountMove(models.Model): "for a request to cancel.") % account_move.name ) return super(AccountMove, self).button_validate() + + def _get_cfdi_datetime(self): + """This base function inherits the creation of the cfdi datetime + what it basically does is respect the date_invoice from invoice form + """ + self.ensure_one() + date_invoice = fields.Datetime.from_string(self.invoice_date) + time_now = fields.Datetime.context_timestamp(self, datetime.now()) + # If we are singing an invoice in the past we need to move forward 1 minute the + # computed time to be in the 72 hours range defined by SAT + # On the opposite if we are singing an invoice for today we move back 1 minute + # the computed time to prevent false errors about being out of 72 hours range + if date_invoice.date() < time_now.date(): + time_now += timedelta(minutes=1) + else: + time_now -= timedelta(minutes=1) + date_create = datetime.combine(date_invoice, time_now.timetz()) + # Needed to save date into the database as until now we are using user timezone + # to express the datetime that will cause double timezone conversion if kept + date_create = date_create.astimezone(tz=pytz.utc) + return fields.Datetime.to_string(date_create) + + @api.depends("journal_id") + def _compute_address_issued(self): + for invoice in self: + if invoice.journal_id.address_issued_id: + invoice.address_issued_id = invoice.journal_id.address_issued_id + else: + invoice.address_issued_id = invoice.company_id.partner_id + + def _compute_sequence_id(self): + for record in self: + record.sequence_id = record.journal_id.secure_sequence_id + + def _compute_l10n_mx_edi_to_cancel(self): + """Computes legend to display when an invoice needs to be cancelled because + have being substituted by a new one. + """ + # Get substitution relation to compare + substitution = self.env.ref("l10n_mx_ir_attachment_facturae.cfdi_relation_type_04") + invoices_to_cancel = self.filtered( + lambda i: i.move_type == "out_invoice" and i.state in ("posted",) + ) + for inv in invoices_to_cancel: + message = "" + origin_documents = inv.refund_invoice_ids.filtered( + lambda i: + i.state in ("posted",) and i.move_type == "out_invoice" + ) + is_substitue = any( + [o.cfdi_relation_type == substitution for o in origin_documents] + ) + if origin_documents and is_substitue: + message00 = _("This invoice must be cancelled because have being " + "replaced with invoice: ") + inv.l10n_mx_edi_original_invoice = origin_documents[0] + + inv.l10n_mx_edi_to_cancel = message + + @api.model + def _search_l10n_mx_edi_to_cancel(self, operator, value): + """Allows to get ids for invoices that must be cancelled""" + substitution = self.env.ref("l10n_mx_ir_attachment_facturae.cfdi_relation_type_04") + + if operator not in ["=", "!="] or not isinstance(value, bool): + raise UserError(_("Operation not supported")) + + if operator != "=": + value = not value + + self._cr.execute( + """ + SELECT id FROM account_move am + WHERE EXISTS ( + SELECT * FROM account_invoice_refunds_rel airr + INNER JOIN account_move air on airr.refund_invoice_id = air.id + WHERE airr.original_invoice_id = am.id + AND air.cfdi_relation_type = %s + LIMIT 1 + ) + AND am.move_state IN ('posted',) + """, + (substitution.id,) + ) + return [ + ("id", "in" if value else "not in", [r[0] for r in self._cr.fetchall()]) + ] + + @api.model + def cron_invoices_pending_to_cancel(self): + invoices = self.env["account.move"].search([("state", "in", ["waiting"])]) + for invoice in invoices: + invoice.action_consult_cancellation_status() + + def _compute_datetime(self): + for record in self: + record.datetime = record.cfdi_datetime + + @api.onchange("partner_id") + def onchange_partner_id(self): + """Copy fields cfdi_use, payment_method_id and cfdi_adenda_id + from selected partner + """ + res = super(AccountMove, self).onchange_partner_id() + for invoice in self.filtered(lambda i: i.move_type in ("out_invoice", "out_refund")): + partner = invoice.partner_id + if partner: + if partner.cfdi_use_id: + invoice.cfdi_use = partner.cfdi_use_id.id + invoice.payment_method_id = partner.payment_method_id.id + invoice.cfdi_adenda_ids = [(6, 0, partner.cfdi_adenda_ids._ids)] + return res + + @api.model + def _prepare_refund( + self, invoice, date=None, period_id=None, description=None, journal_id=None + ): + """Overrides the prepare refund function to set field UsoCFDI""" + values = super(AccountMove, self)._prepare_refund( + invoice, + date=date, + period_id=period_id, + description=description, + journal_id=journal_id, + ) + # We set field UsoCFDI to Descuentos y devoluciones for all refunds + usocfdi = self.env.ref("l10n_mx_base.cfdi_use_G02") + values["cfdi_use"] = usocfdi.id + values["cfdi_relation_type"] = self._context.get("cfdi_relation_type") + payment_type = self.env.ref("l10n_mx_base.pay_method_condonacion") + values["payment_type_ids"] = [(4, payment_type.id, None)] + payment_method = self.env.ref("l10n_mx_base.cfdi_payment_method_1") + values["payment_method_id"] = payment_method.id + + return values + + def action_post(self): + res = super(AccountMove, self).action_post() + for record in self: + record.action_invoice_validate() + return res + + def action_invoice_validate(self): + for account_invoice in self: + if account_invoice.journal_id.sign_sat: + # Create new CFDI object for this invoice + account_invoice.create_cfdi() + + def l10n_mx_action_cancel(self): + """Cancels the CFDI related to the invoice""" + + # Get only invoices with related cfdi to cancel cfdi before cancel invoice + cfdis = self.filtered( + lambda i: + i.journal_id.sign_sat + and i.cfdi_id + and i.cfdi_id.state not in ["draft", "cancel"] + ) + + for invoice in cfdis: + invoice.message_post( + body=_("Cancellation request sent") + ) + cancelacion = invoice.cancel_cfdi() + if cancelacion: + # CFDI cancelled (cancelacion == True) must cancel invoice too + invoice.button_draft() + invoice.button_cancel() + elif cancelacion is None: + # CFDI set to approval (cancelacion == None) must set invoice + # to waiting too + invoice.write({"state": "posted"}) + invoice.message_post( + body=_("Awaiting cancellation") + ) + elif cancelacion is False: + # CFDI cancel denied (cancelacion == False) must get back invoice + # to open state + self.undo_waiting_state() + invoice.message_post( + body=_("Denied cancellation") + ) + + def undo_waiting_state(self): + """When cancel is negate revert invoice to open and post account_move""" + for record in self: + to_update = record.filtered(lambda i: i.cfdi_state == "waiting") + to_update.write({"state": "posted"}) + to_update.mapped("move_id").post() + + @api.depends("state", "cfdi_state") + def _compute_show_reset_to_draft_button(self): + super()._compute_show_reset_to_draft_button() + + for move in self: + if move.state in ("posted", "cancel") and move.cfdi_state in ("signed", "done", "waiting", "cancel"): + move.show_reset_to_draft_button = False + + def action_consult_cancellation_status(self): + """Verify cancellation status""" + # TODO: Is this really needed? Maybe we can reuse the action_cancel + for invoice in self: + try: + with self.env.cr.savepoint(): + status_cancelacion = invoice.consult_cfdi_cancellation_status() + if status_cancelacion is None: + invoice.message_post( + body=_("No status update found on SAT") + ) + elif status_cancelacion is False: + self.undo_waiting_state() + else: + try: + invoice.action_cancel() + except Exception as e: + invoice.message_post( + body=_("The invoice could not be canceled") + ) + except Exception as e: + invoice.message_post( + body=_( + "Could not check SAT invoice status " + "due to the following error: %s." + ) % (e) + ) + + def _compute_export(self): + for record in self: + record.l10n_mx_export = bool( + record.cfdi_adenda_ids.filtered( + lambda i: i.code == "02" + ) and record.commercial_partner_id.country_id.code_alpha3 != "MEX" + ) + + def _compute_invoice_global(self): + for record in self: + record.invoice_global = bool( + record.commercial_partner_id.name == "Publico en general" and + record.commercial_partner_id.vat_split == "XAXX010101000" + ) + + ############################################################ + # This section compute function of decision for xml CFDI 4.0 + ############################################################ + + def l10n_mx_facturae_compute_fecha(self): + tz = pytz.timezone(self.env.user.tz or pytz.utc.zone) + date = pytz.UTC.localize(self.cfdi_datetime).astimezone(tz) + return date.strftime("%Y-%m-%dT%H:%M:%S") + + def l10n_mx_facturae_compute_fecha_global_year(self): + tz = pytz.timezone(self.env.user.tz or pytz.utc.zone) + date = pytz.UTC.localize(self.cfdi_datetime).astimezone(tz) + return date.strftime("%Y") + + def l10n_mx_facturae_compute_fecha_global_month(self): + tz = pytz.timezone(self.env.user.tz or pytz.utc.zone) + date = pytz.UTC.localize(self.cfdi_datetime).astimezone(tz) + return date.strftime("%m") + + def l10n_mx_facturae_compute_serie(self): + if self.journal_id.secure_sequence_id.prefix: + return self.serie + + def l10n_mx_facturae_compute_export(self): + if self.l10n_mx_export: + return "02" + + return "01" + + def l10n_mx_facturae_compute_subtotal(self): + return "{0:.2f}".format(self.subtotal) + + def l10n_mx_facturae_compute_total(self): + return "{0:.2f}".format(self.total) + + def l10n_mx_facturae_compute_payment_terms(self): + paymentterms = self.invoice_payment_term_id.name + return paymentterms + + def l10n_mx_facturae_compute_tipocambio(self): + tipocambio = False + if self.currency_id.name != "MXN": + date = self.invoice_date + from_currency = self.currency_id + to_currency = self.company_id.currency_id + tipocambio = from_currency._get_conversion_rate( + from_currency, + to_currency, + self.company_id, + date, + ) + return tipocambio + + def l10n_mx_facturae_compute_type_document(self): + if self.move_type == "out_invoice": + return "I" + elif self.move_type == "out_refund": + return "E" + + def l10n_mx_facturae_compute_payment_method(self): + if self.payment_method_id.code: + return self.payment_method_id.code + else: + return "PUE" + + def l10n_mx_facturae_compute_rfc(self): + if self.commercial_partner_id.country_id.code_alpha3 != "MEX": + return "XEXX010101000" + else: + return self.commercial_partner_id.vat_split + + def l10n_mx_facturae_compute_use_cfdi(self): + if self.commercial_partner_id.country_id.code_alpha3 != "MEX": + return "S01" + else: + return self.cfdi_use.code + + def l10n_mx_facturae_compute_domicilio_fiscal(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + vat = self.commercial_partner_id.vat_split + domiciliofiscal = self.commercial_partner_id.zip + if code_country == "MEX" and vat == "XAXX010101000" or code_country != "MEX": + domiciliofiscal = self.address_issued_id.zip + + return domiciliofiscal + + def l10n_mx_facturae_compute_regimen_fiscal_receptor(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + regimenfiscal = self.commercial_partner_id.cfdi_fiscal_regime_id.code + if code_country != "MEX": + regimenfiscal = "616" + + return regimenfiscal + + def l10n_mx_facturae_compute_residencia_fiscal(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + residenciafiscal = False + if code_country != "MEX": + residenciafiscal = code_country + + return residenciafiscal + + def l10n_mx_facturae_compute_regimen_tributario(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + regimentribut = False + if code_country != "MEX": + regimentribut = self.commercial_partner_id.vat_split + + return regimentribut + + def l10n_mx_facturae_compute_product(self): + for line in self.invoice_line_ids: + product = line.cfdi_product_code + if not line.product_id.name: + product = "01010101" + + return product + + def l10n_mx_facturae_compute_unit(self): + for line in self.invoice_line_ids: + if len(line.product_uom_id.name) > 20: + unit = line.product_uom_id.name[:17].upper() + "..." + else: + unit = line.product_uom_id.name.upper() + + return unit + + def l10n_mx_facturae_compute_total_traslados(self, taxes): + res = False + if any(tax.type != "Excento" for tax in taxes["traslados"]): + res = taxes["total_traslados"] + + return '{0:.2f}'.format(res) + + def l10n_mx_facturae_compute_total_retenciones(self, taxes): + res = False + if taxes["total_retenciones"]: + res = taxes["total_retenciones"] + '{0:.2f}'.format(res) + + return res + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + cfdi_numero_identificacion = fields.Char( + string="Identification Number", + help="This number is the identification number for invoice line in cfdi", + compute="_compute_cfdi_ident_number", + ) + cfdi_cuentapredial = fields.Char( + string="Cuenta Predial", + help="Predial number for real state lease invoices", + ) + cfdi_custom_number = fields.Many2many( + "cfdi.import.pediment.number", + "invoice_pediment_rel", + "invoice_line_id", + "cfdi_custom_number_id", + "N° Pediment", + ) + + @api.constrains("cfdi_cuentapredial") + def _constraint_cfdi_cuentapredial(self): + for record in self: + if record.cfdi_cuentapredial and not record.cfdi_cuentapredial.isdigit(): + raise ValidationError( + _( + "Predial Account must be only numbers.\n" + "All letters must be replaced by '0'" + ) + ) + + @api.depends("product_id") + def _compute_cfdi_ident_number(self): + """Update cfdi_numero_identificacion only for invoice lines that are not signed + yet and that are customer's documents. + """ + to_update = self.filtered( + lambda line: line.move_id.cfdi_state == "draft" + and line.move_id.move_type in ("out_invoice", "out_refund") + ) + for record in to_update: + record.cfdi_numero_identificacion = record.product_id.default_code + + @property + def cfdi_product_code(self): + """Return computed cfdi code for current line based on code selected + for product or product category. + Raise a validation error if no code found""" + self.ensure_one() + if self.product_id.cfdi_product_service_id.exists(): + return self.product_id.cfdi_product_service_id.code + # Traverse product category to find cfdi product code + category = self.product_id.categ_id + while category: + if category.cfdi_product_service_id: + return category.cfdi_product_service_id.code + else: + category = category.parent_id + # If not have return for this point raise an error + raise ValidationError( + _("Missing SAT code for product: {product}").format( + product=self.product_id.name + ) + ) + + def l10n_mx_facturae_compute_objeto_impuesto(self): + objimp = "01" + + # Process taxes for this line + impuestos = self.impuestos + + # Check if there is a maningfull tax + summ = sum([float(o.base) for o in impuestos["traslados"]]) + summ += sum([float(o.base) for o in impuestos["retenciones"]]) + + if summ: + objimp = "02" + + return objimp + + @property + def importe(self): + """Return computed total line for display on CFDI""" + self.ensure_one() + return self.export_invoice_line_for_xml().importe + + @property + def descuento(self): + """Property that computes the discount amount in currency for CFDI XML + invoice view + """ + self.ensure_one() + return self.export_invoice_line_for_xml().descuento + + @property + def impuestos(self): + """Return computed taxes for display on CFDI""" + self.ensure_one() + taxes = {"traslados": [], "retenciones": [], "locales": []} + for tax in self.export_invoice_line_for_xml().taxes: + if tax.group in ["IVA", "IEPS", "ISR"]: + if tax.amount >= 0: + taxes["traslados"].append(tax) + else: + taxes["retenciones"].append(tax) + else: + taxes["locales"].append(tax) + return taxes + + @property + def valorunitario(self): + """Return computed price unit for display on CFDI""" + self.ensure_one() + return self.export_invoice_line_for_xml().price_unit + + def export_invoice_line_for_xml(self): + """Computes all values needed for export account.move.line as CFDI""" + + class Dict2obj(object): + """Convert dictionary to object + @source http://stackoverflow.com/a/1305561/383912 + """ + + def __init__(self, d): + self.__dict__["d"] = d + + def __getattr__(self, key): + value = self.__dict__["d"][key] + return value + + def process_tax(tax): + """Helper function to populate extra values needed for display + taxes on CFDI representation from account.move + @param tax: tax values computed from original compute_all function + on account.move.tax object + @type tax: dictionary + @return: dictionary populated with all values needed for tax + excluding IEPS tax if partner is not IEPS subjected + @rtype: dictionary or None + """ + tax_record = self.env["account.tax"].browse(tax["id"]) + tax_rep_lines = tax_record.refund_repartition_line_ids._origin.filtered( + lambda x: x.repartition_type == "tax" + ) + # TODO: What happen when more than one tag is found? + tax_group = tax_rep_lines.mapped("tag_ids")[0] + # IEPS tax only must be included when partner is IEPS subjected + if tax_group.name == "IEPS" and not partner.ieps_subjected: + return + tax["group"] = tax_group.name + tax["xml_name"] = ( + "001" if tax_group.name == "ISR" else + "002" if tax_group.name == "IVA" else "003" + ) + tax["type"] = tax_record.l10n_mx_tax_type + tax["xml_amount"] = "{0:.2f}".format(tax["amount"]) + tax["TasaOCuota"] = "{0:.6f}".format(abs(tax_record.amount)/100) + return tax + + currency = self.move_id.currency_id + precision = self.env["decimal.precision"].precision_get("Product Price") + total_discount = 1 - self.discount / 100.0 + # Include global discount + total_discount *= 1 - self.move_id.amount_global_discount / 100 + price = float_round(self.price_unit * total_discount, precision) + partner = self.move_id.partner_id + # Check if IEPS is on taxes, this will be used later to know if need price + # to be recalculated because IEPS must be price included as partner is not + # IEPS subjected and product include IEPS taxes + ieps_group = self.env.ref("l10n_mx_base.tax_group_ieps") + is_ieps_tax_subjected = any( + tax.tax_group_id == ieps_group for tax in self.tax_ids + ) + is_price_included = any( + tax.price_include for tax in self.tax_line_id + ) + # Compute taxes using original compute_all function from + # account.move.tax to get same result for CFDI display + res = self.tax_ids.compute_all( + price, + quantity=self.quantity, + product=self.product_id, + partner=partner, + currency=self.move_id.currency_id, + ) + # pylint: disable=C1801 + if len(res["taxes"]) == 0: + raise ValidationError( + _("Product {p} must have at least one tax selected.").format( + p=self.product_id.name + ) + ) + taxes = [] + taxes_list = iter(res["taxes"]) + tax = next(taxes_list) + # Iterate taxes and append to the new tax list as needed + while True: + tax = process_tax(tax) + if tax: + taxes.append(tax) + try: + tax = next(taxes_list) + except StopIteration: + if tax is None: + raise ValidationError( + _( + "Incorrect tax sequence configuration, check " + "this data in Account >> Tax >> Sequence" + ) + ) + break + res["price_unit"] = self.price_unit + # Recompute price_unit is needed when any tax is setup to price included or + # when product is IEPS subjected but not partner + if is_price_included or (not partner.ieps_subjected and is_ieps_tax_subjected): + # Send round=False in context to avoid rounding to wrong value when working + # with high Product Price precision (6 digits) + res["price_unit"] = self.tax_line_id.with_context( + round=False + ).compute_all( + res["price_unit"], 1.0, product=self.product_id, partner=partner + )[ + "base" + ] + # Round price_unit to Product Price precision after computing taxes + res["price_unit"] = float_round( + res["price_unit"], + self.env["decimal.precision"].precision_get("Product Price"), + ) + + res["importe"] = currency.round(res["price_unit"] * self.quantity) + res["descuento"] = currency.round(res["importe"] * (1 - total_discount)) + # Overrides original taxes with the list computed by us + res["taxes"] = [Dict2obj(t) for t in taxes] + return Dict2obj(res) + + # ==================== + + def l10n_mx_facturae_importe(self): + return "{0:.2f}".format(self.importe) diff --git a/l10n_mx_facturae/models/account_payment.py b/l10n_mx_facturae/models/account_payment.py new file mode 100644 index 0000000000000000000000000000000000000000..e4406af49d74fa856d272e77973c9c7a529f676e --- /dev/null +++ b/l10n_mx_facturae/models/account_payment.py @@ -0,0 +1,435 @@ +# -*- coding: utf-8 -*- + +import pytz +from datetime import datetime, timedelta + +from odoo import api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools import float_round +from odoo.tools.translate import _ + + +class AccountPayment(models.Model): + _name = "account.payment" + _inherit = ["account.payment", "base.cfdi"] + + show_unreconcile = fields.Boolean( + #compute="_compute_show_unreconcile", + help="Helper field to hide unreconcile button", + ) + + ############################################################ + # This section compute function of decision for xml CFDI 4.0 + ############################################################ + + def l10n_mx_facturae_payment_compute_serie(self): + if self.journal_id.secure_sequence_id.prefix: + return self.serie + + def l10n_mx_facturae_payment_compute_fecha(self): + tz = pytz.timezone(self.env.user.tz or pytz.utc.zone) + date = pytz.UTC.localize(self.cfdi_datetime).astimezone(tz) + return date.strftime("%Y-%m-%dT%H:%M:%S") + + def l10n_mx_facturae_payment_compute_rfc(self): + if self.commercial_partner_id.country_id.code_alpha3 != "MEX": + return "XEXX010101000" + else: + return self.commercial_partner_id.vat_split + + def l10n_mx_facturae_payment_compute_domicilio_fiscal(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + vat = self.commercial_partner_id.vat_split + domiciliofiscal = self.commercial_partner_id.zip + if code_country == "MEX" and vat == "XAXX010101000" or code_country != "MEX": + domiciliofiscal = self.address_issued_id.zip + + return domiciliofiscal + + def l10n_mx_facturae_payment_compute_regimen_fiscal_receptor(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + regimenfiscal = self.commercial_partner_id.cfdi_fiscal_regime_id.code + if code_country != "MEX": + regimenfiscal = "616" + + return regimenfiscal + + def l10n_mx_facturae_payment_compute_residencia_fiscal(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + residenciafiscal = False + if code_country != "MEX": + residenciafiscal = code_country + + return residenciafiscal + + def l10n_mx_facturae_payment_compute_regimen_tributario(self): + code_country = self.commercial_partner_id.country_id.code_alpha3 + regimentribut = False + if code_country != "MEX": + regimentribut = self.commercial_partner_id.vat_split + + return regimentribut + + def l10n_mx_facturae_payment_montototalpagos(self): + monto = self.amount + if self.currency_id.name != 'MXN': + monto = self.currency_id.round(self.currency_rate() * self.amount) + return monto + + def l10n_mx_facturae_payment_compute_pago_fecha(self): + return self.date.strftime("%Y-%m-%dT%H:%M:%S") + + def l10n_mx_facturae_payment_compute_tipocambio(self): + tipocambio = "1" + if self.currency_id.name != "MXN": + tipocambio = self.custom_rate + + return tipocambio + + + ############################################################ + # This section calculate for xml CFDI 4.0 + ############################################################ + + @api.depends("state", "cfdi_state") + def _compute_show_unreconcile(self): + for payment in self: + if payment.cfdi_state in ["signed", "done"]: + payment.show_unreconcile = False + elif payment.state in ["draft", "cancel"]: + payment.show_unreconcile = False + else: + payment.show_unreconcile = True + + def _compute_sequence_id(self): + for record in self: + record.sequence_id = record.journal_id.secure_sequence_id + + def sign_payment(self): + """Create CFDI for selected vouchers""" + # Only vouchers to sign are the receipts + receipts = self.filtered(lambda r: r.payment_type == "inbound") + # Get only receipts that doesn't have a CFDI yet and create it + for receipt in receipts.filtered(lambda r: not r.cfdi_id.exists()): + receipt.create_cfdi() + + def cancel_voucher(self): + """Cancel CFDI for selected vouchers""" + res = super(AccountPayment, self).cancel_voucher() + for record in self: + record.cancel_cfdi() + # Delete relation from voucher and CFDI + record.cfdi_id = False + return res + + def substitute_voucher(self): + """Create new voucher for substitute this one""" + for record in self: + record._cancel_cfdi() + # After cancel voucher we must re open it + record.action_cancel_draft() + + def _cancel_cfdi_related_document(self): + for record in self: + self.cancel_voucher() + + def _l10n_mx_get_payments_to_invoice(self, invoice): + # Get all payments done to given invoice + self.ensure_one() + payments = self.env["account.move.line"] + for line in invoice._get_reconciled_invoices_partials(): + payments |= line[2] + + return payments + + def replace_cfdi(self): + """Cancel voucher cfdi by replacing with 1 MXN voucher""" + for record in self: + record.cancel_cfdi() + record.action_draft() + record.action_cancel() + + def currency_rate(self): + """Compute currency rate used for current voucher to display on XML + This calls currency.compute() with the right context, + so that it will take either the rate on the voucher if it is relevant + or will use the default behavior""" + self.ensure_one() + # We need to give the date in the context to get proper rate + voucher_currency = self.journal_id.currency_id or self.company_id.currency_id + voucher_currency = voucher_currency.with_context(date=self.date) + + res = float_round( + voucher_currency.compute(1.0, self.company_id.currency_id, round=False), + precision_digits=6, + ) + return res + + @property + def doctos_relacionados(self): + """Return a list with all related invoices to this payment""" + self.ensure_one() + + # Validate that all related invoices have an uuid + tofix = self.reconciled_invoice_ids.filtered(lambda i: not i.cfdi_folio_fiscal) + if tofix: + raise ValidationError( + _("Some of the invoices that will be paid with this record are not " + "signed, and the UUID is required to indicate the invoices that " + "are paid with this CFDI ") + ) + return self.reconciled_invoice_ids + + def numparcialidad(self, invoice): + """Computes payment number based on all payments done to invoice""" + for record in self: + payments = self._l10n_mx_get_payments_to_invoice(invoice) + payments -= record.line_ids + # Leave only payments done in cash or bank older than current + payments = payments.filtered( + lambda r: + r.date >= invoice.invoice_date + and r.journal_id.type in ("cash", "bank") + ) + return len(payments) + 1 + + def impsaldoant(self, invoice, to_xml=False): + """Computes amount_residual for invoice before current voucher""" + self.ensure_one() + # It is verified that the invoice is in another currency + is_multi_currency = ( + invoice.currency_id != self.company_id.currency_id + ) + # Search the lines of the policy where the account is receivable or payable + move_line_ids = invoice.line_ids.filtered( + lambda x: x.account_internal_type in ("receivable", "payable") + ) + if is_multi_currency: + amount_residual = sum( + move_line.amount_currency for move_line in move_line_ids + ) + else: + amount_residual = sum( + move_line.balance for move_line in move_line_ids + ) + # Get all payments done to given invoice + payments = self._l10n_mx_get_payments_to_invoice(invoice) + payments -= self.line_ids + # Leave only payments done before (older than) the current one: + # 1. Payments with document date before current voucher date + # 2. Payments with same date but created before that current voucher + payments = payments.filtered( + lambda r: + r.date < self.date + or (r.date == self.date and r.create_date < self.create_date) + ) + + for payment in payments: + amount_currency = abs(payment.amount_currency) or 0.0 + # If amount_currency exists it means we are dealing with a multi + # currency payment, and thus we need to sum amount_currency to + # amount_paid instead of normal debit - credit + if payment.currency_id: + amount_residual -= amount_currency + else: + amount_residual -= abs(payment.debit - payment.credit) + + amount_residual = self.currency_id.round(amount_residual) + if to_xml: + amount_residual = "{0:.2f}".format(amount_residual) + return amount_residual + + def imppagado(self, invoice, to_xml=False): + """Computes total amount payment on this voucher for given invoice""" + self.ensure_one() + + amount_paid = 0.0 + # Get payments done in this voucher for given invoice + payments = self._l10n_mx_get_payments_to_invoice(invoice) + payments &= self.line_ids + + for payment in payments: + # If currency_id exists it means we are dealing with a multi + # currency payment, and thus we need to sum amount_currency to + # amount_paid instead of normal debit - credit + # TODO: Relay on currency_id could result in wrong results when + # transactions done in three different currencies or more + is_multi_currency = ( + payment.currency_id + and invoice.currency_id != self.company_id.currency_id + ) + if is_multi_currency: + amount_paid += payment.amount_currency * -1 + else: + amount_paid += (payment.debit - payment.credit) * -1 + + amount_paid = self.currency_id.round(amount_paid) + + # Ensure amount paid is always equal or less than amount residual + amount_residual = self.impsaldoant(invoice) + amount_paid = min(amount_paid, amount_residual) + if to_xml: + amount_paid = "{0:.2f}".format(amount_paid) + return amount_paid + + def impsaldoinsoluto(self, invoice, to_xml=False): + self.ensure_one() + amount_residual = self.impsaldoant(invoice) - self.imppagado(invoice) + if to_xml: + amount_residual = "{0:.2f}".format(amount_residual) + return amount_residual + + def tipocambiodr(self, invoice): + """Compute currency rate used for given invoice to display on XML + This calls curreny.compute() with the right context, + so that it will take either the rate on the voucher if it is relevant + or will use the default behavior""" + self.ensure_one() + company_currency = self.company_id.currency_id + voucher_currency = self.currency_id.with_context(date=self.date) + + value = 1 + if invoice.currency_id != voucher_currency: + value = float_round( + voucher_currency._convert( + 1.0, + invoice.currency_id, + self.company_id, + self.date, + round=False, + ), + precision_digits=6 + ) + + return value + + def impuestos_dr(self, invoice, tax_type="traslados"): + """Computes move lines for on payment taxes related to given invoice""" + self.ensure_one() + + # Get payments done in this voucher for given invoice + payments = self._l10n_mx_get_payments_to_invoice(invoice) + payments &= self.line_ids + + tax_reconciles = invoice.tax_cash_basis_created_move_ids.mapped( + "tax_cash_basis_rec_id" + ).filtered( + lambda rec: rec.credit_move_id in payments + ) + + # Get tax moves done for this payment and this invoice + domain = [("move_id.tax_cash_basis_rec_id", "in", tax_reconciles._ids)] + # Exclude IEPS if partner not IEPS subjected + if not self.partner_id.ieps_subjected: + domain.append( + ("tax_ids.invoice_repartition_line_ids.tag_ids.name", "!=", "IEPS") + ) + + if tax_type == "traslados": + domain.append(("tax_ids.amount", ">=", 0)) + domain.append(("tax_ids.amount_type", "=", "percent")) + + else: + domain.append(("tax_ids.amount", "<", 0)) + domain.append(("tax_ids.amount_type", "=", "percent")) + + tax_moves = self.env["account.move.line"].search(domain) + return tax_moves + + def totales_p(self, tax_type="traslados"): + """Computes move lines for on payment taxes related to given invoice""" + self.ensure_one() + tax_totals = {} + + impuestos = self.impuestos_p(tax_type=tax_type) + # Load tax record for being able to use on template to fill XML + for line in impuestos: + tax = line["tax"] + tax_rep_lines = tax.invoice_repartition_line_ids._origin.filtered( + lambda x: x.repartition_type == "tax" + ) + tax_group = tax_rep_lines.mapped("tag_ids") + key = tax_group.name + str(tax.amount) + + tax_totals["importe" + key] = line["importe"] + tax_totals["base" + key] = line["tax_base"] + + return tax_totals + + def impuestos_p(self, tax_type="traslados"): + """Computes move lines for on payment taxes related to given invoice""" + self.ensure_one() + + tax_totals = dict() + + for invoice in self.doctos_relacionados: + for base_line in self.impuestos_dr(invoice, tax_type=tax_type): + for tax in base_line.tax_ids: + if tax.id not in tax_totals: + tax_totals[tax.id] = dict( + tax=tax, + tax_base=0.0, + importe=0.0, + ) + + tax_base = self._l10n_mx_tax_base_p(invoice, base_line) + importe = self._l10n_mx_tax_importe_p(invoice, base_line) + + tax_totals[tax.id]["tax_base"] += tax_base + tax_totals[tax.id]["importe"] += importe + + return [line for line in tax_totals.values()] + + def _l10n_mx_tax_base_dr(self, invoice, base_line): + amount = base_line.balance + + if invoice.currency_id != self.company_id.currency_id: + amount = base_line.amount_currency + + return -1 * amount + + def _l10n_mx_tax_importe_dr(self, invoice, base_line): + tax_line = base_line.move_id.line_ids.filtered( + lambda l: + l.tax_line_id in base_line.tax_ids + ) + amount = tax_line.balance + + if invoice.currency_id != self.company_id.currency_id: + amount = tax_line.amount_currency + + return -1 * amount + + def _l10n_mx_tax_base_p(self, invoice, base_line): + amount = base_line.balance + + if self.currency_id != self.company_id.currency_id: + amount = base_line.amount_currency + if invoice.currency_id == self.company_id.currency_id: + amount = invoice.currency_id._convert( + amount, + self.currency_id, + self.company_id, + self.date, + ) + + return -1 * amount + + def _l10n_mx_tax_importe_p(self, invoice, base_line): + tax_line = base_line.move_id.line_ids.filtered( + lambda l: + l.tax_line_id in base_line.tax_ids + ) + amount = tax_line.balance + + if self.currency_id != self.company_id.currency_id: + amount = tax_line.amount_currency + if invoice.currency_id == self.company_id.currency_id: + amount = invoice.currency_id._convert( + amount, + self.currency_id, + self.company_id, + self.date, + ) + + return -1 * amount diff --git a/l10n_mx_facturae/models/account_voucher.py b/l10n_mx_facturae/models/account_voucher.py deleted file mode 100644 index 6258e9bfbf5ea35f39e9af818488cb6811e90646..0000000000000000000000000000000000000000 --- a/l10n_mx_facturae/models/account_voucher.py +++ /dev/null @@ -1,415 +0,0 @@ -# -*- coding: utf-8 -*- - -from openerp import api, fields, models -from openerp.exceptions import ValidationError -from openerp.tools import float_round -from openerp.tools.translate import _ - - -class AccountVoucher(models.Model): - _name = "account.voucher" - _inherit = ["account.voucher", "base.cfdi"] - - show_unreconcile = fields.Boolean( - compute="_compute_show_unreconcile", - help="Helper field to hide unreconcile button", - ) - - @api.multi - @api.depends("state", "cfdi_state") - def _compute_show_unreconcile(self): - for voucher in self: - if voucher.cfdi_state in ["signed", "done"]: - voucher.show_unreconcile = False - elif voucher.state in ["draft", "cancel"]: - voucher.show_unreconcile = False - else: - voucher.show_unreconcile = True - - @api.multi - def _compute_sequence_id(self): - for record in self: - record.sequence_id = record.journal_id.sequence_id - - @api.multi - def sign_voucher(self): - """Create CFDI for selected vouchers""" - # Only vouchers to sign are the receipts - receipts = self.filtered(lambda r: r.type == "receipt") - # Get only receipts that doesn't have a CFDI yet and create it - for receipt in receipts.filtered(lambda r: not r.cfdi_id.exists()): - receipt.create_cfdi() - - @api.multi - def cancel_voucher(self): - """Cancel CFDI for selected vouchers""" - res = super(AccountVoucher, self).cancel_voucher() - self.cancel_cfdi() - # Delete relation from voucher and CFDI - self.cfdi_id = False - return res - - @api.multi - def substitute_voucher(self): - """Create new voucher for substitute this one""" - self._cancel_cfdi() - # After cancel voucher we must re open it - self.action_cancel_draft() - - @api.multi - def _cancel_cfdi_related_document(self): - self.cancel_voucher() - - @api.multi - def replace_cfdi(self): - """Cancel voucher cfdi by replacing with 1 MXN voucher""" - self._cancel_cfdi() - - @api.multi - def currency_rate(self): - """Compute currency rate used for current voucher to display on XML - This calls currency.compute() with the right context, - so that it will take either the rate on the voucher if it is relevant - or will use the default behavior""" - self.ensure_one() - # We need to give the date in the context to get proper rate - voucher_currency = self.journal_id.currency or self.company_id.currency_id - voucher_currency = voucher_currency.with_context(date=self.date) - voucher_currency = voucher_currency.with_context( - special_currency_rate=( - voucher_currency.rate * self.payment_rate - ), - special_currency=( - self.payment_rate_currency_id - and self.payment_rate_currency_id.id - or False - ), - ) - res = float_round( - voucher_currency.compute(1.0, self.company_id.currency_id, round=False), - precision_digits=6, - ) - return res - - @property - def doctos_relacionados(self): - """Return a list with all related invoices to this payment""" - self.ensure_one() - - # Get all records from account.move.line related to this voucher but - # affecting an account different than the selected for this voucher - move_lines = self.move_ids.filtered(lambda x: x.account_id != self.account_id) - # Get all lines form partial and full reconciliations - temp_lines = move_lines.mapped("reconcile_partial_id.line_partial_ids") - temp_lines |= move_lines.mapped("reconcile_id.line_id") - - # Get only invoices related to this voucher - invoices = (temp_lines - move_lines).mapped("invoice") - invoices = invoices.filtered(lambda i: i.type == "out_invoice") - - # Validate that all related invoices have an uuid - tofix = invoices.filtered(lambda i: not i.cfdi_folio_fiscal) - if tofix: - raise ValidationError( - _("Some of the invoices that will be paid with this record are not " - "signed, and the UUID is required to indicate the invoices that " - "are paid with this CFDI ") - ) - return invoices - - @api.multi - def numparcialidad(self, invoice): - """Computes payment number based on all payments done to invoice""" - # Get all payments done to given invoice - payments = invoice.mapped("payment_ids") - self.move_ids - # Leave only payments done in cash or bank older than current - payments = payments.filtered( - lambda r: r.date >= invoice.date_invoice - and r.journal_id.type in ("cash", "bank") - ) - return len(payments) + 1 - - @api.multi - def impsaldoant(self, invoice): - """Computes amount_residual for invoice before current voucher""" - self.ensure_one() - # It is verified that the invoice is in another currency - is_multi_currency = ( - invoice.currency_id != self.company_id.currency_id - ) - # Search the lines of the policy where the account is receivable or payable - move_line_ids = invoice.move_id.line_id.filtered( - lambda x: x.account_id.user_type.type in ("receivable", "payable") - ) - if is_multi_currency: - amount_residual = sum( - move_line.amount_currency for move_line in move_line_ids - ) - else: - amount_residual = sum( - move_line.balance for move_line in move_line_ids - ) - # Get all payments done to given invoice - payments = invoice.mapped("payment_ids") - self.move_ids - payment_date = self.move_ids[0].create_date - # Leave only payments done before (older than) the current one: - # 1. Payments with document date before current voucher date - # 2. Payments with same date but created before that current voucher - payments = payments.filtered( - lambda r: - r.date < self.date - or (r.date == self.date and r.create_date < payment_date) - ) - - for payment in payments: - amount_currency = abs(payment.amount_currency) or 0.0 - # If amount_currency exists it means we are dealing with a multi - # currency payment, and thus we need to sum amount_currency to - # amount_paid instead of normal debit - credit - if payment.currency_id: - amount_residual -= amount_currency - else: - amount_residual -= abs(payment.debit - payment.credit) - return self.currency_id.cfdi_round(amount_residual) - - @api.multi - def imppagado(self, invoice): - """Computes total amount payment on this voucher for given invoice""" - self.ensure_one() - - amount_paid = 0.0 - # Get payments done in this voucher for given invoice - payments = invoice.mapped("payment_ids") & self.move_ids - - for payment in payments: - # If currency_id exists it means we are dealing with a multi - # currency payment, and thus we need to sum amount_currency to - # amount_paid instead of normal debit - credit - # TODO: Relay on currency_id could result in wrong results when - # transactions done in three different currencies or more - is_multi_currency = ( - payment.currency_id - and invoice.currency_id != self.company_id.currency_id - ) - if is_multi_currency: - amount_paid += payment.amount_currency * -1 - else: - amount_paid += (payment.debit - payment.credit) * -1 - - # Compute amount of writeoff to adjust amount payment to this invoice - if self.currency_id == invoice.currency_id and self.writeoff_amount < 0.0: - amount_paid += self.writeoff_amount / len(self.doctos_relacionados) - amount_paid = self.currency_id.cfdi_round(amount_paid) - - # Ensure amount paid is always equal or less than amount residual - amount_residual = self.impsaldoant(invoice) - amount_paid = min(amount_paid, amount_residual) - return amount_paid - - @api.multi - def tipocambiodr(self, invoice): - """Compute currency rate used for given invoice to display on XML - This calls curreny.compute() with the right context, - so that it will take either the rate on the voucher if it is relevant - or will use the default behavior""" - self.ensure_one() - company_currency = self.company_id.currency_id - voucher_currency = self.currency_id.with_context(date=self.date) - - if invoice.currency_id == company_currency: - # When invoice is in company currency we are going to use payment rate - voucher_currency = voucher_currency.with_context( - special_currency_rate=(1 / self.payment_rate), - special_currency=voucher_currency.id, - ) - else: - voucher_currency = voucher_currency.with_context( - special_currency_rate=(1 / invoice.currency_rate), - special_currency=invoice.currency_id.id, - ) - - value = float_round( - voucher_currency.compute(1.0, invoice.currency_id, round=False), - precision_digits=6 - ) - - if ( - voucher_currency == company_currency - and invoice.currency_id != company_currency - ): - # Hack to bypass PAC validation, suggested by PAC - value += 0.000001 - - return value - - @api.multi - def impuestos_dr(self, invoice, tax_type="traslados"): - """Computes move lines for on payment taxes related to given invoice""" - self.ensure_one() - # Get payments done in this voucher for given invoice - payments = invoice.mapped("payment_ids") & self.move_ids - tax_reconciles = payments.mapped("tax_reconcile_id") - domain = self._l10n_mx_get_taxes_domain(tax_reconciles, tax_type=tax_type) - tax_moves = self.env["account.move.line"].search(domain) - return tax_moves - - @api.multi - def totales_p(self, tax_type="traslados"): - """Computes move lines for on payment taxes related to given invoice""" - self.ensure_one() - tax_totals = [] - company_currency = self.company_id.currency_id - voucher_currency = self.currency_id.with_context(date=self.date) - impuestos = self.impuestos_p(tax_type=tax_type) - # Load tax record for being able to use on template to fill XML - for line in impuestos: - if company_currency != voucher_currency: - company_currency = company_currency.with_context( - special_currency_rate=self.currency_rate(), - special_currency=voucher_currency.id, - ) - line["importe"] = company_currency.compute( - line["importe"], voucher_currency, - ) - line["tax2_base"] = company_currency.compute( - line["tax2_base"], voucher_currency, - ) - else: - line["importe"] = float_round( - line["importe"], precision_digits=2, - ) - line["tax2_base"] = float_round( - line["tax2_base"], precision_digits=2, - ) - - tax_totals.append(line) - - return tax_totals - - @api.multi - def impuestos_p(self, tax_type="traslados"): - """Computes move lines for on payment taxes related to given invoice""" - self.ensure_one() - company_currency = self.company_id.currency_id - voucher_currency = self.currency_id.with_context(date=self.date) - tax_totals = dict() - - for invoice in self.doctos_relacionados: - for tax_move in self.impuestos_dr(invoice, tax_type=tax_type): - if tax_move.tax2_id.id not in tax_totals: - tax_totals[tax_move.tax2_id.id] = dict( - tax2_id=tax_move.tax2_id, - tax2_base=0.0, - importe=0.0, - ) - - if tax_move.currency_id != voucher_currency: - invoice_currency = ( - tax_move.currency_id - if tax_move.currency_id else company_currency - ) - invoice_currency = invoice_currency.with_context(date=self.date) - if invoice_currency != company_currency: - invoice_currency = invoice_currency.with_context( - special_currency_rate=self.tipocambiodr(invoice), - special_currency=invoice_currency.id, - ) - else: - invoice_currency = invoice_currency.with_context( - special_currency_rate=(1 / self.tipocambiodr(invoice)), - special_currency=voucher_currency.id, - ) - tax2_base = invoice_currency.compute( - tax_move.tax2_base, voucher_currency, round=False, - ) - # Force rounding 6 decimals to use as many decimal as possible and - # avoid rounding errors when validating XML - tax2_base = float_round( - tax2_base, precision_digits=6, rounding_method="DOWN", - ) - else: - tax2_base = tax_move.tax2_base - - tax_totals[tax_move.tax2_id.id]["tax2_base"] += tax2_base - tax_totals[tax_move.tax2_id.id]["importe"] += float_round( - tax2_base * tax_move.tax2_id.amount, - precision_digits=6, - rounding_method="DOWN", - ) - - return [line for line in tax_totals.values()] - - @api.multi - def _l10n_mx_get_taxes_domain(self, tax_reconciles, tax_type="traslados"): - self.ensure_one() - # Get tax moves done for this payment and this invoice - domain = [("tax_reconcile_id", "in", tax_reconciles._ids)] - # Exclude IEPS if partner not IEPS subjected - if not self.partner_id.ieps_subjected: - domain.append( - ("tax2_id.tax_category_id.code", "!=", "IEPS") - ) - - if tax_type == "traslados": - domain.append("|") - domain.append(("tax2_id.type", "=", "none")) - domain.append("&") - domain.append(("tax2_id.amount", ">=", 0)) - domain.append(("tax2_id.type", "=", "percent")) - - else: - domain.append(("tax2_id.amount", "<", 0)) - domain.append(("tax2_id.type", "=", "percent")) - - return domain - - @api.multi - def _validate_cfdi_data(self): - self._validate_account_voucher_vat() - self._validate_account_voucher_payment_type() - - @api.multi - def _validate_account_voucher_vat(self): - """ This is function is to validate that the - partner has a vat, name and property account position """ - - required = [ - "vat", - "name", - "cfdi_fiscal_regime_id" - ] - - for record in self: - # Validate required fields on document - self._cfdi_validate_required_fields( - record, - [ - "partner_id", - "amount", - "journal_id", - "payment_type_id", - "company_id" - ], - ) - - self._cfdi_validate_required_fields( - record.company_id.partner_id, - required, - ) - - self._cfdi_validate_required_fields( - record.partner_id, - required, - ) - - @api.multi - def _validate_account_voucher_payment_type(self): - for record in self: - - # It is validated that there is a payment method or that the - # selected payment method has code 99 - - if not record.payment_type_id or record.payment_type_id.code == "99": - raise ValidationError( - _("The payment method is missing or the payment method has code 99") - ) diff --git a/l10n_mx_facturae/models/email_template.py b/l10n_mx_facturae/models/email_template.py index bfdcf7410b4024b9e39f33599768f7ef1ef5e4e7..c24134b6e2b3eb292504a482f66aa3135d3f1a8e 100644 --- a/l10n_mx_facturae/models/email_template.py +++ b/l10n_mx_facturae/models/email_template.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from openerp import api, models +from odoo import api, models class EmailTemplate(models.Model): diff --git a/l10n_mx_facturae/models/res_company.py b/l10n_mx_facturae/models/res_company.py new file mode 100644 index 0000000000000000000000000000000000000000..4332c84e3137943ee66af144c8f94a5b3498794e --- /dev/null +++ b/l10n_mx_facturae/models/res_company.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + cfdi_use_id = fields.Many2one( + "cfdi.use", + string="Use CFDI", + compute="_compute_use_cfdi", + inverse="_inverse_use_cfdi", + ) + + def _compute_use_cfdi(self): + for company in self: + use_cfdi = company.partner_id.cfdi_use_id + company.cfdi_use_id = use_cfdi.id + + def _inverse_use_cfdi(self): + for company in self: + partner = company.partner_id + partner.cfdi_use_id = company.cfdi_use_id diff --git a/l10n_mx_facturae/models/res_partner.py b/l10n_mx_facturae/models/res_partner.py index 95bc6a89f076676639a836e2b121295ca17a3139..9ea8f093e4b44791e72b6f2d00bb9b35a1b6ed33 100644 --- a/l10n_mx_facturae/models/res_partner.py +++ b/l10n_mx_facturae/models/res_partner.py @@ -1,38 +1,54 @@ # -*- coding: utf-8 -*- -from openerp import api, fields, models +from odoo import api, fields, models class ResPartner(models.Model): _inherit = "res.partner" - cfdi_use = fields.Many2one( + cfdi_use_id = fields.Many2one( "cfdi.use", "CFDI use", help="Cfdi usage that will be used by default on this customer " "invoices and credit notes", ) + cfdi_adenda_ids = fields.Many2many( "cfdi.adenda", string="CFDI Adendas", help="This field allows adding a node or addendum to the invoice", ) + payment_method_id = fields.Many2one( "cfdi.payment.method", string="Payment Method", help="Payment method associated with this partner according" - "to CFDI 3.3 catalog.", + "to CFDI 4.0 catalog.", ) + supplier_number = fields.Char( help="Number or reference that the Client assigned to our company." ) - gln_number = fields.Char("GLN Number", help="Customer or Delivery branch") + + gln_number = fields.Char( + "GLN Number", + help="Customer or Delivery branch" + ) + edi = fields.Char("User EDI") - show_glnnumber = fields.Boolean(compute="_compute_show_number") - show_suppliernumber = fields.Boolean(compute="_compute_show_number") - show_edi = fields.Boolean(compute="_compute_show_number") - @api.multi + show_glnnumber = fields.Boolean( + compute="_compute_show_number" + ) + + show_suppliernumber = fields.Boolean( + compute="_compute_show_number" + ) + + show_edi = fields.Boolean( + compute="_compute_show_number" + ) + @api.depends("cfdi_adenda_ids") def _compute_show_number(self): for record in self: diff --git a/l10n_mx_facturae/report/__init__.py b/l10n_mx_facturae/report/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dd3fb58358d6362b7384006875e04733600719d5 --- /dev/null +++ b/l10n_mx_facturae/report/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import account_move +from . import account_payment diff --git a/l10n_mx_facturae/report/account_invoice.odt b/l10n_mx_facturae/report/account_invoice.odt deleted file mode 100644 index 7fd53ab3112f6263b1541abf65b06af66cd8c284..0000000000000000000000000000000000000000 Binary files a/l10n_mx_facturae/report/account_invoice.odt and /dev/null differ diff --git a/l10n_mx_facturae/report/account_move.odt b/l10n_mx_facturae/report/account_move.odt new file mode 100644 index 0000000000000000000000000000000000000000..75f9031ef26c464fc0cd0e355588980a1be7dd2f Binary files /dev/null and b/l10n_mx_facturae/report/account_move.odt differ diff --git a/l10n_mx_facturae/report/account_move.py b/l10n_mx_facturae/report/account_move.py new file mode 100644 index 0000000000000000000000000000000000000000..91e1bf36d52cb2c255d8006cd94ad3fbad4d6739 --- /dev/null +++ b/l10n_mx_facturae/report/account_move.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from odoo import api, models + + +class Parser(models.AbstractModel): + _inherit = "report.l10n_mx_cfdi" + _description = "report.l10n_mx_facturae.account_move" + _name = "report.l10n_mx_facturae.account_move" + + def _get_report_values(self, docids, data=None): + res = super()._get_report_values(docids, data=data) + docs = self.env["account.move"].browse(docids) + # return a custom rendering context + res.update( + { + "doc_ids": docids, + "doc_model": "account.move", + "docs": docs, + } + ) + return res diff --git a/l10n_mx_facturae/report/account_payment.odt b/l10n_mx_facturae/report/account_payment.odt new file mode 100644 index 0000000000000000000000000000000000000000..248f346134694e2fa203e3993d099b899bc95dba Binary files /dev/null and b/l10n_mx_facturae/report/account_payment.odt differ diff --git a/l10n_mx_facturae/report/account_payment.py b/l10n_mx_facturae/report/account_payment.py new file mode 100644 index 0000000000000000000000000000000000000000..1cd16ae961618a584178506178e2da3a41465830 --- /dev/null +++ b/l10n_mx_facturae/report/account_payment.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from odoo import api, models + + +class Parser(models.AbstractModel): + _inherit = "report.l10n_mx_cfdi" + _description = "report.l10n_mx_facturae.account_payment" + _name = "report.l10n_mx_facturae.account_payment" + + @api.model + def aeroo_report(self, docids, data): + self = self.with_context( + format_impuesto=self._format_impuesto, + format_tasaocuota=self._format_tasaocuota, + ) + return super(Parser, self).aeroo_report(docids, data) + + def _get_report_values(self, docids, data=None): + res = super()._get_report_values(docids, data=data) + docs = self.env["account.payment"].browse(docids) + # return a custom rendering context + res.update( + { + "doc_ids": docids, + "doc_model": "account.payment", + "docs": docs, + "format_impuesto": self._format_impuesto, + "format_tasaocuota": self._format_tasaocuota, + } + ) + return res + + def _format_impuesto(self, tax): + tax_types = { + "ISR": "001", + "IVA": "002", + "IEPS": "003", + } + tax_rep_lines = tax.invoice_repartition_line_ids._origin.filtered( + lambda x: x.repartition_type == "tax" + ) + tax_group = tax_rep_lines.mapped("tag_ids") + return tax_types[tax_group.name] + + def _format_tasaocuota(self, tax): + """Format tasa o cuota""" + return "{0:.6f}".format(abs(tax.amount) / 100) diff --git a/l10n_mx_facturae/report/account_voucher.odt b/l10n_mx_facturae/report/account_voucher.odt deleted file mode 100644 index 04b4379a894f1b130eb544315004ca4c1b1cd335..0000000000000000000000000000000000000000 Binary files a/l10n_mx_facturae/report/account_voucher.odt and /dev/null differ diff --git a/l10n_mx_facturae/security/res_groups.xml b/l10n_mx_facturae/security/res_groups.xml index 9cc098cef4094321eaf3b8c18a3f13ff95903ee0..66505f1597a09d93c3741b89a46656f37f5e5369 100644 --- a/l10n_mx_facturae/security/res_groups.xml +++ b/l10n_mx_facturae/security/res_groups.xml @@ -4,7 +4,7 @@ <record id="cfdi_cuentapredial" model="res.groups"> <field name="name">Cfdi Cuenta Predial</field> - <field name="category_id" ref="l10n_mx.module_category_l10n_mx"/> + <field name="category_id" ref="l10n_mx_base.module_category_l10n_mx"/> <field name="comment">The user will have access to add Cuenta Predial information to invoice lines.</field> </record> diff --git a/l10n_mx_facturae/templates/account_invoice.txt b/l10n_mx_facturae/templates/account_move.txt similarity index 100% rename from l10n_mx_facturae/templates/account_invoice.txt rename to l10n_mx_facturae/templates/account_move.txt diff --git a/l10n_mx_facturae/templates/account_move.xml b/l10n_mx_facturae/templates/account_move.xml new file mode 100644 index 0000000000000000000000000000000000000000..cc81104eb45719e289dc1d70c9946357d356bb8d --- /dev/null +++ b/l10n_mx_facturae/templates/account_move.xml @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <template id="account_move"> + <t t-set="move" t-value="docs[0]" /> + <t t-set="taxes" t-value="move.impuestos" /> + <t t-set="total_traslados" t-value="move.l10n_mx_facturae_compute_total_traslados(taxes)" /> + <t t-set="total_retenciones" t-value="move.l10n_mx_facturae_compute_total_retenciones(taxes)" /> + <t t-set="tipoCambio" t-value="move.l10n_mx_facturae_compute_tipocambio()"/> + <t t-set="residenciaFiscal" t-value="move.l10n_mx_facturae_compute_residencia_fiscal()"/> + <t t-set="regimenTrib" t-value="move.l10n_mx_facturae_compute_regimen_tributario()"/> + <cfdi:Comprobante t-foreach="docs" t-as="o" + xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd" + xmlns:cfdi="http://www.sat.gob.mx/cfd/4" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + t-attf-Version="4.0" + t-attf-Exportacion="{{ o.l10n_mx_facturae_compute_export() }}" + t-attf-Folio="{{ o.folio }}" + t-attf-Fecha="{{ o.l10n_mx_facturae_compute_fecha() }}" + t-attf-Sello="@" + t-attf-NoCertificado="@" + t-attf-Certificado="@" + t-attf-FormaPago="{{ o.formapago }}" + t-attf-CondicionesDePago="{{ o.l10n_mx_facturae_compute_payment_terms() }}" + t-attf-SubTotal="{{ o.l10n_mx_facturae_compute_subtotal()}}" + t-attf-Descuento="{{ o.descuento }}" + t-attf-Moneda="{{ o.currency_id.name }}" + t-att-TipoCambio="tipoCambio" + t-attf-Total="{{ o.l10n_mx_facturae_compute_total() }}" + t-attf-TipoDeComprobante="{{ o.l10n_mx_facturae_compute_type_document() }}" + t-attf-MetodoPago="{{ o.l10n_mx_facturae_compute_payment_method() }}" + t-attf-LugarExpedicion="{{ o.address_issued_id.zip }}"> + <cfdi:CfdiRelacionados t-attf-TipoRelacion="o.cfdi_relation_type.code" t-if="o.reversed_entry_id"> + <cfdi:CfdiRelacionado t-foreach="o.reversed_entry_id" t-as="cfdi" t-attf-UUID="cfdi.cfdi_folio_fiscal"/> + </cfdi:CfdiRelacionados> + <cfdi:CfdiRelacionados t-attf-TipoRelacion="o.cfdi_relation_type.code" t-if="o.related_cfdi_ids"> + <cfdi:CfdiRelacionado t-foreach="o.related_cfdi_ids" t-as="cfdi" t-attf-UUID="cfdi.uuid"/> + </cfdi:CfdiRelacionados> + <cfdi:InformacionGlobal + t-if="o.invoice_global" + t-attf-Año="{{ o.l10n_mx_facturae_compute_fecha_global_year() }}" + t-attf-Meses="{{ o.l10n_mx_facturae_compute_fecha_global_month() }}" + t-attf-Periodicidad="{{ o.cfdi_periodicity }}" /> + <cfdi:Emisor + t-attf-Rfc="{{ o.company_id.partner_id.vat_split }}" + t-attf-Nombre="{{ o.company_id.partner_id.name.upper() }}" + t-attf-RegimenFiscal="{{ o.company_id.partner_id.cfdi_fiscal_regime_id.code }}"/> + <cfdi:Receptor + t-attf-Nombre="{{ o.commercial_partner_id.name.upper() }}" + t-attf-Rfc="{{ o.l10n_mx_facturae_compute_rfc() }}" + t-attf-UsoCFDI="{{ o.l10n_mx_facturae_compute_use_cfdi() }}" + t-attf-DomicilioFiscalReceptor="{{ o.l10n_mx_facturae_compute_domicilio_fiscal() }}" + t-attf-RegimenFiscalReceptor="{{ o.l10n_mx_facturae_compute_regimen_fiscal_receptor() }}" + t-att-ResidenciaFiscal="residenciaFiscal" + t-att-NumRegIdTrib="regimenTrib" /> + <cfdi:Conceptos> + <cfdi:Concepto t-foreach="o.invoice_line_ids" t-as="line" + t-attf-ClaveProdServ="{{ o.l10n_mx_facturae_compute_product() }}" + t-attf-NoIdentificacion="{{ line.cfdi_numero_identificacion }}" + t-attf-Cantidad="{{ line.quantity }}" + t-attf-ClaveUnidad="{{ line.product_uom_id.cfdi_unit_measure_id.code }}" + t-attf-Unidad="{{ o.l10n_mx_facturae_compute_unit() }}" + t-attf-Descripcion="{{ line.name }}" + t-attf-ValorUnitario="{{ line.valorunitario }}" + t-attf-Importe="{{ line.l10n_mx_facturae_importe() }}" + t-attf-Descuento="{{ line.descuento }}" + t-attf-ObjetoImp="{{ line.l10n_mx_facturae_compute_objeto_impuesto() }}"> + <cfdi:Impuestos t-if="line.l10n_mx_facturae_compute_objeto_impuesto() == '02' "> + <cfdi:Traslados t-if="line.impuestos['traslados']"> + <t t-set="impuestos" t-value="line.impuestos"/> + <cfdi:Traslado t-foreach="impuestos['traslados']" t-as="tax" + t-att-Base="format_float(tax.base, o.currency_id.decimal_places)" + t-attf-Impuesto="{{ tax.xml_name }}" + t-attf-TipoFactor="{{ tax.type }}" + t-attf-TasaOCuota="{{ tax.TasaOCuota }}" + t-attf-Importe="{{ tax.xml_amount }}" /> + </cfdi:Traslados> + <cfdi:Retenciones t-if="impuestos['retenciones']"> + <cfdi:Retencion t-foreach="impuestos['retenciones']" t-as="tax" + t-att-Base="format_float(tax.base, o.currency_id.decimal_places)" + t-attf-Impuesto="{{ tax.xml_name }}" + t-attf-TipoFactor="{{ tax.type }}" + t-attf-TasaOCuota="{{ tax.TasaOCuota }}" + t-attf-Importe="{{ tax.xml_amount }}" /> + </cfdi:Retenciones> + </cfdi:Impuestos> + <cfdi:CuentaPredial t-if="line.cfdi_cuentapredial" + t-attf-Numero="line.cfdi_cuentapredial" /> + <cfdi:InformacionAduanera + t-if="line.cfdi_custom_number" + t-foreach="line.cfdi_custom_number" t-as="cfdi_custom_number" + t-attf-NumeroPedimento="{{ cfdi_custom_number.name }}" /> + </cfdi:Concepto> + </cfdi:Conceptos> + <cfdi:Impuestos + t-att-TotalImpuestosTrasladados="total_traslados" + t-att-TotalImpuestosRetenidos="total_retenciones" > + <cfdi:Retenciones t-if="taxes['retenciones']"> + <cfdi:Retencion t-foreach="taxes['retenciones']" t-as="tax" + t-attf-Impuesto="{{ tax.xml_name }}" + t-attf-Importe="{{ tax.xml_amount }}" /> + </cfdi:Retenciones> + <cfdi:Traslados t-if="taxes['traslados']"> + <cfdi:Traslado t-foreach="taxes['traslados']" t-as="tax" + t-att-Base="format_float(tax.base, o.currency_id.decimal_places)" + t-attf-Impuesto="{{ tax.xml_name }}" + t-attf-TipoFactor="{{ tax.type }}" + t-attf-TasaOCuota="{{ tax.TasaOCuota }}" + t-attf-Importe="{{ tax.xml_amount }}" /> + </cfdi:Traslados> + </cfdi:Impuestos> + <!-- + <cfdi:Complemento t-if="is_local_taxes"> + <implocal:ImpuestosLocales + t-attf-version="1.0" + t-attf-TotaldeTraslados="${ '{0:.2f}'.format(sumif(taxes.locales, 'amount', [('amount', '>', 0)])) }" + t-attf-TotaldeRetenciones="${ '{0:.2f}'.format(abs(sumif(taxes.locales, 'amount', [('amount', '<', 0)]))) }"> + <implocal:TrasladosLocales + t-foreach="taxes.locales" t-as="tax_line" + t-if="tax_line.amount > 0" + t-attf-ImpLocTrasladado="html_escape(tax_line.name)" + t-attf-TasadeTraslado="${ '{0:.2f}'.format(tax_line.TasaOCuota * 100) }" + t-attf-Importe="tax_line.amount" /> + <implocal:RetencionesLocales + t-foreach="taxes.locales" t-as="tax_line" + t-if="tax_line.amount < 0" + t-attf-ImpLocRetenido="html_escape(tax_line.name)" + t-attf-TasadeRetencion="tax_line.TasaOCuota * 100 }" + t-attf-Importe="tax_line.amount" /> + </implocal:ImpuestosLocales> + </cfdi:Complemento> + --> + </cfdi:Comprobante> + + </template> + +</odoo> diff --git a/l10n_mx_facturae/templates/account_payment.xml b/l10n_mx_facturae/templates/account_payment.xml new file mode 100644 index 0000000000000000000000000000000000000000..93989a084828268e39dd325b3dbeafde876ed96e --- /dev/null +++ b/l10n_mx_facturae/templates/account_payment.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <template id="account_payment"> + <t t-set="payment" t-value="docs[0]" /> + <t t-set="residenciaFiscal" t-value="payment.l10n_mx_facturae_payment_compute_residencia_fiscal()"/> + <t t-set="regimenTrib" t-value="payment.l10n_mx_facturae_payment_compute_regimen_tributario()"/> + <t t-set="tipoCambio" t-value="payment.l10n_mx_facturae_payment_compute_tipocambio()"/> + <t t-set="traslados" t-value="payment.totales_p()" /> + <t t-set="retenciones" t-value="payment.totales_p(tax_type='retenciones')" /> + <cfdi:Comprobante t-foreach="docs" t-as="o" + xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd" + xmlns:cfdi="http://www.sat.gob.mx/cfd/4" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:pago20="http://www.sat.gob.mx/Pagos20" + t-attf-Version="4.0" + t-attf-Exportacion="01" + t-attf-Folio="{{ o.folio }}" + t-attf-Fecha="{{ o.l10n_mx_facturae_payment_compute_fecha() }}" + t-attf-Sello="@" + t-attf-NoCertificado="@" + t-attf-Certificado="@" + t-attf-SubTotal="0" + t-attf-Moneda="XXX" + t-attf-Total="0" + t-attf-TipoDeComprobante="P" + t-attf-LugarExpedicion="{{ o.company_id.zip }}"> + <cfdi:CfdiRelacionados t-attf-TipoRelacion="o.cfdi_relation_type.code" t-if="o.related_cfdi_ids"> + <cfdi:CfdiRelacionado t-foreach="o.related_cfdi_ids" t-as="cfdi" t-attf-UUID="cfdi.uuid"/> + </cfdi:CfdiRelacionados> + <cfdi:Emisor + t-attf-Rfc="{{ o.company_id.partner_id.vat_split }}" + t-attf-Nombre="{{ o.company_id.partner_id.name.upper() }}" + t-attf-RegimenFiscal="{{ o.company_id.partner_id.cfdi_fiscal_regime_id.code }}"/> + <cfdi:Receptor + t-attf-Nombre="{{ o.commercial_partner_id.name.upper() }}" + t-attf-Rfc="{{ o.l10n_mx_facturae_payment_compute_rfc() }}" + t-attf-UsoCFDI="CP01" + t-attf-DomicilioFiscalReceptor="{{ o.l10n_mx_facturae_payment_compute_domicilio_fiscal() }}" + t-attf-RegimenFiscalReceptor="{{ o.l10n_mx_facturae_payment_compute_regimen_fiscal_receptor() }}" + t-att-ResidenciaFiscal="residenciaFiscal" + t-att-NumRegIdTrib="regimenTrib" /> + <cfdi:Conceptos> + <cfdi:Concepto + t-attf-Cantidad="1" + t-attf-ClaveProdServ="84111506" + t-attf-ClaveUnidad="ACT" + t-attf-Descripcion="Pago" + t-attf-Importe="0" + t-attf-ObjetoImp="01" + t-attf-ValorUnitario="0" /> + </cfdi:Conceptos> + <cfdi:Complemento> + <pago20:Pagos t-attf-Version="2.0"> + <pago20:Totales + t-attf-MontoTotalPagos="{{ o.l10n_mx_facturae_payment_montototalpagos() }}" + t-att-TotalTrasladosBaseIVAExento="traslados.get('baseIVAExento', False)" + t-att-TotalTrasladosBaseIVA16="format_float(traslados.get('baseIVA16.0'), o.currency_id.decimal_places) if traslados.get('baseIVA16.0', False) else False" + t-att-TotalTrasladosImpuestoIVA16="format_float(traslados.get('importeIVA16.0'), o.currency_id.decimal_places) if traslados.get('importeIVA16.0', False) else False" + t-att-TotalTrasladosBaseIVA8="format_float(traslados.get('baseIVA8.0'), o.currency_id.decimal_places) if traslados.get('baseIVA8.0', False) else False" + t-att-TotalTrasladosImpuestoIVA8="format_float(traslados.get('importeIVA8.0'), o.currency_id.decimal_places) if traslados.get('importeIVA8.0', False) else False" + t-att-TotalTrasladosBaseIVA0="format_float(traslados.get('baseIVA0.0'), o.currency_id.decimal_places) if traslados.get('baseIVA0.0', False) else False" + t-att-TotalTrasladosImpuestoIVA0="format_float(traslados.get('importeIVA0.0'), o.currency_id.decimal_places) if traslados.get('baseIVA0.0', False) else False" + t-att-TotalRetencionesIVA="retenciones.get('importeIVA', False)" + t-att-TotalRetencionesISR="retenciones.get('importeISR', False)" + t-att-TotalRetencionesIEPS="retenciones.get('importeIEPS', False)" /> + <pago20:Pago + t-attf-FechaPago="{{ o.l10n_mx_facturae_payment_compute_pago_fecha() }}" + t-attf-FormaDePagoP="{{ o.payment_type_id.code }}" + t-attf-MonedaP="{{ o.currency_id.name }}" + t-att-TipoCambioP="tipoCambio" + t-attf-Monto="{{ o.amount }}" + t-attf-NumOperacion="{{ o.name }}" > + <pago20:DoctoRelacionado t-foreach="o.doctos_relacionados" t-as="invoice" + t-attf-Folio="{{ invoice.folio }}" + t-attf-IdDocumento="{{ invoice.cfdi_folio_fiscal }}" + t-attf-MonedaDR="{{ invoice.currency_id.name }}" + t-attf-EquivalenciaDR="{{ o.tipocambiodr(invoice) }}" + t-attf-NumParcialidad="{{ o.numparcialidad(invoice) }}" + t-attf-ImpSaldoAnt="{{ o.impsaldoant(invoice, to_xml=True) }}" + t-attf-ImpSaldoInsoluto="{{ o.impsaldoinsoluto(invoice, to_xml=True) }}" + t-attf-ImpPagado="{{ o.imppagado(invoice, to_xml=True) }}" + t-attf-ObjetoImpDR="02" > + <pago20:ImpuestosDR> + <t t-set="retenciones" t-value="o.impuestos_dr(invoice, tax_type='retenciones')"/> + <pago20:RetencionesDR t-if="retenciones"> + <pago20:RetencionDR t-foreach="retenciones" t-as="tax_move" + t-att-BaseDR="format_float(tax_move.tax_base_amount, invoice.currency_id.decimal_places)" + t-att-ImpuestoDR="format_impuesto(tax_move.tax_line_id)" + t-att-ImporteDR="format_float(abs(tax_move.balance), invoice.currency_id.decimal_places)" + t-att-TasaOCuotaDR="format_tasaocuota(tax_move.tax_line_id)" + t-att-TipoFactorDR="tax_move.tax_line_id.l10n_mx_tax_type" /> + </pago20:RetencionesDR> + <pago20:TrasladosDR> + <pago20:TrasladoDR + t-foreach="o.impuestos_dr(invoice)" t-as="base_move" + t-att-BaseDR="format_float(o._l10n_mx_tax_base_dr(invoice, base_move), invoice.currency_id.decimal_places)" + t-att-ImpuestoDR="format_impuesto(base_move.tax_ids[0])" + t-att-ImporteDR="format_float(abs(o._l10n_mx_tax_importe_dr(invoice, base_move)), invoice.currency_id.decimal_places)" + t-att-TasaOCuotaDR="format_tasaocuota(base_move.tax_ids[0])" + t-att-TipoFactorDR="base_move.tax_ids[0].l10n_mx_tax_type" /> + </pago20:TrasladosDR> + </pago20:ImpuestosDR> + </pago20:DoctoRelacionado> + <pago20:ImpuestosP> + <t t-set="retenciones" t-value="o.impuestos_p(tax_type='retenciones')"/> + <pago20:RetencionesP t-if="retenciones"> + <pago20:RetencionP t-foreach="retenciones" t-as="tax_total" + t-attf-ImporteP="{{ tax_total['importe'] }}" + t-attf-ImpuestoP="format_impuesto(tax_total['tax_id'])" /> + </pago20:RetencionesP> + <pago20:TrasladosP> + <pago20:TrasladoP t-foreach="o.impuestos_p()" t-as="tax_total" + t-att-BaseP="format_float(tax_total['tax_base'], o.currency_id.decimal_places)" + t-att-ImpuestoP="format_impuesto(tax_total['tax'])" + t-att-ImporteP="format_float(tax_total['importe'], o.currency_id.decimal_places)" + t-att-TasaOCuotaP="format_tasaocuota(tax_total['tax'])" + t-att-TipoFactorP="tax_total['tax'].l10n_mx_tax_type" /> + </pago20:TrasladosP> + </pago20:ImpuestosP> + </pago20:Pago> + </pago20:Pagos> + </cfdi:Complemento> + </cfdi:Comprobante> + + </template> + +</odoo> diff --git a/l10n_mx_facturae/templates/account_voucher.txt b/l10n_mx_facturae/templates/account_voucher.txt index 2a30569ab86b540dba6a2af4dc8e0b8dc515a631..80723d339e619b11ca38a907ce17e49e7061e9a6 100644 --- a/l10n_mx_facturae/templates/account_voucher.txt +++ b/l10n_mx_facturae/templates/account_voucher.txt @@ -163,7 +163,7 @@ {% end %} NumParcialidad="${ o.numparcialidad(invoice) }" ImpSaldoAnt="${ '{0:.2f}'.format(o.impsaldoant(invoice)) }" - ImpSaldoInsoluto="${ '{0:.2f}'.format(o.impsaldoant(invoice) - o.imppagado(invoice)) }" + ImpSaldoInsoluto="${ '{0:.2f}'.format() }" ImpPagado="${ '{0:.2f}'.format(o.imppagado(invoice)) }" ObjetoImpDR="02" > diff --git a/l10n_mx_facturae/tests/__init__.py b/l10n_mx_facturae/tests/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cc3112cf63efe6bf12538cc714c53aefc013e4c5 100644 --- a/l10n_mx_facturae/tests/__init__.py +++ b/l10n_mx_facturae/tests/__init__.py @@ -0,0 +1,4 @@ + +from . import test_account_invoice +# from . import test_account_voucher +# from . import test_cancel_invoice \ No newline at end of file diff --git a/l10n_mx_facturae/tests/test_account_invoice.py b/l10n_mx_facturae/tests/test_account_invoice.py new file mode 100644 index 0000000000000000000000000000000000000000..d9a77406523ea7c335130d12859213bac5276dc9 --- /dev/null +++ b/l10n_mx_facturae/tests/test_account_invoice.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo import fields +from odoo.tests import tagged + + +@tagged('post_install', '-at_install') +class TestCancelInvoice(AccountTestInvoicingCommon): + + @classmethod + def setUpClass(cls, chart_template_ref=None): + # Setup for required fields and configurations + super().setUpClass(chart_template_ref=chart_template_ref) + main_company = cls.env.ref('l10n_mx.demo_company_mx') + """Add a company to the user's allowed & set to current.""" + cls.env.user.write( + { + "company_ids": [(6, 0, (main_company + cls.env.user.company_ids).ids)], + "company_id": main_company.id, + } + ) + cls.tax_group = cls.env.ref('l10n_mx.tax_group_iva_16') + cls.customer = cls.env.ref('l10n_mx_facturae.res_partner_2023') + cls.cfdi_use = cls.env['cfdi.use'].sudo().search([], limit=1) + cls.fiscal_regime = cls.env['cfdi.fiscal.regime'].sudo().search([], limit=1) + cls.product = cls.env.ref('product.product_product_7') + cls.uom = cls.env['uom.uom'].search([], limit=1) + cls.journal = cls.env['account.journal'].sudo().search([('type', '=', 'sale'), + ('company_id', '=', main_company.id)], limit=1) + cls.payment_type_id = cls.env['payment.type'].sudo().search([], limit=1) + if not cls.payment_type_id: + raise ValueError("No payment method was found in the database.") + + # Configure tax group and tax for the test + cls.taxes_ids = cls.env['account.tax'].search([ + ('company_id', '=', main_company.id), + ('type_tax_use', '=', 'sale'), + ('tax_group_id', '=', cls.tax_group.id) + ], limit=1) + if not cls.taxes_ids: + raise ValueError("The tax 'IVA 16% VENTAS' was not found in the database") + + cfdi_product_service = cls.env['cfdi.product.service'].sudo().search([], limit=1).id + + # Set up product with the tax and cfdi_product_service + cls.product.write({ + 'taxes_id': [(6, 0, [cls.taxes_ids.id])], + 'cfdi_product_service_id': cfdi_product_service, + 'company_id': main_company.id, + 'uom_id': cls.uom.id, + }) + + receivable_account = cls.env['account.account'].sudo().search([ + ('company_id', '=', main_company.id), + ('internal_type', '=', 'receivable') + ], limit=1) + + income_account = cls.env['account.account'].sudo().search([ + ('company_id', '=', main_company.id), + ('internal_type', '=', 'income') + ], limit=1) + + cls.customer.sudo().write({ + 'company_id': main_company.id, + 'cfdi_fiscal_regime_id': cls.fiscal_regime.id, + 'zip': '20928', + 'property_account_receivable_id': receivable_account.id, + }) + + # Create invoice with line items + cls.invoice = cls.env['account.move'].sudo().create({ + 'date': fields.Date.context_today(cls.customer), + 'partner_id': cls.customer.id, + 'journal_id': cls.journal.id, + 'move_type': 'out_invoice', + 'cfdi_periodicity': '04', + 'invoice_line_ids': [ + (0, 0, { + 'product_id': cls.product.id, + 'account_id': income_account.id, + 'price_unit': 130.00, + 'quantity': 1.0, + 'tax_ids': [(6, 0, [cls.taxes_ids.id])], + 'uom_id': cls.uom.id, + }), + ], + 'company_id': main_company.id, + }) + + # Configure journal for invoicing with SAT signature enabled + cls.journal.sudo().write({'sign_sat': True}) + cls.customer.sudo().write({'vat': 'MXXAXX010101000'}) + + def test_cancel_invoice_with_out_cfdi(self): + """ + This scenario cancels the invoice, CFDI and verifies that: + - There is 1 CfdiRelacion node + - The TipoRelacion for the node is '04' (Substitution of the previous CFDI) + - The UUID for the node corresponds to the UUID of the cancelled CFDI + """ + self.journal.sign_sat = True + self.invoice.action_post() + self.assertEqual(self.invoice.state, 'posted') + self.assertEqual(self.invoice.cfdi_state, 'done') + + self.invoice.action_cancel() + # self.assertEqual(self.invoice.state, "cancel") + # uuid_cancelado = self.invoice.cfdi_uuid_cancelled + # self.assertTrue(uuid_cancelado, "Debe existir un UUID para el CFDI cancelado") + # cfdi_xml = self.invoice.get_cfdi_xml_cancelled() + # self.assertTrue(cfdi_xml, "Debe existir un XML del CFDI cancelado") + # tree = etree.fromstring(cfdi_xml.encode('utf-8')) + # namespaces = { + # 'cfdi': 'http://www.sat.gob.mx/cfd/4', + # 'tfd': 'http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd', + # } + # cfdi_relacionados = tree.find('.//cfdi:CfdiRelacionados', namespaces=namespaces) + # self.assertIsNotNone(cfdi_relacionados) + # cfdi_relacionados_list = cfdi_relacionados.findall('cfdi:CfdiRelacionado', namespaces=namespaces) + # self.assertEqual(len(cfdi_relacionados_list), 1) + # tipo_relacion = cfdi_relacionados.get('TipoRelacion') + # self.assertEqual(tipo_relacion, '04') + # uuid_relacionado = cfdi_relacionados_list[0].get('UUID') + # self.assertEqual(uuid_relacionado, uuid_cancelado) diff --git a/l10n_mx_facturae/tests/test_account_voucher.py b/l10n_mx_facturae/tests/test_account_voucher.py new file mode 100644 index 0000000000000000000000000000000000000000..d1c83a7d3aa697ac32390d34391a8bb7688e6436 --- /dev/null +++ b/l10n_mx_facturae/tests/test_account_voucher.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +from odoo import fields, Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged + + +@tagged('post_install', '-at_install') +class TestPaymentReceipt(AccountTestInvoicingCommon): + + @classmethod + def setUpClass(cls): + """ Configuración inicial para los datos requeridos. """ + super().setUpClass() + cls.cfdi_use = cls.env["cfdi.use"].search([], limit=1) + cls.fiscal_regime = cls.env["cfdi.fiscal.regime"].search([], limit=1) + cls.payment_method_id = cls.env["payment.method"].search([], limit=1) + cls.customer.write( + { + "cfdi_fiscal_regime_id": cls.fiscal_regime.id, + "payment_method_customer": cls.payment_method_id.id, + "cfdi_use": cls.cfdi_use.id, + "zip": "20928", + } + ) + + # Crear cuenta contable y configuración de IVA + cls.account_iva_pendiente = cls.env['account.account.template'].create({ + 'code': '1151004000', + 'name': 'IVA pendiente de acreditar', + 'user_type_id': cls.env.ref('account.data_account_type_current_liabilities').id, + }) + + cls.account_iva_pagado = cls.env['account.account.template'].create({ + 'code': '1151003000', + 'name': 'IVA efectivamente pagado', + 'user_type_id': cls.env.ref('account.data_account_type_current_liabilities').id, + }) + + cls.account_banco = cls.env['account.account.template'].create({ + 'code': '1113020000', + 'name': 'Banco', + 'user_type_id': cls.env.ref('account.data_account_type_liquidity').id, + }) + + # Configurar diario para pagos + cls.bank_journal = cls.env['account.journal'].create({ + 'name': 'Banco', + 'type': 'bank', + 'code': 'BNK', + 'bank_account_id': cls.account_banco.id, + }) + + cls.invoice = cls.env["account.move"].create( + { + "date": fields.Date.context_today(cls.customer), + "partner_id": cls.customer.id, + "journal_id": cls.sale_journal.id, + "type": "out_invoice", + "account_id": cls.customer.property_account_payable.id, + "cfdi_periodicity": "04", + "cfdi_use": cls.customer.cfdi_use.id, + "fiscal_regime": cls.customer.cfdi_fiscal_regime_id.id, + "payment_method_ids": [(4, cls.customer.payment_method_customer.id)], + "invoice_line": [ + ( + 0, + 0, + { + "name": 'ProductoA', + "account_id": cls.product.property_account_income.id, + "price_unit": 100.00, + "quantity": 1.0, + "product_id": cls.product.id, + "uos_id": cls.product.uom_id.id, + "invoice_line_tax_id": [(4, cls.product.taxes_id.id)], + }, + ), + ], + } + ) + + cls.invoice.action_post() + + def test_payment_receipt_with_higher_amount(self): + """ Escenario: Recibo de pago con monto mayor al de la factura """ + + # Registrar un pago por 116.01 MXN + payment_register = self.env['account.payment'].with_context( + active_model='account.move', + active_ids=self.invoice.ids + ).create({ + 'amount': 116.01, + 'payment_date': fields.Date.context_today(self), + 'journal_id': self.bank_journal.id, + 'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id, + }) + payment = payment_register._create_payments() + + # Validar que la factura se haya pagado + self.assertEqual(self.invoice.state, 'posted') + self.assertEqual(self.invoice.payment_state, 'paid') + + # Generar el recibo electrónico de pagos (CFDI de pago) + payment_move = payment.move_id + payment_move.l10n_mx_edi_is_required() + payment_move._post() + + # Obtener el XML generado + xml_str = payment_move._l10n_mx_edi_create_cfdi() + xml_tree = self.env['account.move'].l10n_mx_edi_get_xml_etree(xml_str) + + # Validar nodo DoctoRelacionado + docto_relacionado_node = xml_tree.find('.//pago10:DoctoRelacionado', + namespaces=self.env['account.move']._l10n_mx_edi_get_namespaces()) + self.assertIsNotNone(docto_relacionado_node, "El XML deberÃa contener el nodo DoctoRelacionado") + + # Validar que el ImpSaldoAnt sea 116.00 MXN + self.assertEqual(docto_relacionado_node.get('ImpSaldoAnt'), '116.00', + "El ImpSaldoAnt deberÃa ser 116.00 MXN") + + # Validar que el ImportePagado sea 116.00 MXN + self.assertEqual(docto_relacionado_node.get('ImportePagado'), '116.00', + "El ImportePagado deberÃa ser 116.00 MXN") + + # Validar que el ImpoSaldoInsoluto sea 0.00 MXN + self.assertEqual(docto_relacionado_node.get('ImpSaldoInsoluto'), '0.00', + "El ImpSaldoInsoluto deberÃa ser 0.00 MXN") diff --git a/l10n_mx_facturae/tests/test_cancel_invoice.py b/l10n_mx_facturae/tests/test_cancel_invoice.py index a32976863f48569154488d8a2130f74f55a026c6..c03fa220fb193a6301f631d344d0379e027b22e6 100644 --- a/l10n_mx_facturae/tests/test_cancel_invoice.py +++ b/l10n_mx_facturae/tests/test_cancel_invoice.py @@ -1,13 +1,16 @@ # -*- coding: utf-8 -*- # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html -from openerp import fields -from openerp.addons.account.tests.common import AccountTestInvoicingCommon +from odoo import fields +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged + +@tagged('post_install', '-at_install') class TestCancelInvoice(AccountTestInvoicingCommon): @classmethod def setUpClass(cls): - super(TestCancelInvoice, cls).setUpClass() + super().setUpClass() # Create required fields and assign to customer cls.cfdi_use = cls.env["cfdi.use"].search([], limit=1) cls.fiscal_regime = cls.env["cfdi.fiscal.regime"].search([], limit=1) diff --git a/l10n_mx_facturae/views/account_invoice.xml b/l10n_mx_facturae/views/account_move.xml similarity index 55% rename from l10n_mx_facturae/views/account_invoice.xml rename to l10n_mx_facturae/views/account_move.xml index 7bc6bcf9e738c64654dfb282fd6a2a04fb2dd2f8..1495f4fece42a962f2a7a0476c9573b136f83be9 100644 --- a/l10n_mx_facturae/views/account_invoice.xml +++ b/l10n_mx_facturae/views/account_move.xml @@ -1,23 +1,15 @@ <?xml version="1.0"?> -<openerp> -<data> +<odoo> - <act_window id="account_invoice_action_cfdi_details" name="CFDI Details" - domain="[ - ('res_id', 'in', active_ids), - ('type_attachment', '=', 'account.invoice') - ]" - res_model="ir.attachment.facturae.mx" src_model="account.invoice" /> - - <record id="account_invoice_view_search" model="ir.ui.view"> - <field name="name">account.invoice.view.search</field> - <field name="model">account.invoice</field> + <record id="account_move_view_search" model="ir.ui.view"> + <field name="name">account.move.view.search</field> + <field name="model">account.move</field> <field name="inherit_id" ref="account.view_account_invoice_filter"/> <field name="arch" type="xml"> - <field name="currency_id" position="after"> + <field name="partner_id" position="after"> <field name="cfdi_id" /> </field> - <group position="before"> + <filter name="due_date" position="after"> <separator/> <filter string="To sign" @@ -27,30 +19,39 @@ <filter string="To cancel" name="to_cancel" - domain="[('l10n_mx_edi_to_cancel', '=', True)]" help="Invoices that being substituted and must be cancelled" /> - </group> + <!--domain="[('l10n_mx_edi_to_cancel', '=', True)]"--> + </filter> </field> </record> - <record id="account_invoice_view_form_customer" model="ir.ui.view"> - <field name="name">account.invoice.view.form.customer</field> - <field name="model">account.invoice</field> - <field name="inherit_id" ref="account.invoice_form"/> + <record id="account_move_view_form_customer" model="ir.ui.view"> + <field name="name">account.move.view.form.customer</field> + <field name="model">account.move</field> + <field name="inherit_id" ref="account.view_move_form"/> <field name="arch" type="xml"> - <button name="invoice_cancel" position="before"> + <button name="button_cancel" position="before"> + <button name="l10n_mx_action_cancel" + string="Cancel CFDI" + type="object" + class="btn-primary" + attrs="{'invisible': [ + '&', + ('state', '!=', 'posted'), + ('cfdi_state', '!=', 'done') + ]}"/> <field name="is_cfdi_candidate" invisible="1" /> </button> <xpath expr="//header" position="after"> <div class="alert alert-danger" attrs="{'invisible':[ - '|', - ('state', '=', 'cancel'), + '|', + ('state', '=', 'draft'), ( 'cfdi_state', 'in', - ['signed', 'cancel', 'done', False] + ['signed', 'done', 'waiting', 'cancel', False] ) ]}" role="alert" @@ -89,63 +90,53 @@ }" /> </h4> </xpath> - <xpath expr="//sheet/group//group[last()]" position="inside"> - <label for="cfdi_state" string="PAC State" - attrs="{ - 'invisible': [ - '|', - ('is_cfdi_candidate', '=', False), - ('state', 'not in', ['open', 'paid', 'cancel']) - ] - }"/> - <div class="o_row" + <xpath expr="//field[@name='invoice_date']" position="after"> + <field name="payment_method_id" + options="{'no_create':True, 'no_open':True}"/> + </xpath> + <xpath expr="//field[@name='payment_reference']" position="after"> + <label + for="cfdi_state" + string="PAC State" attrs="{ - 'invisible': [ + 'invisible':[ '|', ('is_cfdi_candidate', '=', False), - ('state', 'not in', ['open', 'paid', 'cancel']) + ('state', '=', 'draft') ] - }"> - <field name="cfdi_state" class="oe_inline"/> + }" + /> + <div class="o_row" > + <field + name="cfdi_state" + class="oe_inline" + attrs="{ + 'invisible':[ + '|', + ('is_cfdi_candidate', '=', False), + ('state', '=', 'draft') + ] + }" + /> <button name="action_validate_cfdi" string="Retry" class="oe_link oe_inline" type="object" groups="account.group_account_invoice" attrs="{ 'invisible':[ - '|', - ('state', '=', 'cancel'), - ('cfdi_state','in', ['signed', 'cancel', 'done', False]) + '|', + ('state', 'in', ['draft', 'cancel', 'waiting']), + ('cfdi_state','in', ['signed', 'cancel', 'done', 'waiting', False]) ] - }"/> + }" + /> </div> </xpath> - <field name="partner_id" position="after"> - <field name="cfdi_use" - attrs="{ - 'readonly': [('cfdi_state','in', ['signed', 'cancel', 'done'])], - 'invisible': [('is_cfdi_candidate', '=', False)] - }" - options="{'no_create':True}"/> - <field name="cfdi_adenda_ids" - attrs="{ - 'readonly': [('state','in',('cancel','paid'))], - 'invisible': [('is_cfdi_candidate', '=', False)] - }" - options="{'no_create': True,'no_open':True}" - widget="many2many_tags" /> - <field name="l10n_mx_export" invisible="1" /> - </field> - <xpath expr="//page[@string='Payments']" position='after'> - <page name="Adendas" string="Adendas" - attrs="{'invisible': [('cfdi_adenda_ids', '=', False)]}"> - <group name="adenda"> - </group> - </page> + <xpath expr="//page[@name='cfdi_configuration_move_mx']" position='after'> <page string="Related" attrs="{ 'invisible': [ '|', '|', ('is_cfdi_candidate', '=', False), - ('type', 'not in', ('out_refund','out_invoice')), + ('move_type', 'not in', ('out_refund','out_invoice')), ('state', '!=', 'draft'), ('cfdi_relation_type', '=', False), ] @@ -159,11 +150,11 @@ </group> <group> <field name="commercial_partner_id" invisible="1"/> - <field name="origin_invoice_ids" + <!--<field name="origin_invoice_ids" domain="[ ('commercial_partner_id', '=', commercial_partner_id), - ('state','in', ['open','paid']), - ('type','=','out_invoice'), + ('state','in', ['posted']), + ('move_type','=','out_invoice'), ]" widget="many2many_tags" options="{'no_create': True}" @@ -174,17 +165,15 @@ }" context="{ 'form_view_ref': 'account.invoice_form', - }" /> + }" />--> </group> </group> </page> </xpath> - <xpath expr="//field[@name='origin']" position="before"> - <field name="address_issued_id"/> - </xpath> - <xpath expr="//field[@name='payment_term']" position="after"> - <field name="payment_method_id" class="oe_inline" - options="{'no_create':True, 'no_open':True}"/> + <xpath expr="//group[@name='sale_info_group']" position="after"> + <group name="Extra information"> + <field name="address_issued_id"/> + </group> </xpath> <xpath expr="//field[@name='partner_id']" position="attributes"> <attribute @@ -194,7 +183,6 @@ <attribute name="domain" translation="off">[ - ('customer', '=', True), '|', ('is_company', '=', True), ('type', '=', 'invoice')] @@ -204,21 +192,56 @@ translation="off">{'always_reload': True,'no_quick_create': True} </attribute> </xpath> - <xpath expr="//field[@name='invoice_line']//tree//field[@name='account_analytic_id']" position="after"> + <xpath expr="//field[@name='invoice_line_ids']//tree//field[@name='analytic_account_id']" position="after"> <field name="cfdi_cuentapredial" groups="l10n_mx_facturae.cfdi_cuentapredial" /> </xpath> - <xpath expr="//page//tree//field[@name='invoice_line_tax_id']" position="before"> + <xpath expr="//page//tree//field[@name='tax_ids']" position="before"> <field name="cfdi_custom_number" widget="many2many_tags" - groups="l10n_mx.group_cfdi_custom_number"/> + groups="l10n_mx_base.group_cfdi_custom_number"/> + </xpath> + </field> + </record> + + <record id="account_move_cfdi_inherit_view_form" model="ir.ui.view"> + <field name="name">account.move.cfdi.inherit.view.form</field> + <field name="model">account.move</field> + <field name="inherit_id" ref="l10n_mx_base.account_move_cfdi_view_form"/> + <field name="arch" type="xml"> + <xpath expr="//group[@name='Payment Type']" position="after"> + <field name="invoice_global" invisible="True"/> + <group id="global_invoice" name="Global Invoice" + attrs="{ + 'invisible': [('invoice_global', '=', False)] + }"> + <field name="cfdi_periodicity"/> + </group> + <group name="Fiscal data"> + <field name="cfdi_use" + attrs="{ + 'readonly': [('cfdi_state','in', ['signed', 'cancel', 'done'])], + 'invisible': [('is_cfdi_candidate', '=', False)] + }" + options="{'no_create':True}"/> + <field name="l10n_mx_export" invisible="1"/> + <field name="cfdi_adenda_ids" + attrs="{ + 'readonly': [('state','in',('cancel','paid'))], + 'invisible': [('is_cfdi_candidate', '=', False)] + }" + options="{'no_create': True,'no_open':True}" + widget="many2many_tags" /> + </group> + <group name="adenda"> + </group> </xpath> </field> </record> - <record id="account_invoice_view_tree" model="ir.ui.view"> - <field name="name">account.invoice.view.tree</field> - <field name="model">account.invoice</field> - <field name="inherit_id" ref="account.invoice_tree"/> + <record id="account_move_view_tree" model="ir.ui.view"> + <field name="name">account.move.view.tree</field> + <field name="model">account.move</field> + <field name="inherit_id" ref="account.view_out_invoice_tree"/> <field name="arch" type="xml"> <field name="name" position="after"> <field string="Fiscal Number" name="cfdi_id" invisible="1" /> @@ -226,5 +249,4 @@ </field> </record> -</data> -</openerp> +</odoo> diff --git a/l10n_mx_facturae/views/account_payment.xml b/l10n_mx_facturae/views/account_payment.xml new file mode 100644 index 0000000000000000000000000000000000000000..fbee1ae8573548d48c20745ee908fa0bc23ce592 --- /dev/null +++ b/l10n_mx_facturae/views/account_payment.xml @@ -0,0 +1,134 @@ +<?xml version="1.0"?> +<odoo> + + <!--<record id="account_payment_action_sign" model="ir.actions.server"> + <field name="name">Sign payments</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_account_payment"/> + <field name="state">code</field> + <field name="code">self.sign_payment(cr, uid, context.get('active_ids', []), context=context)</field> + </record>--> + + <!--<record id="ir_values_action_sign" model="ir.values"> + <field name="name">Sign payments</field> + <field name="action_id" ref="account_payment_action_sign"/> + <field name="value" eval="'ir.actions.server,' + str(ref('account_payment_action_sign'))"/> + <field name="key">action</field> + <field name="model_id" ref="model_account_payment"/> + <field name="model">account.payment</field> + <field name="key2">client_action_multi</field> + </record>--> + + <record id="account_payment_cfdi_view_form" model="ir.ui.view"> + <field name="name">account.payment.cfdi.view.form</field> + <field name="model">account.payment</field> + <field name="inherit_id" ref="account.view_account_payment_form"/> + <field name="arch" type="xml"> + <button name="action_draft" position="attributes"> + <attribute name="attrs"> + {'invisible': ['&',('state', 'in', ('posted', 'cancel')),('cfdi_state', 'in', ('done', 'cancel'))]} + </attribute> + </button> + <button name="action_post" position="after"> + <button name="sign_payment" type="object" string="Sign" + class="oe_highlight" + attrs="{'invisible': [ + '|', + ('state', '!=', 'posted'), + ('cfdi_state', '!=', False) + ]}" /> + <!--<button name="substitute_payment" type="object" string="Replace" + attrs="{'invisible': ['|', ('cfdi_state', 'not in', ['signed', + 'done']), ('state', '=', 'cancel')]}"/>--> + <button name="replace_cfdi" type="object" class="btn-primary" string="Cancel" + confirm="Are you sure to cancel this payment?" + attrs="{'invisible': ['|', ('cfdi_state', 'not in', ['signed', + 'done']), ('state', '=', 'cancel')]}"/>--> + </button> + <button name="mark_as_sent" position="after"> + <field name="show_unreconcile" invisible="1"/> + </button> + <xpath expr="//header" position="after"> + <div + class="alert alert-danger" + attrs="{'invisible':[ + '|', + ('state', '=', 'draft'), + ( + 'cfdi_state', + 'in', + ['signed', 'cancel', 'done', False] + ) + ]}" + role="alert" + style="margin-bottom:0px;" > + <field + class="oe_inline" + name="l10n_mx_edi_error" + readonly="1" /> + </div> + </xpath> + <xpath expr="//h1[2]" position="after"> + <h4 collspan="2"> + <field string="Fiscal Number" name="cfdi_id" readonly="1"/> + </h4> + </xpath> + <!-- Hide cancel button when payment have a related CFDI + <xpath expr="//button[@string='Unreconcile']" position="attributes"> + <attribute name="states" translation="off"></attribute> + <attribute name="attrs" translation="off">{'invisible': [('show_unreconcile', '=', False)]}</attribute> + </xpath>--> + <field name="partner_bank_id" position="after"> + <label + for="cfdi_state" + string="PAC State" + attrs="{ + 'invisible':[ + ('state', '=', 'draft') + ] + }" + /> + <div class="o_row"> + <field + name="cfdi_state" + class="oe_inline" + attrs="{ + 'invisible':[ + ('state', '=', 'draft') + ] + }" + /> + <button name="action_validate_cfdi" string="Retry" + class="oe_link oe_inline" type="object" + groups="account.group_account_invoice" + attrs="{'invisible':[ + '|', + ('state', 'in', ['draft', 'cancel', 'waiting']), + ( + 'cfdi_state', + 'in', + ['signed', 'cancel', 'done', 'waiting', False] + ) + ]}" /> + </div> + </field> + </field> + </record> + + <!--<record id="account_payment_view_search" model="ir.ui.view"> + <field name="name">account.payment.view.search</field> + <field name="model">account.payment</field> + <field name="inherit_id" ref="account_payment.view_payment_filter_customer_pay"/> + <field name="arch" type="xml"> + <field name="partner_id" position="after"> + <field name="cfdi_id" string="UUID" /> + </field> + <filter string="Posted" position="after"> + <separator/> + <filter string="To Sign" domain="[('cfdi_state', '=', 'draft')]" help="Vochers to be signed"/> + <filter string="Signed" domain="[('cfdi_state', 'in', ['signed', 'done'])]" help="Signed payments"/> + </filter> + </field> + </record>--> + +</odoo> diff --git a/l10n_mx_facturae/views/account_voucher.xml b/l10n_mx_facturae/views/account_voucher.xml deleted file mode 100644 index d8f142e71d7c9a41568c0501761c613db1d21f80..0000000000000000000000000000000000000000 --- a/l10n_mx_facturae/views/account_voucher.xml +++ /dev/null @@ -1,97 +0,0 @@ -<?xml version="1.0"?> -<openerp> -<data> - - <record id="account_voucher_action_sign" model="ir.actions.server"> - <field name="name">Sign Vouchers</field> - <field name="type">ir.actions.server</field> - <field name="model_id" ref="model_account_voucher"/> - <field name="state">code</field> - <field name="code">self.sign_voucher(cr, uid, context.get('active_ids', []), context=context)</field> - </record> - - <record id="ir_values_action_sign" model="ir.values"> - <field name="name">Sign Vouchers</field> - <field name="action_id" ref="account_voucher_action_sign"/> - <field name="value" eval="'ir.actions.server,' + str(ref('account_voucher_action_sign'))"/> - <field name="key">action</field> - <field name="model_id" ref="model_account_voucher"/> - <field name="model">account.voucher</field> - <field name="key2">client_action_multi</field> - </record> - - <record id="account_voucher_view_form" model="ir.ui.view"> - <field name="name">account.voucher.view.form</field> - <field name="model">account.voucher</field> - <field name="inherit_id" ref="account_voucher.view_vendor_receipt_form"/> - <field name="arch" type="xml"> - <button name="proforma_voucher" position="after"> - <field name="show_unreconcile" invisible="1"/> - <button name="sign_voucher" type="object" string="Sign" - class="oe_highlight" - attrs="{'invisible': ['|', ('state', '!=', 'posted'), ('cfdi_state', '!=', False)]}"/> - <button name="substitute_voucher" type="object" string="Replace" - attrs="{'invisible': ['|', ('cfdi_state', 'not in', ['signed', 'done']), ('state', '=', 'cancel')]}"/> - <button name="replace_cfdi" type="object" string="Cancel" - confirm="Are you sure to cancel this voucher?" - attrs="{'invisible': ['|', ('cfdi_state', 'not in', ['signed', 'done']), ('state', '=', 'cancel')]}"/> - </button> - <xpath expr="//header" position="after"> - <div - class="alert alert-danger" - attrs="{'invisible':[ - ( - 'cfdi_state', - 'in', - ['signed', 'cancel', 'done', False] - ) - ]}" - role="alert" - style="margin-bottom:0px;" > - <field - class="oe_inline" - name="l10n_mx_edi_error" - readonly="1" /> - </div> - </xpath> - <xpath expr="//sheet/h1" position="after"> - <h4 attrs="{'invisible': [('number','=',False)]}" collspan="2"> - <field string="Fiscal Number" name="cfdi_id" readonly="1"/> - </h4> - </xpath> - <!-- Hide cancel button when voucher have a related CFDI --> - <xpath expr="//button[@string='Unreconcile']" position="attributes"> - <attribute name="states" translation="off"></attribute> - <attribute name="attrs" translation="off">{'invisible': [('show_unreconcile', '=', False)]}</attribute> - </xpath> - <xpath expr="//field[@name='name']" position="after"> - <label for="cfdi_state" string="PAC State" states="posted,signed,cancel" /> - <div class="o_row" states="posted,signed,cancel"> - <field name="cfdi_state" class="oe_inline"/> - <button name="action_validate_cfdi" string="Retry" - class="oe_link oe_inline" type="object" - groups="account.group_account_invoice" - attrs="{'invisible':[('cfdi_state', 'in', ['signed', 'cancelled', 'done', False])]}"/> - </div> - </xpath> - </field> - </record> - - <record id="account_voucher_view_search" model="ir.ui.view"> - <field name="name">account.voucher.view.search</field> - <field name="model">account.voucher</field> - <field name="inherit_id" ref="account_voucher.view_voucher_filter_customer_pay"/> - <field name="arch" type="xml"> - <field name="partner_id" position="after"> - <field name="cfdi_id" string="UUID" /> - </field> - <filter string="Posted" position="after"> - <separator/> - <filter string="To Sign" domain="[('cfdi_state', '=', 'draft')]" help="Vochers to be signed"/> - <filter string="Signed" domain="[('cfdi_state', 'in', ['signed', 'done'])]" help="Signed Vouchers"/> - </filter> - </field> - </record> - -</data> -</openerp> diff --git a/l10n_mx_facturae/views/res_company.xml b/l10n_mx_facturae/views/res_company.xml new file mode 100644 index 0000000000000000000000000000000000000000..5c6c743672562495d1675e854397355adda535b0 --- /dev/null +++ b/l10n_mx_facturae/views/res_company.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="l10_mx_facturae_res_company_view_form" model="ir.ui.view"> + <field name="name">l10n.mx.facturae.res.company.view.form</field> + <field name="model">res.company</field> + <field name="inherit_id" ref="l10n_mx_base.res_company_view_form"/> + <field name="arch" type="xml"> + <field name="cfdi_fiscal_regime_id" position="after"> + <field name="cfdi_use_id"/> + </field> + </field> + </record> + +</odoo> diff --git a/l10n_mx_facturae/views/res_partner.xml b/l10n_mx_facturae/views/res_partner.xml index 00ed9269703086cd942e449e9325bbe5a93c9ef1..fb28220319066218219adc4d4d0a19b3ad395575 100644 --- a/l10n_mx_facturae/views/res_partner.xml +++ b/l10n_mx_facturae/views/res_partner.xml @@ -1,50 +1,67 @@ <?xml version="1.0"?> -<openerp> -<data> +<odoo> - <record id="res_partner_view_form" model="ir.ui.view"> - <field name="name">res.partner.view.form</field> + <record id="l10n_mx_facturae_res_partner_view_form" model="ir.ui.view"> + <field name="name">l10n.mx.facturae.res.partner.view.form</field> <field name="model">res.partner</field> - <field name="inherit_id" ref="account.view_partner_property_form"/> + <field name="inherit_id" ref="l10n_mx_base.res_partner_view_form"/> <field name="arch" type="xml"> - <xpath expr="//page[@string='Accounting']" position="after"> - <page name="Invoicing" string="Invoicing"> - <group> - <group name="Configuraciones" string="Configuraciones"> - <field name="cfdi_fiscal_regime_id" options="{'no_create': True,'no_open':True}" /> - <field name="cfdi_use" options="{'no_create': True,'no_open':True}"/> - <field name="payment_method_id" options="{'no_create': True,'no_open':True}"/> - </group> - <group> - </group> - <group string="Adendas"> - <p>Addends are used to add additional content to the invoice that the SAT. - An addendum contains information of a commercial, logistic and operation, - often required by the receiving company (client). - <br/><br/> - Once you have selected the addendum, you must add the necessary information - in all or some of the following sections: Customers, suppliers, company, - products, services, invoices or delivery notes - <br/><br/> - Done the necessary configurations, you will be able to stamp your invoices - with the selected complement. - </p> - </group> - <group> - <p colspan="2"><b><br/> </b></p> - <field name="show_glnnumber" invisible="True"/> - <field name="show_suppliernumber" invisible="True"/> - <field name="show_edi" invisible="True"/> - <field name="cfdi_adenda_ids" widget="many2many_tags" options="{'no_create': True,'no_open':True}"/> - <field name="gln_number" attrs="{'required':[('show_glnnumber', '=', True)], 'invisible':[('show_glnnumber', '=', False)]}"/> - <field name="supplier_number" attrs="{'required':[('show_suppliernumber', '=', True)], 'invisible':[('show_suppliernumber', '=', False)]}"/> - <field name="edi" attrs="{'required':[('show_edi', '=', True)], 'invisible':[('show_edi', '=', False)]}"/> - </group> - </group> - </page> - </xpath> + <field name="cfdi_fiscal_regime_id" position="after"> + <field name="cfdi_use_id" options="{'no_create': True,'no_open':True}"/> + <field name="payment_method_id" options="{'no_create': True,'no_open':True}"/> + </field> + <group name="cfdi_taxes" position="after"> + <group name="cfdi_adendum" string="Adendas"> + <p>Addends are used to add additional content to the invoice that the SAT. + An addendum contains information of a commercial, logistic and operation, + often required by the receiving company (client). + <br/><br/> + Once you have selected the addendum, you must add the necessary information + in all or some of the following sections: Customers, suppliers, company, + products, services, invoices or delivery notes + <br/><br/> + Done the necessary configurations, you will be able to stamp your invoices + with the selected complement. + </p> + </group> + <group name="cfdi_adendum_fields"> + <p colspan="2"><b><br/> </b></p> + <field name="show_glnnumber" invisible="True"/> + <field name="show_suppliernumber" invisible="True"/> + <field name="show_edi" invisible="True"/> + <field name="cfdi_adenda_ids" + widget="many2many_tags" + options="{'no_create': True,'no_open':True}"/> + <field name="gln_number" + attrs="{ + 'required':[ + ('show_glnnumber', '=', True) + ], + 'invisible':[ + ('show_glnnumber', '=', False) + ] + }"/> + <field name="supplier_number" + attrs="{ + 'required':[ + ('show_suppliernumber', '=', True) + ], + 'invisible':[ + ('show_suppliernumber', '=', False) + ] + }"/> + <field name="edi" + attrs="{ + 'required':[ + ('show_edi', '=', True) + ], + 'invisible':[ + ('show_edi', '=', False) + ] + }"/> + </group> + </group> </field> </record> -</data> -</openerp> +</odoo> diff --git a/l10n_mx_facturae/wizard/__init__.py b/l10n_mx_facturae/wizard/__init__.py index 30e4ebec8a85a7685bbdf5206a43ad6e21c58ab7..799aa57d0a7f750f4d26befd9e1fce051060a2ac 100644 --- a/l10n_mx_facturae/wizard/__init__.py +++ b/l10n_mx_facturae/wizard/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -from . import account_invoice_refund -from . import wizard_multi_charts_accounts +#from . import account_invoice_refund +#from . import wizard_multi_charts_accounts diff --git a/l10n_mx_facturae/wizard/account_invoice_refund.py b/l10n_mx_facturae/wizard/account_invoice_refund.py index e292b5146ccd344ce209932a1d64f748a2dcd011..1c8e4cb1ae098fabbb50632ec6bde077929d6f54 100644 --- a/l10n_mx_facturae/wizard/account_invoice_refund.py +++ b/l10n_mx_facturae/wizard/account_invoice_refund.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from openerp import fields, models +from odoo import fields, models class AccountInvoiceRefund(models.TransientModel): diff --git a/l10n_mx_facturae/wizard/account_invoice_refund.xml b/l10n_mx_facturae/wizard/account_invoice_refund.xml index e9fc4949fd554e14f08532f2ee0f1618655b6d5d..cd47e01e5d69dcafad9f8c94f9bafa80b77a971b 100644 --- a/l10n_mx_facturae/wizard/account_invoice_refund.xml +++ b/l10n_mx_facturae/wizard/account_invoice_refund.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<openerp> +<odoo> <data> <record id="view_account_invoice_refund" model="ir.ui.view"> @@ -14,4 +14,4 @@ </record> </data> -</openerp> +</odoo> diff --git a/l10n_mx_facturae/wizard/wizard_multi_charts_accounts.py b/l10n_mx_facturae/wizard/wizard_multi_charts_accounts.py index e90f7d41de142e09f554451569e6cb91a9434414..f10ab67b0bbdbadacc53ceeb36f3aa650d8a9248 100644 --- a/l10n_mx_facturae/wizard/wizard_multi_charts_accounts.py +++ b/l10n_mx_facturae/wizard/wizard_multi_charts_accounts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from openerp import api, models +from odoo import api, models class WizardMultiChartsAccounts(models.TransientModel):