diff --git a/l10n_fr_das2/README.rst b/l10n_fr_das2/README.rst index f58837deb..afb488df6 100644 --- a/l10n_fr_das2/README.rst +++ b/l10n_fr_das2/README.rst @@ -1,110 +1 @@ -==== -DAS2 -==== - -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! This file is generated by oca-gen-addon-readme !! - !! changes will be overwritten. !! - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png - :target: https://odoo-community.org/page/development-status - :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 -.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fl10n--france-lightgray.png?logo=github - :target: https://github.com/OCA/l10n-france/tree/14.0/l10n_fr_das2 - :alt: OCA/l10n-france -.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/l10n-france-14-0/l10n-france-14-0-l10n_fr_das2 - :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/121/14.0 - :alt: Try me on Runbot - -|badge1| |badge2| |badge3| |badge4| |badge5| - -This module adds support for `DAS2 _`, which is an annual fiscal declaration also called *Déclaration d'honoraires*. It will allow you to auto-generate the lines of DAS2 from the accounting entries, check the result, manually update lines if needed and eventually generate a declaration file. Then send the declaration file to the French tax office via your professional account on *impots.gouv.fr* - -The specifications of the file are available on `this page `_. - -**Table of contents** - -.. contents:: - :local: - -Configuration -============= - -On the supplier form view, in the *Accounting* tab, you will see a section *DAS2*. For the suppliers that must be declared in DAS2, you must set: - -* the DAS2 Type, -* the job for the DAS2 declaration, -* their SIRET number (for French suppliers only), -* their full address (street, zip code, city and country). - -On the company configuration form, the APE code, SIRET and address must be set. - -For the user responsible for the declaration, the phone number and email must be set on his related partner form (name, email and phone number are used in the DAS2 declaration file). - -Usage -===== - -Go to the menu *Accounting > Reports > French Statements > DAS2* and create a new DAS2 report. - -Then click on the button *Generate Lines*. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the *Note* fields of each line. - -You may have a yellow warning banner that warn you about suppliers that have expenses recorded in accounts such as 622100 Commissions et courtages sur achats, 622200 Commissions et courtages sur ventes, 622600 Honoraires, 622800 Rémunérations d'intermédiaires divers, 653000 Jetons de présence, 651600 Droits d'auteur et de reproduction that are not configured for DAS2. - -Once your declaration is OK, click on the button *Done*: it will generate the DAS2 file and set the declaration to *Done* state (all the fields become readonly). - -To send the file to the French tax office, go to your professional account on `impots.gouv.fr `_, go to the menu **Déclarer > Tiers déclarant** and then click on the button **Déposer un fichier**. Select **Salaires et/ou honoraires** and follow the instructions. - -Bug Tracker -=========== - -Bugs are tracked on `GitHub Issues `_. -In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. - -Do not contact contributors directly about support or help with technical issues. - -Credits -======= - -Authors -~~~~~~~ - -* Akretion - -Contributors -~~~~~~~~~~~~ - -* Alexis de Lattre - -Maintainers -~~~~~~~~~~~ - -This module is maintained by the OCA. - -.. image:: https://odoo-community.org/logo.png - :alt: Odoo Community Association - :target: https://odoo-community.org - -OCA, or the Odoo Community Association, is a nonprofit organization whose -mission is to support the collaborative development of Odoo features and -promote its widespread use. - -.. |maintainer-alexis-via| image:: https://github.com/alexis-via.png?size=40px - :target: https://github.com/alexis-via - :alt: alexis-via - -Current `maintainer `__: - -|maintainer-alexis-via| - -This module is part of the `OCA/l10n-france `_ project on GitHub. - -You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. +Will be generated from readme subdir diff --git a/l10n_fr_das2/__manifest__.py b/l10n_fr_das2/__manifest__.py index 9723ca573..1cee9b275 100644 --- a/l10n_fr_das2/__manifest__.py +++ b/l10n_fr_das2/__manifest__.py @@ -16,7 +16,7 @@ "l10n_fr_cog", ], "external_dependencies": { - "python": ["unidecode", "stdnum", "pgpy"], + "python": ["stdnum", "pyfrdas2"], }, "data": [ "security/das2_security.xml", diff --git a/l10n_fr_das2/models/l10n_fr_das2.py b/l10n_fr_das2/models/l10n_fr_das2.py index 7bcc68f04..98bd9f60a 100644 --- a/l10n_fr_das2/models/l10n_fr_das2.py +++ b/l10n_fr_das2/models/l10n_fr_das2.py @@ -3,10 +3,10 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import base64 -import gzip import logging from datetime import datetime +from pyfrdas2 import format_street_block, generate_file from stdnum.fr.siret import is_valid from odoo import _, api, fields, models, tools @@ -15,17 +15,6 @@ logger = logging.getLogger(__name__) -try: - from unidecode import unidecode -except ImportError: - unidecode = False - logger.debug("Cannot import unidecode") -try: - import pgpy -except ImportError: - pgpy = False - logger.debug("Cannot import pgpy") - FRANCE_CODES = ( "FR", @@ -198,10 +187,10 @@ def done(self): % encryption ) - attach = self.generate_file(encryption=encryption) + attach = self.generate_file_and_attach(encryption=encryption) self.message_post(body=msg) # also generate a clear file, for audit purposes - unencrypted_attach = self.generate_file(encryption="none") + unencrypted_attach = self.generate_file_and_attach(encryption="none") vals.update( { "attachment_id": attach.id, @@ -314,11 +303,8 @@ def _prepare_line(self, partner, base_domain): field_name: amount_int, "parent_id": self.id, "partner_id": partner.id, - "job": partner.fr_das2_job, "note": "
    %s
