From 9f9f27e506d378471fc47673a1938109d8eeeeb5 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 1 Apr 2011 23:24:17 +0200 Subject: [PATCH] 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_base/__init__.py | 1 + l10n_fr_intrastat_base/intrastat_common.py | 249 ++++++++++++++++++ .../security/ir.model.access.csv | 1 + 3 files changed, 251 insertions(+) create mode 100644 l10n_fr_intrastat_base/intrastat_common.py diff --git a/l10n_fr_intrastat_base/__init__.py b/l10n_fr_intrastat_base/__init__.py index f85979af..01b3a141 100644 --- a/l10n_fr_intrastat_base/__init__.py +++ b/l10n_fr_intrastat_base/__init__.py @@ -26,4 +26,5 @@ import product import account_invoice import partner_address import company +import intrastat_common diff --git a/l10n_fr_intrastat_base/intrastat_common.py b/l10n_fr_intrastat_base/intrastat_common.py new file mode 100644 index 00000000..deee25fe --- /dev/null +++ b/l10n_fr_intrastat_base/intrastat_common.py @@ -0,0 +1,249 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Report intrastat module for OpenERP +# Copyright (C) 2010-2011 Akretion (http://www.akretion.com/). All rights reserved. +# Code written by Alexis de Lattre +# +# 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 +import netsvc +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta +from tools.translate import _ + +class report_intrastat_common(osv.osv_memory): + _name = "report.intrastat.common" + _description = "Common functions for intrastat reports for products and services" + + def _compute_numbers(self, cr, uid, ids, object, context=None): + print "COMMON START compute numbers ids=", ids + result = {} + for intrastat in object.browse(cr, uid, ids, context=context): + total_amount = 0.0 + num_lines = 0 + for line in intrastat.intrastat_line_ids: + total_amount += line.amount_company_currency + num_lines += 1 + result[intrastat.id] = {'num_lines': num_lines, 'total_amount': total_amount} + print "COMMON _compute_numbers res = ", result + return result + + + def _compute_end_date(self, cr, uid, ids, object, context=None): + print "COMMON _compute_end_date START ids=", ids + result = {} + for intrastat in object.browse(cr, uid, ids, context=context): + start_date_datetime = datetime.strptime(intrastat.start_date, '%Y-%m-%d') + end_date_str = datetime.strftime(start_date_datetime + relativedelta(day=31), '%Y-%m-%d') + result[intrastat.id] = end_date_str + print "COMMON _compute_end_date res=", result + return result + + + def _check_start_date(self, cr, uid, ids, object, context=None): + '''Check that the start date if the first day of the month''' + for date_to_check in object.read(cr, uid, ids, ['start_date'], context=context): + datetime_to_check = datetime.strptime(date_to_check['start_date'], '%Y-%m-%d') + if datetime_to_check.day != 1: + return False + return True + + def generate_service_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.service.line').unlink(cr, uid, line_remove['intrastat_line_ids'], context=context) + sql = ''' + select + min(inv_line.id) as id, + company.id, + inv.name as name, + inv.number as invoice_number, + inv.date_invoice, + inv.currency_id as invoice_currency_id, + prt.vat as partner_vat, + prt.name as partner_name, + res_currency_rate.rate as invoice_currency_rate, + sum(case when inv.type = 'out_refund' + then inv_line.price_subtotal * (-1) + when inv.type = 'out_invoice' + then inv_line.price_subtotal + end) as amount_invoice_currency, + sum(case when company.currency_id != inv.currency_id and res_currency_rate.rate is not null + then + case when inv.type = 'out_refund' + then round(inv_line.price_subtotal/res_currency_rate.rate * (-1), 2) + when inv.type = 'out_invoice' + then round(inv_line.price_subtotal/res_currency_rate.rate, 2) + end + when company.currency_id = inv.currency_id + then + case when inv.type = 'out_refund' + then inv_line.price_subtotal * (-1) + when inv.type = 'out_invoice' + then inv_line.price_subtotal + end + end) as amount_company_currency, + company.currency_id as company_currency_id + + 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 + ) + on (inv_line.product_id = pt.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 res_partner prt on inv.partner_id = prt.id + left join report_intrastat_type intr on inv.intrastat_type_id = intr.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.type in ('out_invoice', 'out_refund') + and inv.state in ('open', 'paid') + and inv_line.product_id is not null + and inv_line.price_subtotal != 0 + and inv_country.intrastat = true + and pt.type = 'service' + 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 + + group by company.id, inv.date_invoice, inv.number, inv.currency_id, prt.vat, prt.name, inv.name, invoice_currency_rate, company_currency_id + ''' + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + company_id = str(user.company_id.id) + print "company_id =", company_id + start_date_str = intrastat.start_date + end_date_str = intrastat.end_date + # Execute the big SQL query to get all service lines + cr.execute(sql, (company_id, start_date_str, end_date_str)) + res_sql = cr.fetchall() + print "res_sql=", res_sql + line_obj = self.pool.get('report.intrastat.service.line') + for id, name, company_id, invoice_number, date_invoice, invoice_currency_id, partner_vat, partner_name, invoice_currency_rate, amount_invoice_currency, amount_company_currency, company_currency_id in res_sql: + print "amount_invoice_currency =", amount_invoice_currency + print "amount_company_currency =", amount_company_currency + # Store the service lines + line_obj.create(cr, uid, { + 'parent_id': ids[0], + 'name': name, + 'invoice_number': invoice_number, + 'partner_vat': partner_vat, + 'partner_name': partner_name, + 'date_invoice': date_invoice, + 'amount_invoice_currency': int(round(amount_invoice_currency, 0)), + 'invoice_currency_id': invoice_currency_id, + 'amount_company_currency': int(round(amount_company_currency, 0)), + 'company_currency_id': company_currency_id, + }, context=context) + return None + + + def done(self, cr, uid, ids, context=None): + if len(ids) != 1: + raise osv.except_osv(_('Error :'), _('Hara kiri in generate_xml')) + 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 in generate_xml')) + self.write(cr, uid, ids[0], {'state': 'draft'}, context=context) + return None + + + def generate_xml(self, cr, uid, ids, context=None): + 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') + + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + + 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 user.company_id.partner_id.vat: + raise osv.except_osv(_('Error :'), _('The VAT number is not set for the partner "%s".'%user.company_id.partner_id.name)) + my_company_vat = user.company_id.partner_id.vat.replace(' ', '') + + # Tech spec of XML export are available here : + # https://pro.douane.gouv.fr/download/downloadUrl.asp?file=PubliwebBO/fichiers/DES_DTIPlus.pdf + root = etree.Element('fichier_des') + decl = etree.SubElement(root, 'declaration_des') + num_des = etree.SubElement(decl, 'num_des') + num_des.text = datetime.strftime(start_date_datetime, '%Y%m') + num_tva = etree.SubElement(decl, 'num_tvaFr') + num_tva.text = my_company_vat + mois_des = etree.SubElement(decl, 'mois_des') + mois_des.text = datetime.strftime(start_date_datetime, '%m') # month 2 digits + an_des = etree.SubElement(decl, 'an_des') + an_des.text = datetime.strftime(start_date_datetime, '%Y') + line = 0 + # we now go through each service line + for sline in intrastat.intrastat_line_ids: + line += 1 # increment line number + ligne_des = etree.SubElement(decl, 'ligne_des') + numlin_des = etree.SubElement(ligne_des, 'numlin_des') + numlin_des.text = str(line) + valeur = etree.SubElement(ligne_des, 'valeur') + # We take amount_company_currency, to be sure we have amounts in EUR + valeur.text = str(sline.amount_company_currency) + partner_des = etree.SubElement(ligne_des, 'partner_des') + try: partner_des.text = sline.partner_vat.replace(' ', '') + except: raise osv.except_osv(_('Error :'), _('Missing VAT number for partner "%s".'%sline.partner_name)) + xml_string = etree.tostring(root, pretty_print=True, encoding='UTF-8', xml_declaration=True) + print "xml_string", xml_string + + # We now validate the XML file against the official XML Schema Definition + official_des_xml_schema = etree.XMLSchema(etree.fromstring(des_xsd.des_xsd)) + try: official_des_xml_schema.assertValid(root) + except Exception, e: # if the validation of the XSD fails, we arrive here + logger = netsvc.Logger() + logger.notifyChannel('intrastat_service', netsvc.LOG_WARNING, "The XML file is invalid against the XSD") + logger.notifyChannel('intrastat_service', netsvc.LOG_WARNING, xml_string) + logger.notifyChannel('intrastat_service', netsvc.LOG_WARNING, e) + raise osv.except_osv(_('Error :'), _('The generated XML 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))) + #let's give a pretty name to the filename : + filename = datetime.strftime(start_date_datetime, '%Y-%m') + '_des.xml' + attach_name = 'DES ' + datetime.strftime(start_date_datetime, '%Y-%m') + + # Attach the XML file to the intrastat_service object + attach_obj = self.pool.get('ir.attachment') + if not context: + context = {} + context.update({'default_res_id' : ids[0], 'default_res_model': 'report.intrastat.service'}) + attach_id = attach_obj.create(cr, uid, {'name': attach_name, 'datas': base64.encodestring(xml_string), 'datas_fname': filename}, context=context) + return None + + +report_intrastat_common() + diff --git a/l10n_fr_intrastat_base/security/ir.model.access.csv b/l10n_fr_intrastat_base/security/ir.model.access.csv index 6125ed62..62543002 100644 --- a/l10n_fr_intrastat_base/security/ir.model.access.csv +++ b/l10n_fr_intrastat_base/security/ir.model.access.csv @@ -1,2 +1,3 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" "access_report_intrastat_type","report.intrastat.type","model_report_intrastat_type","account.group_account_manager",1,1,1,1 +"access_report_intrastat_common","Read access on report.intrastat.common","model_report_intrastat_common","base.user",1,0,0,0