diff --git a/l10n_fr_fec_group_sale_purchase/README.rst b/l10n_fr_fec_group_sale_purchase/README.rst new file mode 100644 index 000000000..a133777cd --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/README.rst @@ -0,0 +1,38 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +=============================================== +Option to Group lines by sale, purchase journls +=============================================== + +Usage +===== + +* This module extends the features of l10n_fr_account module. + +* It enables to group the report lines by sale and purchase journals. + +| + +.. image:: /l10n_fr_fec_group_sale_purchase/static/src/image/FEC_report_wizard.png + :width: 1100px + + +| + +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. + +Credits +======= + +Contributors +------------ + +* La Louve +* Druidoo diff --git a/l10n_fr_fec_group_sale_purchase/__init__.py b/l10n_fr_fec_group_sale_purchase/__init__.py new file mode 100644 index 000000000..40272379f --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/l10n_fr_fec_group_sale_purchase/__manifest__.py b/l10n_fr_fec_group_sale_purchase/__manifest__.py new file mode 100644 index 000000000..76c138489 --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "France - FEC Group Sale Purchase", + "version": "18.0.1.0.0", + "category": "Localization", + "summary": "Fichier d'Échange Informatisé (FEC) for France", + "author": "Druidoo, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-france", + "license": "AGPL-3", + "depends": ["l10n_fr_fec_oca"], + "data": [ + "wizard/account_fr_fec_view.xml", + ], + "installable": True, +} diff --git a/l10n_fr_fec_group_sale_purchase/i18n/l10n_fr_fec_group_sale_purchase.pot b/l10n_fr_fec_group_sale_purchase/i18n/l10n_fr_fec_group_sale_purchase.pot new file mode 100644 index 000000000..4e22c93c3 --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/i18n/l10n_fr_fec_group_sale_purchase.pot @@ -0,0 +1,25 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_fr_fec_group_sale_purchase +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_fr_fec_group_sale_purchase +#: model:ir.model,name:l10n_fr_fec_group_sale_purchase.model_account_fr_fec +msgid "Ficher Echange Informatise" +msgstr "" + +#. module: l10n_fr_fec_group_sale_purchase +#: model:ir.model.fields,field_description:l10n_fr_fec_group_sale_purchase.field_account_fr_fec__group_sale_purchase +msgid "Group Sale and Purchase Journals" +msgstr "" + diff --git a/l10n_fr_fec_group_sale_purchase/pyproject.toml b/l10n_fr_fec_group_sale_purchase/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/l10n_fr_fec_group_sale_purchase/readme/CONTRIBUTORS.md b/l10n_fr_fec_group_sale_purchase/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..8d790d413 --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- La Louve \<\> +- Druidoo \<\> +- Trobz \<\> + - Phan Hong Phuc \<\> diff --git a/l10n_fr_fec_group_sale_purchase/static/description/icon.png b/l10n_fr_fec_group_sale_purchase/static/description/icon.png new file mode 100644 index 000000000..fd60220fb Binary files /dev/null and b/l10n_fr_fec_group_sale_purchase/static/description/icon.png differ diff --git a/l10n_fr_fec_group_sale_purchase/static/src/image/FEC_report_wizard.png.png b/l10n_fr_fec_group_sale_purchase/static/src/image/FEC_report_wizard.png.png new file mode 100644 index 000000000..9eaf0848d Binary files /dev/null and b/l10n_fr_fec_group_sale_purchase/static/src/image/FEC_report_wizard.png.png differ diff --git a/l10n_fr_fec_group_sale_purchase/tests/__init__.py b/l10n_fr_fec_group_sale_purchase/tests/__init__.py new file mode 100644 index 000000000..0473a9b20 --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/tests/__init__.py @@ -0,0 +1 @@ +from . import test_fec_export diff --git a/l10n_fr_fec_group_sale_purchase/tests/test_fec_export.py b/l10n_fr_fec_group_sale_purchase/tests/test_fec_export.py new file mode 100644 index 000000000..e9b69ad4d --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/tests/test_fec_export.py @@ -0,0 +1,45 @@ +from odoo import Command +from odoo.tests.common import tagged + +from odoo.addons.l10n_fr_fec_oca.tests.test_fec_export import TestFECExportOCA + + +@tagged("post_install_l10n", "post_install", "-at_install") +class TestFECExportGroupSalePurchase(TestFECExportOCA): + def test_fec_export_group_sale_purchase(self): + self.init_invoice( + "out_invoice", self.partner_a, "2019-01-01", amounts=[1000, 2000], post=True + ) + inv = self.init_invoice( + "out_invoice", self.partner_a, "2020-01-01", amounts=[1000, 2000] + ) + inv.write( + { + "line_ids": [ + Command.create( + { + "name": "Note", + "display_type": "line_note", + } + ) + ] + } + ) + inv.action_post() + # Create a new FEC export + fec_export = self.env["l10n_fr.fec.export.wizard"].create( + { + "date_from": "2020-01-01", + "date_to": "2020-12-31", + "group_sale_purchase": True, + } + ) + result = fec_export.generate_fec() + self.assertEqual( + result["file_content"].decode(), + "JournalCode|JournalLib|EcritureNum|EcritureDate|CompteNum|CompteLib|CompAuxNum|CompAuxLib|PieceRef|PieceDate|EcritureLib|Debit|Credit|EcritureLet|DateLet|ValidDate|Montantdevise|Idevise\r\n" # noqa: E501 + "OUV|Balance initiale|OUVERTURE/2020|20200101|999999|Undistributed Profits/Losses|||-|20200101|/|0,00| 000000000003000,00|||20200101||\r\n" # noqa: E501 + f"OUV|Balance initiale|OUVERTURE/2020|20200101|121000|Account Receivable|{self.partner_a.id}|partner_a|-|20200101|/| 000000000003000,00|0,00|||20200101||\r\n" # noqa: E501 + f"INV|Customer Invoices|INV/2020/00001|20200101|121000|Account Receivable|{self.partner_a.id}|partner_a|-|20200101|INV/2020/00001| 000000000003000,00|0,00|||20200101| 000000000003000,00|USD\r\n" # noqa: E501 + "INV|Customer Invoices|INV/2020/00001|20200101|400000|Product Sales|||-|20200101|test line|0,00| 000000000003000,00|||20200101|-000000000002000,00|USD", # noqa: E501 + ) diff --git a/l10n_fr_fec_group_sale_purchase/wizard/__init__.py b/l10n_fr_fec_group_sale_purchase/wizard/__init__.py new file mode 100644 index 000000000..2cc8a2511 --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/wizard/__init__.py @@ -0,0 +1 @@ +from . import account_fr_fec diff --git a/l10n_fr_fec_group_sale_purchase/wizard/account_fr_fec.py b/l10n_fr_fec_group_sale_purchase/wizard/account_fr_fec.py new file mode 100644 index 000000000..83d2fe665 --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/wizard/account_fr_fec.py @@ -0,0 +1,400 @@ +from odoo import fields, models +from odoo.tools import SQL + + +class FecExportWizard(models.TransientModel): + _inherit = "l10n_fr.fec.export.wizard" + + group_sale_purchase = fields.Boolean( + "Group Sale and Purchase Journals", default=True + ) + + def _sql_query_fec_lines(self, query, **kwargs): + columns = SQL( + """ + REGEXP_REPLACE( + replace( + %(journal_alias)s.code, + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS JournalCode, + REGEXP_REPLACE( + replace( + %(aj_name)s, + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS JournalLib, + REGEXP_REPLACE( + replace( + %(move_alias)s.name, + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS EcritureNum, + TO_CHAR(%(move_alias)s.date, 'YYYYMMDD') AS EcritureDate, + %(aa_code)s AS CompteNum, + REGEXP_REPLACE( + replace( + %(aa_name)s, + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS CompteLib, + %(aux_fields)s, + CASE + WHEN %(move_alias)s.ref IS null OR %(move_alias)s.ref = '' + THEN '-' + ELSE REGEXP_REPLACE( + replace( + %(move_alias)s.ref, + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g') + END AS PieceRef, + TO_CHAR(COALESCE( + %(move_alias)s.invoice_date, + %(move_alias)s.date), + 'YYYYMMDD' + ) AS PieceDate, + CASE + WHEN account_move_line.name IS NULL OR account_move_line.name = '' + THEN '/' + WHEN account_move_line.name SIMILAR TO '[\\t|\\s|\\n]*' + THEN '/' + ELSE REGEXP_REPLACE(replace( + account_move_line.name, + '|', + '/'), '[\\t\\n\\r]', ' ', 'g') + END AS EcritureLib, + replace( + CASE + WHEN account_move_line.debit = 0 + THEN '0,00' + ELSE TO_CHAR(account_move_line.debit, '000000000000000D99') + END, '.', ',') AS Debit, + replace( + CASE + WHEN account_move_line.credit = 0 + THEN '0,00' + ELSE TO_CHAR(account_move_line.credit, '000000000000000D99') + END, '.', ',') AS Credit, + CASE + WHEN %(full_alias)s.id IS NULL + THEN ''::text + ELSE %(full_alias)s.id::text + END AS EcritureLet, + CASE + WHEN account_move_line.full_reconcile_id IS NULL + THEN '' + ELSE TO_CHAR(%(full_alias)s.create_date, 'YYYYMMDD') + END AS DateLet, + TO_CHAR(%(move_alias)s.date, 'YYYYMMDD') AS ValidDate, + CASE + WHEN + account_move_line.amount_currency IS NULL OR + account_move_line.amount_currency = 0 + THEN '' + ELSE replace( + TO_CHAR( + account_move_line.amount_currency, + '000000000000000D99' + ), + '.', + ',' + ) END AS Montantdevise, + CASE + WHEN account_move_line.currency_id IS NULL + THEN '' + ELSE %(currency_alias)s.name + END AS Idevise + """, + currency_alias=SQL.identifier( + query.left_join( + "account_move_line", + "currency_id", + "res_currency", + "id", + "currency_id", + ) + ), + full_alias=SQL.identifier( + query.left_join( + "account_move_line", + "full_reconcile_id", + "account_full_reconcile", + "id", + "full_reconcile_id", + ) + ), + journal_alias=SQL.identifier( + query.left_join( + "account_move_line", + "journal_id", + "account_journal", + "id", + "journal_id", + ) + ), + move_alias=SQL.identifier( + query.left_join( + "account_move_line", "move_id", "account_move", "id", "move_id" + ) + ), + partner_alias=SQL.identifier( + query.left_join( + "account_move_line", "partner_id", "res_partner", "id", "partner_id" + ) + ), + account_alias=SQL.identifier(kwargs.get("account_alias")), + aj_name=kwargs.get("aj_name"), + aa_code=kwargs.get("aa_code"), + aa_name=kwargs.get("aa_name"), + aux_fields=self._get_aux_fields(), + ) + return columns + + def _sql_query_fec_lines2(self, query, **kwargs): + columns = SQL( + """ + REGEXP_REPLACE( + replace( + STRING_AGG(DISTINCT %(journal_alias)s.code, ';'), + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS JournalCode, + REGEXP_REPLACE( + replace( + STRING_AGG(DISTINCT %(aj_name)s, ';'), + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS JournalLib, + REGEXP_REPLACE( + replace( + %(move_alias)s.name, + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS EcritureNum, + TO_CHAR(MAX(%(move_alias)s.date), 'YYYYMMDD') AS EcritureDate, + STRING_AGG(DISTINCT %(aa_code)s, ';') AS CompteNum, + REGEXP_REPLACE( + replace( + STRING_AGG(DISTINCT %(aa_name)s, ';'), + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g' + ) AS CompteLib, + %(aux_fields)s, + CASE + WHEN MIN(%(move_alias)s.ref) IS null OR MIN(%(move_alias)s.ref) = '' + THEN '-' + ELSE REGEXP_REPLACE( + replace( + MIN(%(move_alias)s.ref), + '|', + '/' + ), '[\\t\\r\\n]', ' ', 'g') + END AS PieceRef, + TO_CHAR(MAX(COALESCE( + %(move_alias)s.invoice_date, + %(move_alias)s.date)), + 'YYYYMMDD' + ) AS PieceDate, + CASE + WHEN MIN(account_move_line.name) IS NULL + OR MIN(account_move_line.name) = '' + THEN '/' + WHEN MIN(account_move_line.name) SIMILAR TO '[\\t|\\s|\\n]*' + THEN '/' + ELSE REGEXP_REPLACE(replace( + MIN(account_move_line.name), + '|', + '/'), '[\\t\\n\\r]', ' ', 'g') + END AS EcritureLib, + replace( + CASE + WHEN SUM(account_move_line.debit) = 0 + THEN '0,00' + ELSE TO_CHAR( + SUM(account_move_line.debit), + '000000000000000D99' + ) + END, '.', ',') AS Debit, + replace( + CASE + WHEN SUM(account_move_line.credit) = 0 + THEN '0,00' + ELSE TO_CHAR( + SUM(account_move_line.credit), + '000000000000000D99' + ) + END, '.', ',') AS Credit, + CASE + WHEN MIN(%(full_alias)s.id) IS NULL + THEN ''::text + ELSE MIN(%(full_alias)s.id)::text + END AS EcritureLet, + CASE + WHEN MIN(account_move_line.full_reconcile_id) IS NULL + THEN '' + ELSE TO_CHAR(MIN(%(full_alias)s.create_date), 'YYYYMMDD') + END AS DateLet, + TO_CHAR(MIN(%(move_alias)s.date), 'YYYYMMDD') AS ValidDate, + CASE + WHEN + MIN(account_move_line.amount_currency) IS NULL OR + MIN(account_move_line.amount_currency) = 0 + THEN '' + ELSE replace( + TO_CHAR( + MIN(account_move_line.amount_currency), + '000000000000000D99' + ), + '.', + ',' + ) END AS Montantdevise, + CASE + WHEN MIN(account_move_line.currency_id) IS NULL + THEN '' + ELSE MIN(%(currency_alias)s.name) + END AS Idevise + """, + currency_alias=SQL.identifier( + query.left_join( + "account_move_line", + "currency_id", + "res_currency", + "id", + "currency_id", + ) + ), + full_alias=SQL.identifier( + query.left_join( + "account_move_line", + "full_reconcile_id", + "account_full_reconcile", + "id", + "full_reconcile_id", + ) + ), + journal_alias=SQL.identifier( + query.left_join( + "account_move_line", + "journal_id", + "account_journal", + "id", + "journal_id", + ) + ), + move_alias=SQL.identifier( + query.left_join( + "account_move_line", "move_id", "account_move", "id", "move_id" + ) + ), + partner_alias=SQL.identifier( + query.left_join( + "account_move_line", "partner_id", "res_partner", "id", "partner_id" + ) + ), + account_alias=SQL.identifier(kwargs.get("account_alias")), + aj_name=kwargs.get("aj_name"), + aa_code=kwargs.get("aa_code"), + aa_name=kwargs.get("aa_name"), + aux_fields=self._get_aux_fields(), + ) + return columns + + def _get_rows_fec_lines(self): + if not self.group_sale_purchase: + return super()._get_rows_fec_lines() + + query_limit = int( + self.env["ir.config_parameter"] + .sudo() + .get_param("l10n_fr_fec.batch_size", 500000) + ) + query = self.env["account.move.line"]._search( + domain=self._get_base_domain() + + [ + ("date", ">=", self.date_from), + ("date", "<=", self.date_to), + ("journal_id.type", "not in", ["sale", "purchase"]), + ], + limit=query_limit + 1, + order="date, move_name, id", + ) + account_alias = query.join( + "account_move_line", "account_id", "account_account", "id", "account_id" + ) + params = { + "account_alias": account_alias, + "aa_name": self.env["account.account"]._field_to_sql( + account_alias, + "name", + ), + "aa_code": self.env["account.account"]._field_to_sql( + account_alias, "code", query + ), + "aj_name": self.env["account.journal"]._field_to_sql( + "account_move_line__journal_id", "name" + ), + } + columns = self._sql_query_fec_lines(query, **params) + + query2 = self.env["account.move.line"]._search( + domain=self._get_base_domain() + + [ + ("date", ">=", self.date_from), + ("date", "<=", self.date_to), + ("journal_id.type", "in", ["sale", "purchase"]), + ], + limit=query_limit + 1, + order="date,move_name,id", + ) + account_alias2 = query2.join( + "account_move_line", "account_id", "account_account", "id", "account_id" + ) + params2 = { + "account_alias": account_alias2, + "aa_name": self.env["account.account"]._field_to_sql( + account_alias2, + "name", + ), + "aa_code": self.env["account.account"]._field_to_sql( + account_alias2, "code", query2 + ), + "aj_name": self.env["account.journal"]._field_to_sql( + "account_move_line__journal_id", "name" + ), + } + columns2 = self._sql_query_fec_lines2(query2, **params2) + query2.groupby = SQL(""" + account_move_line__move_id.name, + account_move_line__account_id.id, + account_move_line__partner_id.id + """) + query2.order = SQL(""" + MIN(account_move_line.date), + MIN(account_move_line.move_name), + MIN(account_move_line.id) + """) + + sql_query = SQL( + """ + (%s) + UNION + (%s) + ORDER BY PieceDate, EcritureNum + """, + query.select(columns), + query2.select(columns2), + ) + rows_to_write = [] + self._cr.execute(sql_query) + query_results = self._cr.fetchall() + rows_to_write.append(query_results[:query_limit]) + return rows_to_write diff --git a/l10n_fr_fec_group_sale_purchase/wizard/account_fr_fec_view.xml b/l10n_fr_fec_group_sale_purchase/wizard/account_fr_fec_view.xml new file mode 100644 index 000000000..3d9d8e15e --- /dev/null +++ b/l10n_fr_fec_group_sale_purchase/wizard/account_fr_fec_view.xml @@ -0,0 +1,13 @@ + + + + account.fr.fec.form.view + l10n_fr.fec.export.wizard + + + + + + + +