" % note, } - if partner.siren and partner.nic: - res["partner_siret"] = partner.siret return res def generate_warning_msg(self, das2_partners): @@ -438,14 +424,14 @@ def _prepare_field( def _prepare_address(self, partner): cstreet2 = self._prepare_field("Street2", partner, partner.street2, 32) - cstreet = self._prepare_field("Street", partner, partner.street, 32) - # specs section 5.4 : only bureau distributeur and code postal are + cstreet = format_street_block(partner.street) + # specs : only bureau distributeur and code postal are # required. And they say it is tolerated to set city as # bureau distributeur # And we don't set the commune field because we put the same info - # in bureau distributeur (section 5.4.1.3) - # specs section 5.4 and 5.4.1.2.b: it is possible to set the field - # "Adresse voie" without structuration on 32 chars => that's what we do + # in bureau distributeur + # specs 2024 : we now have to have structured adresses with a separation + # for the number, bis/ter and street name. We'll do our best to comply with that. if not partner.city: raise UserError(_("Missing city on partner '%s'.") % partner.display_name) if partner.country_id and partner.country_id.code not in FRANCE_CODES: @@ -696,7 +682,7 @@ def _prepare_file(self): file_content = "\r\n".join(flines) + "\r\n" return file_content - def generate_file(self, encryption="prod"): + def generate_file_and_attach(self, encryption="prod"): self.ensure_one() assert encryption in ("prod", "test", "none") company = self.company_id @@ -746,39 +732,19 @@ def generate_file(self, encryption="prod"): ) ) from e - if encryption in ("prod", "test"): - prefix = "DSAL_" - file_ext = "txt.gz.gpg" - key_path = ( - "l10n_fr_das2/pgp_keys/DGFIP_TIERSDECLARANTS_%s.asc" - % encryption.upper() + try: + file_bytes_result, filename = generate_file( + file_content_encoded, self.year, company.siren, encryption=encryption ) - with tools.file_open(key_path, mode="rb") as key_file: - key_file_blob = key_file.read() - pubkey = pgpy.PGPKey.from_blob(key_file_blob)[0] - file_content_compressed = gzip.compress(file_content_encoded) - to_encrypt_object = pgpy.PGPMessage.new(file_content_compressed) - file_content_final = bytes(pubkey.encrypt(to_encrypt_object)) - else: - file_content_final = file_content_encoded - prefix = "UNENCRYPTED_DAS2_for_audit-" - file_ext = "txt" - - user_local_datetime = fields.Datetime.context_timestamp(self, datetime.now()) - filename = "%(prefix)s%(year)s_%(siren)s_000_%(gentime)s.%(file_ext)s" % { - "prefix": prefix, - "year": self.year, - "siren": company.siren, - "gentime": user_local_datetime.strftime("%Y%m%d%H%M%S"), - "file_ext": file_ext, - } + except Exception as e: + raise UserError(e) from e attach = self.env["ir.attachment"].create( { "name": filename, "res_id": self.id, "res_model": self._name, - "datas": base64.encodebytes(file_content_final), + "datas": base64.encodebytes(file_bytes_result), } ) return attach diff --git a/l10n_fr_das2/pgp_keys/DGFIP_TIERSDECLARANTS_PROD.asc b/l10n_fr_das2/pgp_keys/DGFIP_TIERSDECLARANTS_PROD.asc deleted file mode 100644 index ac9307271..000000000 --- a/l10n_fr_das2/pgp_keys/DGFIP_TIERSDECLARANTS_PROD.asc +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -mQGNBGKMztMBDADQijGmCw+kB+23IZGrg6v8woz2lOmdySHGwysU+sQKpzAMZg45 -8gSTnqO3AgD9ij4M2SAcSuio9RPLzlnVri4/2rAbRcV6wF1EVq+qT7s0ADBvVJtj -a1e+me8/bBsVTtOiF5PRumVqzzxMNgbCpW7QS9QhjmyZLzswgtdyUcZEpo/Bl2J0 -3UhlAugFJs24UFTfJC1rLkupkY+uEgowg1XYvYRLSEa76E+7VKdO7oPpz4Vys74v -IdhZwVKODWCUgAEl1obP8AhLkXaXyxU5rvV9c9u9eYOiwmKLsHwKEY+6jkomMtJM -OL1TJAp+MHlWUrDThXcOEJnNHeaTmSGN4LG/B3He2DD2vVJxcI61KInLo1eHHBwa -dwikUS5kzT4Vg9uY0UT36mMeEBh8PLKGtr+KtU7350jNSwzAZZ1W1RyO0cEYlrq0 -bAOFGEMOsQAhs4Qf9NOvSg//mACdV9uJhV0o0OmM1SfJQsNGUD1DLtdffzAjZZox -UDwEnSmK07tDVSMAEQEAAbRJREdGSVBfVElFUlNERUNMQVJBTlRTX1BST0QgPGJ1 -cmVhdS5zaS1wYXJ0LXRlbGV0ZEBkZ2ZpcC5maW5hbmNlcy5nb3V2LmZyPokB1AQT -AQoAPhYhBLGayiGe5DPffAIMsWGJ5jC/hAIgBQJijM7TAhsDBQkDwmcABQsJCAcC -BhUKCQgLAgQWAgMBAh4BAheAAAoJEGGJ5jC/hAIgLSwMAMW69htPdMasIPq8mB34 -NbU9sLXf11DyoCqb8fQTunnNMJYtHt6Jru2uoTkGqogkR4ssxiRR6uUoomHSTXf0 -u6u0dzI14Dcq18on4D+vTw/+KWa2U1dv1r/kfBK8yPJowBp3rpWDQfI2d58Knxag -DIwQzRnJrQ+X7oTXXw9CNWkiD8K9mI8aJQrQA4052wtMCZ9tDloSbGgKvx+PEMGT -49bo6JB7MUfLkTBm640lrlc+r0ec/+JK8fFfX5busiPV4jm/oLZdQSdIyPSMDLYz -edYpM4dqXHoThzY1R5pj0gcCVdVbiMXuXX/b/uRVXdiQOR519hhIy4GWUBuuMIjj -Oaq83ogqM51u+kAJsnpLBUggqK7S9fNihSJhT7qIs/r03Y5JShHlsRiQbBgCRCYY -GL5Nho2ICTDTUw4ZYOg+v4fZAItCAbJoxt5ZYyOCQIO8le0j6gJnL3esbbUvq7ex -O2E0ZXT7O6V/487Mmem9Um5+R8TS9Bde1KJ3SoiGHyKp+YkBHAQQAQgABgUCYo4h -YQAKCRB3lxWSEiCUh07ICACDQ8/+9VvMqKPUWQ/PwMI7v1zXFKz0CdtSmKxMF70J -EaLX22g7aq5o/6z9npW2jX3eIvoJoXaSZVHnmD0aHDn6g66j2HoEQpVRe6jt4xsW -ChCnRMqxl7S98j4Oyc/xBXS7kP1GCMRA9PDLkk0/fmiZ5hdCFW7lA8hQbVXkLvoC -XBw6ltyhUJaleqk+MMh6MsZQQLEXirDfYb5zzVOTL3zU7ejzS7Xcj0UhCpshuB2a -ecMSgMB4wJaLm6g9jUOsjWuMd9z7ukaApO18TwsT4ggOYwU/FNajexTPL9wxnXW8 -j3zgWNzVqVcmUFfFBIPvxYeObhXLB2sd9tYOtAOQXK8YuQGNBGKMztMBDADeI0Z7 -YFnaBlrGKUgTC5Hr55cTtGjXHAMkMBN4npK/tCVV2bKthTRoLShlKePCq4/tevyM -6sURz22kKb6yU324Co3W2HGrShU5Y5bfeekLxZLvH7J2gMBPHFu16Eem8Fw51POH -ag8Wbg6flYLdlb+pop+Cmth4AalBwF3y6xj4ZtwIO8T/NoPyT7rP0dbU2MSghMfn -dGEmRaOEwvBl1K2BDTjU6dE4j45NMYAunSxi/RAsjZvrGat+J2Ee6QyewovBrxNa -wFeNrvTUlNe5jslFVYstR6YlKUzsrcySP/6KaEz8DyBk1bTpHeiZOIKUNrwpKbE5 -pSVyrWKQl7rCQF7prkGADN4T5OX3LRd4MVYJYohkackd6rs+WpZrnk0Z76V9zEKi -D7n8D5M/R280Nj+Aq7kBY2HpDDqMZKlguvjySKp5WJKe5YOfAs1a5j2YFPCwBMK+ -+4k6o6GZzvT2h+tYdJ/OS47pOBcpzsBqOyyyKpqlWYIe6/0ieJjNCR7VQr0AEQEA -AYkBvAQYAQoAJhYhBLGayiGe5DPffAIMsWGJ5jC/hAIgBQJijM7TAhsMBQkDwmcA -AAoJEGGJ5jC/hAIguO0L/AoEdBD96DUwiTKs0bLRo8YXa04EO7POs87Stsafv4d1 -sEf3ePbGdXj7xWveDku3IZfnVcvTk2jPZ8/fTAHVoDWGTK1xwcibqHQGtaCsTunk -+rumQaJJ95sapXVjiwoVuExZIDnVTxLnNztZXO4TslnAijfdV4NMMzGN/O6xE6ow -q1OLJW+zqJcUgjeVY9h3hz3tYol5wXOqYI/qVLRLZPoxEg1ZSgAwL66TAXO4K22m -tJ+qIm4Kbq5a/PTn0Myv7hr4/y09bwXg19oq1k6GMXGKeEpI6zCyLJbpa7gJa/IJ -DnRO3+FvjNhP5OZnUgUhT1H8gD9JfDgPHhPFxyvG9CJ4O8YZgLwPwKqpQfgOnM5J -x9g1a8O2SLuFF4GCjxy9HM29H15mHUfU1tAR6gGZTKMJD8qZGrlpJjJsS6aRCq3Y -/3PvZqCEiko8qOOLOJMMYoGBI5mh2tddcRNIGyjqlie8CQLmgSgMNf+PGzO19qki -e/3+/EBsKPd3jAz3Q2juIw== -=igW+ ------END PGP PUBLIC KEY BLOCK----- diff --git a/l10n_fr_das2/pgp_keys/DGFIP_TIERSDECLARANTS_TEST.asc b/l10n_fr_das2/pgp_keys/DGFIP_TIERSDECLARANTS_TEST.asc deleted file mode 100644 index 92a748c12..000000000 --- a/l10n_fr_das2/pgp_keys/DGFIP_TIERSDECLARANTS_TEST.asc +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -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 -nWfTy62TF6GgpJqzvdrFQA== -=E7O7 ------END PGP PUBLIC KEY BLOCK----- diff --git a/l10n_fr_das2/readme/INSTALL.rst b/l10n_fr_das2/readme/INSTALL.rst new file mode 100644 index 000000000..a6208db8a --- /dev/null +++ b/l10n_fr_das2/readme/INSTALL.rst @@ -0,0 +1,7 @@ +This module requires the Python lib `pyfrdas2 `_. As this lib contains the PGP encryption keys of the DGFiP used to encrypt the declaration file and as these keys are changed every year, check that the lib is up-to-date before using the module for a new year. + +To install this lib, run: + +.. code:: + + pip3 install --upgrade pyfrdas2 diff --git a/l10n_fr_das2/static/description/index.html b/l10n_fr_das2/static/description/index.html index 2f674ac05..4f4222b13 100644 --- a/l10n_fr_das2/static/description/index.html +++ b/l10n_fr_das2/static/description/index.html @@ -366,6 +366,11 @@

DAS2

Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runbot

This module adds support for DAS2 <https://www.impots.gouv.fr/portail/formulaire/das2/etat-des-honoraires-vacations-commissions-courtages-ristournes-et-jetons>_, which is an annual fiscal declaration also called Déclaration d’honoraires. It will allow you to auto-generate the lines of DAS2 from the accounting entries, check the result, manually update lines if needed and eventually generate a declaration file. Then send the declaration file to the French tax office via your professional account on impots.gouv.fr

@@ -373,6 +378,7 @@

DAS2

Table of contents

+
+

Installation

+

This module requires the Python lib pyfrdas2. As this lib contains the PGP encryption keys of the DGFiP used to encrypt the declaration file and as these keys are changed every year, check that the lib is up-to-date before using the module for a new year.

+

To install this lib, run:

+
+pip3 install --upgrade pyfrdas2
+
+
+<<<<<<< HEAD

Configuration

+======= +

Configuration

+>>>>>>> 7ffd40ba (l10n_fr_das2: update for millesime 2024 using pyfrdas2)

On the supplier form view, in the Accounting tab, you will see a section DAS2. For the suppliers that must be declared in DAS2, you must set:

  • the DAS2 Type,
  • @@ -397,7 +425,11 @@

    Configuration

    For the user responsible for the declaration, the phone number and email must be set on his related partner form (name, email and phone number are used in the DAS2 declaration file).

