mirror of
				https://github.com/brain-tec/account_ebics.git
				synced 2025-10-31 03:27:02 +00:00 
			
		
		
		
	Merge pull request #30 from Noviat/14.0-mig-account_ebics
14.0 mig account ebics
This commit is contained in:
		
							
								
								
									
										191
									
								
								account_ebics/README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								account_ebics/README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| .. image:: https://img.shields.io/badge/license-LGPL--3-blue.png | ||||
|    :target: https://www.gnu.org/licenses/lpgl | ||||
|    :alt: License: LGPL-3 | ||||
|  | ||||
| ====================== | ||||
| EBICS banking protocol | ||||
| ====================== | ||||
|  | ||||
| Implementation of the  EBICS banking protocol. | ||||
|  | ||||
| This module facilitates the exchange of files with banks via the EBICS protocol. | ||||
|  | ||||
| | | ||||
|  | ||||
| Installation | ||||
| ============ | ||||
|  | ||||
| The module depends upon | ||||
|  | ||||
| - https://pypi.python.org/pypi/fintech | ||||
| - https://pypi.python.org/pypi/cryptography | ||||
|  | ||||
| Remark: | ||||
|  | ||||
| The EBICS 'Test Mode' for uploading orders requires Fintech 4.3.4 or higher. | ||||
|  | ||||
| SWIFT 3SKey support requires Fintech 6.4 or higher. | ||||
| | | ||||
|  | ||||
| We also recommend to consider the installation of the following modules: | ||||
|  | ||||
| | | ||||
|  | ||||
| - account_ebics_oe | ||||
|  | ||||
|   Required if you are running Odoo Enterprise | ||||
|  | ||||
| | | ||||
|  | ||||
| - account_ebics_batch_payment | ||||
|  | ||||
|   Recommended if you are using the Odoo Enterprise account_batch_payment module | ||||
|  | ||||
| | | ||||
|  | ||||
| - account_ebics_payment_order | ||||
|  | ||||
|   Recommended if you are using the OCA account_payment_order module. | ||||
|  | ||||
|   Cf. https://github.com/OCA/bank-payment | ||||
|  | ||||
| | | ||||
|  | ||||
| - account_statement_import_fr_cfonb | ||||
|  | ||||
|   Required to handle french CFONB files. | ||||
|  | ||||
|   Cf. https://github.com/OCA/l10n_france | ||||
|  | ||||
| | | ||||
|  | ||||
| - account_statement_import_camt | ||||
|  | ||||
|   Required to handle camt.052 and camt.054 files. | ||||
|  | ||||
|   Cf. https://github.com/OCA/bank_statement_import | ||||
|  | ||||
| | | ||||
|  | ||||
| - account_statement_import_helper | ||||
|  | ||||
|   Required if you are processing bank statements with local bank account numbers (e.g. french CFONB files) | ||||
|   and using import parsers based upon the OCA account_statement_import module. | ||||
|  | ||||
|   The import helper will match the local bank account number with the IBAN number specified on the Odoo Financial journal. | ||||
|  | ||||
|   Cf. https://github.com/Noviat/noviat-apps | ||||
|  | ||||
| | | ||||
|  | ||||
| - account_bank_statement_import_helper | ||||
|  | ||||
|   Required if you are processing bank statements with local bank account numbers | ||||
|   and using import parsers based upon the Odoo Enterprise account_bank_statement_import module. | ||||
|  | ||||
|   The import helper will match the local bank account number with the IBAN number specified on the Odoo Financial journal. | ||||
|  | ||||
|   Cf. https://github.com/Noviat/noviat-apps | ||||
|  | ||||
| | | ||||
|  | ||||
| Fintech license | ||||
| --------------- | ||||
|  | ||||
| If you have a valid Fintech.ebics license, you should add the following | ||||
| licensing parameters to the odoo server configuration file: | ||||
|  | ||||
|  | ||||
| - fintech_register_name | ||||
|  | ||||
| The name of the licensee. | ||||
|  | ||||
| - fintech_register_keycode | ||||
|  | ||||
| The keycode of the licensed version. | ||||
|  | ||||
| - fintech_register_users | ||||
|  | ||||
| The licensed EBICS user ids. It must be a string or a list of user ids. | ||||
|  | ||||
| You should NOT specify this parameter if your license is subsciption | ||||
| based (with monthly recurring billing). | ||||
|  | ||||
| | | ||||
| | Example: | ||||
| | | ||||
|  | ||||
| :: | ||||
|  | ||||
|  ; fintech | ||||
|  fintech_register_name = MyCompany | ||||
|  fintech_register_keycode = AB1CD-E2FG-3H-IJ4K-5L | ||||
|  fintech_register_users = USER1, USER2 | ||||
|  | ||||
| | | ||||
|  | ||||
| Configuration | ||||
| ============= | ||||
|  | ||||
| Go to **Settings > Users** | ||||
|  | ||||
| Add the users that are authorised to maintain the EBICS configuration to the 'EBICS Manager' Group. | ||||
|  | ||||
| | | ||||
|  | ||||
| Go to **Accounting > Configuration > Miscellaneous > EBICS > EBICS File Formats** | ||||
|  | ||||
| Check if the EBICS File formats that you want to process in Odoo are defined. | ||||
|  | ||||
| Most commonly used formats for which support is available in Odoo should be there already. | ||||
|  | ||||
| Please open an issue on https://github.com/Noviat/account_ebics to report missing EBICS File Formats. | ||||
|  | ||||
| For File Formats of type 'Downloads' you can also specifiy a 'Download Process Method'. | ||||
|  | ||||
| This is the method that will be executed when hitting the 'Process' button on the downloaded file. | ||||
|  | ||||
| The following methods are currently available: | ||||
|  | ||||
| - cfonb120 | ||||
| - camt.053 | ||||
| - camt.052 | ||||
| - camt.054 | ||||
|  | ||||
| All these methods require complimentary modules to be installed (cf. Installation section supra). | ||||
|  | ||||
| You'll get an error message when the required module is not installed on your Odoo instance. | ||||
|  | ||||
| | | ||||
|  | ||||
| Go to **Accounting > Configuration > Miscellaneous > EBICS > EBICS Configuration** | ||||
|  | ||||
| Configure your EBICS configuration according to the contract with your bank. | ||||
|  | ||||
| | | ||||
|  | ||||
| Usage | ||||
| ===== | ||||
|  | ||||
| Go to **Accounting > Bank and Cash > EBICS Processing** | ||||
|  | ||||
| | | ||||
|  | ||||
| EBICS Return Codes | ||||
| ------------------ | ||||
|  | ||||
| During the processing of your EBICS upload/download, your bank may return an Error Code, e.g. | ||||
|  | ||||
| EBICS Functional Error: | ||||
| EBICS_NO_DOWNLOAD_DATA_AVAILABLE (code: 90005) | ||||
|  | ||||
| A detailled explanation of the codes can be found on http://www.ebics.org. | ||||
| You can also find this information in the doc folder of this module (file EBICS_Annex1_ReturnCodes). | ||||
|  | ||||
| | | ||||
|  | ||||
| Known Issues / Roadmap | ||||
| ====================== | ||||
|  | ||||
| - add support for EBICS 3.0 | ||||
| - add support to import externally generated keys & certificates (currently only 3SKey signature certificate) | ||||
							
								
								
									
										9
									
								
								account_ebics/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								account_ebics/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import logging | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
| try: | ||||
|     from . import models | ||||
|     from . import wizards | ||||
| except Exception: | ||||
|     _logger.warning("Import Error, check if fintech lib has been installed") | ||||
							
								
								
									
										32
									
								
								account_ebics/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								account_ebics/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| # Copyright 2009-2021 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| { | ||||
|     'name': 'EBICS banking protocol', | ||||
|     'version': '14.0.1.0.0', | ||||
|     'license': 'LGPL-3', | ||||
|     'author': 'Noviat', | ||||
|     'website': 'www.noviat.com', | ||||
|     'category': 'Accounting & Finance', | ||||
|     'depends': ['account'], | ||||
|     'data': [ | ||||
|         'security/ebics_security.xml', | ||||
|         'security/ir.model.access.csv', | ||||
|         'data/ebics_file_format.xml', | ||||
|         'views/ebics_config_views.xml', | ||||
|         'views/ebics_file_views.xml', | ||||
|         'views/ebics_userid_views.xml', | ||||
|         'views/ebics_file_format_views.xml', | ||||
|         'wizards/ebics_change_passphrase.xml', | ||||
|         'wizards/ebics_xfer.xml', | ||||
|         'views/menu.xml', | ||||
|     ], | ||||
|     'installable': True, | ||||
|     'application': True, | ||||
|     'external_dependencies': { | ||||
|         'python': [ | ||||
|             'fintech', | ||||
|             'cryptography', | ||||
|             ] | ||||
|         }, | ||||
| } | ||||
							
								
								
									
										154
									
								
								account_ebics/data/ebics_file_format.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								account_ebics/data/ebics_file_format.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <odoo> | ||||
|   <data noupdate="1"> | ||||
|  | ||||
|     <!-- Download formats --> | ||||
|  | ||||
|     <record id="ebics_ff_C52" model="ebics.file.format"> | ||||
|       <field name="name">camt.052</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">C52</field> | ||||
|       <field name="download_process_method">camt.052</field> | ||||
|       <field name="description">bank to customer account report in format camt.052</field> | ||||
|       <field name="suffix">c52.xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_Z52" model="ebics.file.format"> | ||||
|       <field name="name">camt.052</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">Z52</field> | ||||
|       <field name="download_process_method">camt.052</field> | ||||
|       <field name="description">bank to customer account report in format camt.052</field> | ||||
|       <field name="suffix">c52.xml</field> | ||||
|     </record> | ||||
|      | ||||
|     <record id="ebics_ff_C53" model="ebics.file.format"> | ||||
|       <field name="name">camt.053</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">C53</field> | ||||
|       <field name="download_process_method">camt.053</field> | ||||
|       <field name="description">Bank to customer statement report in format camt.053</field> | ||||
|       <field name="suffix">c53.xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_Z53" model="ebics.file.format"> | ||||
|       <field name="name">camt.053</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">Z53</field> | ||||
|       <field name="download_process_method">camt.053</field> | ||||
|       <field name="description">Bank to customer statement report in format camt.053</field> | ||||
|       <field name="suffix">c53.xml</field> | ||||
|     </record> | ||||
|      | ||||
|     <record id="ebics_ff_C54" model="ebics.file.format"> | ||||
|       <field name="name">camt.054</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">C54</field> | ||||
|       <field name="download_process_method">camt.054</field> | ||||
|       <field name="description">Bank to customer debit credit notification in format camt.054</field> | ||||
|       <field name="suffix">c52.xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_Z54" model="ebics.file.format"> | ||||
|       <field name="name">camt.054</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">Z54</field> | ||||
|       <field name="download_process_method">camt.054</field> | ||||
|       <field name="description">Bank to customer debit credit notification in format camt.054</field> | ||||
|       <field name="suffix">c52.xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_FDL_camt_xxx_cfonb120_stm" model="ebics.file.format"> | ||||
|       <field name="name">camt.xxx.cfonb120.stm</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">FDL</field> | ||||
|       <field name="download_process_method">cfonb120</field> | ||||
|       <field name="description">Bank to customer statement report in format cfonb120</field> | ||||
|       <field name="suffix">cfonb120.dat</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_CDZ" model="ebics.file.format"> | ||||
|       <field name="name">pain.002</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">CDZ</field> | ||||
|       <field name="description">Payment status report for direct debit in format pain.002</field> | ||||
|       <field name="suffix">psr.xml</field> | ||||
|     </record> | ||||
|      | ||||
|     <record id="ebics_ff_Z01" model="ebics.file.format"> | ||||
|       <field name="name">pain.002</field> | ||||
|       <field name="type">down</field> | ||||
|       <field name="order_type">Z01</field> | ||||
|       <field name="download_process_method">pain.002</field> | ||||
|       <field name="description">Payment status report for direct debit in format pain.002</field> | ||||
|       <field name="suffix">psr.xml</field> | ||||
|     </record> | ||||
|      | ||||
|     <!-- Upload formats --> | ||||
|  | ||||
|     <record id="ebics_ff_LCR" model="ebics.file.format"> | ||||
|       <field name="name">pain.xxx.cfonb160.dco</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">FUL</field> | ||||
|       <field name="description">Remises de LCR</field> | ||||
|       <field name="suffix">txt</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_CCT" model="ebics.file.format"> | ||||
|       <field name="name">pain.001.001.03</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">CCT</field> | ||||
|       <field name="description">Payment Order in format pain.001.001.03</field> | ||||
|       <field name="suffix">xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_XE2" model="ebics.file.format"> | ||||
|       <field name="name">pain.001.001.03</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">XE2</field> | ||||
|       <field name="description">Payment Order in format pain.001.001.03</field> | ||||
|       <field name="suffix">xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_CDD" model="ebics.file.format"> | ||||
|       <field name="name">pain.008.001.02.sdd</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">CDD</field> | ||||
|       <field name="description">Sepa Core Direct Debit Order in format pain.008.001.02</field> | ||||
|       <field name="suffix">xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_XE3" model="ebics.file.format"> | ||||
|       <field name="name">pain.008.001.02.sdd</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">XE3</field> | ||||
|       <field name="description">Sepa Core Direct Debit Order in format pain.008.001.02</field> | ||||
|       <field name="suffix">xml</field> | ||||
|     </record> | ||||
|      | ||||
|     <record id="ebics_ff_CDB" model="ebics.file.format"> | ||||
|       <field name="name">pain.008.001.02.sbb</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">CDB</field> | ||||
|       <field name="description">Sepa Direct Debit (B2B) Order in format pain.008.001.02</field> | ||||
|       <field name="suffix">xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_XE4" model="ebics.file.format"> | ||||
|       <field name="name">pain.008.001.02.sbb</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">XE4</field> | ||||
|       <field name="description">Sepa Direct Debit (B2B) Order in format pain.008.001.02</field> | ||||
|       <field name="suffix">xml</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_ff_FUL_pain_001_001_02_sct" model="ebics.file.format"> | ||||
|       <field name="name">pain.001.001.02.sct</field> | ||||
|       <field name="type">up</field> | ||||
|       <field name="order_type">FUL</field> | ||||
|       <field name="description">Payment Order in format pain.001.001.02</field> | ||||
|       <field name="suffix">xml</field> | ||||
|     </record> | ||||
|  | ||||
|   </data> | ||||
| </odoo> | ||||
							
								
								
									
										
											BIN
										
									
								
								account_ebics/doc/EBICS_Annex1_ReturnCodes_final-16-05-2011.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								account_ebics/doc/EBICS_Annex1_ReturnCodes_final-16-05-2011.pdf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								account_ebics/doc/EBICS_Common_IG_based_EBICS_2.5.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								account_ebics/doc/EBICS_Common_IG_based_EBICS_2.5.pdf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								account_ebics/doc/EBICS_Specification_2.5_final-16-05-2011.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								account_ebics/doc/EBICS_Specification_2.5_final-16-05-2011.pdf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										18
									
								
								account_ebics/migrations/13.0.1.1/noupdate_changes.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								account_ebics/migrations/13.0.1.1/noupdate_changes.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| <?xml version='1.0' encoding='utf-8' ?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="ebics_config_comp_rule" model="ir.rule"> | ||||
|     <field name="name">EBICS Configuration model company rule</field> | ||||
|     <field name="model_id" ref="model_ebics_config"/> | ||||
|     <field eval="True" name="global"/> | ||||
|     <field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_comp_rule" model="ir.rule"> | ||||
|     <field name="name">EBICS File model company rule</field> | ||||
|     <field name="model_id" ref="model_ebics_file"/> | ||||
|     <field eval="True" name="global"/> | ||||
|     <field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										73
									
								
								account_ebics/migrations/13.0.1.1/post-migration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								account_ebics/migrations/13.0.1.1/post-migration.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||||
|  | ||||
| from openupgradelib import openupgrade  # pylint: disable=W7936 | ||||
| import os | ||||
|  | ||||
|  | ||||
| @openupgrade.migrate() | ||||
| def migrate(env, version): | ||||
|  | ||||
|     _ebics_config_upgrade(env, version) | ||||
|     _noupdate_changes(env, version) | ||||
|  | ||||
|  | ||||
| def _ebics_config_upgrade(env, version): | ||||
|     env.cr.execute("SELECT * FROM ebics_config") | ||||
|     cfg_datas = env.cr.dictfetchall() | ||||
|     for cfg_data in cfg_datas: | ||||
|         cfg = env['ebics.config'].browse(cfg_data['id']) | ||||
|         journal = env['account.journal'].search( | ||||
|             [('bank_account_id', '=', cfg_data['bank_id'])]) | ||||
|         keys_fn_old = cfg_data['ebics_keys'] | ||||
|         ebics_keys_root = os.path.dirname(keys_fn_old) | ||||
|         if os.path.isfile(keys_fn_old): | ||||
|             keys_fn = ebics_keys_root + '/' + cfg_data['ebics_user'] + '_keys' | ||||
|             os.rename(keys_fn_old, keys_fn) | ||||
|         state = cfg_data['state'] == 'active' and 'confirm' or 'draft' | ||||
|         cfg.write({ | ||||
|             'company_ids': [(6, 0, [cfg_data['company_id']])], | ||||
|             'journal_ids': [(6, 0, [journal.id])], | ||||
|             'ebics_keys': ebics_keys_root, | ||||
|             'state': state, | ||||
|         }) | ||||
|  | ||||
|         user_vals = { | ||||
|             'ebics_config_id': cfg_data['id'], | ||||
|             'name': cfg_data['ebics_user'], | ||||
|         } | ||||
|         for fld in [ | ||||
|                 'signature_class', 'ebics_passphrase', | ||||
|                 'ebics_ini_letter_fn', 'ebics_public_bank_keys_fn', | ||||
|                 'ebics_key_x509', 'ebics_key_x509_dn_cn', | ||||
|                 'ebics_key_x509_dn_o', 'ebics_key_x509_dn_ou', | ||||
|                 'ebics_key_x509_dn_c', 'ebics_key_x509_dn_st', | ||||
|                 'ebics_key_x509_dn_l', 'ebics_key_x509_dn_e', | ||||
|                 'ebics_file_format_ids', 'state']: | ||||
|             if cfg_data.get(fld): | ||||
|                 if fld == 'ebics_file_format_ids': | ||||
|                     user_vals[fld] = [(6, 0, cfg_data[fld])] | ||||
|                 elif fld == 'state' and cfg_data['state'] == 'active': | ||||
|                     user_vals['state'] = 'active_keys' | ||||
|                 else: | ||||
|                     user_vals[fld] = cfg_data[fld] | ||||
|         ebics_userid = env['ebics.userid'].create(user_vals) | ||||
|         env.cr.execute( | ||||
|             """ | ||||
|             UPDATE ir_attachment | ||||
|             SET res_model = 'ebics.userid', res_id = %s | ||||
|             WHERE name in ('ebics_ini_letter', 'ebics_public_bank_keys'); | ||||
|             """ | ||||
|             % ebics_userid.id) | ||||
|  | ||||
|     if len(cfg_datas) == 1: | ||||
|         env.cr.execute( | ||||
|             "UPDATE ebics_file SET ebics_userid_id = %s" % ebics_userid.id) | ||||
|  | ||||
|  | ||||
| def _noupdate_changes(env, version): | ||||
|     openupgrade.load_data( | ||||
|         env.cr, | ||||
|         'account_ebics', | ||||
|         'migrations/13.0.1.1/noupdate_changes.xml' | ||||
|     ) | ||||
							
								
								
									
										9
									
								
								account_ebics/migrations/13.0.1.1/pre-migration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								account_ebics/migrations/13.0.1.1/pre-migration.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||||
|  | ||||
|  | ||||
| def migrate(cr, version): | ||||
|     if not version: | ||||
|         return | ||||
|  | ||||
|     cr.execute("DELETE FROM ebics_xfer;") | ||||
							
								
								
									
										41
									
								
								account_ebics/migrations/13.0.1.3/post-migration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								account_ebics/migrations/13.0.1.3/post-migration.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||||
|  | ||||
| _FILE_FORMATS = [ | ||||
|     {'xml_id_name': 'ebics_ff_C52', | ||||
|      'download_process_method': 'camt.052', | ||||
|      }, | ||||
|     {'xml_id_name': 'ebics_ff_C53', | ||||
|      'download_process_method': 'camt.053', | ||||
|      }, | ||||
|     {'xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', | ||||
|      'download_process_method': 'cfonb120', | ||||
|      }, | ||||
|  | ||||
| ] | ||||
|  | ||||
|  | ||||
| def migrate(cr, version): | ||||
|     for ff in _FILE_FORMATS: | ||||
|         _update_file_format(cr, ff) | ||||
|  | ||||
|  | ||||
| def _update_file_format(cr, ff): | ||||
|     cr.execute( | ||||
|         """ | ||||
|         SELECT res_id FROM ir_model_data | ||||
|         WHERE module='account_ebics' AND name='{}' | ||||
|         """.format(ff['xml_id_name']) | ||||
|     ) | ||||
|     res = cr.fetchone() | ||||
|     if res: | ||||
|         cr.execute( | ||||
|             """ | ||||
|     UPDATE ebics_file_format | ||||
|     SET download_process_method='{download_process_method}' | ||||
|     WHERE id={ff_id}; | ||||
|             """.format( | ||||
|                     download_process_method=ff['download_process_method'], | ||||
|                     ff_id=res[0] | ||||
|                 ) | ||||
|         ) | ||||
							
								
								
									
										70
									
								
								account_ebics/migrations/13.0.1.3/pre-migration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								account_ebics/migrations/13.0.1.3/pre-migration.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). | ||||
|  | ||||
| _FILE_FORMATS = [ | ||||
|     {'old_xml_id_name': 'ebics_ff_camt_052_001_02_stm', | ||||
|      'new_xml_id_name': 'ebics_ff_C52', | ||||
|      'new_name': 'camt.052', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_camt_053_001_02_stm', | ||||
|      'new_xml_id_name': 'ebics_ff_C53', | ||||
|      'new_name': 'camt.053', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_camt_xxx_cfonb120_stm', | ||||
|      'new_xml_id_name': 'ebics_ff_FDL_camt_xxx_cfonb120_stm', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_pain_001_001_03_sct', | ||||
|      'new_xml_id_name': 'ebics_ff_CCT', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_pain_001', | ||||
|      'new_xml_id_name': 'ebics_ff_XE2', | ||||
|      'new_name': 'pain.001.001.03', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_pain_008_001_02_sdd', | ||||
|      'new_xml_id_name': 'ebics_ff_CDD', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_pain_008', | ||||
|      'new_xml_id_name': 'ebics_ff_XE3', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_pain_008_001_02_sbb', | ||||
|      'new_xml_id_name': 'ebics_ff_CDB', | ||||
|      }, | ||||
|     {'old_xml_id_name': 'ebics_ff_pain_001_001_02_sct', | ||||
|      'new_xml_id_name': 'ebics_ff_FUL_pain_001_001_02_sct', | ||||
|      }, | ||||
| ] | ||||
|  | ||||
|  | ||||
| def migrate(cr, version): | ||||
|     if not version: | ||||
|         return | ||||
|  | ||||
|     for ff in _FILE_FORMATS: | ||||
|         _update_file_format(cr, ff) | ||||
|  | ||||
|  | ||||
| def _update_file_format(cr, ff): | ||||
|     cr.execute( | ||||
|         """ | ||||
|     SELECT id, res_id FROM ir_model_data | ||||
|     WHERE module='account_ebics' AND name='{}' | ||||
|         """.format(ff['old_xml_id_name']) | ||||
|     ) | ||||
|     res = cr.fetchone() | ||||
|     if res: | ||||
|         query = """ | ||||
|         UPDATE ir_model_data | ||||
|         SET name='{new_xml_id_name}' | ||||
|         WHERE id={xml_id}; | ||||
|         """.format( | ||||
|             new_xml_id_name=ff["new_xml_id_name"], xml_id=res[0] | ||||
|         ) | ||||
|         if ff.get('new_name'): | ||||
|             query += """ | ||||
|             UPDATE ebics_file_format | ||||
|             SET name='{new_name}' | ||||
|             WHERE id={ff_id}; | ||||
|             """.format( | ||||
|                 new_name=ff["new_name"], ff_id=res[1] | ||||
|             ) | ||||
|         cr.execute(query) | ||||
							
								
								
									
										6
									
								
								account_ebics/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								account_ebics/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| from . import fintech_ebics_register | ||||
| from . import account_bank_statement | ||||
| from . import ebics_config | ||||
| from . import ebics_file | ||||
| from . import ebics_file_format | ||||
| from . import ebics_userid | ||||
							
								
								
									
										11
									
								
								account_ebics/models/account_bank_statement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								account_ebics/models/account_bank_statement.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| from odoo import fields, models | ||||
|  | ||||
|  | ||||
| class AccountBankStatement(models.Model): | ||||
|     _inherit = 'account.bank.statement' | ||||
|  | ||||
|     ebics_file_id = fields.Many2one( | ||||
|         comodel_name='ebics.file', string='EBICS Data File') | ||||
							
								
								
									
										197
									
								
								account_ebics/models/ebics_config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								account_ebics/models/ebics_config.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,197 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| import logging | ||||
| import re | ||||
| import os | ||||
|  | ||||
| from odoo import _, api, fields, models | ||||
| from odoo.exceptions import UserError | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class EbicsConfig(models.Model): | ||||
|     """ | ||||
|     EBICS configuration is stored in a separate object in order to | ||||
|     allow extra security policies on this object. | ||||
|     """ | ||||
|     _name = 'ebics.config' | ||||
|     _description = 'EBICS Configuration' | ||||
|     _order = 'name' | ||||
|  | ||||
|     name = fields.Char( | ||||
|         string='Name', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         required=True) | ||||
|     journal_ids = fields.Many2many( | ||||
|         comodel_name='account.journal', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         string='Bank Accounts', | ||||
|         domain="[('type', '=', 'bank')]", | ||||
|         required=True) | ||||
|     ebics_host = fields.Char( | ||||
|         string='EBICS HostID', required=True, | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="Contact your bank to get the EBICS HostID." | ||||
|              "\nIn France the BIC is usually allocated to the HostID " | ||||
|              "whereas in Germany it tends to be an institute specific string " | ||||
|              "of 8 characters.") | ||||
|     ebics_url = fields.Char( | ||||
|         string='EBICS URL', required=True, | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="Contact your bank to get the EBICS URL.") | ||||
|     ebics_version = fields.Selection( | ||||
|         selection=[('H003', 'H003 (2.4)'), | ||||
|                    ('H004', 'H004 (2.5)')], | ||||
|         string='EBICS protocol version', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         required=True, default='H004') | ||||
|     ebics_partner = fields.Char( | ||||
|         string='EBICS PartnerID', required=True, | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="Organizational unit (company or individual) " | ||||
|              "that concludes a contract with the bank. " | ||||
|              "\nIn this contract it will be agreed which order types " | ||||
|              "(file formats) are used, which accounts are concerned, " | ||||
|              "which of the customer's users (subscribers) " | ||||
|              "communicate with the EBICS bank server and the authorisations " | ||||
|              "that these users will possess. " | ||||
|              "\nIt is identified by the PartnerID.") | ||||
|     ebics_userid_ids = fields.One2many( | ||||
|         comodel_name='ebics.userid', | ||||
|         inverse_name='ebics_config_id', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="Human users or a technical system that is/are " | ||||
|              "assigned to a customer. " | ||||
|              "\nOn the EBICS bank server it is identified " | ||||
|              "by the combination of UserID and PartnerID. " | ||||
|              "The technical subscriber serves only for the data exchange " | ||||
|              "between customer and financial institution. " | ||||
|              "The human user also can authorise orders.") | ||||
|     ebics_files = fields.Char( | ||||
|         string='EBICS Files Root', required=True, | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         default=lambda self: self._default_ebics_files(), | ||||
|         help="Root Directory for EBICS File Transfer Folders.") | ||||
|     # We store the EBICS keys in a separate directory in the file system. | ||||
|     # This directory requires special protection to reduce fraude. | ||||
|     ebics_keys = fields.Char( | ||||
|         string='EBICS Keys Root', required=True, | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         default=lambda self: self._default_ebics_keys(), | ||||
|         help="Root Directory for storing the EBICS Keys.") | ||||
|     ebics_key_version = fields.Selection( | ||||
|         selection=[('A005', 'A005 (RSASSA-PKCS1-v1_5)'), | ||||
|                    ('A006', 'A006 (RSASSA-PSS)')], | ||||
|         string='EBICS key version', | ||||
|         default='A006', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="The key version of the electronic signature.") | ||||
|     ebics_key_bitlength = fields.Integer( | ||||
|         string='EBICS key bitlength', | ||||
|         default=2048, | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="The bit length of the generated keys. " | ||||
|              "\nThe value must be between 1536 and 4096.") | ||||
|     ebics_file_format_ids = fields.Many2many( | ||||
|         comodel_name='ebics.file.format', | ||||
|         column1='config_id', column2='format_id', | ||||
|         string='EBICS File Formats', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     state = fields.Selection( | ||||
|         [('draft', 'Draft'), | ||||
|          ('confirm', 'Confirmed')], | ||||
|         string='State', | ||||
|         default='draft', | ||||
|         required=True, readonly=True) | ||||
|     order_number = fields.Char( | ||||
|         size=4, readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="Specify the number for the next order." | ||||
|              "\nThis number should match the following pattern : " | ||||
|              "[A-Z]{1}[A-Z0-9]{3}") | ||||
|     active = fields.Boolean( | ||||
|         string='Active', default=True) | ||||
|     company_ids = fields.Many2many( | ||||
|         comodel_name='res.company', | ||||
|         string='Companies', | ||||
|         required=True, | ||||
|         help="Companies sharing this EBICS contract.") | ||||
|  | ||||
|     @api.model | ||||
|     def _default_ebics_files(self): | ||||
|         return '/'.join(['/home/odoo/ebics_files', self._cr.dbname]) | ||||
|  | ||||
|     @api.model | ||||
|     def _default_ebics_keys(self): | ||||
|         return '/'.join(['/etc/odoo/ebics_keys', self._cr.dbname]) | ||||
|  | ||||
|     @api.constrains('order_number') | ||||
|     def _check_order_number(self): | ||||
|         for cfg in self: | ||||
|             nbr = cfg.order_number | ||||
|             ok = True | ||||
|             if nbr: | ||||
|                 if len(nbr) != 4: | ||||
|                     ok = False | ||||
|                 else: | ||||
|                     pattern = re.compile("[A-Z]{1}[A-Z0-9]{3}") | ||||
|                     if not pattern.match(nbr): | ||||
|                         ok = False | ||||
|             if not ok: | ||||
|                 raise UserError(_( | ||||
|                     "Order Number should comply with the following pattern:" | ||||
|                     "\n[A-Z]{1}[A-Z0-9]{3}")) | ||||
|  | ||||
|     @api.onchange('journal_ids') | ||||
|     def _onchange_journal_ids(self): | ||||
|         self.company_ids = self.journal_ids.mapped('company_id') | ||||
|  | ||||
|     def unlink(self): | ||||
|         for ebics_config in self: | ||||
|             if ebics_config.state == 'active': | ||||
|                 raise UserError(_( | ||||
|                     "You cannot remove active EBICS configurations.")) | ||||
|         return super(EbicsConfig, self).unlink() | ||||
|  | ||||
|     def set_to_draft(self): | ||||
|         return self.write({'state': 'draft'}) | ||||
|  | ||||
|     def set_to_confirm(self): | ||||
|         return self.write({'state': 'confirm'}) | ||||
|  | ||||
|     def _get_order_number(self): | ||||
|         return self.order_number | ||||
|  | ||||
|     def _update_order_number(self, OrderID): | ||||
|         o_list = list(OrderID) | ||||
|         for i, c in enumerate(reversed(o_list), start=1): | ||||
|             if c == '9': | ||||
|                 o_list[-i] = 'A' | ||||
|                 break | ||||
|             if c == 'Z': | ||||
|                 continue | ||||
|             else: | ||||
|                 o_list[-i] = chr(ord(c) + 1) | ||||
|                 break | ||||
|         next = ''.join(o_list) | ||||
|         if next == 'ZZZZ': | ||||
|             next = 'A000' | ||||
|         self.order_number = next | ||||
|  | ||||
|     def _check_ebics_keys(self): | ||||
|         dirname = self.ebics_keys or '' | ||||
|         if not os.path.exists(dirname): | ||||
|             raise UserError(_( | ||||
|                 "EBICS Keys Root Directory %s is not available." | ||||
|                 "\nPlease contact your system administrator.") | ||||
|                 % dirname) | ||||
|  | ||||
|     def _check_ebics_files(self): | ||||
|         dirname = self.ebics_files or '' | ||||
|         if not os.path.exists(dirname): | ||||
|             raise UserError(_( | ||||
|                 "EBICS Files Root Directory %s is not available." | ||||
|                 "\nPlease contact your system administrator.") | ||||
|                 % dirname) | ||||
							
								
								
									
										426
									
								
								account_ebics/models/ebics_file.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								account_ebics/models/ebics_file.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,426 @@ | ||||
| # Copyright 2009-2021 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| import base64 | ||||
| import logging | ||||
|  | ||||
| from odoo import _, fields, models | ||||
| from odoo.exceptions import UserError | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class EbicsFile(models.Model): | ||||
|     _name = 'ebics.file' | ||||
|     _description = 'Object to store EBICS Data Files' | ||||
|     _order = 'date desc' | ||||
|     _sql_constraints = [ | ||||
|         ('name_uniq', 'unique (name, format_id)', | ||||
|          'This File has already been down- or uploaded !') | ||||
|     ] | ||||
|  | ||||
|     name = fields.Char(string='Filename') | ||||
|     data = fields.Binary(string='File', readonly=True) | ||||
|     format_id = fields.Many2one( | ||||
|         comodel_name='ebics.file.format', | ||||
|         string='EBICS File Formats', | ||||
|         readonly=True) | ||||
|     type = fields.Selection( | ||||
|         related='format_id.type', | ||||
|         readonly=True) | ||||
|     date_from = fields.Date( | ||||
|         readonly=True, | ||||
|         help="'Date From' as entered in the download wizard.") | ||||
|     date_to = fields.Date( | ||||
|         readonly=True, | ||||
|         help="'Date To' as entered in the download wizard.") | ||||
|     date = fields.Datetime( | ||||
|         required=True, readonly=True, | ||||
|         help='File Upload/Download date') | ||||
|     bank_statement_ids = fields.One2many( | ||||
|         comodel_name='account.bank.statement', | ||||
|         inverse_name='ebics_file_id', | ||||
|         string='Generated Bank Statements', readonly=True) | ||||
|     state = fields.Selection( | ||||
|         [('draft', 'Draft'), | ||||
|          ('done', 'Done')], | ||||
|         string='State', | ||||
|         default='draft', | ||||
|         required=True, readonly=True) | ||||
|     user_id = fields.Many2one( | ||||
|         comodel_name='res.users', string='User', | ||||
|         default=lambda self: self.env.user, | ||||
|         readonly=True) | ||||
|     ebics_userid_id = fields.Many2one( | ||||
|         comodel_name='ebics.userid', | ||||
|         string='EBICS UserID', | ||||
|         ondelete='restrict', | ||||
|         readonly=True) | ||||
|     note = fields.Text(string='Notes') | ||||
|     note_process = fields.Text(string='Notes') | ||||
|     company_ids = fields.Many2many( | ||||
|         comodel_name='res.company', | ||||
|         string='Companies', | ||||
|         help="Companies sharing this EBICS file.") | ||||
|  | ||||
|     def unlink(self): | ||||
|         ff_methods = self._file_format_methods() | ||||
|         for ebics_file in self: | ||||
|             if ebics_file.state == 'done': | ||||
|                 raise UserError(_( | ||||
|                     "You can only remove EBICS files in state 'Draft'.")) | ||||
|             # execute format specific actions | ||||
|             ff = ebics_file.format_id.download_process_method | ||||
|             if ff in ff_methods: | ||||
|                 if ff_methods[ff].get('unlink'): | ||||
|                     ff_methods[ff]['unlink'](ebics_file) | ||||
|             # remove bank statements | ||||
|             ebics_file.bank_statement_ids.unlink() | ||||
|         return super(EbicsFile, self).unlink() | ||||
|  | ||||
|     def set_to_draft(self): | ||||
|         return self.write({'state': 'draft'}) | ||||
|  | ||||
|     def set_to_done(self): | ||||
|         return self.write({'state': 'done'}) | ||||
|  | ||||
|     def process(self): | ||||
|         self.ensure_one() | ||||
|         ctx = dict( | ||||
|             self.env.context, | ||||
|             allowed_company_ids=self.env.user.company_ids.ids) | ||||
|         self = self.with_context(ctx) | ||||
|         self.note_process = '' | ||||
|         ff_methods = self._file_format_methods() | ||||
|         ff = self.format_id.download_process_method | ||||
|         if ff in ff_methods: | ||||
|             if ff_methods[ff].get('process'): | ||||
|                 res = ff_methods[ff]['process'](self) | ||||
|                 self.state = 'done' | ||||
|                 return res | ||||
|         else: | ||||
|             return self._process_undefined_format() | ||||
|  | ||||
|     def action_open_bank_statements(self): | ||||
|         self.ensure_one() | ||||
|         action = self.env['ir.actions.act_window']._for_xml_id( | ||||
|             'account.action_bank_statement_tree') | ||||
|         domain = eval(action.get('domain') or '[]') | ||||
|         domain += [('id', 'in', self._context.get('statement_ids'))] | ||||
|         action.update({'domain': domain}) | ||||
|         return action | ||||
|  | ||||
|     def button_close(self): | ||||
|         self.ensure_one() | ||||
|         return {'type': 'ir.actions.act_window_close'} | ||||
|  | ||||
|     def _file_format_methods(self): | ||||
|         """ | ||||
|         Extend this dictionary in order to add support | ||||
|         for extra file formats. | ||||
|         """ | ||||
|         res = { | ||||
|             'cfonb120': | ||||
|                 {'process': self._process_cfonb120, | ||||
|                  'unlink': self._unlink_cfonb120}, | ||||
|             'camt.052': | ||||
|                 {'process': self._process_camt052, | ||||
|                  'unlink': self._unlink_camt052}, | ||||
|             'camt.053': | ||||
|                 {'process': self._process_camt053, | ||||
|                  'unlink': self._unlink_camt053}, | ||||
|             'camt.054': | ||||
|                 {'process': self._process_camt054, | ||||
|                  'unlink': self._unlink_camt054}, | ||||
|             'pain.002': | ||||
|                 {'process': self._process_pain002, | ||||
|                  'unlink': self._unlink_pain002}, | ||||
|         } | ||||
|         return res | ||||
|  | ||||
|     def _check_import_module(self, module, raise_if_not_found=True): | ||||
|         mod = self.env['ir.module.module'].sudo().search( | ||||
|             [('name', '=like', module), | ||||
|              ('state', '=', 'installed')]) | ||||
|         if not mod: | ||||
|             if raise_if_not_found: | ||||
|                 raise UserError(_( | ||||
|                     "The module to process the '%s' format is not installed " | ||||
|                     "on your system. " | ||||
|                     "\nPlease install module '%s'") | ||||
|                     % (self.format_id.name, module)) | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|     def _process_result_action(self, res): | ||||
|         notifications = [] | ||||
|         st_line_ids = [] | ||||
|         statement_ids = [] | ||||
|         if res.get('context'): | ||||
|             notifications = res['context'].get('notifications', []) | ||||
|             st_line_ids = res['context'].get('statement_line_ids', []) | ||||
|         if notifications: | ||||
|             for notif in notifications: | ||||
|                 parts = [] | ||||
|                 for k in ['type', 'message', 'details']: | ||||
|                     if notif.get(k): | ||||
|                         msg = '%s: %s' % (k, notif[k]) | ||||
|                         parts.append(msg) | ||||
|                 self.note_process += '\n'.join(parts) | ||||
|                 self.note_process += '\n' | ||||
|             self.note_process += '\n' | ||||
|         if st_line_ids: | ||||
|             self.flush() | ||||
|             self.env.cr.execute( | ||||
|                 """ | ||||
|     SELECT DISTINCT | ||||
|         absl.statement_id, | ||||
|         abs.name, abs.date, abs.company_id, | ||||
|         rc.name AS company_name | ||||
|       FROM account_bank_statement_line absl | ||||
|       INNER JOIN account_bank_statement abs | ||||
|         ON abs.id = absl.statement_id | ||||
|       INNER JOIN res_company rc | ||||
|         ON rc.id = abs.company_id | ||||
|       WHERE absl.id IN %s | ||||
|       ORDER BY date, company_id | ||||
|                 """, | ||||
|                 (tuple(st_line_ids),) | ||||
|             ) | ||||
|             sts_data = self.env.cr.dictfetchall() | ||||
|         else: | ||||
|             sts_data = [] | ||||
|         st_cnt = len(sts_data) | ||||
|         if st_cnt: | ||||
|             self.note_process += _( | ||||
|                 "%s bank statements have been imported: " | ||||
|             ) % st_cnt | ||||
|             self.note_process += '\n' | ||||
|         for st_data in sts_data: | ||||
|             self.note_process += ("\n%s, %s (%s)") % ( | ||||
|                 st_data['date'], st_data['name'], st_data['company_name']) | ||||
|         statement_ids = [x['statement_id'] for x in sts_data] | ||||
|         if statement_ids: | ||||
|             self.sudo().bank_statement_ids = [(6, 0, statement_ids)] | ||||
|         ctx = dict(self.env.context, statement_ids=statement_ids) | ||||
|         module = __name__.split('addons.')[1].split('.')[0] | ||||
|         result_view = self.env.ref('%s.ebics_file_view_form_result' % module) | ||||
|         return { | ||||
|             'name': _('Import EBICS File'), | ||||
|             'res_id': self.id, | ||||
|             'view_type': 'form', | ||||
|             'view_mode': 'form', | ||||
|             'res_model': self._name, | ||||
|             'view_id': result_view.id, | ||||
|             'target': 'new', | ||||
|             'context': ctx, | ||||
|             'type': 'ir.actions.act_window', | ||||
|         } | ||||
|  | ||||
|     @staticmethod | ||||
|     def _process_cfonb120(self): | ||||
|         """ | ||||
|         We do not support the standard _journal_creation_wizard since a single | ||||
|         cfonb120 file may contain statements from different legal entities. | ||||
|         """ | ||||
|         import_module = 'account_statement_import_fr_cfonb' | ||||
|         self._check_import_module(import_module) | ||||
|         wiz_model = 'account.statement.import' | ||||
|         data_file = base64.b64decode(self.data) | ||||
|         lines = data_file.split(b'\n') | ||||
|         wiz_vals_list = [] | ||||
|         st_lines = b'' | ||||
|         transactions = False | ||||
|         for line in lines: | ||||
|             rec_type = line[0:2] | ||||
|             acc_number = line[21:32] | ||||
|             st_lines += line + b'\n' | ||||
|             if rec_type == b'04': | ||||
|                 transactions = True | ||||
|             if rec_type == b'07': | ||||
|                 if transactions: | ||||
|                     fn = '_'.join([acc_number.decode(), self.name]) | ||||
|                     wiz_vals_list.append({ | ||||
|                         'statement_filename': fn, | ||||
|                         'statement_file': base64.b64encode(st_lines) | ||||
|                     }) | ||||
|                 st_lines = b'' | ||||
|                 transactions = False | ||||
|         result = { | ||||
|             'type': 'ir.actions.client', | ||||
|             'tag': 'bank_statement_reconciliation_view', | ||||
|             'context': {'statement_line_ids': [], | ||||
|                         'company_ids': self.env.user.company_ids.ids, | ||||
|                         'notifications': []}, | ||||
|         } | ||||
|         wiz_ctx = dict(self.env.context, active_model='ebics.file') | ||||
|         for i, wiz_vals in enumerate(wiz_vals_list, start=1): | ||||
|             wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals) | ||||
|             res = wiz.import_file_button() | ||||
|             ctx = res.get('context') | ||||
|             if (res.get('res_model') | ||||
|                     == 'account.bank.statement.import.journal.creation'): | ||||
|                 message = _( | ||||
|                     "Error detected while importing statement number %s.\n" | ||||
|                 ) % i | ||||
|                 message += _("No financial journal found.") | ||||
|                 details = _( | ||||
|                     'Bank account number: %s' | ||||
|                 ) % ctx.get('default_bank_acc_number') | ||||
|                 result['context']['notifications'].extend([{ | ||||
|                     'type': 'warning', | ||||
|                     'message': message, | ||||
|                     'details': details, | ||||
|                 }]) | ||||
|                 continue | ||||
|             result['context']['statement_line_ids'].extend( | ||||
|                 ctx['statement_line_ids']) | ||||
|             result['context']['notifications'].extend( | ||||
|                 ctx['notifications']) | ||||
|         return self._process_result_action(result) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _unlink_cfonb120(self): | ||||
|         """ | ||||
|         Placeholder for cfonb120 specific actions before removing the | ||||
|         EBICS data file and its related bank statements. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     @staticmethod | ||||
|     def _process_camt052(self): | ||||
|         import_module = 'account_statement_import_camt' | ||||
|         self._check_import_module(import_module) | ||||
|         return self._process_camt053(self) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _unlink_camt052(self): | ||||
|         """ | ||||
|         Placeholder for camt052 specific actions before removing the | ||||
|         EBICS data file and its related bank statements. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     @staticmethod | ||||
|     def _process_camt054(self): | ||||
|         import_module = 'account_statement_import_camt' | ||||
|         self._check_import_module(import_module) | ||||
|         return self._process_camt053(self) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _unlink_camt054(self): | ||||
|         """ | ||||
|         Placeholder for camt054 specific actions before removing the | ||||
|         EBICS data file and its related bank statements. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     @staticmethod | ||||
|     def _process_camt053(self): | ||||
|         modules = [ | ||||
|             ('oca', 'account_statement_import_camt'), | ||||
|             ('oe', 'account_bank_statement_import_camt'), | ||||
|         ] | ||||
|         found = False | ||||
|         for src, mod in modules: | ||||
|             if self._check_import_module(mod, raise_if_not_found=False): | ||||
|                 found = True | ||||
|                 break | ||||
|         if not found: | ||||
|             raise UserError(_( | ||||
|                 "The module to process the '%s' format is not installed " | ||||
|                 "on your system. " | ||||
|                 "\nPlease install one of the following modules: \n%s." | ||||
|                 ) % (self.format_id.name, ', '.join([x[1] for x in modules])) | ||||
|             ) | ||||
|         if src == 'oca': | ||||
|             self._process_camt053_oca() | ||||
|         else: | ||||
|             self._process_camt053_oe() | ||||
|  | ||||
|     def _process_camt053_oca(self): | ||||
|         wiz_model = 'account.statement.import' | ||||
|         wiz_vals = { | ||||
|             'statement_filename': self.name, | ||||
|             'statement_file': self.data, | ||||
|         } | ||||
|         result = { | ||||
|             'type': 'ir.actions.client', | ||||
|             'tag': 'bank_statement_reconciliation_view', | ||||
|             'context': {'statement_line_ids': [], | ||||
|                         'company_ids': self.env.user.company_ids.ids, | ||||
|                         'notifications': []}, | ||||
|         } | ||||
|         wiz_ctx = dict(self.env.context, active_model='ebics.file') | ||||
|         wiz = self.env[wiz_model].with_context(wiz_ctx).create(wiz_vals) | ||||
|         res = wiz.import_file_button() | ||||
|         ctx = res.get('context') | ||||
|         if (res.get('res_model') | ||||
|                 == 'account.bank.statement.import.journal.creation'): | ||||
|             message = _( | ||||
|                 "Error detected while importing statement %s.\n" | ||||
|             ) % self.name | ||||
|             message += _("No financial journal found.") | ||||
|             details = _( | ||||
|                 'Bank account number: %s' | ||||
|             ) % ctx.get('default_bank_acc_number') | ||||
|             result['context']['notifications'].extend([{ | ||||
|                 'type': 'warning', | ||||
|                 'message': message, | ||||
|                 'details': details, | ||||
|             }]) | ||||
|         result['context']['statement_line_ids'].extend( | ||||
|             ctx['statement_line_ids']) | ||||
|         result['context']['notifications'].extend( | ||||
|             ctx['notifications']) | ||||
|         return self._process_result_action(result) | ||||
|  | ||||
|     def _process_camt053_oe(self): | ||||
|         wiz_model = 'account.bank.statement.import' | ||||
|         wiz_vals = { | ||||
|             'attachment_ids': [(0, 0, {'name': self.name, | ||||
|                                        'datas': self.data, | ||||
|                                        'store_fname': self.name})]} | ||||
|         ctx = dict(self.env.context, active_model='ebics.file') | ||||
|         wiz = self.env[wiz_model].with_context(ctx).create(wiz_vals) | ||||
|         res = wiz.import_file() | ||||
|         if res.get('res_model') \ | ||||
|                 == 'account.bank.statement.import.journal.creation': | ||||
|             if res.get('context'): | ||||
|                 bank_account = res['context'].get('default_bank_acc_number') | ||||
|                 raise UserError(_( | ||||
|                     "No financial journal found for Company Bank Account %s" | ||||
|                 ) % bank_account) | ||||
|         return self._process_result_action(res) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _unlink_camt053(self): | ||||
|         """ | ||||
|         Placeholder for camt053 specific actions before removing the | ||||
|         EBICS data file and its related bank statements. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     @staticmethod | ||||
|     def _process_pain002(self): | ||||
|         """ | ||||
|         Placeholder for processing pain.002 files. | ||||
|         TODO: | ||||
|         add import logic based upon OCA 'account_payment_return_import' | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     @staticmethod | ||||
|     def _unlink_pain002(self): | ||||
|         """ | ||||
|         Placeholder for pain.002 specific actions before removing the | ||||
|         EBICS data file. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def _process_undefined_format(self): | ||||
|         raise UserError(_( | ||||
|             "The current version of the 'account_ebics' module " | ||||
|             "has no support to automatically process EBICS files " | ||||
|             "with format %s." | ||||
|         ) % self.format_id.name) | ||||
							
								
								
									
										62
									
								
								account_ebics/models/ebics_file_format.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								account_ebics/models/ebics_file_format.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| from odoo import api, fields, models | ||||
|  | ||||
|  | ||||
| class EbicsFileFormat(models.Model): | ||||
|     _name = 'ebics.file.format' | ||||
|     _description = 'EBICS File Formats' | ||||
|     _order = 'type,name,order_type' | ||||
|  | ||||
|     name = fields.Char( | ||||
|         string='Request Type', | ||||
|         required=True, | ||||
|         help="E.g. camt.xxx.cfonb120.stm, pain.001.001.03.sct.\n" | ||||
|              "Specify camt.052, camt.053, camt.054 for camt " | ||||
|              "Order Types such as C53, Z53, C54, Z54.\n" | ||||
|              "This name has to match the 'Request Type' in your " | ||||
|              "EBICS contract for Order Type 'FDL' or 'FUL'.\n") | ||||
|     type = fields.Selection( | ||||
|         selection=[('down', 'Download'), | ||||
|                    ('up', 'Upload')], | ||||
|         required=True) | ||||
|     order_type = fields.Char( | ||||
|         string='Order Type', | ||||
|         required=True, | ||||
|         help="E.g. C53 (check your EBICS contract).\n" | ||||
|              "For most banks in France you should use the " | ||||
|              "format neutral Order Types 'FUL' for upload " | ||||
|              "and 'FDL' for download.") | ||||
|     download_process_method = fields.Selection( | ||||
|         selection='_selection_download_process_method', | ||||
|         help="Enable processing within Odoo of the downloaded file " | ||||
|              "via the 'Process' button." | ||||
|              "E.g. specify camt.053 to import a camt.053 file and create " | ||||
|              "a bank statement.") | ||||
|     # TODO: | ||||
|     # move signature_class parameter so that it can be set per EBICS config | ||||
|     signature_class = fields.Selection( | ||||
|         selection=[('E', 'Single signature'), | ||||
|                    ('T', 'Transport signature')], | ||||
|         string='Signature Class', | ||||
|         help="Please doublecheck the security of your Odoo " | ||||
|              "ERP system when using class 'E' to prevent unauthorised " | ||||
|              "users to make supplier payments." | ||||
|              "\nLeave this field empty to use the default " | ||||
|              "defined for your EBICS UserID.") | ||||
|     description = fields.Char() | ||||
|     suffix = fields.Char( | ||||
|         required=True, | ||||
|         help="Specify the filename suffix for this File Format." | ||||
|              "\nE.g. c53.xml") | ||||
|  | ||||
|     @api.model | ||||
|     def _selection_download_process_method(self): | ||||
|         methods = self.env['ebics.file']._file_format_methods().keys() | ||||
|         return [(x, x) for x in methods] | ||||
|  | ||||
|     @api.onchange('type') | ||||
|     def _onchange_type(self): | ||||
|         if self.type == 'up': | ||||
|             self.download_process_method = False | ||||
							
								
								
									
										466
									
								
								account_ebics/models/ebics_userid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										466
									
								
								account_ebics/models/ebics_userid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,466 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| import base64 | ||||
| import logging | ||||
| import os | ||||
| from sys import exc_info | ||||
| from traceback import format_exception | ||||
| from urllib.error import URLError | ||||
|  | ||||
| from odoo import _, api, fields, models | ||||
| from odoo.exceptions import UserError | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| """ | ||||
| logging.basicConfig( | ||||
|     level=logging.DEBUG, | ||||
|     format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s') | ||||
| """ | ||||
|  | ||||
|  | ||||
| try: | ||||
|     import fintech | ||||
|     from fintech.ebics import EbicsKeyRing, EbicsBank, EbicsUser,\ | ||||
|         EbicsClient, EbicsFunctionalError, EbicsTechnicalError | ||||
|     fintech.cryptolib = 'cryptography' | ||||
| except ImportError: | ||||
|     _logger.warning('Failed to import fintech') | ||||
|  | ||||
|  | ||||
| class EbicsBank(EbicsBank): | ||||
|  | ||||
|     def _next_order_id(self, partnerid): | ||||
|         """ | ||||
|         EBICS protocol version H003 requires generation of the OrderID. | ||||
|         The OrderID must be a string between 'A000' and 'ZZZZ' and | ||||
|         unique for each partner id. | ||||
|         """ | ||||
|         return hasattr(self, '_order_number') and self._order_number or 'A000' | ||||
|  | ||||
|  | ||||
| class EbicsUserID(models.Model): | ||||
|     _name = 'ebics.userid' | ||||
|     _description = 'EBICS UserID' | ||||
|     _order = 'name' | ||||
|  | ||||
|     name = fields.Char( | ||||
|         string='EBICS UserID', required=True, | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="Human users or a technical system that is/are " | ||||
|              "assigned to a customer. " | ||||
|              "\nOn the EBICS bank server it is identified " | ||||
|              "by the combination of UserID and PartnerID. " | ||||
|              "The technical subscriber serves only for the data exchange " | ||||
|              "between customer and financial institution. " | ||||
|              "The human user also can authorise orders.") | ||||
|     ebics_config_id = fields.Many2one( | ||||
|         comodel_name='ebics.config', | ||||
|         string='EBICS Configuration', | ||||
|         ondelete='cascade') | ||||
|     user_ids = fields.Many2many( | ||||
|         comodel_name='res.users', | ||||
|         string='Users', | ||||
|         required=True, | ||||
|         help="Users who are allowed to use this EBICS UserID for " | ||||
|              " bank transactions.") | ||||
|     # Currently only a singe signature class per user is supported | ||||
|     # Classes A and B are not yet supported. | ||||
|     signature_class = fields.Selection( | ||||
|         selection=[('E', 'Single signature'), | ||||
|                    ('T', 'Transport signature')], | ||||
|         string='Signature Class', | ||||
|         required=True, default='T', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|         help="Default signature class." | ||||
|              "This default can be overriden for specific " | ||||
|              "EBICS transactions (cf. File Formats).") | ||||
|     ebics_keys_fn = fields.Char( | ||||
|         compute='_compute_ebics_keys_fn') | ||||
|     ebics_keys_found = fields.Boolean( | ||||
|         compute='_compute_ebics_keys_found') | ||||
|     ebics_passphrase = fields.Char( | ||||
|         string='EBICS Passphrase') | ||||
|     ebics_ini_letter = fields.Binary( | ||||
|         string='EBICS INI Letter', readonly=True, | ||||
|         help="INI-letter PDF document to be sent to your bank.") | ||||
|     ebics_ini_letter_fn = fields.Char( | ||||
|         string='INI-letter Filename', readonly=True) | ||||
|     ebics_public_bank_keys = fields.Binary( | ||||
|         string='EBICS Public Bank Keys', readonly=True, | ||||
|         help="EBICS Public Bank Keys to be checked for consistency.") | ||||
|     ebics_public_bank_keys_fn = fields.Char( | ||||
|         string='EBICS Public Bank Keys Filename', readonly=True) | ||||
|     swift_3skey = fields.Boolean( | ||||
|         string='Enable 3SKey support', | ||||
|         help="Transactions for this user will be signed " | ||||
|              "by means of the SWIFT 3SKey token.") | ||||
|     swift_3skey_certificate = fields.Binary( | ||||
|         string='3SKey Certficate') | ||||
|     swift_3skey_certificate_fn = fields.Char( | ||||
|         string='EBICS Public Bank Keys Filename') | ||||
|     # X.509 Distinguished Name attributes used to | ||||
|     # create self-signed X.509 certificates | ||||
|     ebics_key_x509 = fields.Boolean( | ||||
|         string='X509 support', | ||||
|         help="Set this flag in order to work with " | ||||
|              "self-signed X.509 certificates") | ||||
|     ebics_key_x509_dn_cn = fields.Char( | ||||
|         string='Common Name [CN]', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     ebics_key_x509_dn_o = fields.Char( | ||||
|         string='Organization Name [O]', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     ebics_key_x509_dn_ou = fields.Char( | ||||
|         string='Organizational Unit Name [OU]', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     ebics_key_x509_dn_c = fields.Char( | ||||
|         string='Country Name [C]', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     ebics_key_x509_dn_st = fields.Char( | ||||
|         string='State Or Province Name [ST]', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     ebics_key_x509_dn_l = fields.Char( | ||||
|         string='Locality Name [L]', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     ebics_key_x509_dn_e = fields.Char( | ||||
|         string='Email Address', | ||||
|         readonly=True, states={'draft': [('readonly', False)]}, | ||||
|     ) | ||||
|     state = fields.Selection( | ||||
|         [('draft', 'Draft'), | ||||
|          ('init', 'Initialisation'), | ||||
|          ('get_bank_keys', 'Get Keys from Bank'), | ||||
|          ('to_verify', 'Verification'), | ||||
|          ('active_keys', 'Active Keys')], | ||||
|         string='State', | ||||
|         default='draft', | ||||
|         required=True, readonly=True) | ||||
|     active = fields.Boolean( | ||||
|         string='Active', default=True) | ||||
|     company_ids = fields.Many2many( | ||||
|         comodel_name='res.company', | ||||
|         string='Companies', | ||||
|         required=True, | ||||
|         help="Companies sharing this EBICS contract.") | ||||
|  | ||||
|     @api.depends('name') | ||||
|     def _compute_ebics_keys_fn(self): | ||||
|         for rec in self: | ||||
|             keys_dir = rec.ebics_config_id.ebics_keys | ||||
|             rec.ebics_keys_fn = ( | ||||
|                 rec.name | ||||
|                 and keys_dir | ||||
|                 and (keys_dir + '/' + rec.name + '_keys')) | ||||
|  | ||||
|     @api.depends('ebics_keys_fn') | ||||
|     def _compute_ebics_keys_found(self): | ||||
|         for rec in self: | ||||
|             rec.ebics_keys_found = ( | ||||
|                 rec.ebics_keys_fn | ||||
|                 and os.path.isfile(rec.ebics_keys_fn) | ||||
|             ) | ||||
|  | ||||
|     @api.constrains('ebics_passphrase') | ||||
|     def _check_ebics_passphrase(self): | ||||
|         for rec in self: | ||||
|             if not rec.ebics_passphrase or len(rec.ebics_passphrase) < 8: | ||||
|                 raise UserError(_( | ||||
|                     "The passphrase must be at least 8 characters long")) | ||||
|  | ||||
|     @api.onchange('signature_class') | ||||
|     def _onchange_signature_class(self): | ||||
|         if self.signature_class == 'T': | ||||
|             self.swift_3skey = False | ||||
|  | ||||
|     @api.onchange('swift_3skey') | ||||
|     def _onchange_swift_3skey(self): | ||||
|         if self.swift_3skey: | ||||
|             self.ebics_key_x509 = True | ||||
|  | ||||
|     def set_to_draft(self): | ||||
|         return self.write({'state': 'draft'}) | ||||
|  | ||||
|     def set_to_active_keys(self): | ||||
|         return self.write({'state': 'active_keys'}) | ||||
|  | ||||
|     def set_to_get_bank_keys(self): | ||||
|         return self.write({'state': 'get_bank_keys'}) | ||||
|  | ||||
|     def ebics_init_1(self): | ||||
|         """ | ||||
|         Initialization of bank keys - Step 1: | ||||
|         Create new keys and certificates for this user | ||||
|         """ | ||||
|         self.ensure_one() | ||||
|         self.ebics_config_id._check_ebics_files() | ||||
|         if self.state != 'draft': | ||||
|             raise UserError( | ||||
|                 _("Set state to 'draft' before Bank Key (re)initialisation.")) | ||||
|  | ||||
|         if not self.ebics_passphrase: | ||||
|             raise UserError( | ||||
|                 _("Set a passphrase.")) | ||||
|  | ||||
|         if self.swift_3skey and not self.swift_3skey_certificate: | ||||
|             raise UserError( | ||||
|                 _("3SKey certificate missing.")) | ||||
|  | ||||
|         ebics_version = self.ebics_config_id.ebics_version | ||||
|         try: | ||||
|             keyring = EbicsKeyRing( | ||||
|                 keys=self.ebics_keys_fn, | ||||
|                 passphrase=self.ebics_passphrase) | ||||
|             bank = EbicsBank( | ||||
|                 keyring=keyring, | ||||
|                 hostid=self.ebics_config_id.ebics_host, | ||||
|                 url=self.ebics_config_id.ebics_url) | ||||
|             user = EbicsUser( | ||||
|                 keyring=keyring, | ||||
|                 partnerid=self.ebics_config_id.ebics_partner, | ||||
|                 userid=self.name) | ||||
|         except Exception: | ||||
|             exctype, value = exc_info()[:2] | ||||
|             error = _("EBICS Initialisation Error:") | ||||
|             error += '\n' + str(exctype) + '\n' + str(value) | ||||
|             raise UserError(error) | ||||
|  | ||||
|         self.ebics_config_id._check_ebics_keys() | ||||
|         if not os.path.isfile(self.ebics_keys_fn): | ||||
|             try: | ||||
|                 # TODO: | ||||
|                 # enable import of all type of certicates: A00x, X002, E002 | ||||
|                 if self.swift_3skey: | ||||
|                     kwargs = { | ||||
|                         self.ebics_config_id.ebics_key_version: | ||||
|                         base64.decodestring(self.swift_3skey_certificate), | ||||
|                     } | ||||
|                     user.import_certificates(**kwargs) | ||||
|                 user.create_keys( | ||||
|                     keyversion=self.ebics_config_id.ebics_key_version, | ||||
|                     bitlength=self.ebics_config_id.ebics_key_bitlength) | ||||
|             except Exception: | ||||
|                 exctype, value = exc_info()[:2] | ||||
|                 error = _("EBICS Initialisation Error:") | ||||
|                 error += '\n' + str(exctype) + '\n' + str(value) | ||||
|                 raise UserError(error) | ||||
|  | ||||
|         if self.swift_3skey and not self.ebics_key_x509: | ||||
|             raise UserError(_( | ||||
|                 "The current version of this module " | ||||
|                 "requires to X509 support when enabling 3SKey")) | ||||
|  | ||||
|         if self.ebics_key_x509: | ||||
|             dn_attrs = { | ||||
|                 'commonName': self.ebics_key_x509_dn_cn, | ||||
|                 'organizationName': self.ebics_key_x509_dn_o, | ||||
|                 'organizationalUnitName': self.ebics_key_x509_dn_ou, | ||||
|                 'countryName': self.ebics_key_x509_dn_c, | ||||
|                 'stateOrProvinceName': self.ebics_key_x509_dn_st, | ||||
|                 'localityName': self.ebics_key_x509_dn_l, | ||||
|                 'emailAddress': self.ebics_key_x509_dn_e, | ||||
|             } | ||||
|             kwargs = {k: v for k, v in dn_attrs.items() if v} | ||||
|             user.create_certificates(**kwargs) | ||||
|  | ||||
|         client = EbicsClient( | ||||
|             bank, user, version=ebics_version) | ||||
|  | ||||
|         # Send the public electronic signature key to the bank. | ||||
|         ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id | ||||
|         if not ebics_config_bank: | ||||
|             raise UserError(_( | ||||
|                 "No bank defined for the financial journal " | ||||
|                 "of the EBICS Config")) | ||||
|         try: | ||||
|             supported_versions = client.HEV() | ||||
|             if ebics_version not in supported_versions: | ||||
|                 err_msg = _("EBICS version mismatch.") + "\n" | ||||
|                 err_msg += _("Versions supported by your bank:") | ||||
|                 for k in supported_versions: | ||||
|                     err_msg += "\n%s: %s " % (k, supported_versions[k]) | ||||
|                 raise UserError(err_msg) | ||||
|             if ebics_version == 'H003': | ||||
|                 bank._order_number = self.ebics_config_id._get_order_number() | ||||
|             OrderID = client.INI() | ||||
|             _logger.info( | ||||
|                 '%s, EBICS INI command, OrderID=%s', self._name, OrderID) | ||||
|             if ebics_version == 'H003': | ||||
|                 self.ebics_config_id._update_order_number(OrderID) | ||||
|         except URLError: | ||||
|             exctype, value = exc_info()[:2] | ||||
|             tb = "".join(format_exception(*exc_info())) | ||||
|             _logger.error( | ||||
|                 "EBICS INI command error\nUserID: %s\n%s", | ||||
|                 self.name, | ||||
|                 tb, | ||||
|             ) | ||||
|             raise UserError(_( | ||||
|                 "urlopen error:\n url '%s' - %s") | ||||
|                 % (self.ebics_config_id.ebics_url, str(value))) | ||||
|         except EbicsFunctionalError: | ||||
|             e = exc_info() | ||||
|             error = _("EBICS Functional Error:") | ||||
|             error += '\n' | ||||
|             error += '%s (code: %s)' % (e[1].message, e[1].code) | ||||
|             raise UserError(error) | ||||
|         except EbicsTechnicalError: | ||||
|             e = exc_info() | ||||
|             error = _("EBICS Technical Error:") | ||||
|             error += '\n' | ||||
|             error += '%s (code: %s)' % (e[1].message, e[1].code) | ||||
|             raise UserError(error) | ||||
|  | ||||
|         # Send the public authentication and encryption keys to the bank. | ||||
|         if ebics_version == 'H003': | ||||
|             bank._order_number = self.ebics_config_id._get_order_number() | ||||
|         OrderID = client.HIA() | ||||
|         _logger.info('%s, EBICS HIA command, OrderID=%s', self._name, OrderID) | ||||
|         if ebics_version == 'H003': | ||||
|             self.ebics_config_id._update_order_number(OrderID) | ||||
|  | ||||
|         # Create an INI-letter which must be printed and sent to the bank. | ||||
|         ebics_config_bank = self.ebics_config_id.journal_ids[0].bank_id | ||||
|         cc = ebics_config_bank.country.code | ||||
|         if cc in ['FR', 'DE']: | ||||
|             lang = cc | ||||
|         else: | ||||
|             lang = self.env.user.lang or \ | ||||
|                 self.env['res.lang'].search([])[0].code | ||||
|             lang = lang[:2] | ||||
|         tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + '/tmp') | ||||
|         if not os.path.isdir(tmp_dir): | ||||
|             os.makedirs(tmp_dir, mode=0o700) | ||||
|         fn_date = fields.Date.today().isoformat() | ||||
|         fn = '_'.join( | ||||
|             [self.ebics_config_id.ebics_host, 'ini_letter', fn_date]) + '.pdf' | ||||
|         full_tmp_fn = os.path.normpath(tmp_dir + '/' + fn) | ||||
|         user.create_ini_letter( | ||||
|             bankname=ebics_config_bank.name, | ||||
|             path=full_tmp_fn, | ||||
|             lang=lang) | ||||
|         with open(full_tmp_fn, 'rb') as f: | ||||
|             letter = f.read() | ||||
|             self.write({ | ||||
|                 'ebics_ini_letter': base64.encodebytes(letter), | ||||
|                 'ebics_ini_letter_fn': fn, | ||||
|             }) | ||||
|  | ||||
|         return self.write({'state': 'init'}) | ||||
|  | ||||
|     def ebics_init_2(self): | ||||
|         """ | ||||
|         Initialization of bank keys - Step 2: | ||||
|         Activation of the account by the bank. | ||||
|         """ | ||||
|         if self.state != 'init': | ||||
|             raise UserError( | ||||
|                 _("Set state to 'Initialisation'.")) | ||||
|         self.ensure_one() | ||||
|         return self.write({'state': 'get_bank_keys'}) | ||||
|  | ||||
|     def ebics_init_3(self): | ||||
|         """ | ||||
|         Initialization of bank keys - Step 3: | ||||
|  | ||||
|         After the account has been activated the public bank keys | ||||
|         must be downloaded and checked for consistency. | ||||
|         """ | ||||
|         self.ensure_one() | ||||
|         self.ebics_config_id._check_ebics_files() | ||||
|         if self.state != 'get_bank_keys': | ||||
|             raise UserError( | ||||
|                 _("Set state to 'Get Keys from Bank'.")) | ||||
|         try: | ||||
|             keyring = EbicsKeyRing( | ||||
|                 keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase) | ||||
|             bank = EbicsBank( | ||||
|                 keyring=keyring, | ||||
|                 hostid=self.ebics_config_id.ebics_host, | ||||
|                 url=self.ebics_config_id.ebics_url) | ||||
|             user = EbicsUser( | ||||
|                 keyring=keyring, | ||||
|                 partnerid=self.ebics_config_id.ebics_partner, | ||||
|                 userid=self.name) | ||||
|             client = EbicsClient( | ||||
|                 bank, user, version=self.ebics_config_id.ebics_version) | ||||
|         except Exception: | ||||
|             exctype, value = exc_info()[:2] | ||||
|             error = _("EBICS Initialisation Error:") | ||||
|             error += '\n' + str(exctype) + '\n' + str(value) | ||||
|             raise UserError(error) | ||||
|  | ||||
|         try: | ||||
|             public_bank_keys = client.HPB() | ||||
|         except EbicsFunctionalError: | ||||
|             e = exc_info() | ||||
|             error = _("EBICS Functional Error:") | ||||
|             error += '\n' | ||||
|             error += '%s (code: %s)' % (e[1].message, e[1].code) | ||||
|             raise UserError(error) | ||||
|         except Exception: | ||||
|             exctype, value = exc_info()[:2] | ||||
|             error = _("EBICS Initialisation Error:") | ||||
|             error += '\n' + str(exctype) + '\n' + str(value) | ||||
|             raise UserError(error) | ||||
|  | ||||
|         public_bank_keys = public_bank_keys.encode() | ||||
|         tmp_dir = os.path.normpath(self.ebics_config_id.ebics_files + '/tmp') | ||||
|         if not os.path.isdir(tmp_dir): | ||||
|             os.makedirs(tmp_dir, mode=0o700) | ||||
|         fn_date = fields.Date.today().isoformat() | ||||
|         fn = '_'.join( | ||||
|             [self.ebics_config_id.ebics_host, 'public_bank_keys', fn_date] | ||||
|         ) + '.txt' | ||||
|         self.write({ | ||||
|             'ebics_public_bank_keys': base64.encodestring(public_bank_keys), | ||||
|             'ebics_public_bank_keys_fn': fn, | ||||
|             'state': 'to_verify', | ||||
|         }) | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def ebics_init_4(self): | ||||
|         """ | ||||
|         Initialization of bank keys - Step 2: | ||||
|         Confirm Verification of the public bank keys | ||||
|         and activate the bank keyu. | ||||
|         """ | ||||
|         self.ensure_one() | ||||
|         if self.state != 'to_verify': | ||||
|             raise UserError( | ||||
|                 _("Set state to 'Verification'.")) | ||||
|  | ||||
|         keyring = EbicsKeyRing( | ||||
|             keys=self.ebics_keys_fn, passphrase=self.ebics_passphrase) | ||||
|         bank = EbicsBank( | ||||
|             keyring=keyring, | ||||
|             hostid=self.ebics_config_id.ebics_host, | ||||
|             url=self.ebics_config_id.ebics_url) | ||||
|         bank.activate_keys() | ||||
|         return self.write({'state': 'active_keys'}) | ||||
|  | ||||
|     def change_passphrase(self): | ||||
|         self.ensure_one() | ||||
|         ctx = dict(self._context, default_ebics_userid_id=self.id) | ||||
|         module = __name__.split('addons.')[1].split('.')[0] | ||||
|         view = self.env.ref( | ||||
|             '%s.ebics_change_passphrase_view_form' % module) | ||||
|         return { | ||||
|             'name': _('EBICS keys change passphrase'), | ||||
|             'view_type': 'form', | ||||
|             'view_mode': 'form', | ||||
|             'res_model': 'ebics.change.passphrase', | ||||
|             'view_id': view.id, | ||||
|             'target': 'new', | ||||
|             'context': ctx, | ||||
|             'type': 'ir.actions.act_window', | ||||
|         } | ||||
							
								
								
									
										45
									
								
								account_ebics/models/fintech_ebics_register.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								account_ebics/models/fintech_ebics_register.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| import logging | ||||
| from sys import exc_info | ||||
| from traceback import format_exception | ||||
|  | ||||
| from odoo.tools import config | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
| try: | ||||
|     import fintech | ||||
| except ImportError: | ||||
|     fintech = None | ||||
|     _logger.warning('Failed to import fintech') | ||||
|  | ||||
| fintech_register_name = config.get('fintech_register_name') | ||||
| fintech_register_keycode = config.get('fintech_register_keycode') | ||||
| fintech_register_users = config.get('fintech_register_users') | ||||
|  | ||||
| try: | ||||
|     if fintech: | ||||
|         fintech_register_users = ( | ||||
|             fintech_register_users | ||||
|             and [x.strip() for x in fintech_register_users.split(',')] | ||||
|             or None | ||||
|         ) | ||||
|         fintech.cryptolib = 'cryptography' | ||||
|         fintech.register( | ||||
|             name=fintech_register_name, | ||||
|             keycode=fintech_register_keycode, | ||||
|             users=fintech_register_users) | ||||
| except RuntimeError as e: | ||||
|     if str(e) == "'register' can be called only once": | ||||
|         pass | ||||
|     else: | ||||
|         _logger.error(str(e)) | ||||
|         fintech.register() | ||||
| except Exception: | ||||
|     msg = "fintech.register error" | ||||
|     tb = ''.join(format_exception(*exc_info())) | ||||
|     msg += '\n%s' % tb | ||||
|     _logger.error(msg) | ||||
|     fintech.register() | ||||
							
								
								
									
										34
									
								
								account_ebics/security/ebics_security.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								account_ebics/security/ebics_security.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <odoo> | ||||
|  | ||||
|   <record model="res.groups" id="group_ebics_manager"> | ||||
|     <field name="name">EBICS Manager</field> | ||||
|     <field name="category_id" ref="base.module_category_hidden"/> | ||||
|   </record> | ||||
|  | ||||
|   <data noupdate="1"> | ||||
|  | ||||
|     <record id="ebics_config_comp_rule" model="ir.rule"> | ||||
|       <field name="name">EBICS Configuration model company rule</field> | ||||
|       <field name="model_id" ref="model_ebics_config"/> | ||||
|       <field eval="True" name="global"/> | ||||
|       <field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_userid_comp_rule" model="ir.rule"> | ||||
|       <field name="name">EBICS UserID model company rule</field> | ||||
|       <field name="model_id" ref="model_ebics_userid"/> | ||||
|       <field eval="True" name="global"/> | ||||
|       <field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field> | ||||
|     </record> | ||||
|  | ||||
|     <record id="ebics_file_comp_rule" model="ir.rule"> | ||||
|       <field name="name">EBICS File model company rule</field> | ||||
|       <field name="model_id" ref="model_ebics_file"/> | ||||
|       <field eval="True" name="global"/> | ||||
|       <field name="domain_force">['|', ('company_ids', '=', False), ('company_ids', 'in', user.company_ids.ids)]</field> | ||||
|     </record> | ||||
|  | ||||
|   </data> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										12
									
								
								account_ebics/security/ir.model.access.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								account_ebics/security/ir.model.access.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink | ||||
| access_ebics_config_manager,ebics_config manager,model_ebics_config,group_ebics_manager,1,1,1,1 | ||||
| access_ebics_config_user,ebics_config user,model_ebics_config,account.group_account_invoice,1,0,0,0 | ||||
| access_ebics_userid_manager,ebics_userid manager,model_ebics_userid,group_ebics_manager,1,1,1,1 | ||||
| access_ebics_userid_user,ebics_userid user,model_ebics_userid,account.group_account_invoice,1,0,0,0 | ||||
| access_ebics_file_format_manager,ebics_file_format manager,model_ebics_file_format,group_ebics_manager,1,1,1,1 | ||||
| access_ebics_file_format_user,ebics_file_format user,model_ebics_file_format,account.group_account_invoice,1,0,0,0 | ||||
| access_ebics_file_manager,ebics_file manager,model_ebics_file,group_ebics_manager,1,1,1,1 | ||||
| access_ebics_file_user,ebics_file user,model_ebics_file,account.group_account_invoice,1,1,1,0 | ||||
|  | ||||
| access_ebics_change_passphrase,access_ebics_change_passphrase,model_ebics_change_passphrase,group_ebics_manager,1,1,1,0 | ||||
| access_ebics_xfer,access_ebics_xfer,model_ebics_xfer,account.group_account_invoice,1,1,1,0 | ||||
| 
 | 
							
								
								
									
										
											BIN
										
									
								
								account_ebics/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								account_ebics/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										513
									
								
								account_ebics/static/description/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								account_ebics/static/description/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,513 @@ | ||||
| <?xml version="1.0" encoding="utf-8" ?> | ||||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||||
| <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | ||||
| <head> | ||||
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||||
| <meta name="generator" content="Docutils 0.12: http://docutils.sourceforge.net/" /> | ||||
| <title></title> | ||||
| <style type="text/css"> | ||||
|  | ||||
| /* | ||||
| :Author: David Goodger (goodger@python.org) | ||||
| :Id: $Id: html4css1.css 7614 2013-02-21 15:55:51Z milde $ | ||||
| :Copyright: This stylesheet has been placed in the public domain. | ||||
|  | ||||
| Default cascading style sheet for the HTML output of Docutils. | ||||
|  | ||||
| See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to | ||||
| customize this style sheet. | ||||
| */ | ||||
|  | ||||
| /* used to remove borders from tables and images */ | ||||
| .borderless, table.borderless td, table.borderless th { | ||||
|   border: 0 } | ||||
|  | ||||
| table.borderless td, table.borderless th { | ||||
|   /* Override padding for "table.docutils td" with "! important". | ||||
|      The right padding separates the table cells. */ | ||||
|   padding: 0 0.5em 0 0 ! important } | ||||
|  | ||||
| .first { | ||||
|   /* Override more specific margin styles with "! important". */ | ||||
|   margin-top: 0 ! important } | ||||
|  | ||||
| .last, .with-subtitle { | ||||
|   margin-bottom: 0 ! important } | ||||
|  | ||||
| .hidden { | ||||
|   display: none } | ||||
|  | ||||
| a.toc-backref { | ||||
|   text-decoration: none ; | ||||
|   color: black } | ||||
|  | ||||
| blockquote.epigraph { | ||||
|   margin: 2em 5em ; } | ||||
|  | ||||
| dl.docutils dd { | ||||
|   margin-bottom: 0.5em } | ||||
|  | ||||
| object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { | ||||
|   overflow: hidden; | ||||
| } | ||||
|  | ||||
| /* Uncomment (and remove this text!) to get bold-faced definition list terms | ||||
| dl.docutils dt { | ||||
|   font-weight: bold } | ||||
| */ | ||||
|  | ||||
| div.abstract { | ||||
|   margin: 2em 5em } | ||||
|  | ||||
| div.abstract p.topic-title { | ||||
|   font-weight: bold ; | ||||
|   text-align: center } | ||||
|  | ||||
| div.admonition, div.attention, div.caution, div.danger, div.error, | ||||
| div.hint, div.important, div.note, div.tip, div.warning { | ||||
|   margin: 2em ; | ||||
|   border: medium outset ; | ||||
|   padding: 1em } | ||||
|  | ||||
| div.admonition p.admonition-title, div.hint p.admonition-title, | ||||
| div.important p.admonition-title, div.note p.admonition-title, | ||||
| div.tip p.admonition-title { | ||||
|   font-weight: bold ; | ||||
|   font-family: sans-serif } | ||||
|  | ||||
| div.attention p.admonition-title, div.caution p.admonition-title, | ||||
| div.danger p.admonition-title, div.error p.admonition-title, | ||||
| div.warning p.admonition-title, .code .error { | ||||
|   color: red ; | ||||
|   font-weight: bold ; | ||||
|   font-family: sans-serif } | ||||
|  | ||||
| /* Uncomment (and remove this text!) to get reduced vertical space in | ||||
|    compound paragraphs. | ||||
| div.compound .compound-first, div.compound .compound-middle { | ||||
|   margin-bottom: 0.5em } | ||||
|  | ||||
| div.compound .compound-last, div.compound .compound-middle { | ||||
|   margin-top: 0.5em } | ||||
| */ | ||||
|  | ||||
| div.dedication { | ||||
|   margin: 2em 5em ; | ||||
|   text-align: center ; | ||||
|   font-style: italic } | ||||
|  | ||||
| div.dedication p.topic-title { | ||||
|   font-weight: bold ; | ||||
|   font-style: normal } | ||||
|  | ||||
| div.figure { | ||||
|   margin-left: 2em ; | ||||
|   margin-right: 2em } | ||||
|  | ||||
| div.footer, div.header { | ||||
|   clear: both; | ||||
|   font-size: smaller } | ||||
|  | ||||
| div.line-block { | ||||
|   display: block ; | ||||
|   margin-top: 1em ; | ||||
|   margin-bottom: 1em } | ||||
|  | ||||
| div.line-block div.line-block { | ||||
|   margin-top: 0 ; | ||||
|   margin-bottom: 0 ; | ||||
|   margin-left: 1.5em } | ||||
|  | ||||
| div.sidebar { | ||||
|   margin: 0 0 0.5em 1em ; | ||||
|   border: medium outset ; | ||||
|   padding: 1em ; | ||||
|   background-color: #ffffee ; | ||||
|   width: 40% ; | ||||
|   float: right ; | ||||
|   clear: right } | ||||
|  | ||||
| div.sidebar p.rubric { | ||||
|   font-family: sans-serif ; | ||||
|   font-size: medium } | ||||
|  | ||||
| div.system-messages { | ||||
|   margin: 5em } | ||||
|  | ||||
| div.system-messages h1 { | ||||
|   color: red } | ||||
|  | ||||
| div.system-message { | ||||
|   border: medium outset ; | ||||
|   padding: 1em } | ||||
|  | ||||
| div.system-message p.system-message-title { | ||||
|   color: red ; | ||||
|   font-weight: bold } | ||||
|  | ||||
| div.topic { | ||||
|   margin: 2em } | ||||
|  | ||||
| h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, | ||||
| h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { | ||||
|   margin-top: 0.4em } | ||||
|  | ||||
| h1.title { | ||||
|   text-align: center } | ||||
|  | ||||
| h2.subtitle { | ||||
|   text-align: center } | ||||
|  | ||||
| hr.docutils { | ||||
|   width: 75% } | ||||
|  | ||||
| img.align-left, .figure.align-left, object.align-left { | ||||
|   clear: left ; | ||||
|   float: left ; | ||||
|   margin-right: 1em } | ||||
|  | ||||
| img.align-right, .figure.align-right, object.align-right { | ||||
|   clear: right ; | ||||
|   float: right ; | ||||
|   margin-left: 1em } | ||||
|  | ||||
| img.align-center, .figure.align-center, object.align-center { | ||||
|   display: block; | ||||
|   margin-left: auto; | ||||
|   margin-right: auto; | ||||
| } | ||||
|  | ||||
| .align-left { | ||||
|   text-align: left } | ||||
|  | ||||
| .align-center { | ||||
|   clear: both ; | ||||
|   text-align: center } | ||||
|  | ||||
| .align-right { | ||||
|   text-align: right } | ||||
|  | ||||
| /* reset inner alignment in figures */ | ||||
| div.align-right { | ||||
|   text-align: inherit } | ||||
|  | ||||
| /* div.align-center * { */ | ||||
| /*   text-align: left } */ | ||||
|  | ||||
| ol.simple, ul.simple { | ||||
|   margin-bottom: 1em } | ||||
|  | ||||
| ol.arabic { | ||||
|   list-style: decimal } | ||||
|  | ||||
| ol.loweralpha { | ||||
|   list-style: lower-alpha } | ||||
|  | ||||
| ol.upperalpha { | ||||
|   list-style: upper-alpha } | ||||
|  | ||||
| ol.lowerroman { | ||||
|   list-style: lower-roman } | ||||
|  | ||||
| ol.upperroman { | ||||
|   list-style: upper-roman } | ||||
|  | ||||
| p.attribution { | ||||
|   text-align: right ; | ||||
|   margin-left: 50% } | ||||
|  | ||||
| p.caption { | ||||
|   font-style: italic } | ||||
|  | ||||
| p.credits { | ||||
|   font-style: italic ; | ||||
|   font-size: smaller } | ||||
|  | ||||
| p.label { | ||||
|   white-space: nowrap } | ||||
|  | ||||
| p.rubric { | ||||
|   font-weight: bold ; | ||||
|   font-size: larger ; | ||||
|   color: maroon ; | ||||
|   text-align: center } | ||||
|  | ||||
| p.sidebar-title { | ||||
|   font-family: sans-serif ; | ||||
|   font-weight: bold ; | ||||
|   font-size: larger } | ||||
|  | ||||
| p.sidebar-subtitle { | ||||
|   font-family: sans-serif ; | ||||
|   font-weight: bold } | ||||
|  | ||||
| p.topic-title { | ||||
|   font-weight: bold } | ||||
|  | ||||
| pre.address { | ||||
|   margin-bottom: 0 ; | ||||
|   margin-top: 0 ; | ||||
|   font: inherit } | ||||
|  | ||||
| pre.literal-block, pre.doctest-block, pre.math, pre.code { | ||||
|   margin-left: 2em ; | ||||
|   margin-right: 2em } | ||||
|  | ||||
| pre.code .ln { color: grey; } /* line numbers */ | ||||
| pre.code, code { background-color: #eeeeee } | ||||
| pre.code .comment, code .comment { color: #5C6576 } | ||||
| pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } | ||||
| pre.code .literal.string, code .literal.string { color: #0C5404 } | ||||
| pre.code .name.builtin, code .name.builtin { color: #352B84 } | ||||
| pre.code .deleted, code .deleted { background-color: #DEB0A1} | ||||
| pre.code .inserted, code .inserted { background-color: #A3D289} | ||||
|  | ||||
| span.classifier { | ||||
|   font-family: sans-serif ; | ||||
|   font-style: oblique } | ||||
|  | ||||
| span.classifier-delimiter { | ||||
|   font-family: sans-serif ; | ||||
|   font-weight: bold } | ||||
|  | ||||
| span.interpreted { | ||||
|   font-family: sans-serif } | ||||
|  | ||||
| span.option { | ||||
|   white-space: nowrap } | ||||
|  | ||||
| span.pre { | ||||
|   white-space: pre } | ||||
|  | ||||
| span.problematic { | ||||
|   color: red } | ||||
|  | ||||
| span.section-subtitle { | ||||
|   /* font-size relative to parent (h1..h6 element) */ | ||||
|   font-size: 80% } | ||||
|  | ||||
| table.citation { | ||||
|   border-left: solid 1px gray; | ||||
|   margin-left: 1px } | ||||
|  | ||||
| table.docinfo { | ||||
|   margin: 2em 4em } | ||||
|  | ||||
| table.docutils { | ||||
|   margin-top: 0.5em ; | ||||
|   margin-bottom: 0.5em } | ||||
|  | ||||
| table.footnote { | ||||
|   border-left: solid 1px black; | ||||
|   margin-left: 1px } | ||||
|  | ||||
| table.docutils td, table.docutils th, | ||||
| table.docinfo td, table.docinfo th { | ||||
|   padding-left: 0.5em ; | ||||
|   padding-right: 0.5em ; | ||||
|   vertical-align: top } | ||||
|  | ||||
| table.docutils th.field-name, table.docinfo th.docinfo-name { | ||||
|   font-weight: bold ; | ||||
|   text-align: left ; | ||||
|   white-space: nowrap ; | ||||
|   padding-left: 0 } | ||||
|  | ||||
| /* "booktabs" style (no vertical lines) */ | ||||
| table.docutils.booktabs { | ||||
|   border: 0px; | ||||
|   border-top: 2px solid; | ||||
|   border-bottom: 2px solid; | ||||
|   border-collapse: collapse; | ||||
| } | ||||
| table.docutils.booktabs * { | ||||
|   border: 0px; | ||||
| } | ||||
| table.docutils.booktabs th { | ||||
|   border-bottom: thin solid; | ||||
|   text-align: left; | ||||
| } | ||||
|  | ||||
| h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, | ||||
| h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { | ||||
|   font-size: 100% } | ||||
|  | ||||
| ul.auto-toc { | ||||
|   list-style-type: none } | ||||
|  | ||||
| </style> | ||||
| </head> | ||||
| <body> | ||||
| <div class="document"> | ||||
|  | ||||
|  | ||||
| <a class="reference external image-reference" href="https://www.gnu.org/licenses/lpgl"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> | ||||
| <div class="section" id="ebics-banking-protocol"> | ||||
| <h1>EBICS banking protocol</h1> | ||||
| <p>Implementation of the  EBICS banking protocol.</p> | ||||
| <p>This module facilitates the exchange of files with banks via the EBICS protocol.</p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <div class="section" id="installation"> | ||||
| <h2>Installation</h2> | ||||
| <p>The module depends upon</p> | ||||
| <ul class="simple"> | ||||
| <li><a class="reference external" href="https://pypi.python.org/pypi/fintech">https://pypi.python.org/pypi/fintech</a></li> | ||||
| <li><a class="reference external" href="https://pypi.python.org/pypi/cryptography">https://pypi.python.org/pypi/cryptography</a></li> | ||||
| </ul> | ||||
| <p>Remark:</p> | ||||
| <p>The EBICS 'Test Mode' for uploading orders requires Fintech 4.3.4 or higher.</p> | ||||
| <p>SWIFT 3SKey support requires Fintech 6.4 or higher. | ||||
| |</p> | ||||
| <p>We also recommend to consider the installation of the following modules:</p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <ul> | ||||
| <li><p class="first">account_ebics_oe</p> | ||||
| <p>Required if you are running Odoo Enterprise</p> | ||||
| </li> | ||||
| </ul> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <ul> | ||||
| <li><p class="first">account_ebics_batch_payment</p> | ||||
| <p>Recommended if you are using the Odoo Enterprise account_batch_payment module</p> | ||||
| </li> | ||||
| </ul> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <ul> | ||||
| <li><p class="first">account_ebics_payment_order</p> | ||||
| <p>Recommended if you are using the OCA account_payment_order module.</p> | ||||
| <p>Cf. <a class="reference external" href="https://github.com/OCA/bank-payment">https://github.com/OCA/bank-payment</a></p> | ||||
| </li> | ||||
| </ul> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <ul> | ||||
| <li><p class="first">account_bank_statement_import_fr_cfonb</p> | ||||
| <p>Required to handle french CFONB files.</p> | ||||
| <p>Cf. <a class="reference external" href="https://github.com/OCA/l10n_fr">https://github.com/OCA/l10n_fr</a></p> | ||||
| </li> | ||||
| </ul> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <ul> | ||||
| <li><p class="first">account_bank_statement_import_camt_oca</p> | ||||
| <p>Required to handle camt.052 and camt.054 files.</p> | ||||
| <p>Cf. <a class="reference external" href="https://github.com/OCA/bank_statement_import">https://github.com/OCA/bank_statement_import</a></p> | ||||
| </li> | ||||
| </ul> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <ul> | ||||
| <li><p class="first">account_bank_statement_import_helper</p> | ||||
| <p>Required if you are processing bank statements with local bank account numbers (e.g. french CFONB files).</p> | ||||
| <p>The import helper will match the local bank account number with the IBAN number specified on the Odoo Financial journal.</p> | ||||
| <p>Cf. <a class="reference external" href="https://github.com/Noviat/noviat-apps">https://github.com/Noviat/noviat-apps</a></p> | ||||
| </li> | ||||
| </ul> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <div class="section" id="fintech-license"> | ||||
| <h3>Fintech license</h3> | ||||
| <p>If you have a valid Fintech.ebics license, you should add the following | ||||
| licensing parameters to the odoo server configuration file:</p> | ||||
| <ul class="simple"> | ||||
| <li>fintech_register_name</li> | ||||
| </ul> | ||||
| <p>The name of the licensee.</p> | ||||
| <ul class="simple"> | ||||
| <li>fintech_register_keycode</li> | ||||
| </ul> | ||||
| <p>The keycode of the licensed version.</p> | ||||
| <ul class="simple"> | ||||
| <li>fintech_register_users</li> | ||||
| </ul> | ||||
| <p>The licensed EBICS user ids. It must be a string or a list of user ids.</p> | ||||
| <p>You should NOT specify this parameter if your license is subsciption | ||||
| based (with monthly recurring billing).</p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| <div class="line">Example:</div> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <pre class="literal-block"> | ||||
| ; fintech | ||||
| fintech_register_name = MyCompany | ||||
| fintech_register_keycode = AB1CD-E2FG-3H-IJ4K-5L | ||||
| fintech_register_users = USER1, USER2 | ||||
| </pre> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| </div> | ||||
| </div> | ||||
| <div class="section" id="configuration"> | ||||
| <h2>Configuration</h2> | ||||
| <p>Go to <strong>Settings > Users</strong></p> | ||||
| <p>Add the users that are authorised to maintain the EBICS configuration to the 'EBICS Manager' Group.</p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <p>Go to <strong>Accounting > Configuration > Miscellaneous > EBICS > EBICS File Formats</strong></p> | ||||
| <p>Check if the EBICS File formats that you want to process in Odoo are defined.</p> | ||||
| <p>Most commonly used formats for which support is available in Odoo should be there already.</p> | ||||
| <p>Please open an issue on <a class="reference external" href="https://github.com/Noviat/account_ebics">https://github.com/Noviat/account_ebics</a> to report missing EBICS File Formats.</p> | ||||
| <p>For File Formats of type 'Downloads' you can also specifiy a 'Download Process Method'.</p> | ||||
| <p>This is the method that will be executed when hitting the 'Process' button on the downloaded file.</p> | ||||
| <p>The following methods are currently available:</p> | ||||
| <ul class="simple"> | ||||
| <li>cfonb120</li> | ||||
| <li>camt.053</li> | ||||
| <li>camt.052</li> | ||||
| <li>camt.054</li> | ||||
| </ul> | ||||
| <p>All these methods require complimentary modules to be installed (cf. Installation section supra).</p> | ||||
| <p>You'll get an error message when the required module is not installed on your Odoo instance.</p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <p>Go to <strong>Accounting > Configuration > Miscellaneous > EBICS > EBICS Configuration</strong></p> | ||||
| <p>Configure your EBICS configuration according to the contract with your bank.</p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| </div> | ||||
| <div class="section" id="usage"> | ||||
| <h2>Usage</h2> | ||||
| <p>Go to <strong>Accounting > Bank and Cash > EBICS Processing</strong></p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| <div class="section" id="ebics-return-codes"> | ||||
| <h3>EBICS Return Codes</h3> | ||||
| <p>During the processing of your EBICS upload/download, your bank may return an Error Code, e.g.</p> | ||||
| <p>EBICS Functional Error: | ||||
| EBICS_NO_DOWNLOAD_DATA_AVAILABLE (code: 90005)</p> | ||||
| <p>A detailled explanation of the codes can be found on <a class="reference external" href="http://www.ebics.org">http://www.ebics.org</a>. | ||||
| You can also find this information in the doc folder of this module (file EBICS_Annex1_ReturnCodes).</p> | ||||
| <div class="line-block"> | ||||
| <div class="line"><br /></div> | ||||
| </div> | ||||
| </div> | ||||
| </div> | ||||
| <div class="section" id="known-issues-roadmap"> | ||||
| <h2>Known Issues / Roadmap</h2> | ||||
| <ul class="simple"> | ||||
| <li>add support for EBICS 3.0</li> | ||||
| <li>add support to import externally generated keys & certificates (currently only 3SKey signature certificate)</li> | ||||
| </ul> | ||||
| </div> | ||||
| </div> | ||||
| </div> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										74
									
								
								account_ebics/views/ebics_config_views.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								account_ebics/views/ebics_config_views.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| <?xml version="1.0" ?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="ebics_config_view_tree" model="ir.ui.view"> | ||||
|     <field name="name">ebics.config.tree</field> | ||||
|     <field name="model">ebics.config</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <tree string="EBICS Configuration" decoration-muted="state == 'draft'"> | ||||
|         <field name="name"/> | ||||
|         <field name="ebics_host"/> | ||||
|         <field name="state"/> | ||||
|         <field name="active"/> | ||||
|       </tree> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_config_view_form" model="ir.ui.view"> | ||||
|     <field name="name">ebics.config.form</field> | ||||
|     <field name="model">ebics.config</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS Configuration"> | ||||
|         <header> | ||||
|           <button name="set_to_draft" states="confirm" string="Set to Draft" type="object" | ||||
|                   groups="account_ebics.group_ebics_manager" | ||||
|                   help="Set to Draft in order to change the EBICS configuration parameters."/> | ||||
|           <button name="set_to_confirm" states="draft" string="Confirm" type="object" class="oe_highlight" | ||||
|                   groups="account_ebics.group_ebics_manager" | ||||
|                   help="The EBICS configuration must be confirmed before it can used for bank transactions."/> | ||||
|           <field name="state" widget="statusbar"/> | ||||
|         </header> | ||||
|         <field name="active" invisible="1" /> | ||||
|         <widget name="web_ribbon" | ||||
|                 text="Archived" | ||||
|                 bg_color="bg-danger" | ||||
|                 attrs="{'invisible': [('active', '=', True)]}"/> | ||||
|         <group name="main"> | ||||
|           <group name="main-left"> | ||||
|             <field name="name" colspan="2"/> | ||||
|             <field name="ebics_host"/> | ||||
|             <field name="ebics_url"/> | ||||
|             <field name="ebics_partner"/> | ||||
|             <field name="ebics_files"/> | ||||
|             <field name="ebics_keys"/> | ||||
|           </group> | ||||
|           <group name="main-right"> | ||||
|             <field name="journal_ids" widget="many2many_tags" options="{'no_create': True}"/> | ||||
|             <field name="ebics_version"/> | ||||
|             <field name="ebics_key_version"/> | ||||
|             <field name="ebics_key_bitlength"/> | ||||
|             <field name="order_number" | ||||
|                    attrs="{'invisible': [('ebics_version', '=', 'H004')]}"/> | ||||
|           </group> | ||||
|           <field name="company_ids" invisible="1"/> | ||||
|         </group> | ||||
|         <notebook> | ||||
|           <page string="EBICS Users" groups="account_ebics.group_ebics_manager"> | ||||
|             <field name="ebics_userid_ids"/> | ||||
|           </page> | ||||
|           <page string="File Formats" groups="account_ebics.group_ebics_manager"> | ||||
|             <field name="ebics_file_format_ids"/> | ||||
|           </page> | ||||
|         </notebook> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_config_action" model="ir.actions.act_window"> | ||||
|     <field name="name">EBICS Configuration</field> | ||||
|     <field name="res_model">ebics.config</field> | ||||
|     <field name="view_mode">tree,form</field> | ||||
|     <field name="context">{'active_test': False}</field> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										50
									
								
								account_ebics/views/ebics_file_format_views.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								account_ebics/views/ebics_file_format_views.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| <?xml version="1.0" ?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="ebics_file_format_view_tree" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.format.tree</field> | ||||
|     <field name="model">ebics.file.format</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <tree string="EBICS File Formats"> | ||||
|         <field name="type"/> | ||||
|         <field name="order_type"/> | ||||
|         <field name="signature_class"/> | ||||
|         <field name="name"/> | ||||
|         <field name="description"/> | ||||
|       </tree> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_format_view_form" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.format.form</field> | ||||
|     <field name="model">ebics.file.format</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS File Format"> | ||||
|         <group name="main"> | ||||
|           <group name="main-left"> | ||||
|             <field name="type"/> | ||||
|             <field name="suffix"/> | ||||
|             <field name="download_process_method" | ||||
|                    attrs="{'invisible': [('type', '=', 'up')]}" | ||||
|                    force_save="1"/> | ||||
|             <field name="signature_class"/> | ||||
|           </group> | ||||
|           <group name="main-right"> | ||||
|             <field name="order_type"/> | ||||
|             <field name="name"/> | ||||
|           </group> | ||||
|         </group> | ||||
|         <group name="description"> | ||||
|           <field name="description"/> | ||||
|         </group> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_format_action" model="ir.actions.act_window"> | ||||
|     <field name="name">EBICS File Formats</field> | ||||
|     <field name="res_model">ebics.file.format</field> | ||||
|     <field name="view_mode">tree,form</field> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										199
									
								
								account_ebics/views/ebics_file_views.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								account_ebics/views/ebics_file_views.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| <?xml version="1.0" ?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="ebics_file_view_search" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.search</field> | ||||
|     <field name="model">ebics.file</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <search string="Search EBICS Files"> | ||||
|         <group col="10" colspan="4"> | ||||
|           <field name="date_from"/> | ||||
|           <field name="date_to"/> | ||||
|           <field name="name"/> | ||||
|           <field name="format_id"/> | ||||
|           <field name="user_id"/> | ||||
|           <field name="company_ids" widget="selection" groups="base.group_multi_company"/> | ||||
|         </group> | ||||
|         <newline/> | ||||
|         <group expand="0" string="Group By"> | ||||
|           <filter string="File Format" name="file_format" context="{'group_by':'format_id'}"/> | ||||
|           <filter string="State" name="state" context="{'group_by':'state'}"/> | ||||
|           <filter string="User" name="user" context="{'group_by':'user_id'}"/> | ||||
|         </group> | ||||
|       </search> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <!-- Download --> | ||||
|  | ||||
|   <record id="ebics_file_view_tree_download" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.tree</field> | ||||
|     <field name="model">ebics.file</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <tree string="EBICS Files" decoration-muted="state=='draft'" create="false"> | ||||
|         <field name="date" string="Download Date"/> | ||||
|         <field name="name"/> | ||||
|         <field name="date_from"/> | ||||
|         <field name="date_to"/> | ||||
|         <field name="user_id"/> | ||||
|         <field name="state"/> | ||||
|         <field name="format_id"/> | ||||
|         <field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> | ||||
|       </tree> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_view_form_download" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.form</field> | ||||
|     <field name="model">ebics.file</field> | ||||
|     <field name="priority">1</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS File" create="false"> | ||||
|         <header> | ||||
|           <button name="set_to_draft" states="done" string="Set to Draft" type="object" groups="account.group_account_manager"/> | ||||
|           <button name="process" | ||||
|                   class="oe_highlight" | ||||
|                   states="draft" | ||||
|                   string="Process" | ||||
|                   type="object" | ||||
|                   groups="account.group_account_invoice" | ||||
|                   help="Process the EBICS File"/> | ||||
|           <button name="set_to_done" states="draft" string="Set to Done" type="object" groups="account.group_account_manager"/> | ||||
|           <field name="state" widget="statusbar"/> | ||||
|         </header> | ||||
|         <group colspan="4" col="4"> | ||||
|           <field name="date" string="Download Date"/> | ||||
|           <field name="name"/> | ||||
|           <field name="data" filename="name"/> | ||||
|           <field name="format_id"/> | ||||
|           <field name="date_from"/> | ||||
|           <field name="date_to"/> | ||||
|           <field name="user_id"/> | ||||
|           <field name="ebics_userid_id"/> | ||||
|           <field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> | ||||
|         </group> | ||||
|         <notebook> | ||||
|           <page string="Additional Information"> | ||||
|             <field name="note" nolabel="1"/> | ||||
|           </page> | ||||
|           <page string="Bank Statements" attrs="{'invisible':[('bank_statement_ids','=',[])]}"> | ||||
|             <field name="bank_statement_ids" nolabel="1"/> | ||||
|           </page> | ||||
|         </notebook> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_view_form_result" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.process.result</field> | ||||
|     <field name="model">ebics.file</field> | ||||
|     <field name="priority">2</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="Process EBICS File"> | ||||
|         <separator colspan="4" string="Results :" /> | ||||
|         <field name="note_process" colspan="4" nolabel="1" width="850" height="400"/> | ||||
|         <footer> | ||||
|           <button name="action_open_bank_statements" string="View Bank Statement(s)" | ||||
|                   type="object" class="oe_highlight" | ||||
|                   invisible="not context.get('statement_ids')"/> | ||||
|           <button name="button_close" type="object" string="Close"/> | ||||
|         </footer> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_action_download" model="ir.actions.act_window"> | ||||
|     <field name="name">EBICS Download Files</field> | ||||
|     <field name="type">ir.actions.act_window</field> | ||||
|     <field name="res_model">ebics.file</field> | ||||
|     <field name="view_mode">tree,form</field> | ||||
|     <field name="view_id" eval="False"/> | ||||
|     <field name="domain">[('type','=','down')]</field> | ||||
|     <field name="search_view_id" ref="ebics_file_view_search"/> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_action_download_tree" model="ir.actions.act_window.view"> | ||||
|     <field eval="1" name="sequence"/> | ||||
|     <field name="view_mode">tree</field> | ||||
|     <field name="view_id" ref="ebics_file_view_tree_download"/> | ||||
|     <field name="act_window_id" ref="ebics_file_action_download"/> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_action_download_form" model="ir.actions.act_window.view"> | ||||
|     <field eval="2" name="sequence"/> | ||||
|     <field name="view_mode">form</field> | ||||
|     <field name="view_id" ref="ebics_file_view_form_download"/> | ||||
|     <field name="act_window_id" ref="ebics_file_action_download"/> | ||||
|   </record> | ||||
|  | ||||
|   <!-- Upload --> | ||||
|  | ||||
|   <record id="ebics_file_view_tree_upload" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.tree</field> | ||||
|     <field name="model">ebics.file</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <tree string="EBICS Files" decoration-muted="state=='draft'" create="false"> | ||||
|         <field name="date" string="Upload Date"/> | ||||
|         <field name="name"/> | ||||
|         <field name="user_id"/> | ||||
|         <field name="state"/> | ||||
|         <field name="format_id"/> | ||||
|         <field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> | ||||
|       </tree> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_view_form_upload" model="ir.ui.view"> | ||||
|     <field name="name">ebics.file.form</field> | ||||
|     <field name="model">ebics.file</field> | ||||
|     <field name="priority">1</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS File" create="false"> | ||||
|         <header> | ||||
|           <button name="set_to_draft" states="done" string="Set to Draft" type="object" groups="account.group_account_manager"/> | ||||
|           <button name="set_to_done" states="draft" string="Set to Done" type="object" groups="account.group_account_manager"/> | ||||
|           <field name="state" widget="statusbar"/> | ||||
|         </header> | ||||
|         <group colspan="4" col="4"> | ||||
|           <field name="date" string="Upload Date"/> | ||||
|           <field name="name"/> | ||||
|           <field name="data" filename="name"/> | ||||
|           <field name="format_id"/> | ||||
|           <field name="user_id"/> | ||||
|           <field name="ebics_userid_id"/> | ||||
|           <field name="company_ids" widget="many2many_tags" groups="base.group_multi_company"/> | ||||
|         </group> | ||||
|         <notebook> | ||||
|           <page string="Additional Information"> | ||||
|             <field name="note" nolabel="1"/> | ||||
|           </page> | ||||
|         </notebook> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_action_upload" model="ir.actions.act_window"> | ||||
|     <field name="name">EBICS Upload Files</field> | ||||
|     <field name="type">ir.actions.act_window</field> | ||||
|     <field name="res_model">ebics.file</field> | ||||
|     <field name="view_mode">tree,form</field> | ||||
|     <field name="view_id" eval="False"/> | ||||
|     <field name="domain">[('type','=','up')]</field> | ||||
|     <field name="search_view_id" ref="ebics_file_view_search"/> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_action_upload_tree" model="ir.actions.act_window.view"> | ||||
|     <field eval="1" name="sequence"/> | ||||
|     <field name="view_mode">tree</field> | ||||
|     <field name="view_id" ref="ebics_file_view_tree_upload"/> | ||||
|     <field name="act_window_id" ref="ebics_file_action_upload"/> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_file_action_upload_form" model="ir.actions.act_window.view"> | ||||
|     <field eval="2" name="sequence"/> | ||||
|     <field name="view_mode">form</field> | ||||
|     <field name="view_id" ref="ebics_file_view_form_upload"/> | ||||
|     <field name="act_window_id" ref="ebics_file_action_upload"/> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										89
									
								
								account_ebics/views/ebics_userid_views.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								account_ebics/views/ebics_userid_views.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| <?xml version="1.0" ?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="ebics_userid_view_tree" model="ir.ui.view"> | ||||
|     <field name="name">ebics.userid.tree</field> | ||||
|     <field name="model">ebics.userid</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <tree string="EBICS UserID" decoration-muted="state != 'active_keys'"> | ||||
|         <field name="name"/> | ||||
|         <field name="signature_class"/> | ||||
|         <field name="state"/> | ||||
|         <field name="active"/> | ||||
|       </tree> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_userid_view_form" model="ir.ui.view"> | ||||
|     <field name="name">ebics.userid.form</field> | ||||
|     <field name="model">ebics.userid</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS UserID"> | ||||
|         <header groups="account_ebics.group_ebics_manager"> | ||||
|           <button name="ebics_init_1" states="draft" string="EBICS Initialisation" type="object" class="oe_highlight" | ||||
|                   help="Initialise EBICS Bank Keys"/> | ||||
|           <button name="ebics_init_2" states="init" string="Account activated" type="object" class="oe_highlight" | ||||
|                   help="EBICS Initialisation - Push this button when the account has been activated by the bank."/> | ||||
|           <button name="ebics_init_3" states="get_bank_keys" string="Get Bank Keys" type="object" class="oe_highlight" | ||||
|                   help="EBICS Initialisation - After the account has been activated the public bank keys must be downloaded and checked for consistency."/> | ||||
|           <button name="ebics_init_4" states="to_verify" string="Bank Keys Verified" type="object" class="oe_highlight" | ||||
|                   help="EBICS Initialisation - Push this button when the public have been checked for consistency."/> | ||||
|           <button name="change_passphrase" string="Change Passphrase" type="object" class="oe_highlight" | ||||
|                   attrs="{'invisible': [('ebics_keys_found', '=', False)]}"/> | ||||
|           <button name="set_to_draft" states="active_keys" string="Set to Draft" type="object" | ||||
|                   help="Set to Draft in order to reinitialize your bank connection."/> | ||||
|           <button name="set_to_get_bank_keys" states="active_keys" string="Renew Bank Keys" type="object" | ||||
|                   help="Use this button to update the EBICS certificates of your bank."/> | ||||
|           <button name="set_to_active_keys" states="draft" string="Force Active Keys" type="object" | ||||
|                   help="Use this button to bypass the EBICS initialization (e.g. in case you have manually transferred active EBICS keys from another system."/> | ||||
|           <field name="state" widget="statusbar"/> | ||||
|         </header> | ||||
|         <group name="main" attrs="{'readonly': [('state', '!=', 'draft')]}"> | ||||
|           <field name="ebics_keys_found" invisible="1"/> | ||||
|           <field name="ebics_keys_fn" invisible="1"/> | ||||
|           <group name="main-left"> | ||||
|             <field name="name"/> | ||||
|             <field name="ebics_passphrase" password="True" | ||||
|                    attrs="{'required': [('state', '=', 'draft')]}"/> | ||||
|             <field name="swift_3skey" | ||||
|                    attrs="{'invisible': [('signature_class', '=', 'T')]}"/> | ||||
|             <field name="swift_3skey_certificate_fn" invisible="1"/> | ||||
|             <field name="swift_3skey_certificate" filename="swift_3skey_certificate_fn" | ||||
|                    attrs="{'invisible': [('swift_3skey', '=', False)], 'required': [('swift_3skey', '=', True)]}"/> | ||||
|             <field name="active"/> | ||||
|           </group> | ||||
|           <group name="main-right"> | ||||
|             <field name="signature_class"/> | ||||
|             <field name="user_ids" widget="many2many_tags" options="{'no_create': True}"/> | ||||
|             <field name="ebics_key_x509"/> | ||||
|           </group> | ||||
|         </group> | ||||
|         <group col="4" name="dn" attrs="{'invisible': [('ebics_key_x509', '=', False)], 'readonly': [('state', '!=', 'draft')]}"> | ||||
|           <group colspan="4" col="1"> | ||||
|             <strong>Distinguished Name attributes used to create self-signed X.509 certificates:</strong> | ||||
|           </group> | ||||
|           <group name="dn_l" colspan="2"> | ||||
|             <field name="ebics_key_x509_dn_cn"/> | ||||
|             <field name="ebics_key_x509_dn_o"/> | ||||
|             <field name="ebics_key_x509_dn_l"/> | ||||
|             <field name="ebics_key_x509_dn_c"/> | ||||
|           </group> | ||||
|           <group name="dn_r" colspan="2"> | ||||
|             <field name="ebics_key_x509_dn_e"/> | ||||
|             <field name="ebics_key_x509_dn_ou"/> | ||||
|             <field name="ebics_key_x509_dn_st"/> | ||||
|           </group> | ||||
|         </group> | ||||
|         <group colspan="4" name="ebics_ini_letter" attrs="{'invisible': [('ebics_ini_letter', '=', False)]}"> | ||||
|           <field name="ebics_ini_letter_fn" invisible="1"/> | ||||
|           <field name="ebics_ini_letter" filename="ebics_ini_letter_fn"/> | ||||
|         </group> | ||||
|         <group colspan="4" name="ebics_public_bank_keys" attrs="{'invisible': [('ebics_public_bank_keys', '=', False)]}"> | ||||
|           <field name="ebics_public_bank_keys_fn" invisible="1"/> | ||||
|           <field name="ebics_public_bank_keys" filename="ebics_public_bank_keys_fn"/> | ||||
|         </group> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										58
									
								
								account_ebics/views/menu.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								account_ebics/views/menu.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| <?xml version="1.0" ?> | ||||
| <odoo> | ||||
|  | ||||
|   <menuitem id="ebics_processing_menu" | ||||
|             name="EBICS Processing" | ||||
|             parent="account.menu_finance" | ||||
|             sequence="4"/> | ||||
|  | ||||
|   <menuitem id="ebics_xfer_menu_download" | ||||
|             name="EBICS Download" | ||||
|             parent="ebics_processing_menu" | ||||
|             action="ebics_xfer_action_download" | ||||
|             sequence="10"/> | ||||
|  | ||||
|   <menuitem id="ebics_xfer_menu_upload" | ||||
|             name="EBICS Upload" | ||||
|             parent="ebics_processing_menu" | ||||
|             action="ebics_xfer_action_upload" | ||||
|             sequence="20"/> | ||||
|  | ||||
|   <menuitem id="ebics_file_menu" | ||||
|             name="EBICS Files" | ||||
|             parent="ebics_processing_menu" | ||||
|             sequence="30"/> | ||||
|  | ||||
|   <menuitem id="ebics_file_menu_download" | ||||
|             name="Download" | ||||
|             parent="ebics_file_menu" | ||||
|             action="ebics_file_action_download" | ||||
|             sequence="10"/> | ||||
|  | ||||
|   <menuitem id="ebics_file_menu_upload" | ||||
|             name="Upload" | ||||
|             parent="ebics_file_menu" | ||||
|             action="ebics_file_action_upload" | ||||
|             sequence="20"/> | ||||
|  | ||||
|   <menuitem id="ebics_menu" | ||||
|             name="EBICS" | ||||
|             parent='account.menu_finance_configuration' | ||||
|             groups="account_ebics.group_ebics_manager" | ||||
|             sequence="100"/> | ||||
|  | ||||
|   <menuitem id="ebics_config_menu" | ||||
|             name="EBICS Configuration" | ||||
|             parent="ebics_menu" | ||||
|             action="ebics_config_action" | ||||
|             groups="account_ebics.group_ebics_manager" | ||||
|             sequence="10"/> | ||||
|  | ||||
|   <menuitem id="ebics_file_format_menu" | ||||
|             name="EBICS File Formats" | ||||
|             parent="ebics_menu" | ||||
|             action="ebics_file_format_action" | ||||
|             groups="account_ebics.group_ebics_manager" | ||||
|             sequence="20"/> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										2
									
								
								account_ebics/wizards/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								account_ebics/wizards/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| from . import ebics_change_passphrase | ||||
| from . import ebics_xfer | ||||
							
								
								
									
										75
									
								
								account_ebics/wizards/ebics_change_passphrase.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								account_ebics/wizards/ebics_change_passphrase.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from odoo import _, fields, models | ||||
| from odoo.exceptions import UserError | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
| try: | ||||
|     import fintech | ||||
|     from fintech.ebics import EbicsKeyRing | ||||
|     fintech.cryptolib = 'cryptography' | ||||
| except ImportError: | ||||
|     _logger.warning('Failed to import fintech') | ||||
|  | ||||
|  | ||||
| class EbicsChangePassphrase(models.TransientModel): | ||||
|     _name = 'ebics.change.passphrase' | ||||
|     _description = 'Change EBICS keys passphrase' | ||||
|  | ||||
|     ebics_userid_id = fields.Many2one( | ||||
|         comodel_name='ebics.userid', | ||||
|         string='EBICS UserID', | ||||
|         readonly=True) | ||||
|     old_pass = fields.Char( | ||||
|         string='Old Passphrase', | ||||
|         required=True) | ||||
|     new_pass = fields.Char( | ||||
|         string='New Passphrase', | ||||
|         required=True) | ||||
|     new_pass_check = fields.Char( | ||||
|         string='New Passphrase (verification)', | ||||
|         required=True) | ||||
|     note = fields.Text(string='Notes', readonly=True) | ||||
|  | ||||
|     def change_passphrase(self): | ||||
|         self.ensure_one() | ||||
|         if self.old_pass != self.ebics_userid_id.ebics_passphrase: | ||||
|             raise UserError(_( | ||||
|                 "Incorrect old passphrase.")) | ||||
|         if self.new_pass != self.new_pass_check: | ||||
|             raise UserError(_( | ||||
|                 "New passphrase verification error.")) | ||||
|         if self.new_pass == self.ebics_userid_id.ebics_passphrase: | ||||
|             raise UserError(_( | ||||
|                 "New passphrase equal to old passphrase.")) | ||||
|         try: | ||||
|             keyring = EbicsKeyRing( | ||||
|                 keys=self.ebics_userid_id.ebics_keys_fn, | ||||
|                 passphrase=self.ebics_userid_id.ebics_passphrase) | ||||
|             keyring.change_passphrase(self.new_pass) | ||||
|         except ValueError as e: | ||||
|             raise UserError(str(e)) | ||||
|         self.ebics_userid.ebics_passphrase = self.new_pass | ||||
|         self.note = "The EBICS Passphrase has been changed." | ||||
|  | ||||
|         module = __name__.split('addons.')[1].split('.')[0] | ||||
|         result_view = self.env.ref( | ||||
|             '%s.ebics_change_passphrase_view_form_result' % module) | ||||
|         return { | ||||
|             'name': _('EBICS Keys Change Passphrase'), | ||||
|             'res_id': self.id, | ||||
|             'view_type': 'form', | ||||
|             'view_mode': 'form', | ||||
|             'res_model': 'ebics.change.passphrase', | ||||
|             'view_id': result_view.id, | ||||
|             'target': 'new', | ||||
|             'type': 'ir.actions.act_window', | ||||
|         } | ||||
|  | ||||
|     def button_close(self): | ||||
|         self.ensure_one() | ||||
|         return {'type': 'ir.actions.act_window_close'} | ||||
							
								
								
									
										39
									
								
								account_ebics/wizards/ebics_change_passphrase.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								account_ebics/wizards/ebics_change_passphrase.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="ebics_change_passphrase_view_form" model="ir.ui.view"> | ||||
|     <field name="name">EBICS Keys Change Passphrase</field> | ||||
|     <field name="model">ebics.change.passphrase</field> | ||||
|     <field name="priority">1</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS Keys Change Passphrase"> | ||||
|         <group> | ||||
|           <field name="old_pass" password="True"/> | ||||
|           <field name="new_pass" password="True"/> | ||||
|           <field name="new_pass_check" password="True"/> | ||||
|         </group> | ||||
|         <footer> | ||||
|           <button name="change_passphrase" string="Change Passphrase" type="object" class="oe_highlight"/> | ||||
|           or | ||||
|           <button string="Cancel" class="oe_link" special="cancel"/> | ||||
|         </footer> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_change_passphrase_view_form_result" model="ir.ui.view"> | ||||
|     <field name="name">EBICS Keys Change Passphrase</field> | ||||
|     <field name="model">ebics.change.passphrase</field> | ||||
|     <field name="priority">2</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS Keys Change Passphrase"> | ||||
|         <separator colspan="4" string="Results :" /> | ||||
|         <field name="note" colspan="4" nolabel="1" width="850" height="400"/> | ||||
|         <footer> | ||||
|           <button name="button_close" type="object" string="Close"/> | ||||
|         </footer> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										567
									
								
								account_ebics/wizards/ebics_xfer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										567
									
								
								account_ebics/wizards/ebics_xfer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,567 @@ | ||||
| # Copyright 2009-2021 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| """ | ||||
| import logging | ||||
| logging.basicConfig( | ||||
|     level=logging.DEBUG, | ||||
|     format='[%(asctime)s] %(levelname)s - %(name)s: %(message)s') | ||||
| """ | ||||
|  | ||||
| import base64 | ||||
| import logging | ||||
| import os | ||||
| from sys import exc_info | ||||
| from traceback import format_exception | ||||
|  | ||||
| from odoo import api, fields, models, _ | ||||
| from odoo.exceptions import UserError | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
| try: | ||||
|     import fintech | ||||
|     from fintech.ebics import EbicsKeyRing, EbicsBank, EbicsUser, EbicsClient,\ | ||||
|         EbicsFunctionalError, EbicsTechnicalError, EbicsVerificationError | ||||
|     fintech.cryptolib = 'cryptography' | ||||
| except ImportError: | ||||
|     EbicsBank = object | ||||
|     _logger.warning('Failed to import fintech') | ||||
|  | ||||
|  | ||||
| class EbicsBank(EbicsBank): | ||||
|  | ||||
|     def _next_order_id(self, partnerid): | ||||
|         """ | ||||
|         EBICS protocol version H003 requires generation of the OrderID. | ||||
|         The OrderID must be a string between 'A000' and 'ZZZZ' and | ||||
|         unique for each partner id. | ||||
|         """ | ||||
|         return hasattr(self, '_order_number') and self._order_number or 'A000' | ||||
|  | ||||
|  | ||||
| class EbicsXfer(models.TransientModel): | ||||
|     _name = 'ebics.xfer' | ||||
|     _description = 'EBICS file transfer' | ||||
|  | ||||
|     ebics_config_id = fields.Many2one( | ||||
|         comodel_name='ebics.config', | ||||
|         string='EBICS Configuration', | ||||
|         domain=[('state', '=', 'confirm')], | ||||
|         default=lambda self: self._default_ebics_config_id()) | ||||
|     ebics_userid_id = fields.Many2one( | ||||
|         comodel_name='ebics.userid', | ||||
|         string='EBICS UserID') | ||||
|     ebics_passphrase = fields.Char( | ||||
|         string='EBICS Passphrase') | ||||
|     date_from = fields.Date() | ||||
|     date_to = fields.Date() | ||||
|     upload_data = fields.Binary(string='File to Upload') | ||||
|     upload_fname = fields.Char( | ||||
|         string='Upload Filename', default='') | ||||
|     upload_fname_dummy = fields.Char( | ||||
|         related='upload_fname', string='Upload Filename', readonly=True) | ||||
|     format_id = fields.Many2one( | ||||
|         comodel_name='ebics.file.format', | ||||
|         string='EBICS File Format', | ||||
|         help="Select EBICS File Format to upload/download." | ||||
|              "\nLeave blank to download all available files.") | ||||
|     allowed_format_ids = fields.Many2many( | ||||
|         related='ebics_config_id.ebics_file_format_ids', | ||||
|         string='Allowed EBICS File Formats') | ||||
|     order_type = fields.Char( | ||||
|         related='format_id.order_type', | ||||
|         string='Order Type', | ||||
|         help="For most banks in France you should use the " | ||||
|              "format neutral Order Types 'FUL' for upload " | ||||
|              "and 'FDL' for download.") | ||||
|     test_mode = fields.Boolean( | ||||
|         string='Test Mode', | ||||
|         help="Select this option to test if the syntax of " | ||||
|              "the upload file is correct." | ||||
|              "\nThis option is only available for " | ||||
|              "Order Type 'FUL'.") | ||||
|     note = fields.Text(string='EBICS file transfer Log', readonly=True) | ||||
|  | ||||
|     @api.model | ||||
|     def _default_ebics_config_id(self): | ||||
|         cfg_mod = self.env['ebics.config'] | ||||
|         cfg = cfg_mod.search( | ||||
|             [('company_ids', 'in', self.env.user.company_ids.ids), | ||||
|              ('state', '=', 'confirm')]) | ||||
|         if cfg and len(cfg) == 1: | ||||
|             return cfg | ||||
|         else: | ||||
|             return cfg_mod | ||||
|  | ||||
|     @api.onchange('ebics_config_id') | ||||
|     def _onchange_ebics_config_id(self): | ||||
|         ebics_userids = self.ebics_config_id.ebics_userid_ids | ||||
|         if self._context.get('ebics_download'): | ||||
|             download_formats = self.ebics_config_id.ebics_file_format_ids\ | ||||
|                 .filtered(lambda r: r.type == 'down') | ||||
|             if len(download_formats) == 1: | ||||
|                 self.format_id = download_formats | ||||
|             if len(ebics_userids) == 1: | ||||
|                 self.ebics_userid_id = ebics_userids | ||||
|             else: | ||||
|                 transport_users = ebics_userids.filtered( | ||||
|                     lambda r: r.signature_class == 'T') | ||||
|                 if len(transport_users) == 1: | ||||
|                     self.ebics_userid_id = transport_users | ||||
|         else: | ||||
|             upload_formats = self.ebics_config_id.ebics_file_format_ids\ | ||||
|                 .filtered(lambda r: r.type == 'up') | ||||
|             if len(upload_formats) == 1: | ||||
|                 self.format_id = upload_formats | ||||
|             if len(ebics_userids) == 1: | ||||
|                 self.ebics_userid_id = ebics_userids | ||||
|  | ||||
|     @api.onchange('upload_data') | ||||
|     def _onchange_upload_data(self): | ||||
|         self.upload_fname_dummy = self.upload_fname | ||||
|         self.format_id = False | ||||
|         self._detect_upload_format() | ||||
|         if not self.format_id: | ||||
|             upload_formats = self.format_id \ | ||||
|                 or self.ebics_config_id.ebics_file_format_ids.filtered( | ||||
|                     lambda r: r.type == 'up') | ||||
|             if len(upload_formats) > 1: | ||||
|                 upload_formats = upload_formats.filtered( | ||||
|                     lambda r: self.upload_fname.endswith(r.suffix)) | ||||
|             if len(upload_formats) == 1: | ||||
|                 self.format_id = upload_formats | ||||
|  | ||||
|     @api.onchange('format_id') | ||||
|     def _onchange_format_id(self): | ||||
|         self.order_type = self.format_id.order_type | ||||
|  | ||||
|     def ebics_upload(self): | ||||
|         self.ensure_one() | ||||
|         ctx = self._context.copy() | ||||
|         ebics_file = self._ebics_upload() | ||||
|         if ebics_file: | ||||
|             ctx['ebics_file_id'] = ebics_file.id | ||||
|         module = __name__.split('addons.')[1].split('.')[0] | ||||
|         result_view = self.env.ref( | ||||
|             '%s.ebics_xfer_view_form_result' % module) | ||||
|         return { | ||||
|             'name': _('EBICS file transfer result'), | ||||
|             'res_id': self.id, | ||||
|             'view_type': 'form', | ||||
|             'view_mode': 'form', | ||||
|             'res_model': 'ebics.xfer', | ||||
|             'view_id': result_view.id, | ||||
|             'target': 'new', | ||||
|             'context': ctx, | ||||
|             'type': 'ir.actions.act_window', | ||||
|         } | ||||
|  | ||||
|     def ebics_download(self): | ||||
|         self.ensure_one() | ||||
|         self.ebics_config_id._check_ebics_files() | ||||
|         ctx = self._context.copy() | ||||
|         self.note = '' | ||||
|         client = self._setup_client() | ||||
|         if client: | ||||
|             download_formats = ( | ||||
|                 self.format_id | ||||
|                 or self.ebics_config_id.ebics_file_format_ids.filtered( | ||||
|                     lambda r: r.type == 'down' | ||||
|                 ) | ||||
|             ) | ||||
|             ebics_files = self.env['ebics.file'] | ||||
|             date_from = self.date_from and self.date_from.isoformat() or None | ||||
|             date_to = self.date_to and self.date_to.isoformat() or None | ||||
|             for df in download_formats: | ||||
|                 try: | ||||
|                     success = False | ||||
|                     if df.order_type == 'FDL': | ||||
|                         data = client.FDL(df.name, date_from, date_to) | ||||
|                     else: | ||||
|                         params = None | ||||
|                         if date_from and date_to: | ||||
|                             params = {'DateRange': { | ||||
|                                 'Start': date_from, | ||||
|                                 'End': date_to, | ||||
|                             }} | ||||
|                         data = client.download(df.order_type, params=params) | ||||
|                     ebics_files += self._handle_download_data(data, df) | ||||
|                     success = True | ||||
|                 except EbicsFunctionalError: | ||||
|                     e = exc_info() | ||||
|                     self.note += '\n' | ||||
|                     self.note += _( | ||||
|                         "EBICS Functional Error during download of File Format %s (%s):" | ||||
|                     ) % (df.name, df.order_type) | ||||
|                     self.note += '\n' | ||||
|                     self.note += '%s (code: %s)' % (e[1].message, e[1].code) | ||||
|                 except EbicsTechnicalError: | ||||
|                     e = exc_info() | ||||
|                     self.note += '\n' | ||||
|                     self.note += _( | ||||
|                         "EBICS Technical Error during download of File Format %s (%s):" | ||||
|                     ) % (df.name, df.order_type) | ||||
|                     self.note += '\n' | ||||
|                     self.note += '%s (code: %s)' % (e[1].message, e[1].code) | ||||
|                 except EbicsVerificationError: | ||||
|                     self.note += '\n' | ||||
|                     self.note += _( | ||||
|                         "EBICS Verification Error during download of " | ||||
|                         "File Format %s (%s):" | ||||
|                     ) % (df.name, df.order_type) | ||||
|                     self.note += '\n' | ||||
|                     self.note += _("The EBICS response could not be verified.") | ||||
|                 except UserError as e: | ||||
|                     self.note += '\n' | ||||
|                     self.note += _( | ||||
|                         "Warning during download of File Format %s (%s):" | ||||
|                     ) % (df.name, df.order_type) | ||||
|                     self.note += '\n' | ||||
|                     self.note += e.name | ||||
|                 except Exception: | ||||
|                     self.note += '\n' | ||||
|                     self.note += _( | ||||
|                         "Unknown Error during download of File Format %s (%s):" | ||||
|                     ) % (df.name, df.order_type) | ||||
|                     tb = ''.join(format_exception(*exc_info())) | ||||
|                     self.note += '\n%s' % tb | ||||
|                 else: | ||||
|                     # mark received data so that it is not included in further | ||||
|                     # downloads | ||||
|                     trans_id = client.last_trans_id | ||||
|                     client.confirm_download(trans_id=trans_id, success=success) | ||||
|  | ||||
|             ctx['ebics_file_ids'] = ebics_files._ids | ||||
|  | ||||
|             if ebics_files: | ||||
|                 self.note += '\n' | ||||
|                 for f in ebics_files: | ||||
|                     self.note += _( | ||||
|                         "EBICS File '%s' is available for further processing." | ||||
|                     ) % f.name | ||||
|                     self.note += '\n' | ||||
|  | ||||
|         module = __name__.split('addons.')[1].split('.')[0] | ||||
|         result_view = self.env.ref( | ||||
|             '%s.ebics_xfer_view_form_result' % module) | ||||
|         return { | ||||
|             'name': _('EBICS file transfer result'), | ||||
|             'res_id': self.id, | ||||
|             'view_type': 'form', | ||||
|             'view_mode': 'form', | ||||
|             'res_model': 'ebics.xfer', | ||||
|             'view_id': result_view.id, | ||||
|             'target': 'new', | ||||
|             'context': ctx, | ||||
|             'type': 'ir.actions.act_window', | ||||
|         } | ||||
|  | ||||
|     def button_close(self): | ||||
|         self.ensure_one() | ||||
|         return {'type': 'ir.actions.act_window_close'} | ||||
|  | ||||
|     def view_ebics_file(self): | ||||
|         self.ensure_one() | ||||
|         module = __name__.split('addons.')[1].split('.')[0] | ||||
|         act = self.env['ir.actions.act_window']._for_xml_id( | ||||
|             '{}.ebics_file_action_download'.format(module)) | ||||
|         act['domain'] = [('id', 'in', self._context['ebics_file_ids'])] | ||||
|         return act | ||||
|  | ||||
|     def _ebics_upload(self): | ||||
|         self.ensure_one() | ||||
|         ebics_file = self.env['ebics.file'] | ||||
|         self.note = '' | ||||
|         client = self._setup_client() | ||||
|         if client: | ||||
|             upload_data = base64.decodestring(self.upload_data) | ||||
|             ef_format = self.format_id | ||||
|             OrderID = False | ||||
|             try: | ||||
|                 order_type = self.order_type | ||||
|                 if order_type == 'FUL': | ||||
|                     kwargs = {} | ||||
|                     bank = self.ebics_config_id.journal_ids[0].bank_id | ||||
|                     cc = bank.country.code | ||||
|                     if cc: | ||||
|                         kwargs['country'] = cc | ||||
|                     if self.test_mode: | ||||
|                         kwargs['TEST'] = 'TRUE' | ||||
|                     OrderID = client.FUL(ef_format.name, upload_data, **kwargs) | ||||
|                 else: | ||||
|                     OrderID = client.upload(order_type, upload_data) | ||||
|                 if OrderID: | ||||
|                     self.note += '\n' | ||||
|                     self.note += _( | ||||
|                         "EBICS File has been uploaded (OrderID %s)." | ||||
|                     ) % OrderID | ||||
|                     ef_note = _("EBICS OrderID: %s") % OrderID | ||||
|                     if self.env.context.get('origin'): | ||||
|                         ef_note += '\n' + _( | ||||
|                             "Origin: %s") % self._context['origin'] | ||||
|                     suffix = self.format_id.suffix | ||||
|                     fn = self.upload_fname | ||||
|                     if not fn.endswith(suffix): | ||||
|                         fn = '.'.join([fn, suffix]) | ||||
|                     ef_vals = { | ||||
|                         'name': self.upload_fname, | ||||
|                         'data': self.upload_data, | ||||
|                         'date': fields.Datetime.now(), | ||||
|                         'format_id': self.format_id.id, | ||||
|                         'state': 'done', | ||||
|                         'user_id': self._uid, | ||||
|                         'ebics_userid_id': self.ebics_userid_id.id, | ||||
|                         'note': ef_note, | ||||
|                         "company_ids": [ | ||||
|                             self.env.context.get("force_company", self.env.company.id) | ||||
|                         ], | ||||
|                     } | ||||
|                     self._update_ef_vals(ef_vals) | ||||
|                     ebics_file = self.env['ebics.file'].create(ef_vals) | ||||
|  | ||||
|             except EbicsFunctionalError: | ||||
|                 e = exc_info() | ||||
|                 self.note += '\n' | ||||
|                 self.note += _("EBICS Functional Error:") | ||||
|                 self.note += '\n' | ||||
|                 self.note += '%s (code: %s)' % (e[1].message, e[1].code) | ||||
|             except EbicsTechnicalError: | ||||
|                 e = exc_info() | ||||
|                 self.note += '\n' | ||||
|                 self.note += _("EBICS Technical Error:") | ||||
|                 self.note += '\n' | ||||
|                 self.note += '%s (code: %s)' % (e[1].message, e[1].code) | ||||
|             except EbicsVerificationError: | ||||
|                 self.note += '\n' | ||||
|                 self.note += _("EBICS Verification Error:") | ||||
|                 self.note += '\n' | ||||
|                 self.note += _("The EBICS response could not be verified.") | ||||
|             except Exception: | ||||
|                 self.note += '\n' | ||||
|                 self.note += _("Unknown Error") | ||||
|                 tb = ''.join(format_exception(*exc_info())) | ||||
|                 self.note += '\n%s' % tb | ||||
|  | ||||
|             if self.ebics_config_id.ebics_version == 'H003' and OrderID: | ||||
|                 self.ebics_config_id._update_order_number(OrderID) | ||||
|  | ||||
|         return ebics_file | ||||
|  | ||||
|     def _setup_client(self): | ||||
|         self.ebics_config_id._check_ebics_keys() | ||||
|         passphrase = self._get_passphrase() | ||||
|         keyring = EbicsKeyRing( | ||||
|             keys=self.ebics_userid_id.ebics_keys_fn, | ||||
|             passphrase=passphrase) | ||||
|  | ||||
|         bank = EbicsBank( | ||||
|             keyring=keyring, | ||||
|             hostid=self.ebics_config_id.ebics_host, | ||||
|             url=self.ebics_config_id.ebics_url) | ||||
|         if self.ebics_config_id.ebics_version == 'H003': | ||||
|             bank._order_number = self.ebics_config_id._get_order_number() | ||||
|  | ||||
|         user = EbicsUser( | ||||
|             keyring=keyring, | ||||
|             partnerid=self.ebics_config_id.ebics_partner, | ||||
|             userid=self.ebics_userid_id.name) | ||||
|         signature_class = self.format_id.signature_class \ | ||||
|             or self.ebics_userid_id.signature_class | ||||
|         if signature_class == 'T': | ||||
|             user.manual_approval = True | ||||
|  | ||||
|         try: | ||||
|             client = EbicsClient( | ||||
|                 bank, user, version=self.ebics_config_id.ebics_version) | ||||
|         except Exception: | ||||
|             self.note += '\n' | ||||
|             self.note += _("Unknown Error") | ||||
|             tb = ''.join(format_exception(*exc_info())) | ||||
|             self.note += '\n%s' % tb | ||||
|             client = False | ||||
|  | ||||
|         return client | ||||
|  | ||||
|     def _get_passphrase(self): | ||||
|         passphrase = self.ebics_userid_id.ebics_passphrase | ||||
|  | ||||
|         if passphrase: | ||||
|             return passphrase | ||||
|  | ||||
|         module = __name__.split('addons.')[1].split('.')[0] | ||||
|         passphrase_view = self.env.ref( | ||||
|             '%s.ebics_xfer_view_form_passphrase' % module) | ||||
|         return { | ||||
|             'name': _('EBICS file transfer'), | ||||
|             'res_id': self.id, | ||||
|             'view_type': 'form', | ||||
|             'view_mode': 'form', | ||||
|             'res_model': 'ebics.xfer', | ||||
|             'view_id': passphrase_view.id, | ||||
|             'target': 'new', | ||||
|             'context': self._context, | ||||
|             'type': 'ir.actions.act_window', | ||||
|         } | ||||
|  | ||||
|     def _file_format_methods(self): | ||||
|         """ | ||||
|         Extend this dictionary in order to add support | ||||
|         for extra file formats. | ||||
|         """ | ||||
|         res = { | ||||
|             'camt.xxx.cfonb120.stm': self._handle_cfonb120, | ||||
|             'camt.052.001.02.stm': self._handle_camt052, | ||||
|             'camt.053.001.02.stm': self._handle_camt053, | ||||
|         } | ||||
|         return res | ||||
|  | ||||
|     def _update_ef_vals(self, ef_vals): | ||||
|         """ | ||||
|         Adapt this method to customize the EBICS File values. | ||||
|         """ | ||||
|         if self.format_id and self.format_id.type == 'up': | ||||
|             fn = ef_vals['name'] | ||||
|             dups = self._check_duplicate_ebics_file( | ||||
|                 fn, self.format_id) | ||||
|             if dups: | ||||
|                 n = 1 | ||||
|                 fn = '_'.join([fn, str(n)]) | ||||
|                 while self._check_duplicate_ebics_file(fn, self.format_id): | ||||
|                     n += 1 | ||||
|                     fn = '_'.join([fn, str(n)]) | ||||
|                 ef_vals['name'] = fn | ||||
|  | ||||
|     def _handle_download_data(self, data, file_format): | ||||
|         ebics_files = self.env['ebics.file'] | ||||
|         if isinstance(data, dict): | ||||
|             for doc in data: | ||||
|                 ebics_files += self._create_ebics_file( | ||||
|                     data[doc], file_format, docname=doc) | ||||
|         else: | ||||
|             ebics_files += self._create_ebics_file(data, file_format) | ||||
|         return ebics_files | ||||
|  | ||||
|     def _create_ebics_file(self, data, file_format, docname=None): | ||||
|         """ | ||||
|         Write the data as received over the EBICS connection | ||||
|         to a temporary file so that is is available for | ||||
|         analysis (e.g. in case formats are received that cannot | ||||
|         be handled in the current version of this module). | ||||
|  | ||||
|         TODO: add code to clean-up /tmp on a regular basis. | ||||
|  | ||||
|         After saving the data received we call the method to perform | ||||
|         file format specific processing. | ||||
|         """ | ||||
|         ebics_files_root = self.ebics_config_id.ebics_files | ||||
|         tmp_dir = os.path.normpath(ebics_files_root + '/tmp') | ||||
|         if not os.path.isdir(tmp_dir): | ||||
|             os.makedirs(tmp_dir, mode=0o700) | ||||
|         fn_parts = [self.ebics_config_id.ebics_host, | ||||
|                     self.ebics_config_id.ebics_partner] | ||||
|         if docname: | ||||
|             fn_parts.append(docname) | ||||
|         else: | ||||
|             fn_date = self.date_to or fields.Date.today() | ||||
|             fn_parts.append(fn_date.isoformat()) | ||||
|         base_fn = '_'.join(fn_parts) | ||||
|         n = 1 | ||||
|         full_tmp_fn = os.path.normpath(tmp_dir + '/' + base_fn) | ||||
|         while os.path.exists(full_tmp_fn): | ||||
|             n += 1 | ||||
|             tmp_fn = base_fn + '_' + str(n).rjust(3, '0') | ||||
|             full_tmp_fn = os.path.normpath(tmp_dir + '/' + tmp_fn) | ||||
|  | ||||
|         with open(full_tmp_fn, 'wb') as f: | ||||
|             f.write(data) | ||||
|  | ||||
|         ff_methods = self._file_format_methods() | ||||
|         if file_format.name in ff_methods: | ||||
|             data = ff_methods[file_format.name](data) | ||||
|  | ||||
|         fn = '.'.join([base_fn, file_format.suffix]) | ||||
|         dups = self._check_duplicate_ebics_file(fn, file_format) | ||||
|         if dups: | ||||
|             raise UserError(_( | ||||
|                 "EBICS File with name '%s' has already been downloaded." | ||||
|                 "\nPlease check this file and rename in case there is " | ||||
|                 "no risk on duplicate transactions.") | ||||
|                 % fn) | ||||
|         data = base64.encodebytes(data) | ||||
|         ef_vals = { | ||||
|             'name': fn, | ||||
|             'data': data, | ||||
|             'date': fields.Datetime.now(), | ||||
|             'date_from': self.date_from, | ||||
|             'date_to': self.date_to, | ||||
|             'format_id': file_format.id, | ||||
|             'user_id': self._uid, | ||||
|             'ebics_userid_id': self.ebics_userid_id.id, | ||||
|             'company_ids': self.ebics_config_id.company_ids.ids, | ||||
|         } | ||||
|         self._update_ef_vals(ef_vals) | ||||
|         ebics_file = self.env['ebics.file'].create(ef_vals) | ||||
|         return ebics_file | ||||
|  | ||||
|     def _check_duplicate_ebics_file(self, fn, file_format): | ||||
|         dups = self.env['ebics.file'].search( | ||||
|             [('name', '=', fn), | ||||
|              ('format_id', '=', file_format.id)]) | ||||
|         return dups | ||||
|  | ||||
|     def _detect_upload_format(self): | ||||
|         """ | ||||
|         Use this method in order to automatically detect and set the | ||||
|         EBICS upload file format. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def _update_order_number(self, OrderID): | ||||
|         o_list = list(OrderID) | ||||
|         for i, c in enumerate(reversed(o_list), start=1): | ||||
|             if c == '9': | ||||
|                 o_list[-i] = 'A' | ||||
|                 break | ||||
|             if c == 'Z': | ||||
|                 continue | ||||
|             else: | ||||
|                 o_list[-i] = chr(ord(c) + 1) | ||||
|                 break | ||||
|         next_nr = ''.join(o_list) | ||||
|         if next_nr == 'ZZZZ': | ||||
|             next_nr = 'A000' | ||||
|         self.ebics_config_id.order_number = next_nr | ||||
|  | ||||
|     def _insert_line_terminator(self, data_in, line_len): | ||||
|         data_in = data_in.replace(b'\n', b'').replace(b'\r', b'') | ||||
|         data_out = b'' | ||||
|         max_len = len(data_in) | ||||
|         i = 0 | ||||
|         while i + line_len <= max_len: | ||||
|             data_out += data_in[i:i + line_len] + b'\n' | ||||
|             i += line_len | ||||
|         return data_out | ||||
|  | ||||
|     def _handle_cfonb120(self, data_in): | ||||
|         return self._insert_line_terminator(data_in, 120) | ||||
|  | ||||
|     def _handle_cfonb240(self, data_in): | ||||
|         return self._insert_line_terminator(data_in, 240) | ||||
|  | ||||
|     def _handle_camt052(self, data_in): | ||||
|         """ | ||||
|         Use this method if you need to fix camt files received | ||||
|         from your bank before passing them to the | ||||
|         Odoo Community CAMT parser. | ||||
|         Remark: Odoo Enterprise doesn't support camt.052. | ||||
|         """ | ||||
|         return data_in | ||||
|  | ||||
|     def _handle_camt053(self, data_in): | ||||
|         """ | ||||
|         Use this method if you need to fix camt files received | ||||
|         from your bank before passing them to the | ||||
|         Odoo Enterprise or Community CAMT parser. | ||||
|         """ | ||||
|         return data_in | ||||
							
								
								
									
										102
									
								
								account_ebics/wizards/ebics_xfer.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								account_ebics/wizards/ebics_xfer.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="ebics_xfer_view_form_download" model="ir.ui.view"> | ||||
|     <field name="name">EBICS File Download</field> | ||||
|     <field name="model">ebics.xfer</field> | ||||
|     <field name="priority">1</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS File Download"> | ||||
|         <group> | ||||
|           <separator string="Select your bank :" colspan="2"/> | ||||
|           <field name="ebics_config_id" required="1" options="{'no_create': True, 'no_open': True}"/> | ||||
|           <field name="ebics_userid_id" | ||||
|                  domain="[('ebics_config_id', '=', ebics_config_id)]" | ||||
|                  required="1" | ||||
|                  options="{'no_create': True, 'no_open': True}"/> | ||||
|           <field name="date_from"/> | ||||
|           <field name="date_to"/> | ||||
|           <field name="format_id" | ||||
|                  domain="[('type', '=', 'down'), ('id', 'in', allowed_format_ids)]"/> | ||||
|           <field name="order_type"/> | ||||
|           <field name="allowed_format_ids" invisible="1"/> | ||||
|         </group> | ||||
|         <footer> | ||||
|           <button name="ebics_download" string="Download Files" type="object" class="oe_highlight"/> | ||||
|           or | ||||
|           <button string="Cancel" class="oe_link" special="cancel"/> | ||||
|         </footer> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_xfer_view_form_upload" model="ir.ui.view"> | ||||
|     <field name="name">EBICS File Upload</field> | ||||
|     <field name="model">ebics.xfer</field> | ||||
|     <field name="priority">1</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS File Upload"> | ||||
|         <group> | ||||
|           <separator string="Select your bank :" colspan="2"/> | ||||
|           <field name="ebics_config_id" required="1" options="{'no_create': True, 'no_open': True}"/> | ||||
|           <field name="ebics_userid_id" | ||||
|                  domain="[('ebics_config_id', '=', ebics_config_id)]" | ||||
|                  required="1" | ||||
|                  options="{'no_create': True, 'no_open': True}"/> | ||||
|           <separator string="Select your file :" colspan="2"/> | ||||
|           <field name="upload_data" filename="upload_fname" required="1"/> | ||||
|           <field name="upload_fname" invisible="1"/> | ||||
|           <field name="upload_fname_dummy"/> | ||||
|           <field name="format_id" required="1" | ||||
|                  domain="[('type', '=', 'up'), ('id', 'in', allowed_format_ids)]"/> | ||||
|           <field name="order_type"/> | ||||
|           <field name="test_mode" attrs="{'invisible': [('order_type', '!=', 'FUL')]}"/> | ||||
|           <field name="allowed_format_ids" invisible="1"/> | ||||
|         </group> | ||||
|         <footer> | ||||
|           <button name="ebics_upload" string="Upload File" type="object" class="oe_highlight"/> | ||||
|           or | ||||
|           <button string="Cancel" class="oe_link" special="cancel"/> | ||||
|         </footer> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_xfer_view_form_result" model="ir.ui.view"> | ||||
|     <field name="name">EBICS File Transfer</field> | ||||
|     <field name="model">ebics.xfer</field> | ||||
|     <field name="priority">2</field> | ||||
|     <field name="arch" type="xml"> | ||||
|       <form string="EBICS File Transfer"> | ||||
|         <separator colspan="4" string="Results :" /> | ||||
|         <field name="note" colspan="4" nolabel="1" width="850" height="400"/> | ||||
|         <footer> | ||||
|           <button name="view_ebics_file" type="object" string="View EBICS File(s)" class="oe_highlight" | ||||
|                   invisible="not context.get('ebics_file_ids')"/> | ||||
|           <button name="button_close" type="object" string="Close"/> | ||||
|         </footer> | ||||
|       </form> | ||||
|     </field> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_xfer_action_download" model="ir.actions.act_window"> | ||||
|     <field name="name">EBICS File Transfer</field> | ||||
|     <field name="type">ir.actions.act_window</field> | ||||
|     <field name="res_model">ebics.xfer</field> | ||||
|     <field name="view_mode">form</field> | ||||
|     <field name="target">new</field> | ||||
|     <field name="context">{'ebics_download': 1}</field> | ||||
|     <field name="view_id" ref="ebics_xfer_view_form_download"/> | ||||
|   </record> | ||||
|  | ||||
|   <record id="ebics_xfer_action_upload" model="ir.actions.act_window"> | ||||
|     <field name="name">EBICS File Transfer</field> | ||||
|     <field name="type">ir.actions.act_window</field> | ||||
|     <field name="res_model">ebics.xfer</field> | ||||
|     <field name="view_mode">form</field> | ||||
|     <field name="target">new</field> | ||||
|     <field name="context">{'ebics_upload': 1}</field> | ||||
|     <field name="view_id" ref="ebics_xfer_view_form_upload"/> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										17
									
								
								account_ebics_oca_statement_import/README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								account_ebics_oca_statement_import/README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| .. image:: https://img.shields.io/badge/license-LGPL--3-blue.png | ||||
|    :target: https://www.gnu.org/licenses/lgpl | ||||
|    :alt: License: LGPL-3 | ||||
|  | ||||
| ========================================================== | ||||
| Deploy account_ebics module with OCA Bank Statement Import | ||||
| ========================================================== | ||||
|  | ||||
| This module makes it possible to use OCA account_statement_import | ||||
| in combination with 'account_ebics'. | ||||
|  | ||||
| This module will be installed automatically when following modules are activated | ||||
| on your odoo database : | ||||
|  | ||||
| - account_ebics | ||||
| - account_statement_import | ||||
|  | ||||
							
								
								
									
										1
									
								
								account_ebics_oca_statement_import/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								account_ebics_oca_statement_import/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| from . import wizards | ||||
							
								
								
									
										17
									
								
								account_ebics_oca_statement_import/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								account_ebics_oca_statement_import/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| # Copyright 2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| { | ||||
|     'name': 'account_ebics with OCA Bank Statement Imoort', | ||||
|     'summary': "Use OCA Bank Statement Import with account_ebics", | ||||
|     'version': '14.0.1.0.0', | ||||
|     'author': 'Noviat', | ||||
|     'category': 'Hidden', | ||||
|     'license': 'LGPL-3', | ||||
|     'depends': [ | ||||
|         'account_ebics', | ||||
|         'account_statement_import', | ||||
|     ], | ||||
|     'installable': True, | ||||
|     'auto_install': True, | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								account_ebics_oca_statement_import/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								account_ebics_oca_statement_import/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										1
									
								
								account_ebics_oca_statement_import/wizards/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								account_ebics_oca_statement_import/wizards/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| from . import account_statement_import | ||||
| @@ -0,0 +1,62 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from odoo import models, _ | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class AccountStatementImport(models.TransientModel): | ||||
|     _inherit = 'account.statement.import' | ||||
|  | ||||
|     def _check_parsed_data(self, stmts_vals): | ||||
|         """ Basic and structural verifications """ | ||||
|         if self.env.context.get('active_model') == 'ebics.file': | ||||
|             message = False | ||||
|             if len(stmts_vals) == 0: | ||||
|                 message = _("This file doesn't contain any statement.") | ||||
|             if not message: | ||||
|                 no_st_line = True | ||||
|                 for vals in stmts_vals: | ||||
|                     if vals['transactions'] and len(vals['transactions']) > 0: | ||||
|                         no_st_line = False | ||||
|                         break | ||||
|                 if no_st_line: | ||||
|                     message = _('This file doesn\'t contain any transaction.') | ||||
|                 if message: | ||||
|                     log_msg = _( | ||||
|                         "Error detected while processing and EBICS File" | ||||
|                     ) + ':\n' + message | ||||
|                     _logger.warn(log_msg) | ||||
|                     return | ||||
|         super()._check_parsed_data(stmts_vals) | ||||
|  | ||||
|     def _create_bank_statements(self, stmts_vals, result): | ||||
|         """ | ||||
|         Return error message to ebics.file when handling empty camt. | ||||
|  | ||||
|         Remarks/TODO: | ||||
|         We could add more info to the message (e.g. date, balance, ...) | ||||
|         and write this to the ebics.file, note field. | ||||
|         We could also create empty bank statement (in state done) to clearly | ||||
|         show days without transactions via the bank statement list view. | ||||
|         """ | ||||
|         if self.env.context.get('active_model') == 'ebics.file': | ||||
|             transactions = False | ||||
|             for st_vals in stmts_vals: | ||||
|                 if st_vals.get('transactions'): | ||||
|                     transactions = True | ||||
|                     break | ||||
|             if not transactions: | ||||
|                 message = _('This file doesn\'t contain any transaction.') | ||||
|                 st_line_ids = [] | ||||
|                 notifications = { | ||||
|                     'type': 'warning', | ||||
|                     'message': message, | ||||
|                     'details': '' | ||||
|                 } | ||||
|                 return st_line_ids, [notifications] | ||||
|  | ||||
|         return super()._create_bank_statements(stmts_vals, result) | ||||
							
								
								
									
										16
									
								
								account_ebics_oe/README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								account_ebics_oe/README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| .. image:: https://img.shields.io/badge/license-LGPL--3-blue.png | ||||
|    :target: https://www.gnu.org/licenses/lgpl | ||||
|    :alt: License: LGPL-3 | ||||
|  | ||||
| ============================================== | ||||
| Deploy account_ebics module on Odoo Enterprise | ||||
| ============================================== | ||||
|  | ||||
| This module makes it possible to deploy the 'account_ebics' | ||||
| module on Odoo Enterprise. | ||||
|  | ||||
| This module will be installed automatically when following modules are activated | ||||
| on your odoo database : | ||||
|  | ||||
| - account_ebics | ||||
| - account_accountant | ||||
							
								
								
									
										0
									
								
								account_ebics_oe/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								account_ebics_oe/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										20
									
								
								account_ebics_oe/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								account_ebics_oe/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| # Copyright 2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| { | ||||
|     'name': 'account_ebics on Odoo Enterprise', | ||||
|     'summary': "Deploy account_ebics module on Odoo Enterprise", | ||||
|     'version': '14.0.1.0.0', | ||||
|     'author': 'Noviat', | ||||
|     'category': 'Hidden', | ||||
|     'license': 'LGPL-3', | ||||
|     'depends': [ | ||||
|         'account_ebics', | ||||
|         'account_accountant', | ||||
|     ], | ||||
|     'data': [ | ||||
|         'views/account_ebics_menu.xml' | ||||
|     ], | ||||
|     'installable': True, | ||||
|     'auto_install': True, | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								account_ebics_oe/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								account_ebics_oe/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										8
									
								
								account_ebics_oe/views/account_ebics_menu.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								account_ebics_oe/views/account_ebics_menu.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <odoo> | ||||
|  | ||||
|   <record id="account_ebics.ebics_processing_menu" model="ir.ui.menu"> | ||||
|     <field name="parent_id" eval="ref('account_accountant.menu_accounting')"/> | ||||
|   </record> | ||||
|  | ||||
| </odoo> | ||||
							
								
								
									
										17
									
								
								account_ebics_oe_statement_import/README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								account_ebics_oe_statement_import/README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| .. image:: https://img.shields.io/badge/license-LGPL--3-blue.png | ||||
|    :target: https://www.gnu.org/licenses/lgpl | ||||
|    :alt: License: LGPL-3 | ||||
|  | ||||
| ====================================================================== | ||||
| Deploy account_ebics module with Odoo Enterprise Bank Statement Import | ||||
| ====================================================================== | ||||
|  | ||||
| This module makes it possible to use Odoo Enterprise account_bank_statement_import | ||||
| in combination with 'account_ebics'. | ||||
|  | ||||
| This module will be installed automatically when following modules are activated | ||||
| on your odoo database : | ||||
|  | ||||
| - account_ebics_oe | ||||
| - account_bank_statement_import | ||||
|  | ||||
							
								
								
									
										1
									
								
								account_ebics_oe_statement_import/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								account_ebics_oe_statement_import/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| from . import wizards | ||||
							
								
								
									
										17
									
								
								account_ebics_oe_statement_import/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								account_ebics_oe_statement_import/__manifest__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| # Copyright 2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| { | ||||
|     'name': 'account_ebics with Odoo Enterprise Bank Statement Import', | ||||
|     'summary': "Use Odoo Enterprise Bank Statement Import with account_ebics", | ||||
|     'version': '14.0.1.0.0', | ||||
|     'author': 'Noviat', | ||||
|     'category': 'Hidden', | ||||
|     'license': 'LGPL-3', | ||||
|     'depends': [ | ||||
|         'account_ebics_oe', | ||||
|         'account_bank_statement_import', | ||||
|     ], | ||||
|     'installable': True, | ||||
|     'auto_install': True, | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								account_ebics_oe_statement_import/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								account_ebics_oe_statement_import/static/description/icon.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										1
									
								
								account_ebics_oe_statement_import/wizards/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								account_ebics_oe_statement_import/wizards/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| from . import account_bank_statement_import | ||||
| @@ -0,0 +1,62 @@ | ||||
| # Copyright 2009-2020 Noviat. | ||||
| # License LGPL-3 or later (http://www.gnu.org/licenses/lpgl). | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from odoo import models, _ | ||||
|  | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class AccountBankStatementImport(models.TransientModel): | ||||
|     _inherit = 'account.bank.statement.import' | ||||
|  | ||||
|     def _check_parsed_data(self, stmts_vals, account_number): | ||||
|         """ Basic and structural verifications """ | ||||
|         if self.env.context.get('active_model') == 'ebics.file': | ||||
|             message = False | ||||
|             if len(stmts_vals) == 0: | ||||
|                 message = _("This file doesn't contain any statement.") | ||||
|             if not message: | ||||
|                 no_st_line = True | ||||
|                 for vals in stmts_vals: | ||||
|                     if vals['transactions'] and len(vals['transactions']) > 0: | ||||
|                         no_st_line = False | ||||
|                         break | ||||
|                 if no_st_line: | ||||
|                     message = _('This file doesn\'t contain any transaction.') | ||||
|                 if message: | ||||
|                     log_msg = _( | ||||
|                         "Error detected while processing and EBICS File" | ||||
|                     ) + ':\n' + message | ||||
|                     _logger.warn(log_msg) | ||||
|                     return | ||||
|         super()._check_parsed_data(stmts_vals, account_number) | ||||
|  | ||||
|     def _create_bank_statements(self, stmts_vals): | ||||
|         """ | ||||
|         Return error message to ebics.file when handling empty camt. | ||||
|  | ||||
|         Remarks/TODO: | ||||
|         We could add more info to the message (e.g. date, balance, ...) | ||||
|         and write this to the ebics.file, note field. | ||||
|         We could also create empty bank statement (in state done) to clearly | ||||
|         show days without transactions via the bank statement list view. | ||||
|         """ | ||||
|         if self.env.context.get('active_model') == 'ebics.file': | ||||
|             transactions = False | ||||
|             for st_vals in stmts_vals: | ||||
|                 if st_vals.get('transactions'): | ||||
|                     transactions = True | ||||
|                     break | ||||
|             if not transactions: | ||||
|                 message = _('This file doesn\'t contain any transaction.') | ||||
|                 st_line_ids = [] | ||||
|                 notifications = { | ||||
|                     'type': 'warning', | ||||
|                     'message': message, | ||||
|                     'details': '' | ||||
|                 } | ||||
|                 return st_line_ids, [notifications] | ||||
|  | ||||
|         return super()._create_bank_statements(stmts_vals) | ||||
		Reference in New Issue
	
	Block a user