From 48f5949363115f66a81334c992c2792c71026083 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 1 Apr 2011 00:36:23 +0200 Subject: [PATCH 001/211] Started work on l10n_fr_intrastat_product --- l10n_fr_intrastat_product/__init__.py | 25 ++++++ l10n_fr_intrastat_product/__terp__.py | 43 ++++++++++ l10n_fr_intrastat_product/company.py | 78 +++++++++++++++++++ l10n_fr_intrastat_product/company_view.xml | 23 ++++++ .../intrastat_product.py | 21 +++++ l10n_fr_intrastat_product/product.py | 75 ++++++++++++++++++ .../security/ir.model.access.csv | 4 + l10n_fr_intrastat_product/stock.py | 40 ++++++++++ 8 files changed, 309 insertions(+) create mode 100644 l10n_fr_intrastat_product/__init__.py create mode 100644 l10n_fr_intrastat_product/__terp__.py create mode 100644 l10n_fr_intrastat_product/company.py create mode 100644 l10n_fr_intrastat_product/company_view.xml create mode 100644 l10n_fr_intrastat_product/intrastat_product.py create mode 100644 l10n_fr_intrastat_product/product.py create mode 100644 l10n_fr_intrastat_product/security/ir.model.access.csv create mode 100644 l10n_fr_intrastat_product/stock.py diff --git a/l10n_fr_intrastat_product/__init__.py b/l10n_fr_intrastat_product/__init__.py new file mode 100644 index 000000000..c0b7aa88a --- /dev/null +++ b/l10n_fr_intrastat_product/__init__.py @@ -0,0 +1,25 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Report intrastat product module for OpenERP +# Copyright (C) 2010-2011 Akretion (http://www.akretion.com). All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +#import intrastat_product +import product +import stock +import company diff --git a/l10n_fr_intrastat_product/__terp__.py b/l10n_fr_intrastat_product/__terp__.py new file mode 100644 index 000000000..da935494d --- /dev/null +++ b/l10n_fr_intrastat_product/__terp__.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Report intrastat product module for OpenERP +# Copyright (C) 2010-2011 Akretion (http://www.akretion.com). All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'Module for Intrastat product reporting (DEB) for France', + 'version': '1.1', + 'category': 'Localisation/Report Intrastat', + 'license': 'AGPL-3', + 'description': """This module adds support for the "Déclaration d'Echange de Biens" (DEB). +Please contact Alexis de Lattre from Akretion for any help or question. + """, + 'author': 'Akretion', + 'website': 'http://www.akretion.com', + 'depends': ['l10n_fr_intrastat_base', 'stock'], + 'init_xml': [], + 'update_xml': [ + 'security/ir.model.access.csv', +# 'intrastat_product_view.xml', + 'company_view.xml', + ], + 'demo_xml': [], + 'installable': True, + 'active': False, +} diff --git a/l10n_fr_intrastat_product/company.py b/l10n_fr_intrastat_product/company.py new file mode 100644 index 000000000..d408c07b6 --- /dev/null +++ b/l10n_fr_intrastat_product/company.py @@ -0,0 +1,78 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Report intrastat product module for OpenERP +# Copyright (C) 2010-2011 Akretion (http://www.akretion.com). All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import osv, fields +from tools.translate import _ + +class res_company(osv.osv): + _inherit = "res.company" + _columns = { + # In France, the customs_accreditation ("numéro d'habilitation") + # is 4 char long. But the spec of the XML file says it can go up + # to 8... because other EU countries may have identifiers up to 8 chars + # As this module only implement DEB for France, we use size=4 + 'customs_accreditation': fields.char('Customs accreditation identifier', size=4, help="Company identifier for Intrastat file export. Size : 4 characters."), + 'siret_complement' : fields.char('SIRET complement', size=5, help="5 last digits of the SIRET number of the company."), + 'export_obligation_level': fields.selection([('detailed', 'Detailed'), ('simplified', 'Simplified')], 'Obligation level for export', help='If your volume of export of products to EU countries is over 460 000 € per year, your obligation level for export for the DEB is "Detailed". If you are under this limit, your obligation level for export for the DEB is "Simplified".'), + 'default_intrastat_department': fields.char('Default departement code', size=2, help='If the Departement code is not set on the invoice line, OpenERP will use this value.'), + 'default_intrastat_transport': fields.selection([(1, 'Transport maritime'), \ + (2, 'Transport par chemin de fer'), \ + (3, 'Transport par route'), \ + (4, 'Transport par air'), \ + (5, 'Envois postaux'), \ + (7, 'Installations de transport fixes'), \ + (8, 'Transport par navigation intérieure'), \ + (9, 'Propulsion propre')], 'Type of transport', \ + help="If the 'Type of Transport' is not set on the invoice, OpenERP will use this value."), + } + + def _5digits(self, cr, uid, ids): + for siret_compl_to_check in self.read(cr, uid, ids, ['siret_complement']): + if siret_compl_to_check['siret_complement']: + if not siret_compl_to_check['siret_complement'].isdigit() or len(siret_compl_to_check['siret_complement']) != 5: + return False + return True + + def real_department_check(self, dpt_list): + for dpt in dpt_list: + if not dpt: + continue + if dpt in ['2A', '2B']: + continue + if not dpt.isdigit(): + raise osv.except_osv(_('Error :'), _("The department code must be a number or have the value '2A' or '2B' for Corsica.")) + if int(dpt) < 1 or int(dpt) > 95: + raise osv.except_osv(_('Error :'), _("The department code must be between 01 and 95 or have the value '2A' or '2B'.")) + return True + + def _check_default_intrastat_department(self, cr, uid, ids): + dpt_list = [] + for dpt_to_check in self.read(cr, uid, ids, ['default_intrastat_department']): + dpt_list.append(dpt_to_check['default_intrastat_department']) + return self.real_department_check(dpt_list) + + _constraints = [ + (_5digits, "'SIRET complement' should have exactly 5 digits !", ['siret_complement']), + (_check_default_intrastat_department, "Error msg is in raise", ['default_intrastat_department']), + ] + +res_company() + diff --git a/l10n_fr_intrastat_product/company_view.xml b/l10n_fr_intrastat_product/company_view.xml new file mode 100644 index 000000000..7f6fe7c64 --- /dev/null +++ b/l10n_fr_intrastat_product/company_view.xml @@ -0,0 +1,23 @@ + + + + + + fr.intrastat.product.company.form + res.company + + + + + + + + + + + + + + + + diff --git a/l10n_fr_intrastat_product/intrastat_product.py b/l10n_fr_intrastat_product/intrastat_product.py new file mode 100644 index 000000000..8d444c695 --- /dev/null +++ b/l10n_fr_intrastat_product/intrastat_product.py @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Report intrastat product module for OpenERP +# Copyright (C) 2010-2011 Akretion (http://www.akretion.com). All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + diff --git a/l10n_fr_intrastat_product/product.py b/l10n_fr_intrastat_product/product.py new file mode 100644 index 000000000..5fa927801 --- /dev/null +++ b/l10n_fr_intrastat_product/product.py @@ -0,0 +1,75 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Report intrastat product module for OpenERP +# Copyright (C) 2010-2011 Akretion (http://www.akretion.com). All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import osv, fields +from tools.translate import _ + +class report_intrastat_code(osv.osv): + _name = "report.intrastat.code" + _description = "Intrastat code" + _order = "name" + _columns = { + 'name': fields.char('Intrastat code', size=16, required=True, help="Full lenght H.S. code"), + 'description': fields.char('Description', size=128, help='Short text description of the H.S. category'), + 'code8': fields.char('Intrastat code 8 digits', size=8, required=True, help="8 digits long H.S. code. Used for the DEB in France (called 'Nomenclature combinée NC8')"), + 'intrastat_uom_id': fields.many2one('product.uom', 'UoM for intrastat product report', help="Select the unit of measure if one is required for this particular intrastat code. If no particular unit of measure is required, leave empty and the Intrastat product report will contain the weight."), + } + + def _8digits(self, cr, uid, ids): + for code8_to_check in self.read(cr, uid, ids, ['code8']): + if code8_to_check['code8']: + if not code8_to_check['code8'].isdigit() or len(code8_to_check['code8']) != 8: + return False + return True + + def _digits(self, cr, uid, ids): + for code_to_check in self.read(cr, uid, ids, ['name']): + if code_to_check['name']: + if not code_to_check.isdigit(): + return False + return True + + _constraints = [ + (_8digits, "'Intrastat code 8 digits' should have exactly 8 digits !", ['code8']), + (_digits, "'Intrastat code' should only contain digits.", ['name']), + ] + +report_intrastat_code() + + +class product_uom(osv.osv): + _inherit = "product.uom" + _columns = { + 'intrastat_label': fields.char('Intrastat label', size=12, help="Label used in the INSTAT XML export of the Intrastat product report for this unit of measure."), + } + +product_uom() + + +class product_template(osv.osv): + _inherit = "product.template" + _columns = { + 'intrastat_id': fields.many2one('report.intrastat.code', 'Intrastat code', help="Code from the Harmonised System. Nomenclature is available from the World Customs Organisation, see http://www.wcoomd.org/. Some countries have made their own extensions to this nomenclature."), + 'country_id' : fields.many2one('res.country', 'Country of origin', help="Country of origin of the product i.e. product 'made in ...'."), + } + +product_template() + diff --git a/l10n_fr_intrastat_product/security/ir.model.access.csv b/l10n_fr_intrastat_product/security/ir.model.access.csv new file mode 100644 index 000000000..ede50742a --- /dev/null +++ b/l10n_fr_intrastat_product/security/ir.model.access.csv @@ -0,0 +1,4 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_report_intrastat_code","report.intrastat.code finance manager","model_report_intrastat_code","account.group_account_manager",1,1,1,1 +"access_report_intrastat_code_product_manager","report.intrastat.code product manager","model_report_intrastat_code","product.group_product_manager",1,1,1,1 +"access_report_intrastat_code_employee","report.intrastat.code read for employee","model_report_intrastat_code","base.group_user",1,0,0,0 diff --git a/l10n_fr_intrastat_product/stock.py b/l10n_fr_intrastat_product/stock.py new file mode 100644 index 000000000..f22f7d905 --- /dev/null +++ b/l10n_fr_intrastat_product/stock.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Report intrastat product module for OpenERP +# Copyright (C) 2010-2011 Akretion (http://www.akretion.com). All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from osv import osv, fields +from tools.translate import _ + +class stock_picking(osv.osv): + _inherit = "stock.picking" + _columns = { + 'intrastat_transport' : fields.selection([(1, 'Transport maritime'), \ + (2, 'Transport par chemin de fer'), \ + (3, 'Transport par route'), \ + (4, 'Transport par air'), \ + (5, 'Envois postaux'), \ + (7, 'Installations de transport fixes'), \ + (8, 'Transport par navigation intérieure'), \ + (9, 'Propulsion propre')], 'Type of transport', \ + help="Select the type of transport. This information is required for the product intrastat report.") + } + +stock_picking() + From 2faa51c1880875e5d09e60242e03d451726ae425 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 1 Apr 2011 23:24:17 +0200 Subject: [PATCH 002/211] First alpha version of the new module "l10n_fr_intrastat_product". First factorisation of common functionnal code between the product and service module (intrastat_common.py) - to be continued. Some code clean-up in the service module. --- l10n_fr_intrastat_product/__init__.py | 3 +- l10n_fr_intrastat_product/__terp__.py | 6 +- l10n_fr_intrastat_product/deb_xsd.py | 143 +++++ .../intrastat_product.py | 490 +++++++++++++++++- .../intrastat_product_view.xml | 133 +++++ .../intrastat_type_view.xml | 34 ++ l10n_fr_intrastat_product/invoice.py | 40 ++ l10n_fr_intrastat_product/invoice_view.xml | 34 ++ l10n_fr_intrastat_product/product.py | 2 +- l10n_fr_intrastat_product/product_view.xml | 97 ++++ .../security/ir.model.access.csv | 2 + l10n_fr_intrastat_product/stock.py | 4 +- l10n_fr_intrastat_product/stock_view.xml | 45 ++ 13 files changed, 1028 insertions(+), 5 deletions(-) create mode 100644 l10n_fr_intrastat_product/deb_xsd.py create mode 100644 l10n_fr_intrastat_product/intrastat_product_view.xml create mode 100644 l10n_fr_intrastat_product/intrastat_type_view.xml create mode 100644 l10n_fr_intrastat_product/invoice.py create mode 100644 l10n_fr_intrastat_product/invoice_view.xml create mode 100644 l10n_fr_intrastat_product/product_view.xml create mode 100644 l10n_fr_intrastat_product/stock_view.xml diff --git a/l10n_fr_intrastat_product/__init__.py b/l10n_fr_intrastat_product/__init__.py index c0b7aa88a..bc2dda5ac 100644 --- a/l10n_fr_intrastat_product/__init__.py +++ b/l10n_fr_intrastat_product/__init__.py @@ -19,7 +19,8 @@ # ############################################################################## -#import intrastat_product +import intrastat_product import product import stock import company +import invoice diff --git a/l10n_fr_intrastat_product/__terp__.py b/l10n_fr_intrastat_product/__terp__.py index da935494d..185492440 100644 --- a/l10n_fr_intrastat_product/__terp__.py +++ b/l10n_fr_intrastat_product/__terp__.py @@ -34,8 +34,12 @@ 'init_xml': [], 'update_xml': [ 'security/ir.model.access.csv', -# 'intrastat_product_view.xml', + 'intrastat_product_view.xml', + 'intrastat_type_view.xml', 'company_view.xml', + 'product_view.xml', + 'stock_view.xml', + 'invoice_view.xml', ], 'demo_xml': [], 'installable': True, diff --git a/l10n_fr_intrastat_product/deb_xsd.py b/l10n_fr_intrastat_product/deb_xsd.py new file mode 100644 index 000000000..7143443d4 --- /dev/null +++ b/l10n_fr_intrastat_product/deb_xsd.py @@ -0,0 +1,143 @@ +# -*- encoding: utf-8 -*- + +deb_xsd = '''\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +''' diff --git a/l10n_fr_intrastat_product/intrastat_product.py b/l10n_fr_intrastat_product/intrastat_product.py index 8d444c695..1bf6beaee 100644 --- a/l10n_fr_intrastat_product/intrastat_product.py +++ b/l10n_fr_intrastat_product/intrastat_product.py @@ -2,7 +2,7 @@ ############################################################################## # # Report intrastat product module for OpenERP -# Copyright (C) 2010-2011 Akretion (http://www.akretion.com). All Rights Reserved +# Copyright (C) 2009-2011 Akretion (http://www.akretion.com). All Rights Reserved. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -19,3 +19,491 @@ # ############################################################################## +# TODO : +# Roud the value amounts in the SQL view, not in the XML export +# When a line is selected, only this line will be in XML export +# -> convert the SQL view to a real object, and re-organise everything accordingly +# We don't have the statistical value in the XML export !!! +# Fully implement the 2 levels +# Re-organise modules +# Full implementation of detailed DEB +# WISH : modify title of report_intrastat tree view if export vs import + +#TODO : vérifier le mult-company + +#TODO ajouter données pour procedure code et transaction code + +# TODO add mode de trp on SQL, view, columns, etc... write + +from osv import osv, fields +import time +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta +from tools.translate import _ +import netsvc + +class report_intrastat_type(osv.osv): + _inherit = "report.intrastat.type" + _columns = { + 'procedure_code': fields.integer('Procedure code', help="For the 'DEB' declaration to France's customs administration, you should enter the 'code régime' here."), + 'transaction_code': fields.integer('Transaction code', help="For the 'DEB' declaration to France's customs administration, you should enter the number 'nature de la transaction' here."), + } + + def _code_check(self, cr, uid, ids): + # Procedure_code and transaction codes are an integers, so they always have a value + for intrastat_type in self.read(cr, uid, ids, ['type', 'procedure_code', 'transaction_code'], context=context): + if intrastat_type['type'] == 'other': + continue + else: + if not 10 <= intrastat_type['procedure_code'] <= 99: + raise osv.except_osv(_('Error :'), _('Procedure code must be between 10 and 99')) + if not 10 <= intrastat_type['transaction_code'] <= 99: + raise osv.except_osv(_('Error :'), _('Transaction code must be between 10 and 99')) + return True + + _constraints = [ + (_code_check, "Error msg in raise", ['procedure_code', 'transaction_code']), + ] + +report_intrastat_type() + +class report_intrastat_product(osv.osv): + _name = "report.intrastat.product" + _description = "Intrastat report for products" + _rec_name = "start_date" + _order = "start_date, type" + + + def _compute_numbers(self, cr, uid, ids, name, arg, context=None): + print "PRODUCT _compute numbers start ids=", ids + return self.pool.get('report.intrastat.common')._compute_numbers(cr, uid, ids, self, context=context) + + def _compute_end_date(self, cr, uid, ids, name, arg, context=None): + print "PRODUCT _compute_end_date START ids=", ids + return self.pool.get('report.intrastat.common')._compute_end_date(cr, uid, ids, self, context=context) + + def _get_intrastat_from_product_line(self, cr, uid, ids, context=None): + print "invalidation function CALLED !!!" + return self.pool.get('report.intrastat.product').search(cr, uid, [('intrastat_line_ids', 'in', ids)], context=context) + + _columns = { + 'company_id': fields.many2one('res.company', 'Company', required=True, states={'done':[('readonly',True)]}, help="Related company."), + 'start_date': fields.date('Start date', required=True, states={'done':[('readonly',True)]}, help="Start date of the declaration. Must be the first day of a month."), + 'end_date': fields.function(_compute_end_date, method=True, type='date', string='End date', store=True, help="End date for the declaration. Must be the last day of the month of the start date."), + 'type': fields.selection([('import', 'Import'), ('export', 'Export')], 'Type', required=True, states={'done':[('readonly',True)]}, help="Select the type of DEB."), + 'obligation_level' : fields.selection([('detailed', 'Detailed'), ('simplified', 'Simplified')], 'Obligation level', required=True, states={'done':[('readonly',True)]}, help="Your obligation level for a certain type of DEB (Import or Export) depends on the total value that you export or import per year. Note that the obligation level 'Simplified' doesn't exist for Import."), + 'intrastat_line_ids': fields.one2many('report.intrastat.product.line', 'parent_id', 'Report intrastat product lines', states={'done':[('readonly',True)]}), + 'num_lines': fields.function(_compute_numbers, method=True, type='integer', multi='numbers', string='Number of lines', store={ + 'report.intrastat.product.line': (_get_intrastat_from_product_line, ['parent_id'], 20), + }, help="Number of lines in this declaration."), + 'total_amount': fields.function(_compute_numbers, method=True, digits=(16,0), multi='numbers', string='Total amount', store={ + 'report.intrastat.product.line': (_get_intrastat_from_product_line, ['amount_company_currency', 'parent_id'], 20), + }, help="Total amount in company currency of the declaration."), + 'currency_id': fields.related('company_id', 'currency_id', readonly=True, type='many2one', relation='res.currency', string='Currency'), + 'state' : fields.selection([ + ('draft','Draft'), + ('done','Done'), + ], 'State', select=True, readonly=True, help="State of the declaration. When the state is set to 'Done', the parameters become read-only."), + 'notes' : fields.text('Notes', help="You can add some comments here if you want."), + } + + _defaults = { + # By default, we propose 'current month -1', because you prepare in + # February the DEB of January + 'start_date': lambda *a: datetime.strftime(datetime.today() + relativedelta(day=1, months=-1), '%Y-%m-%d'), + 'state': lambda *a: 'draft', + 'company_id': lambda self, cr, uid, context: \ + self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id, + } + + + def type_on_change(self, cr, uid, ids, company_id=False, type=False): + result = {} + result['value'] = {} + if type and company_id: + if type == 'import': # The only level possible for DEB import is detailed + result['value'].update({'obligation_level': 'detailed'}) + if type == 'export': + print "company_id", company_id + company = self.pool.get('res.company').read(cr, uid, company_id, ['export_obligation_level']) + print "company =", company + if company['export_obligation_level']: + result['value'].update({'obligation_level': company['export_obligation_level']}) + return result + + def _check_start_date(self, cr, uid, ids, context=None): + return self.pool.get('report.intrastat.common')._check_start_date(cr, uid, ids, self, context=context) + + def _check_obligation_level(self, cr, uid, ids, context=None): + for intrastat_to_check in self.read(cr, uid, ids, ['type', 'obligation_level'], context=context): + if intrastat_to_check['type'] == 'import' and intrastat_to_check['obligation_level'] == 'simplified': + return False + return True + + _constraints = [ + (_check_start_date, "Start date must be the first day of a month", ['start_date']), + (_check_obligation_level, "Obligation level can't be 'Simplified' for Import", ['obligation_level']), + ] + + _sql_constraints = [ + ('date_uniq', 'unique(start_date, company_id, type)', 'You have already created a DEB of the same type for this month !'), + ] + + def generate_product_lines(self, cr, uid, ids, context=None): + print "generate lines, ids=", ids + if len(ids) != 1: + raise osv.except_osv(_('Error :'), _('Hara kiri in generate_service_lines')) + intrastat = self.browse(cr, uid, ids[0], context=context) + # Check that the company currency is EUR + if not intrastat.currency_id.code == 'EUR': + raise osv.except_osv(_('Error :'), _('The company currency must be "EUR", but is currently "%s".'%intrastat.currency_id.code)) + # Get current service lines and delete them + line_remove = self.read(cr, uid, ids[0], ['intrastat_line_ids'], context=context) + print "line_remove", line_remove + if line_remove['intrastat_line_ids']: + self.pool.get('report.intrastat.product.line').unlink(cr, uid, line_remove['intrastat_line_ids'], context=context) + +# What about if we sell a product with 100% discount ? +# Here, we suppose that list_price on product template is in company_currency + sql = ''' + SELECT + min(inv_line.id) as id, + company.id, + inv.date_invoice, + inv.name as name, + inv.currency_id as invoice_currency_id, + company.currency_id as company_currency_id, + hs.code8 as intrastat_code_8, + upper(inv_country.code) as partner_country, + upper(product_country.code) as product_country_origin, + sum(case when not intr.intrastat_only + then inv_line.price_subtotal + else 0 + end) as amount_invoice_currency, + sum(case when not intr.intrastat_only and company.currency_id != inv.currency_id and res_currency_rate.rate is not null + then round(inv_line.price_subtotal/res_currency_rate.rate, 2) + when not intr.intrastat_only and company.currency_id = inv.currency_id + then inv_line.price_subtotal + else 0 + end) as amount_company_currency, + + sum(case when intr.intrastat_only and company.currency_id != inv.currency_id and res_currency_rate.rate is not null + then round(inv_line.price_subtotal/res_currency_rate.rate, 2) + when intr.intrastat_only and company.currency_id = inv.currency_id + then inv_line.price_subtotal + else 0 + end) as stat_value_company_currency, + + sum( + case when inv_uom.category_id != pt_uom.category_id then pt.weight_net * inv_line.quantity + else + case when inv_uom.factor_inv_data > 0 + then + pt.weight_net * inv_line.quantity * inv_uom.factor_inv_data + else + pt.weight_net * inv_line.quantity / inv_uom.factor + end + end + ) as weight, + + sum( + case when inv_uom.category_id != pt_uom.category_id then inv_line.quantity + else + case when inv_uom.factor_inv_data > 0 + then + inv_line.quantity * inv_uom.factor_inv_data + else + inv_line.quantity / inv_uom.factor + end + end + ) as invoice_qty, + inv_uom.intrastat_label as invoice_uom, + intrastat_uom.intrastat_label as intrastat_uom, + + inv.number as invoice_number, + intr.procedure_code, + intr.transaction_code, + prt.vat as partner_vat, + prt.name as partner_name, + intr.type as type + + FROM + account_invoice inv + left join account_invoice_line inv_line on inv_line.invoice_id=inv.id + left join (product_template pt + left join product_product pp on (pp.product_tmpl_id = pt.id) + left join res_country product_country on (product_country.id = pt.country_id) + left join product_uom pt_uom on (pt.uom_id = pt_uom.id ) + left join (report_intrastat_code hs + left join product_uom intrastat_uom on ( intrastat_uom.id = hs.intrastat_uom_id ) + ) on (hs.id = pt.intrastat_id)) + on (inv_line.product_id = pp.id) + left join product_uom inv_uom on inv_uom.id=inv_line.uos_id + left join (res_partner_address inv_address + left join res_country inv_country on (inv_country.id = inv_address.country_id)) + on (inv_address.id = inv.address_invoice_id) + left join report_intrastat_type intr on inv.intrastat_type_id = intr.id + left join res_partner prt on inv.partner_id = prt.id + left join res_currency_rate on (inv.currency_id = res_currency_rate.currency_id and inv.date_invoice = res_currency_rate.name) + left join res_company company on inv.company_id=company.id + + WHERE + inv.state in ('open', 'paid', 'legal_intrastat') + and inv_line.product_id is not null + and inv_line.price_subtotal != 0 + and inv_country.intrastat=true + and intrastat_id is not null + and pt.exclude_from_intrastat is not true + and intr.type != 'other' + and company.id = %s + and inv.date_invoice >= %s + and inv.date_invoice <= %s + and intr.type = %s + + GROUP BY company.id, inv.name, inv.date_invoice, code8, intr.type, product_country_origin, inv_country.code, inv.number, inv.currency_id, intr.procedure_code, intr.transaction_code, prt.vat, prt.name, company_currency_id, intrastat_uom, invoice_uom + ''' + # Execute the big SQL query to get all the lines + cr.execute(sql, (intrastat.company_id.id, intrastat.start_date, intrastat.end_date, intrastat.type)) + res_sql = cr.fetchall() + print "res_sql=", res_sql + line_obj = self.pool.get('report.intrastat.product.line') + for id, company_id, date_invoice, name, invoice_currency_id, company_currency_id, intrastat_code_8, partner_country, product_country_origin, amount_invoice_currency, amount_company_currency, stat_value_company_currency, weight, invoice_qty, invoice_uom, intrastat_uom, invoice_number, procedure_code, transaction_code, partner_vat, partner_name, type in res_sql: + line_obj.create(cr, uid, { + 'parent_id': ids[0], + 'name': name, + 'invoice_qty': int(round(invoice_qty, 0)), + 'invoice_uom': invoice_uom, + 'intrastat_uom': intrastat_uom, + 'invoice_number': invoice_number, + 'partner_country': partner_country, + 'intrastat_code_8': intrastat_code_8, + 'weight': int(round(weight, 0)), + 'amount_invoice_currency': int(round(amount_invoice_currency, 0)), + 'amount_company_currency': int(round(amount_company_currency, 0)), + 'stat_value_company_currency': int(round(stat_value_company_currency, 0)), + 'invoice_currency_id': invoice_currency_id, + 'company_currency_id': company_currency_id, + 'product_country_origin': product_country_origin, + 'procedure_code': procedure_code, + 'transaction_code': transaction_code, + 'partner_vat': partner_vat, + 'partner_name': partner_name, + 'date_invoice': date_invoice, + }, context=context) + return None + + + def done(self, cr, uid, ids, context=None): + if len(ids) != 1: raise osv.except_osv(_('Error :'), _('Hara kiri')) + self.write(cr, uid, ids[0], {'state': 'done'}, context=context) + return None + + def back2draft(self, cr, uid, ids, context=None): + if len(ids) != 1: raise osv.except_osv(_('Error :'), _('Hara kiri')) + self.write(cr, uid, ids[0], {'state': 'done'}, context=context) + return None + + def check_len(self, field, size): + '''Convert to string and check size of the string''' + if len(str(field)) == size: + return str(field) + else: + raise osv.except_osv(_('Error :'), _("OpenERP failed at generating the INSTAT file because the value '%s' is not %d caracters long")%(str(field), size)) + + def generate_xml(self, cr, uid, ids, context=None): + '''Generate the INSTAT XML file export.''' + import base64 + from lxml import etree + import deb_xsd + if len(ids) != 1: + raise osv.except_osv(_('Error :'), 'Hara kiri in generate_xml') + intrastat = self.browse(cr, uid, ids[0], context=context) + start_date_str = intrastat.start_date + end_date_str = intrastat.end_date + start_date_datetime = datetime.strptime(start_date_str, '%Y-%m-%d') +#TODO : factoriser les checks entre product/service, et entre gene lines et gen xml ? + if not intrastat.company_id.currency_id.code: + raise osv.except_osv(_('Error :'), _('The currency code is not set on the currency "%s".'%intrastat.company_id.currency_id.name)) + + if not intrastat.currency_id.code == 'EUR': + raise osv.except_osv(_('Error :'), _('The company currency must be "EUR", but is currently "%s".'%intrastat.currency_id.code)) + + if not intrastat.company_id.partner_id.vat: + raise osv.except_osv(_('Error :'), _('The VAT number is not set for the partner "%s".'%intrastat.company_id.partner_id.name)) + my_company_vat = intrastat.company_id.partner_id.vat.replace(' ', '') + + if not my_company_vat[0:2] == 'FR': + raise osv.except_osv(_('Error :'), _('The INSTAT file export only works for France-based companies.')) + if not intrastat.company_id.siret_complement: + raise osv.except_osv(_('Error :'), _('The SIRET complement is not set.')) + my_company_id = my_company_vat + intrastat.company_id.siret_complement + + my_company_currency = intrastat.company_id.currency_id.code + + root = etree.Element('INSTAT') + envelope = etree.SubElement(root, 'Envelope') + envelope_id = etree.SubElement(envelope, 'envelopeId') + try: envelope_id.text = intrastat.company_id.customs_accreditation + except: raise osv.except_osv(_('Error :'), _('The customs accreditation identifier is not set for the company "%s".'%intrastat.company_id.name)) + create_date_time = etree.SubElement(envelope, 'DateTime') + create_date = etree.SubElement(create_date_time, 'date') + create_date.text = datetime.strftime(datetime.today(), '%Y-%m-%d') + create_time = etree.SubElement(create_date_time, 'time') + create_time.text = datetime.strftime(datetime.today(), '%H:%M:%S') + party = etree.SubElement(envelope, 'Party', partyType="PSI", partyRole="PSI") + party_id = etree.SubElement(party, 'partyId') + party_id.text = my_company_id + party_name = etree.SubElement(party, 'partyName') + party_name.text = intrastat.company_id.name + software_used = etree.SubElement(envelope, 'softwareUsed') + software_used.text = 'OpenERP' + declaration = etree.SubElement(envelope, 'Declaration') + declaration_id = etree.SubElement(declaration, 'declarationId') + declaration_id.text = datetime.strftime(start_date_datetime, '%Y%m') # 6 digits + reference_period = etree.SubElement(declaration, 'referencePeriod') + reference_period.text = datetime.strftime(start_date_datetime, '%Y-%m') + psi_id = etree.SubElement(declaration, 'PSIId') + psi_id.text = my_company_id + function = etree.SubElement(declaration, 'Function') + function_code = etree.SubElement(function, 'functionCode') + function_code.text = 'O' + declaration_type_code = etree.SubElement(declaration, 'declarationTypeCode') + if intrastat.obligation_level == 'detailed': + declaration_type_code.text = '1' + elif intrastat.obligation_level == 'simplified': + declaration_type_code.text = '4' + else: + raise osv.except_osv(_('Error :'), _("The obligation level for DEB should be 'simplified' or 'detailed'.")) + flow_code = etree.SubElement(declaration, 'flowCode') + + if intrastat.type == 'export': + flow_code.text = 'D' + elif intrastat.type == 'import': + flow_code.text = 'A' + else: + raise osv.except_osv(_('Error :'), _('The DEB must be of type "Import" or "Export"')) + currency_code = etree.SubElement(declaration, 'currencyCode') + if my_company_currency == 'EUR': # TODO : remove, already tested + currency_code.text = my_company_currency + else: + raise osv.except_osv(_('Error :'), _("Company currency must be 'EUR' for the INSTAT file export, but it is currently '%s'." %my_company_currency)) + + # THEN, the fields which vary from a line to the next + line = 0 + for pline in intrastat.intrastat_line_ids: + line += 1 #increment line number + item = etree.SubElement(declaration, 'Item') + item_number = etree.SubElement(item, 'itemNumber') + item_number.text = str(line) + # START of elements which are only required in "detailed" level + if intrastat.obligation_level == 'detailed': + cn8 = etree.SubElement(item, 'CN8') + cn8_code = etree.SubElement(cn8, 'CN8Code') + try: cn8_code.text = pline.intrastat_code_8 + except: raise osv.except_osv(_('Error :'), _('Missing Intrastat code on line %d.' %line)) + if pline.intrastat_uom: + if pline.intrastat_uom != pline.invoice_uom: + raise osv.except_osv(_('Error :'), _("On line %d, the unit of measure on invoice and intrastat code are different. We don't handle this scenario for the moment."%i)) + else: + su_code = etree.SubElement(cn8, 'SUCode') + try: su_code.text = str(pline.intrastat_uom) + except: raise osv.except_osv(_('Error :'), _('Missing Intrastat UoM on line %d.' %line)) + destination_country = etree.SubElement(item, 'MSConsDestCode') + country_origin = etree.SubElement(item, 'countryOfOriginCode') + weight = etree.SubElement(item, 'netMass') + quantity_in_SU = etree.SubElement(item, 'quantityInSU') + + try: quantity_in_SU.text = str(pline.invoice_qty) + except: raise osv.except_osv(_('Error :'), _('Missing invoice quantity on line %d.' %line)) + else: + destination_country = etree.SubElement(item, 'MSConsDestCode') + country_origin = etree.SubElement(item, 'countryOfOriginCode') + weight = etree.SubElement(item, 'netMass') + + try: destination_country.text = pline.partner_country + except: raise osv.except_osv(_('Error :'), _('Missing partner country on line %d.' %line)) + try: country_origin.text = pline.product_country_origin + except: raise osv.except_osv(_('Error :'), _('Missing product country of origin on line %d.' %line)) + try: weight.text = str(pline.weight) + except: raise osv.except_osv(_('Error :'), _('Missing weight on line %d.' %line)) + + # START of elements that are part of all DEBs + invoiced_amount = etree.SubElement(item, 'invoicedAmount') + try: + invoiced_amount.text = str(pline.fiscal_value_company_currency) + except: raise osv.except_osv(_('Error :'), _('Missing fiscal value on line %d.' %line)) + partner_id = etree.SubElement(item, 'partnerId') + try: partner_id.text = pline.partner_vat.replace(' ', '') + except: raise osv.except_osv(_('Error :'), _('Missing VAT number for partner "%s".' %pline.partner_name)) + statistical_procedure_code = etree.SubElement(item, 'statisticalProcedureCode') + statistical_procedure_code.text = str(pline.procedure_code) # str(integrer) always have a value, so it should never crash here -> no try/except + + # START of elements which are only required in "detailed" level + if intrastat.obligation_level == 'detailed': + transaction_nature = etree.SubElement(item, 'NatureOfTransaction') + transaction_nature_a = etree.SubElement(transaction_nature, 'natureOfTransactionACode') + transaction_nature_a.text = str(pline.transaction_code)[0] # str(integer)[0] always have a value, so it should never crash here -> no try/except + transaction_nature_b = etree.SubElement(transaction_nature, 'natureOfTransactionBCode') + try: transaction_nature_b.text = str(pline.transaction_code)[1] + except: raise osv.except_osv(_('Error :'), _('Transaction code on line %d should have 2 digits.' %line)) + mode_of_transport_code = etree.SubElement(item, 'modeOfTransportCode') + try: mode_of_transport_code.text = str(intrastat.company_id.default_intrastat_transport) + except: raise osv.except_osv(_('Error :'), _('Mode of transport is not set on line %d nor the default mode of transport for the company %s.' %(line, intrastat.company_id.name))) + region_code = etree.SubElement(item, 'regionCode') + try: region_code.text = intrastat.company_id.default_intrastat_department + except: raise osv.except_osv(_('Error :'), _('Department code is not set on line %d nor the default department code for the company %s.' % (line, intrastat.company_id.name))) + + xml_string = etree.tostring(root, pretty_print=True, encoding='UTF-8', xml_declaration=True) + # We now validate the XML file against the official XML Schema Definition + # Because we may catch some problems with the content of the XML file this way + official_instat_xml_schema = etree.XMLSchema(etree.fromstring(deb_xsd.deb_xsd)) + try: official_instat_xml_schema.assertValid(root) + except Exception, e: + logger = netsvc.Logger() + logger.notifyChannel('intrastat_product', netsvc.LOG_WARNING, "The XML file is invalid against the XSD") + logger.notifyChannel('intrastat_product', netsvc.LOG_WARNING, xml_string) + logger.notifyChannel('intrastat_product', netsvc.LOG_WARNING, e) + + raise osv.except_osv(_('Error :'), _('The generated XML INSTAT file is not valid against the official XML schema. The generated XML file and the full error have been written in the server logs. Here is the exact error, which may give you an idea of the cause of the problem : ' + str(e))) + filename = datetime.strftime(start_date_datetime, '%Y-%m') + '_deb.xml' + attach_name = 'DEB ' + datetime.strftime(start_date_datetime, '%Y-%m') + + # Attach the XML file to the intrastat_product object + attach_obj = self.pool.get('ir.attachment') + if not context: + context = {} + context.update({'default_res_id' : ids[0], 'default_res_model': 'report.intrastat.product'}) + attach_id = attach_obj.create(cr, uid, {'name': attach_name, 'datas': base64.encodestring(xml_string), 'datas_fname': filename}, context=context) + return None + +report_intrastat_product() + + +class report_intrastat_product_line(osv.osv): + _name = "report.intrastat.product.line" + _description = "Lines of intrastat product declaration (DEB)" + _order = 'invoice_number' + _columns = { + 'parent_id': fields.many2one('report.intrastat.product', 'Intrastat product ref', ondelete='cascade', select=True), + 'name': fields.char('Invoice description', size=64, readonly=True), + 'invoice_qty': fields.integer('Invoice quantity', readonly=True), + 'invoice_uom': fields.char('Invoice UoM', size=12, readonly=True), + 'intrastat_uom': fields.char('Intrastat UoM', size=12, readonly=True), + 'invoice_number': fields.char('Invoice number', size=32, readonly=True), + 'partner_country': fields.char('Partner country', size=2, readonly=True), + 'intrastat_code_8': fields.char('Intrastat code', size=8, readonly=True), + 'weight': fields.integer('Weight', readonly=True), + 'amount_invoice_currency': fields.integer('Fiscal value in invoice currency', readonly=True), + 'amount_company_currency': fields.integer('Fiscal value in company currency', readonly=True), + 'stat_value_company_currency' : fields.integer('Stat value in company currency', readonly=True), + 'invoice_currency_id': fields.many2one('res.currency', "Invoice currency", readonly=True), + 'company_currency_id': fields.many2one('res.currency', "Company currency", readonly=True), + 'product_country_origin' : fields.char('Product country of origin', size=2, readonly=True), + 'procedure_code': fields.integer('Procedure code', readonly=True), + 'transaction_code': fields.integer('Transaction code', readonly=True), + 'partner_vat': fields.char('Partner VAT', size=32, readonly=True), + 'partner_name': fields.char('Partner name', size=128, readonly=True), + 'date_invoice' : fields.date('Invoice date', readonly=True), + } + +report_intrastat_product_line() diff --git a/l10n_fr_intrastat_product/intrastat_product_view.xml b/l10n_fr_intrastat_product/intrastat_product_view.xml new file mode 100644 index 000000000..124394be7 --- /dev/null +++ b/l10n_fr_intrastat_product/intrastat_product_view.xml @@ -0,0 +1,133 @@ + + + + + + + fr.intrastat.product.form + report.intrastat.product + form + +
+ + + + + + + + + + + + + + + + +