+<<<<<<< HEAD

Usage

+======= +

Usage

+>>>>>>> 7ffd40ba (l10n_fr_das2: update for millesime 2024 using pyfrdas2)

Go to the menu Accounting > Reports > French Statements > DAS2 and create a new DAS2 report.

Then click on the button Generate Lines. Check and edit the generated lines. You can get the details of the computation performed by Odoo in the Note fields of each line.

You may have a yellow warning banner that warn you about suppliers that have expenses recorded in accounts such as 622100 Commissions et courtages sur achats, 622200 Commissions et courtages sur ventes, 622600 Honoraires, 622800 Rémunérations d’intermédiaires divers, 653000 Jetons de présence, 651600 Droits d’auteur et de reproduction that are not configured for DAS2.

@@ -405,7 +437,11 @@

Usage

To send the file to the French tax office, go to your professional account on impots.gouv.fr, go to the menu Déclarer > Tiers déclarant and then click on the button Déposer un fichier. Select Salaires et/ou honoraires and follow the instructions.

+<<<<<<< HEAD

Bug Tracker

+======= +

Bug Tracker

+>>>>>>> 7ffd40ba (l10n_fr_das2: update for millesime 2024 using pyfrdas2)

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed @@ -413,21 +449,35 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

+<<<<<<< HEAD

