1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (c) 2009 Alejandro Sanchez (http://www.asr-oss.com) All Rights Reserved.
6
# Alejandro Sanchez <alejandro@asr-oss.com>
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 3 of the License, or
12
# (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
19
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
##############################################################################
28
from mx.DateTime import now
30
from tools.translate import _
32
logger = netsvc.Logger()
34
facturae_form = """<?xml version="1.0"?>
35
<form string="Create Factura-E">
36
<label string="Do you want to Create Factura-E?"/>
41
export_form = """<?xml version="1.0"?>
42
<form string="Payment order export">
43
<field name="facturae" filename="facturae_fname"/>
44
<field name="facturae_fname" invisible="1"/>
45
<field name="note" colspan="4" nolabel="1"/>
50
'string':'Factura-E file',
55
'facturae_fname': {'string':'File name', 'type':'char', 'size':64},
56
'note' : {'string':'Log', 'type':'text'},
60
"""Convierte vocales accentuadas, ñ y ç a sus caracteres equivalentes ASCII"""
61
old_chars = ['á','é','í','ó','ú','à','è','ì','ò','ù','ä','ë','ï','ö','ü','â','ê','î','ô','û','Á','É','Í','Ú','Ó','À','È','Ì','Ò','Ù','Ä','Ë','Ï','Ö','Ü','Â','Ê','Î','Ô','Û','ñ','Ñ','ç','Ç','ª','º']
62
new_chars = ['a','e','i','o','u','a','e','i','o','u','a','e','i','o','u','a','e','i','o','u','A','E','I','O','U','A','E','I','O','U','A','E','I','O','U','A','E','I','O','U','n','N','c','C','a','o']
63
for old, new in zip(old_chars, new_chars):
64
text = text.replace(unicode(old,'UTF-8'), new)
71
def add(self, s, error=True):
72
self.content = self.content + s
81
def _create_facturae_file(self, cr, uid, data, context):
84
#formato y definicion del fichero xml
85
texto = '<?xml version="1.0" encoding="UTF-8"?>'
86
texto = '<fe:Facturae xmlns:fe="http://www.facturae.es/Facturae/2007/v3.1/Facturae" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'
91
if vat[2:3].isdigit() == True:
100
currency = invoice.currency_id
102
texto += '<TotalInvoicesAmount>'
103
texto += '<TotalAmount>' + str('%.2f' % invoice.amount_total) + '</TotalAmount>'
104
texto += '</TotalInvoicesAmount>'
106
texto += '<TotalOutstandingAmount>'
107
texto += '<TotalAmount>' + str('%.2f' % invoice.amount_total) + '</TotalAmount>'
108
texto += '</TotalOutstandingAmount>'
110
texto += '<TotalExecutableAmount>'
111
texto += '<TotalAmount>' + str('%.2f' % invoice.amount_total) + '</TotalAmount>'
112
texto += '</TotalExecutableAmount>'
113
texto += '<InvoiceCurrencyCode>' + currency.code + '</InvoiceCurrencyCode>'
117
def _header_facturae(cr, context):
119
company_partner_obj = invoice.company_id.partner_id
121
schemaversion = '3.1'
124
if not invoice.number:
125
log.add(_('User error:\n\nCan not create Factura-E file if invoice has no number.'))
128
if company_partner_obj.vat:
129
BatchIdentifier = invoice.number + company_partner_obj.vat
131
log.add(_('User error:\n\nCompany %s has no VAT number.') % (company_partner_obj.name), True)
135
texto += '<FileHeader>'
136
texto += '<SchemaVersion>' + schemaversion + '</SchemaVersion>'
137
texto += '<Modality>' + modality + '</Modality>'
138
texto += '<InvoiceIssuerType>EM</InvoiceIssuerType>'
140
texto += '<BatchIdentifier>' + BatchIdentifier + '</BatchIdentifier>'
141
texto += '<InvoicesCount>1</InvoicesCount>'
142
texto += _apoyo_batch()
144
texto += '</FileHeader>'
147
def _parties_facturae(cr, context):
149
pool = pooler.get_pool(cr.dbname)
150
company_obj = invoice.company_id
151
company_partner_obj = company_obj.partner_id
152
invoice_partner_obj = invoice.partner_id
153
invoice_partner_address_obj = invoice.address_invoice_id
155
#obtencion direccion company recogemos la de facura adress_get si no encuentra invoice devuelve primera
156
company_address_id = pool.get('res.partner').address_get(cr, uid, [company_obj.partner_id.id], ['invoice'])
157
if not company_address_id['invoice']:
158
log.add(_('User error:\n\nCompany %s does not have an invoicing address.') % (company_partner_obj.name))
160
company_address_obj = pool.get('res.partner.address').browse(cr, uid, company_address_id['invoice'])
162
#obtencion de la direccion del partner
163
partner_address_invoice = invoice.address_invoice_id
165
tipo_seller = _persona(company_partner_obj.vat)
167
if invoice_partner_obj.vat:
168
tipo_buyer = _persona(invoice_partner_obj.vat)
170
log.add(_('User error:\n\nPartner %s does not have a VAT number.') % (invoice_partner_obj.name), True)
175
texto += '<SellerParty>'
176
texto += '<TaxIdentification>'
177
texto += '<PersonTypeCode>' + tipo_seller + '</PersonTypeCode>'
178
texto += '<ResidenceTypeCode>U</ResidenceTypeCode>'
179
texto += '<TaxIdentificationNumber>' + company_partner_obj.vat + '</TaxIdentificationNumber>'
180
texto += '</TaxIdentification>'
182
if tipo_seller == 'F':
183
texto += '<Individual>'
184
texto += '<Name>' + invoice_partner_obj.name + '</Name>'
185
texto += '<FirstSurname></FirstSurname>'
186
texto += '<SecondSurname></SecondSurname>'
188
texto += '<LegalEntity>'
189
texto += '<CorporateName>' + company_obj.name + '</CorporateName>'
190
texto += '<TradeName>' + company_obj.name + '</TradeName>'
192
#Fijo hasta que se tome una decision no son obligatorios
193
#texto += '<RegistrationData>'
194
#texto += '<Book>1</Book>'
195
#texto += '<RegisterOfCompaniesLocation>12AP22</RegisterOfCompaniesLocation>'
196
#texto += '<Sheet>3</Sheet>'
197
#texto += '<Folio>15</Folio>'
198
#texto += '<Section>2</Section>'
199
#texto += '<Volume>12</Volume>'
200
#texto += '<AdditionalRegistrationData>Sin datos</AdditionalRegistrationData>'
201
#texto += '</RegistrationData>'
203
texto += '<AddressInSpain>'
204
if company_address_obj.street:
205
if company_address_obj.street2:
206
texto += '<Address>' + company_address_obj.street + ' ' + company_address_obj.street2 + '</Address>'
208
texto += '<Address>' + company_address_obj.street + '</Address>'
210
log.add(_('User error:\n\nCompany %s has no street.') % (company_partner_obj.name), True)
212
if company_address_obj.zip:
213
texto += '<PostCode>' + company_address_obj.zip + '</PostCode>'
215
log.add(_('User error:\n\nCompany %s has no zip code.') % (company_partner_obj.name), True)
217
if company_address_obj.city:
218
texto += '<Town>' + company_address_obj.city + '</Town>'
220
log.add(_('User error:\n\nCompany %s has no city.') % (company_partner_obj.name), True)
222
if company_address_obj.state_id.name:
223
texto += '<Province>' + company_address_obj.state_id.name + '</Province>'
225
log.add(_('User error:\n\nCompany %s has no province.') % (company_partner_obj.name), True)
227
if company_address_obj.country_id.code_3166:
228
texto += '<CountryCode>' + company_address_obj.country_id.code_3166 + '</CountryCode>'
230
log.add(_('User error:\n\nCompany %s has no country.') % (company_partner_obj.name), True)
232
texto += '</AddressInSpain>'
234
texto += '<ContactDetails>'
235
if company_address_obj.phone:
236
texto += '<Telephone>' + company_address_obj.phone + '</Telephone>'
237
if company_address_obj.fax:
238
texto += '<TeleFax>' + company_address_obj.fax + '</TeleFax>'
239
if company_partner_obj.website:
240
texto += '<WebAddress>' + company_partner_obj.website + '</WebAddress>'
241
if company_address_obj.email:
242
texto += '<ElectronicMail>' + company_address_obj.email + '</ElectronicMail>'
243
if company_address_obj.name:
244
texto += '<ContactPersons>' + company_address_obj.name + '</ContactPersons>'
245
texto += '</ContactDetails>'
247
if tipo_seller == 'F':
248
texto += '</Individual>'
250
texto += '</LegalEntity>'
252
texto += '</SellerParty>'
253
texto += '<BuyerParty>'
254
texto += '<TaxIdentification>'
255
texto += '<PersonTypeCode>' + tipo_buyer + '</PersonTypeCode>'
256
texto += '<ResidenceTypeCode>U</ResidenceTypeCode>'
257
texto += '<TaxIdentificationNumber>' + invoice_partner_obj.vat + '</TaxIdentificationNumber>'
258
texto += '</TaxIdentification>'
260
if tipo_buyer == 'F':
261
texto += '<Individual>'
262
texto += '<Name>' + invoice_partner_obj.name + '</Name>'
263
texto += '<FirstSurname></FirstSurname>'
264
texto += '<SecondSurname></SecondSurname>'
266
texto += '<LegalEntity>'
267
texto += '<CorporateName>' + invoice_partner_obj.name + '</CorporateName>'
270
texto += '<AddressInSpain>'
271
if invoice_partner_address_obj.street:
272
if company_address_obj.street2:
273
texto += '<Address>' + invoice_partner_address_obj.street + ' ' + company_address_obj.street2 + '</Address>'
275
texto += '<Address>' + invoice_partner_address_obj.street + '</Address>'
277
log.add(_('User error:\n\nPartner %s has no street.') % (invoice_partner_address_obj.name), True)
279
if invoice_partner_address_obj.zip:
280
texto += '<PostCode>' + invoice_partner_address_obj.zip + '</PostCode>'
282
log.add(_('User error:\n\nPartner %s has no zip code.') % (invoice_partner_obj.name), True)
285
if invoice_partner_address_obj.city:
286
texto += '<Town>' + invoice_partner_address_obj.city + '</Town>'
288
log.add(_('User error:\n\nPartner %s has no city.') % (invoice_partner_obj.name), True)
290
if invoice_partner_address_obj.state_id.name:
291
texto += '<Province>' + invoice_partner_address_obj.state_id.name + '</Province>'
293
log.add(_('User error:\n\nPartner %s has no province.') % (invoice_partner_obj.name), True)
295
if invoice_partner_address_obj.country_id.code_3166:
296
texto += '<CountryCode>' + invoice_partner_address_obj.country_id.code_3166 + '</CountryCode>'
298
log.add(_('User error:\n\nPartner %s has no country.') % (invoice_partner_obj.name), True)
300
texto += '</AddressInSpain>'
302
texto += '<ContactDetails>'
303
if invoice_partner_address_obj.phone:
304
texto += '<Telephone>' + invoice_partner_address_obj.phone + '</Telephone>'
305
if invoice_partner_address_obj.fax:
306
texto += '<TeleFax>' + invoice_partner_address_obj.fax + '</TeleFax>'
307
if invoice_partner_obj.website:
308
texto += '<WebAddress>' + invoice_partner_obj.website + '</WebAddress>'
309
if invoice_partner_address_obj.email:
310
texto += '<ElectronicMail>' + invoice_partner_address_obj.email + '</ElectronicMail>'
311
if invoice_partner_address_obj.name:
312
texto += '<ContactPersons>' + invoice_partner_address_obj.name + '</ContactPersons>'
313
texto += '</ContactDetails>'
315
if tipo_buyer == 'F':
316
texto += '</Individual>'
318
texto += '</LegalEntity>'
319
texto += '</BuyerParty>'
320
texto += '</Parties>'
329
texto += '<TaxesOutputs>'
331
for l in invoice.tax_line:
332
taxes_withhel += l.base_amount
334
texto += '<TaxTypeCode>01</TaxTypeCode>'
335
cr.execute('SELECT t.amount FROM account_tax t WHERE t.tax_code_id =%s',(l.tax_code_id.id,))
337
texto += '<TaxRate>' + str('%.2f' % (res[0] * 100)) + '</TaxRate>'
338
texto += '<TaxableBase>'
339
texto += '<TotalAmount>' + str('%.2f' % l.base_amount) + '</TotalAmount>'
340
texto += '</TaxableBase>'
341
texto += '<TaxAmount>'
342
texto += '<TotalAmount>' + str('%.2f' % l.tax_amount) + '</TotalAmount>'
343
texto += '</TaxAmount>'
346
texto += '</TaxesOutputs>'
348
texto += '<TaxesWithheld>'
350
texto += '<TaxTypeCode>01</TaxTypeCode>'
351
texto += '<TaxRate>0.00</TaxRate>'
352
texto += '<TaxableBase>'
353
texto += '<TotalAmount>' + str('%.2f' % taxes_withhel) + '</TotalAmount>'
354
texto += '</TaxableBase>'
355
texto += '<TaxAmount>'
356
texto += '<TotalAmount>0.00</TotalAmount>'
357
texto += '</TaxAmount>'
359
texto += '</TaxesWithheld>'
363
def _invoice_totals():
365
total_gross_amount = 0.0
368
for line in invoice.invoice_line:
369
total_gross_amount += line.price_subtotal
371
texto += '<InvoiceTotals>'
372
texto += '<TotalGrossAmount>' + str('%.2f' % total_gross_amount) + '</TotalGrossAmount>'
373
#despues descuentos cabercera pero en OpenERP no se donde estan
374
#despues gastos de envio no se como aplicar si se pueden
375
#si se utilizaran los anteriores aqui se le restaria descuentos y sumarian gastos
376
texto += '<TotalGrossAmountBeforeTaxes>' + str('%.2f' % total_gross_amount) + '</TotalGrossAmountBeforeTaxes>'
377
texto += '<TotalTaxOutputs>' + str('%.2f' % invoice.amount_tax) + '</TotalTaxOutputs>'
378
texto += '<TotalTaxesWithheld>0.00</TotalTaxesWithheld>'
379
texto += '<InvoiceTotal>' + str('%.2f' % invoice.amount_total) + '</InvoiceTotal>'
380
#aqui se descontaria los pagos realizados a cuenta
381
texto += '<TotalOutstandingAmount>' + str('%.2f' % invoice.amount_total) + '</TotalOutstandingAmount>'
382
texto += '<TotalExecutableAmount>' + str('%.2f' % invoice.amount_total) + '</TotalExecutableAmount>'
384
texto += '</InvoiceTotals>'
387
def _invoice_items():
393
for line in invoice.invoice_line:
394
texto += '<InvoiceLine>'
395
texto += '<ItemDescription>' + line.name + '</ItemDescription>'
396
texto += '<Quantity>' + str(line.quantity) + '</Quantity>'
397
texto += '<UnitPriceWithoutTax>' + str('%.6f' % line.price_unit) + '</UnitPriceWithoutTax>'
398
texto += '<TotalCost>' + str('%.2f' % (line.quantity * line.price_unit)) + '</TotalCost>'
399
texto += '<DiscountsAndRebates>'
400
texto += '<Discount>'
401
texto += '<DiscountReason>Descuento</DiscountReason>'
402
texto += '<DiscountRate>' + str('%.4f' % line.discount) + '</DiscountRate>'
403
texto += '<DiscountAmount>' + str('%.2f' % ( (line.price_unit*line.quantity) - line.price_subtotal)) + '</DiscountAmount>'
404
texto += '</Discount>'
405
texto += '</DiscountsAndRebates>'
406
texto += '<GrossAmount>' + str('%.2f' % line.price_subtotal) + '</GrossAmount>'
407
texto += '<TaxesWithheld>'
409
texto += '<TaxTypeCode>01</TaxTypeCode>'
410
texto += '<TaxRate>0.00</TaxRate>'
411
texto += '<TaxableBase>'
412
texto += '<TotalAmount>' + str('%.2f' % line.price_subtotal) + '</TotalAmount>'
413
texto += '</TaxableBase>'
415
texto += '</TaxesWithheld>'
416
texto += '<TaxesOutputs>'
417
for l in line.invoice_line_tax_id:
418
rate = '%.2f' % (l.amount * 100)
420
texto += '<TaxTypeCode>01</TaxTypeCode>'
421
texto += '<TaxRate>' + str(rate) + '</TaxRate>'
422
texto += '<TaxableBase>'
423
texto += '<TotalAmount>' + str('%.2f' % line.price_subtotal) + '</TotalAmount>'
424
texto += '</TaxableBase>'
426
texto += '</TaxesOutputs>'
427
texto += '</InvoiceLine>'
432
def _invoices_facturae():
435
texto += '<Invoices>'
437
texto += '<InvoiceHeader>'
438
texto += '<InvoiceNumber>' + invoice.number + '</InvoiceNumber>'
439
texto += '<InvoiceSeriesCode>' + invoice.number + '</InvoiceSeriesCode>'
440
texto += '<InvoiceDocumentType>FC</InvoiceDocumentType>'
441
texto += '<InvoiceClass>OO</InvoiceClass>'
442
texto += '</InvoiceHeader>'
443
texto += '<InvoiceIssueData>'
444
texto += '<IssueDate>' + invoice.date_invoice + '</IssueDate>'
445
texto += '<InvoiceCurrencyCode>' + invoice.currency_id.code + '</InvoiceCurrencyCode>'
446
texto += '<TaxCurrencyCode>' + invoice.currency_id.code + '</TaxCurrencyCode>'
447
texto += '<LanguageName>es</LanguageName>'
448
texto += '</InvoiceIssueData>'
449
texto += _taxes_output()
450
texto += _invoice_totals()
451
texto += _invoice_items()
452
texto += '<AdditionalData>'
453
texto += '<InvoiceAdditionalInformation>' + str(invoice.comment) + '</InvoiceAdditionalInformation>'
454
texto += '</AdditionalData>'
455
texto += '</Invoice>'
456
texto += '</Invoices>'
460
return '</fe:Facturae>'
465
pool = pooler.get_pool(cr.dbname)
466
invoice = pool.get('account.invoice').browse(cr, uid, data['id'], context)
469
xml_facturae += _format_xml()
470
xml_facturae += _header_facturae(cr, context)
471
xml_facturae += _parties_facturae(cr, context)
472
xml_facturae += _invoices_facturae()
473
xml_facturae += _end_document()
474
xml_facturae = conv_ascii(xml_facturae)
476
return {'note':log(), 'reference': 'id', 'facturae':False, 'state':'failed'}
478
file = base64.encodestring(xml_facturae)
479
fname = (_('facturae') + '_' + invoice.number + '.xml').replace('/','-')
480
pool.get('ir.attachment').create(cr, uid, {
481
'name': '%s %s' % (_('FacturaE'), invoice.number),
483
'datas_fname': fname,
484
'res_model': 'account.invoice',
485
'res_id': invoice.id,
487
log.add(_("Export successful\n\nSummary:\nInvoice number: %s\n") % (invoice.number))
488
pool.get('account.invoice').set_done(cr,uid,invoice.id,context)
490
return {'note':log(), 'reference':invoice.id, 'facturae':file, 'facturae_fname':fname, 'state':'succeeded'}
492
class wizard_facturae_file(wizard.interface):
496
'result' : {'type' : 'form',
497
'arch' : facturae_form,
498
'fields' : facturae_fields,
499
'state' : [('end', 'Cancel'),('export', 'Export','gtk-ok') ]}
502
'actions' : [_create_facturae_file],
503
'result' : {'type' : 'form',
504
'arch' : export_form,
505
'fields' : export_fields,
506
'state' : [('end', 'Ok','gtk-ok') ]}
510
wizard_facturae_file('create_facturae_file')
511
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: