Initial commit: L10N_Europe packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:52 +02:00
commit 9803722600
2377 changed files with 380711 additions and 0 deletions

View file

@ -0,0 +1,45 @@
# France - FEC Export
Odoo addon: l10n_fr_fec
## Installation
```bash
pip install odoo-bringout-oca-ocb-l10n_fr_fec
```
## Dependencies
This addon depends on:
- l10n_fr
- account
## Manifest Information
- **Name**: France - FEC Export
- **Version**: N/A
- **Category**: Accounting/Localizations/Reporting
- **License**: LGPL-3
- **Installable**: False
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `l10n_fr_fec`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph L10n_fr_fec Module - l10n_fr_fec
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for l10n_fr_fec. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,6 @@
# Dependencies
This addon depends on:
- [l10n_fr](../../odoo-bringout-oca-ocb-l10n_fr)
- [account](../../odoo-bringout-oca-ocb-account)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon l10n_fr_fec or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-ocb-l10n_fr_fec"
# or
uv pip install odoo-bringout-oca-ocb-l10n_fr_fec"
```

View file

@ -0,0 +1,11 @@
# Models
Detected core models and extensions in l10n_fr_fec.
```mermaid
classDiagram
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: l10n_fr_fec. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon l10n_fr_fec
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,41 @@
# Security
Access control and security definitions in l10n_fr_fec.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../l10n_fr_fec/security/ir.model.access.csv)**
- 1 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[security.xml](../l10n_fr_fec/security/security.xml)**
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../l10n_fr_fec/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
- **[security.xml](../l10n_fr_fec/security/security.xml)**
- Security groups, categories, and XML-based rules
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon l10n_fr_fec
```

View file

@ -0,0 +1,8 @@
# Wizards
Transient models exposed as UI wizards in l10n_fr_fec.
```mermaid
classDiagram
class AccountFrFec
```

View file

@ -0,0 +1,40 @@
Fichier d'Échange Informatisé (FEC) pour la France
==================================================
Ce module permet de générer le fichier FEC tel que définit par `l'arrêté du 29
Juillet 2013 <http://legifrance.gouv.fr/eli/arrete/2013/7/29/BUDE1315492A/jo/texte>`
portant modification des dispositions de l'article A. 47 A-1 du
livre des procédures fiscales.
Cet arrêté prévoit l'obligation pour les sociétés ayant une comptabilité
informatisée de pouvoir fournir à l'administration fiscale un fichier
regroupant l'ensemble des écritures comptables de l'exercice. Le format de ce
fichier, appelé *FEC*, est définit dans l'arrêté.
Le détail du format du FEC est spécifié dans le bulletin officiel des finances publiques `BOI-CF-IOR-60-40-20-20131213 <http://bofip.impots.gouv.fr/bofip/ext/pdf/createPdfWithAnnexePermalien/BOI-CF-IOR-60-40-20-20131213.pdf?doc=9028-PGP&identifiant=BOI-CF-IOR-60-40-20-20131213>` du 13 Décembre 2013. Ce module implémente le fichier
FEC au format texte et non au format XML, car le format texte sera facilement
lisible et vérifiable par le comptable en utilisant un tableur.
La structure du fichier FEC généré par ce module a été vérifiée avec le logiciel
*Test Compta Demat* version 1_00_05 disponible sur
`le site de la direction générale des finances publiques <http://www.economie.gouv.fr/dgfip/outil-test-des-fichiers-des-ecritures-comptables-fec>`
en utilisant une base de donnée Odoo réelle.
Configuration
=============
Aucune configuration n'est nécessaire.
Utilisation
===========
Pour générer le *FEC*, allez dans le menu *Accounting > Reporting > French Statements > FEC* qui va démarrer l'assistant de génération du FEC.
Credits
=======
Contributors
------------
* Alexis de Lattre <alexis.delattre@akretion.com>

View file

@ -0,0 +1,6 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) 2013-2015 Akretion (http://www.akretion.com)
from . import wizard

View file

@ -0,0 +1,20 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) 2013-2015 Akretion (http://www.akretion.com)
{
'name': 'France - FEC Export',
'icon': '/l10n_fr/static/description/icon.png',
'category': 'Accounting/Localizations/Reporting',
'summary': "Fichier d'Échange Informatisé (FEC) for France",
'author': "Akretion,Odoo Community Association (OCA)",
'depends': ['l10n_fr', 'account'],
'data': [
'security/ir.model.access.csv',
'security/security.xml',
'wizard/account_fr_fec_view.xml',
],
'auto_install': True,
'license': 'LGPL-3',
}

View file

@ -0,0 +1,323 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_fr_fec
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-30 09:34+0000\n"
"PO-Revision-Date: 2024-10-30 09:34+0000\n"
"Last-Translator: Manon Rondou <ronm@odoo.com>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 10"
msgstr "# 10"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 11"
msgstr "# 11"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 12"
msgstr "# 12"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 13"
msgstr "# 13"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 14"
msgstr "# 14"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 15"
msgstr "# 15"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 16"
msgstr "# 16"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 17"
msgstr "# 17"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Cancel"
msgstr "Annuler"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Column"
msgstr "Colonne"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Comment"
msgstr "Note"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompAuxLib"
msgstr "CompAuxLib"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompAuxNum"
msgstr "CompAuxNum"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompteLib"
msgstr "CompteLib"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompteNum"
msgstr "CompteNum"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__create_date
msgid "Created on"
msgstr "Créé le"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Credit"
msgstr "Credit"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "DateLet"
msgstr "DateLet"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Debit"
msgstr "Debit"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__display_name
msgid "Display Name"
msgstr "Nom Affiché"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureDate"
msgstr "EcritureDate"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureLet"
msgstr "EcritureLet"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureLib"
msgstr "EcritureLib"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureNum"
msgstr "EcritureNum"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__date_to
msgid "End Date"
msgstr "Date de fin"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__export_type
msgid "Export Type"
msgstr "Type dexport"
#. module: l10n_fr_fec
#: model:ir.actions.act_window,name:l10n_fr_fec.account_fr_fec_action
#: model:ir.ui.menu,name:l10n_fr_fec.account_fr_fec_menu
msgid "FEC"
msgstr "FEC"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__fec_data
msgid "FEC File"
msgstr "Fichier FEC"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "FEC File Generation"
msgstr "Génération du fichier FEC"
#. module: l10n_fr_fec
#: model:ir.model,name:l10n_fr_fec.model_account_fr_fec
msgid "Ficher Echange Informatise"
msgstr "Fichier échange informatisé"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__filename
msgid "Filename"
msgstr "Nom du fichier"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Generate"
msgstr "Générer"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__id
msgid "ID"
msgstr "ID"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Idevise"
msgstr "Idevise"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "JournalCode"
msgstr "JournalCode"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "JournalLib"
msgstr "JournalLib"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Montantdevise"
msgstr "Montantdevise"
#. module: l10n_fr_fec
#: model:ir.model.fields.selection,name:l10n_fr_fec.selection__account_fr_fec__export_type__nonofficial
msgid "Non-official FEC report (posted and unposted entries)"
msgstr ""
"Rapport FEC non-officiel (avec à la fois les entrées comptabilisées et non "
"comptabilisées)"
#. module: l10n_fr_fec
#: model:ir.model.fields.selection,name:l10n_fr_fec.selection__account_fr_fec__export_type__official
msgid "Official FEC report (posted entries only)"
msgstr "Rapport FEC officiel (uniquement les entrées comptabilisées)"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Options"
msgstr "Options"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "PieceDate"
msgstr "PieceDate"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "PieceRef"
msgstr "PieceRef"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__date_from
msgid "Start Date"
msgstr "Date de début"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Technical Info"
msgstr "Information technique"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Technical Name"
msgstr "Nom technique"
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__test_file
msgid "Test File"
msgstr "Fichier de test"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid ""
"The encoding of this text file is UTF-8. The structure of file is CSV "
"separated by pipe '|'."
msgstr ""
"Le fichier CSV généré est encodé avec UTF-8 et utilise la barre verticale (« "
"| ») comme séparateur."
#. module: l10n_fr_fec
#. odoo-python
#: code:addons/l10n_fr_fec/wizard/account_fr_fec.py:0
#, python-format
msgid "The start date must be inferior to the end date."
msgstr "La date de début doit être avant la date de fin."
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "ValidDate"
msgstr "ValidDate"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "We use partner.id"
msgstr "Nous utilisons partner.id"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid ""
"When you download a FEC file, the lock date is set to the end date.\n"
" If you want to test the FEC file generation, please tick the "
"test file checkbox."
msgstr ""
"Quand vous téléchargez un fichier FEC, la date de verrouillage est "
"configurée avec la date de fin.\n"
" Si vous voulez tester la génération du fichier FEC, cochez "
"la case « Fichier de test »."
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid ""
"You are in test mode. The FEC file generation will not set the lock date."
msgstr ""
"Vous êtes en mode test. La génération du fichier FEC ne configurera la date "
"de verrouillage."
#. module: l10n_fr_fec
#. odoo-python
#: code:addons/l10n_fr_fec/wizard/account_fr_fec.py:0
#, python-format
msgid "You could not set the start date or the end date in the future."
msgstr "Vous ne pouvez pas utiliser une date de début ou de fin dans le futur."

View file

@ -0,0 +1,311 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_fr_fec
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-30 09:34+0000\n"
"PO-Revision-Date: 2024-10-30 09:34+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 10"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 11"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 12"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 13"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 14"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 15"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 16"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "# 17"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Cancel"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Column"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Comment"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompAuxLib"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompAuxNum"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompteLib"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "CompteNum"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__create_uid
msgid "Created by"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__create_date
msgid "Created on"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Credit"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "DateLet"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Debit"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__display_name
msgid "Display Name"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureDate"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureLet"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureLib"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "EcritureNum"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__date_to
msgid "End Date"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__export_type
msgid "Export Type"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.actions.act_window,name:l10n_fr_fec.account_fr_fec_action
#: model:ir.ui.menu,name:l10n_fr_fec.account_fr_fec_menu
msgid "FEC"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__fec_data
msgid "FEC File"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "FEC File Generation"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model,name:l10n_fr_fec.model_account_fr_fec
msgid "Ficher Echange Informatise"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__filename
msgid "Filename"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Generate"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__id
msgid "ID"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Idevise"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "JournalCode"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "JournalLib"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec____last_update
msgid "Last Modified on"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__write_uid
msgid "Last Updated by"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__write_date
msgid "Last Updated on"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Montantdevise"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields.selection,name:l10n_fr_fec.selection__account_fr_fec__export_type__nonofficial
msgid "Non-official FEC report (posted and unposted entries)"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields.selection,name:l10n_fr_fec.selection__account_fr_fec__export_type__official
msgid "Official FEC report (posted entries only)"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Options"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "PieceDate"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "PieceRef"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__date_from
msgid "Start Date"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Technical Info"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "Technical Name"
msgstr ""
#. module: l10n_fr_fec
#: model:ir.model.fields,field_description:l10n_fr_fec.field_account_fr_fec__test_file
msgid "Test File"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid ""
"The encoding of this text file is UTF-8. The structure of file is CSV "
"separated by pipe '|'."
msgstr ""
#. module: l10n_fr_fec
#. odoo-python
#: code:addons/l10n_fr_fec/wizard/account_fr_fec.py:0
#, python-format
msgid "The start date must be inferior to the end date."
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "ValidDate"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid "We use partner.id"
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid ""
"When you download a FEC file, the lock date is set to the end date.\n"
" If you want to test the FEC file generation, please tick the test file checkbox."
msgstr ""
#. module: l10n_fr_fec
#: model_terms:ir.ui.view,arch_db:l10n_fr_fec.account_fr_fec_view
msgid ""
"You are in test mode. The FEC file generation will not set the lock date."
msgstr ""
#. module: l10n_fr_fec
#. odoo-python
#: code:addons/l10n_fr_fec/wizard/account_fr_fec.py:0
#, python-format
msgid "You could not set the start date or the end date in the future."
msgstr ""

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_fr_fec,account.fr.fec,model_account_fr_fec,account.group_account_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_fr_fec account.fr.fec model_account_fr_fec account.group_account_user 1 1 1 0

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="account_fr_fec_rule" model="ir.rule">
<field name="name">Account Fr Fec Rule</field>
<field name="model_id" ref="model_account_fr_fec"/>
<field name="domain_force">[('create_uid', '=', user.id)]</field>
</record>
</odoo>

View file

@ -0,0 +1,4 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_wizard

View file

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from datetime import timedelta
from freezegun import freeze_time
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
from odoo import fields, Command
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestAccountFrFec(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_fr.l10n_fr_pcg_chart_template'):
super().setUpClass(chart_template_ref=chart_template_ref)
company = cls.company_data['company']
company.vat = 'FR13542107651'
lines_data = [(1437.12, 'Hello\tDarkness'), (1676.64, 'my\rold\nfriend'), (3353.28, '\t\t\r')]
with freeze_time('2021-05-02'):
today = fields.Date.today().strftime('%Y-%m-%d')
cls.wizard = cls.env['account.fr.fec'].create({
'date_from': fields.Date.today() - timedelta(days=1),
'date_to': fields.Date.today(),
'export_type': 'official',
'test_file': True,
})
cls.tax_sale_a = cls.env['account.tax'].create({
'name': "TVA 20,0%",
'amount_type': 'percent',
'type_tax_use': 'sale',
'amount': 20,
'invoice_repartition_line_ids': [
Command.create({
'factor_percent': 100.0,
'repartition_type': 'base',
}),
Command.create({
'repartition_type': 'tax',
'factor_percent': 100.0,
'account_id': cls.env['account.account'].search([('code', '=', "445710")], limit=1).id,
})
]
})
cls.invoice_a = cls.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': cls.partner_a.id,
'date': today,
'invoice_date': today,
'currency_id': company.currency_id.id,
'invoice_line_ids': [(0, None, {
'name': name,
'product_id': cls.product_a.id,
'quantity': 1,
'tax_ids': [(6, 0, [cls.tax_sale_a.id])],
'price_unit': price_unit,
}) for price_unit, name in lines_data]
})
cls.invoice_a.action_post()
def test_generate_fec_sanitize_pieceref(self):
self.wizard.generate_fec()
expected_content = (
"JournalCode|JournalLib|EcritureNum|EcritureDate|CompteNum|CompteLib|CompAuxNum|CompAuxLib|PieceRef|PieceDate|EcritureLib|Debit|Credit|EcritureLet|DateLet|ValidDate|Montantdevise|Idevise\r\n"
"INV|Customer Invoices|INV/2021/00001|20210502|701000|Ventes de produits finis|||-|20210502|Hello Darkness|0,00| 000000000001437,12|||20210502|-000000000001437,12|EUR\r\n"
"INV|Customer Invoices|INV/2021/00001|20210502|701000|Ventes de produits finis|||-|20210502|my old friend|0,00| 000000000001676,64|||20210502|-000000000001676,64|EUR\r\n"
"INV|Customer Invoices|INV/2021/00001|20210502|701000|Ventes de produits finis|||-|20210502|/|0,00| 000000000003353,28|||20210502|-000000000003353,28|EUR\r\n"
"INV|Customer Invoices|INV/2021/00001|20210502|445710|TVA collectée|||-|20210502|TVA 20,0%|0,00| 000000000001293,41|||20210502|-000000000001293,41|EUR\r\n"
f"INV|Customer Invoices|INV/2021/00001|20210502|411100|Clients - Ventes de biens ou de prestations de services|{self.partner_a.id}|partner_a|-|20210502|INV/2021/00001| 000000000007760,45|0,00|||20210502| 000000000007760,45|EUR"
)
content = base64.b64decode(self.wizard.fec_data).decode()
self.assertEqual(expected_content, content)

View file

@ -0,0 +1,6 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) 2013-2015 Akretion (http://www.akretion.com)
from . import account_fr_fec

View file

@ -0,0 +1,457 @@
#-*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) 2013-2015 Akretion (http://www.akretion.com)
import base64
import io
from odoo import api, fields, models, _
from odoo.exceptions import UserError, AccessDenied
from odoo.tools import float_is_zero, pycompat
from odoo.tools.misc import get_lang
from stdnum.fr import siren
class AccountFrFec(models.TransientModel):
_name = 'account.fr.fec'
_description = 'Ficher Echange Informatise'
date_from = fields.Date(string='Start Date', required=True)
date_to = fields.Date(string='End Date', required=True)
fec_data = fields.Binary('FEC File', readonly=True)
filename = fields.Char(string='Filename', size=256, readonly=True)
test_file = fields.Boolean()
export_type = fields.Selection([
('official', 'Official FEC report (posted entries only)'),
('nonofficial', 'Non-official FEC report (posted and unposted entries)'),
], string='Export Type', required=True, default='official')
@api.onchange('test_file')
def _onchange_export_file(self):
if not self.test_file:
self.export_type = 'official'
def _do_query_unaffected_earnings(self):
''' Compute the sum of ending balances for all accounts that are of a type that does not bring forward the balance in new fiscal years.
This is needed because we have to display only one line for the initial balance of all expense/revenue accounts in the FEC.
'''
sql_query = '''
SELECT
'OUV' AS JournalCode,
'Balance initiale' AS JournalLib,
'OUVERTURE/' || %s AS EcritureNum,
%s AS EcritureDate,
'120/129' AS CompteNum,
'Benefice (perte) reporte(e)' AS CompteLib,
'' AS CompAuxNum,
'' AS CompAuxLib,
'-' AS PieceRef,
%s AS PieceDate,
'/' AS EcritureLib,
replace(CASE WHEN COALESCE(sum(aml.balance), 0) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit,
replace(CASE WHEN COALESCE(sum(aml.balance), 0) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit,
'' AS EcritureLet,
'' AS DateLet,
%s AS ValidDate,
'' AS Montantdevise,
'' AS Idevise
FROM
account_move_line aml
LEFT JOIN account_move am ON am.id=aml.move_id
JOIN account_account aa ON aa.id = aml.account_id
WHERE
am.date < %s
AND am.company_id = %s
AND aa.include_initial_balance IS NOT TRUE
'''
# For official report: only use posted entries
if self.export_type == "official":
sql_query += '''
AND am.state = 'posted'
'''
company = self.env.company
formatted_date_from = fields.Date.to_string(self.date_from).replace('-', '')
date_from = self.date_from
formatted_date_year = date_from.year
self._cr.execute(
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
listrow = []
row = self._cr.fetchone()
listrow = list(row)
return listrow
def _get_company_legal_data(self, company):
"""
Dom-Tom are excluded from the EU's fiscal territory
Those regions do not have SIREN
sources:
https://www.service-public.fr/professionnels-entreprises/vosdroits/F23570
http://www.douane.gouv.fr/articles/a11024-tva-dans-les-dom
* Returns the siren if the company is french or an empty siren for dom-tom
* For non-french companies -> returns the complete vat number
"""
dom_tom_group = self.env.ref('l10n_fr.dom-tom')
is_dom_tom = company.account_fiscal_country_id.code in dom_tom_group.country_ids.mapped('code')
if not company.vat or is_dom_tom:
return ''
elif company.country_id.code == 'FR' and len(company.vat) >= 13 and siren.is_valid(company.vat[4:13]):
return company.vat[4:13]
else:
return company.vat
def generate_fec(self):
self.ensure_one()
if not (self.env.is_admin() or self.env.user.has_group('account.group_account_user')):
raise AccessDenied()
# We choose to implement the flat file instead of the XML
# file for 2 reasons :
# 1) the XSD file impose to have the label on the account.move
# but Odoo has the label on the account.move.line, so that's a
# problem !
# 2) CSV files are easier to read/use for a regular accountant.
# So it will be easier for the accountant to check the file before
# sending it to the fiscal administration
today = fields.Date.today()
if self.date_from > today or self.date_to > today:
raise UserError(_('You could not set the start date or the end date in the future.'))
if self.date_from >= self.date_to:
raise UserError(_('The start date must be inferior to the end date.'))
company = self.env.company
company_legal_data = self._get_company_legal_data(company)
header = [
u'JournalCode', # 0
u'JournalLib', # 1
u'EcritureNum', # 2
u'EcritureDate', # 3
u'CompteNum', # 4
u'CompteLib', # 5
u'CompAuxNum', # 6 We use partner.id
u'CompAuxLib', # 7
u'PieceRef', # 8
u'PieceDate', # 9
u'EcritureLib', # 10
u'Debit', # 11
u'Credit', # 12
u'EcritureLet', # 13
u'DateLet', # 14
u'ValidDate', # 15
u'Montantdevise', # 16
u'Idevise', # 17
]
rows_to_write = [header]
# INITIAL BALANCE
unaffected_earnings_account = self.env['account.account'].search([
('account_type', '=', 'equity_unaffected'),
('company_id', '=', company.id)
], limit=1)
unaffected_earnings_line = True # used to make sure that we add the unaffected earning initial balance only once
if unaffected_earnings_account:
#compute the benefit/loss of last year to add in the initial balance of the current year earnings account
unaffected_earnings_results = self._do_query_unaffected_earnings()
unaffected_earnings_line = False
if self.pool['account.account'].name.translate:
lang = self.env.user.lang or get_lang(self.env).code
aa_name = f"COALESCE(aa.name->>'{lang}', aa.name->>'en_US')"
else:
aa_name = "aa.name"
sql_query = f'''
SELECT
'OUV' AS JournalCode,
'Balance initiale' AS JournalLib,
'OUVERTURE/' || %s AS EcritureNum,
%s AS EcritureDate,
MIN(aa.code) AS CompteNum,
replace(replace(MIN({aa_name}), '|', '/'), '\t', '') AS CompteLib,
'' AS CompAuxNum,
'' AS CompAuxLib,
'-' AS PieceRef,
%s AS PieceDate,
'/' AS EcritureLib,
replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit,
replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit,
'' AS EcritureLet,
'' AS DateLet,
%s AS ValidDate,
'' AS Montantdevise,
'' AS Idevise,
MIN(aa.id) AS CompteID
FROM
account_move_line aml
LEFT JOIN account_move am ON am.id=aml.move_id
JOIN account_account aa ON aa.id = aml.account_id
WHERE
am.date < %s
AND am.company_id = %s
AND aa.include_initial_balance = 't'
'''
# For official report: only use posted entries
if self.export_type == "official":
sql_query += '''
AND am.state = 'posted'
'''
sql_query += '''
GROUP BY aml.account_id, aa.account_type
HAVING aa.account_type not in ('asset_receivable', 'liability_payable') AND round(sum(aml.balance), %s) != 0
'''
formatted_date_from = fields.Date.to_string(self.date_from).replace('-', '')
date_from = self.date_from
formatted_date_year = date_from.year
currency_digits = 2
self._cr.execute(
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id,
currency_digits))
for row in self._cr.fetchall():
listrow = list(row)
account_id = listrow.pop()
if not unaffected_earnings_line:
account = self.env['account.account'].browse(account_id)
if account.account_type == 'equity_unaffected':
#add the benefit/loss of previous fiscal year to the first unaffected earnings account found.
unaffected_earnings_line = True
current_amount = float(listrow[11].replace(',', '.')) - float(listrow[12].replace(',', '.'))
unaffected_earnings_amount = float(unaffected_earnings_results[11].replace(',', '.')) - float(unaffected_earnings_results[12].replace(',', '.'))
listrow_amount = current_amount + unaffected_earnings_amount
if float_is_zero(listrow_amount, precision_digits=currency_digits):
continue
if listrow_amount > 0:
listrow[11] = str(listrow_amount).replace('.', ',')
listrow[12] = '0,00'
else:
listrow[11] = '0,00'
listrow[12] = str(-listrow_amount).replace('.', ',')
rows_to_write.append(listrow)
#if the unaffected earnings account wasn't in the selection yet: add it manually
if (not unaffected_earnings_line
and unaffected_earnings_results
and (unaffected_earnings_results[11] != '0,00'
or unaffected_earnings_results[12] != '0,00')):
#search an unaffected earnings account
unaffected_earnings_account = self.env['account.account'].search([('account_type', '=', 'equity_unaffected'),
('company_id', '=', company.id)], limit=1)
if unaffected_earnings_account:
unaffected_earnings_results[4] = unaffected_earnings_account.code
unaffected_earnings_results[5] = unaffected_earnings_account.name
rows_to_write.append(unaffected_earnings_results)
# INITIAL BALANCE - receivable/payable
sql_query = f'''
SELECT
'OUV' AS JournalCode,
'Balance initiale' AS JournalLib,
'OUVERTURE/' || %s AS EcritureNum,
%s AS EcritureDate,
MIN(aa.code) AS CompteNum,
replace(MIN({aa_name}), '|', '/') AS CompteLib,
CASE WHEN MIN(aa.account_type) IN ('asset_receivable', 'liability_payable')
THEN
CASE WHEN rp.ref IS null OR rp.ref = ''
THEN rp.id::text
ELSE replace(rp.ref, '|', '/')
END
ELSE ''
END
AS CompAuxNum,
CASE WHEN aa.account_type IN ('asset_receivable', 'liability_payable')
THEN COALESCE(replace(rp.name, '|', '/'), '')
ELSE ''
END AS CompAuxLib,
'-' AS PieceRef,
%s AS PieceDate,
'/' AS EcritureLib,
replace(CASE WHEN sum(aml.balance) <= 0 THEN '0,00' ELSE to_char(SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Debit,
replace(CASE WHEN sum(aml.balance) >= 0 THEN '0,00' ELSE to_char(-SUM(aml.balance), '000000000000000D99') END, '.', ',') AS Credit,
'' AS EcritureLet,
'' AS DateLet,
%s AS ValidDate,
'' AS Montantdevise,
'' AS Idevise,
MIN(aa.id) AS CompteID
FROM
account_move_line aml
LEFT JOIN account_move am ON am.id=aml.move_id
LEFT JOIN res_partner rp ON rp.id=aml.partner_id
JOIN account_account aa ON aa.id = aml.account_id
WHERE
am.date < %s
AND am.company_id = %s
AND aa.include_initial_balance = 't'
'''
# For official report: only use posted entries
if self.export_type == "official":
sql_query += '''
AND am.state = 'posted'
'''
sql_query += '''
GROUP BY aml.account_id, aa.account_type, rp.ref, rp.id
HAVING aa.account_type in ('asset_receivable', 'liability_payable') AND round(sum(aml.balance), %s) != 0
'''
self._cr.execute(
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id,
currency_digits))
for row in self._cr.fetchall():
listrow = list(row)
account_id = listrow.pop()
rows_to_write.append(listrow)
# LINES
if self.pool['account.journal'].name.translate:
lang = self.env.user.lang or get_lang(self.env).code
aj_name = f"COALESCE(aj.name->>'{lang}', aj.name->>'en_US')"
else:
aj_name = "aj.name"
query_limit = int(self.env['ir.config_parameter'].sudo().get_param('l10n_fr_fec.batch_size', 500000)) # To prevent memory errors when fetching the results
sql_query = f'''
SELECT
REGEXP_REPLACE(replace(aj.code, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS JournalCode,
REGEXP_REPLACE(replace({aj_name}, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS JournalLib,
REGEXP_REPLACE(replace(am.name, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS EcritureNum,
TO_CHAR(am.date, 'YYYYMMDD') AS EcritureDate,
aa.code AS CompteNum,
REGEXP_REPLACE(replace({aa_name}, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS CompteLib,
CASE WHEN aa.account_type IN ('asset_receivable', 'liability_payable')
THEN
CASE WHEN rp.ref IS null OR rp.ref = ''
THEN rp.id::text
ELSE replace(rp.ref, '|', '/')
END
ELSE ''
END
AS CompAuxNum,
CASE WHEN aa.account_type IN ('asset_receivable', 'liability_payable')
THEN COALESCE(REGEXP_REPLACE(replace(rp.name, '|', '/'), '[\\t\\r\\n]', ' ', 'g'), '')
ELSE ''
END AS CompAuxLib,
CASE WHEN am.ref IS null OR am.ref = ''
THEN '-'
ELSE REGEXP_REPLACE(replace(am.ref, '|', '/'), '[\\t\\r\\n]', ' ', 'g')
END
AS PieceRef,
TO_CHAR(COALESCE(am.invoice_date, am.date), 'YYYYMMDD') AS PieceDate,
CASE WHEN aml.name IS NULL OR aml.name = '' THEN '/'
WHEN aml.name SIMILAR TO '[\\t|\\s|\\n]*' THEN '/'
ELSE REGEXP_REPLACE(replace(aml.name, '|', '/'), '[\\t\\n\\r]', ' ', 'g') END AS EcritureLib,
replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit,
replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit,
CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
CASE WHEN aml.full_reconcile_id IS NULL THEN '' ELSE TO_CHAR(rec.create_date, 'YYYYMMDD') END AS DateLet,
TO_CHAR(am.date, 'YYYYMMDD') AS ValidDate,
CASE
WHEN aml.amount_currency IS NULL OR aml.amount_currency = 0 THEN ''
ELSE replace(to_char(aml.amount_currency, '000000000000000D99'), '.', ',')
END AS Montantdevise,
CASE WHEN aml.currency_id IS NULL THEN '' ELSE rc.name END AS Idevise
FROM
account_move_line aml
LEFT JOIN account_move am ON am.id=aml.move_id
LEFT JOIN res_partner rp ON rp.id=aml.partner_id
JOIN account_journal aj ON aj.id = am.journal_id
JOIN account_account aa ON aa.id = aml.account_id
LEFT JOIN res_currency rc ON rc.id = aml.currency_id
LEFT JOIN account_full_reconcile rec ON rec.id = aml.full_reconcile_id
WHERE
am.date >= %s
AND am.date <= %s
AND am.company_id = %s
{"AND am.state = 'posted'" if self.export_type == 'official' else ""}
ORDER BY
am.date,
am.name,
aml.id
LIMIT %s
OFFSET %s
'''
with io.BytesIO() as fecfile:
csv_writer = pycompat.csv_writer(fecfile, delimiter='|', lineterminator='')
# Write header and initial balances
for initial_row in rows_to_write:
initial_row = list(initial_row)
# We don't skip \n at then end of the file if there are only initial balances, for simplicity. An empty period export shouldn't happen IRL.
initial_row[-1] += u'\r\n'
csv_writer.writerow(initial_row)
# Write current period's data
query_offset = 0
has_more_results = True
while has_more_results:
self._cr.execute(
sql_query,
(self.date_from, self.date_to, company.id, query_limit + 1, query_offset)
)
query_offset += query_limit
has_more_results = self._cr.rowcount > query_limit # we load one more result than the limit to check if there is more
query_results = self._cr.fetchall()
for i, row in enumerate(query_results[:query_limit]):
if i < len(query_results) - 1:
# The file is not allowed to end with an empty line, so we can't use lineterminator on the writer
row = list(row)
row[-1] += u'\r\n'
csv_writer.writerow(row)
base64_result = base64.encodebytes(fecfile.getvalue())
end_date = fields.Date.to_string(self.date_to).replace('-', '')
suffix = ''
if self.export_type == "nonofficial":
suffix = '-NONOFFICIAL'
self.write({
'fec_data': base64_result,
# Filename = <siren>FECYYYYMMDD where YYYMMDD is the closing date
'filename': '%sFEC%s%s.csv' % (company_legal_data, end_date, suffix),
})
# Set fiscal year lock date to the end date (not in test)
fiscalyear_lock_date = self.env.company.fiscalyear_lock_date
if not self.test_file and (not fiscalyear_lock_date or fiscalyear_lock_date < self.date_to):
self.env.company.write({'fiscalyear_lock_date': self.date_to})
return {
'name': 'FEC',
'type': 'ir.actions.act_url',
'url': "web/content/?model=account.fr.fec&id=" + str(self.id) + "&filename_field=filename&field=fec_data&download=true&filename=" + self.filename,
'target': 'self',
}
def _csv_write_rows(self, rows, lineterminator=u'\r\n'): #DEPRECATED; will disappear in master
"""
Write FEC rows into a file
It seems that Bercy's bureaucracy is not too happy about the
empty new line at the End Of File.
@param {list(list)} rows: the list of rows. Each row is a list of strings
@param {unicode string} [optional] lineterminator: effective line terminator
Has nothing to do with the csv writer parameter
The last line written won't be terminated with it
@return the value of the file
"""
fecfile = io.BytesIO()
writer = pycompat.csv_writer(fecfile, delimiter='|', lineterminator='')
rows_length = len(rows)
for i, row in enumerate(rows):
if not i == rows_length - 1:
row[-1] += lineterminator
writer.writerow(row)
fecvalue = fecfile.getvalue()
fecfile.close()
return fecvalue