Credits

Authors

+======= +

Credits

+
+

Authors

+>>>>>>> 7ffd40ba (l10n_fr_das2: update for millesime 2024 using pyfrdas2)
  • Akretion
+<<<<<<< HEAD

Contributors

+======= +

Contributors

+>>>>>>> 7ffd40ba (l10n_fr_das2: update for millesime 2024 using pyfrdas2)
+<<<<<<< HEAD

Maintainers

+======= +

Maintainers

+>>>>>>> 7ffd40ba (l10n_fr_das2: update for millesime 2024 using pyfrdas2)

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose diff --git a/l10n_fr_das2/tests/__init__.py b/l10n_fr_das2/tests/__init__.py new file mode 100644 index 000000000..26e0d6fdd --- /dev/null +++ b/l10n_fr_das2/tests/__init__.py @@ -0,0 +1 @@ +from . import test_das2 diff --git a/l10n_fr_das2/tests/test_das2.py b/l10n_fr_das2/tests/test_das2.py new file mode 100644 index 000000000..d374f808f --- /dev/null +++ b/l10n_fr_das2/tests/test_das2.py @@ -0,0 +1,94 @@ +# Copyright 2024 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + + +@tagged("post_install", "-at_install") +class TestFrDas2(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.company = cls.env["res.company"].create( + { + "name": "Test company DAS2", + "street": "42, rue de la paix", + "zip": "69100", + "city": "Villeurbanne", + "country_id": cls.env.ref("base.fr").id, + "currency_id": cls.env.ref("base.EUR").id, + "siren": "792377731", + "nic": "00023", + "ape": "6202Z", + } + ) + cls.env.user.phone = "+33742124212" + cls.partner1 = cls.env["res.partner"].create( + { + "is_company": True, + "name": "Mon expert comptable préféré", + "street": "42 rue du chiffre", + "zip": "69009", + "city": "Lyon", + "country_id": cls.env.ref("base.fr").id, + "email": "experts@comptables.example.com", + "fr_das2_type": "fee", + "fr_das2_job": "Expert comptable", + "siren": "555555556", + "nic": "00011", + } + ) + cls.partner2 = cls.env["res.partner"].create( + { + "is_company": True, + "name": "Mon avocat", + "street": "12 rue du tribunal", + "zip": "69009", + "city": "Lyon", + "country_id": cls.env.ref("base.fr").id, + "email": "avocat@example.com", + "fr_das2_type": "fee", + "fr_das2_job": "Avocat", + "siren": "666666664", + "nic": "00014", + } + ) + + def test_das2(self): + decl = self.env["l10n.fr.das2"].create( + { + "year": 2023, + "company_id": self.company.id, + "payment_journal_ids": [], + } + ) + self.assertEqual(self.partner1.siret, "55555555600011") + self.env["l10n.fr.das2.line"].create( + { + "partner_id": self.partner1.id, + "parent_id": decl.id, + "fee_amount": 2000, + } + ) + self.env["l10n.fr.das2.line"].create( + { + "partner_id": self.partner2.id, + "parent_id": decl.id, + "fee_amount": 3123, + } + ) + self.assertEqual(decl.state, "draft") + decl.done() + self.assertEqual(decl.state, "done") + self.assertTrue(decl.attachment_id) + self.assertTrue( + decl.attachment_id.name.startswith( + f"DSAL_{decl.year}_{decl.company_id.siren}_000_" + ) + ) + self.assertTrue(decl.attachment_id.name.endswith(".txt.gz.gpg")) + self.assertTrue(decl.unencrypted_attachment_id) + self.assertTrue(decl.unencrypted_attachment_id.name.endswith(".txt")) diff --git a/requirements.txt b/requirements.txt index 90943749b..ec9236dd5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ # generated from manifests external_dependencies -pgpy +pyfrdas2 +pypdf>=3.1.0 python-stdnum python-stdnum>=1.18 requests_oauthlib