View file

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_fr_fec_view" model="ir.ui.view">
<field name="name">account.fr.fec.form.view</field>
<field name="model">account.fr.fec</field>
<field name="arch" type="xml">
<form string="FEC File Generation">
<div class="alert alert-info" role="alert" attrs="{'invisible': [('test_file', '=', True)]}">
When you download a FEC file, the lock date is set to the end date.
If you want to test the FEC file generation, please tick the test file checkbox.
</div>
<div class="alert alert-info" role="alert" attrs="{'invisible': [('test_file', '=', False)]}">
You are in test mode. The FEC file generation will not set the lock date.
</div>
<notebook>
<page string="Options" name="options">
<group>
<field name="date_from"/>
<field name="date_to"/>
<field name="test_file"/>
<field name="export_type" attrs="{'invisible': [('test_file', '=', False)]}"/>
</group>
</page>
<page string="Technical Info" name="technical_info">
<group>
<div colspan="2">
The encoding of this text file is UTF-8. The structure of file is CSV separated by pipe '|'.
</div>
</group>
<group>
<table style="width:80%" colspan="2">
<tr>
<th>Technical Name</th>
<th>Column</th>
<th>Comment</th>
</tr>
<tr>
<td>JournalCode</td>
<td># 0</td>
</tr>
<tr>
<td>JournalLib</td>
<td># 1</td>
</tr>
<tr>
<td>EcritureNum</td>
<td># 2</td>
</tr>
<tr>
<td>EcritureDate</td>
<td># 3</td>
</tr>
<tr>
<td>CompteNum</td>
<td># 4</td>
</tr>
<tr>
<td>CompteLib</td>
<td># 5</td>
</tr>
<tr>
<td>CompAuxNum</td>
<td># 6</td>
<td>We use partner.id</td>
</tr>
<tr>
<td>CompAuxLib</td>
<td># 7</td>
</tr>
<tr>
<td>PieceRef</td>
<td># 8</td>
</tr>
<tr>
<td>PieceDate</td>
<td># 9</td>
</tr>
<tr>
<td>EcritureLib</td>
<td># 10</td>
</tr>
<tr>
<td>Debit</td>
<td># 11</td>
</tr>
<tr>
<td>Credit</td>
<td># 12</td>
</tr>
<tr>
<td>EcritureLet</td>
<td># 13</td>
</tr>
<tr>
<td>DateLet</td>
<td># 14</td>
</tr>
<tr>
<td>ValidDate</td>
<td># 15</td>
</tr>
<tr>
<td>Montantdevise</td>
<td># 16</td>
</tr>
<tr>
<td>Idevise</td>
<td># 17</td>
</tr>
</table>
</group>
</page>
</notebook>
<footer>
<button string="Generate" name="generate_fec" type="object"
class="oe_highlight"/>
<button string="Cancel" class="btn btn-secondary" special="cancel" data-hotkey="z"/>
</footer>
</form>
</field>
</record>
<record id="account_fr_fec_action" model="ir.actions.act_window">
<field name="name">FEC</field>
<field name="res_model">account.fr.fec</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="account_fr_fec_menu"
parent="l10n_fr.account_reports_fr_statements_menu"
action="account_fr_fec_action"
sequence="100" />
</odoo>

View file

@ -0,0 +1,43 @@
[project]
name = "odoo-bringout-oca-ocb-l10n_fr_fec"
version = "16.0.0"
description = "France - FEC Export - Fichier d'Échange Informatisé (FEC) for France"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-l10n_fr>=16.0.0",
"odoo-bringout-oca-ocb-account>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["l10n_fr_fec"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]