[MERGE] sync with latest trunk
[odoo/odoo.git] / addons / account / edi_invoice.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from osv import fields, osv, orm
23 from base.ir import ir_edi
24 from tools.translate import _
25
26 class account_invoice(osv.osv, ir_edi.edi):
27     _inherit = 'account.invoice'
28
29     def edi_export(self, cr, uid, records, edi_struct=None, context=None):
30         """Exports a supplier or customer invoice"""
31         edi_struct = {
32                 'name': True,
33                 'origin': True,
34                 'company_id': True, # -> to be changed into partner
35                 'type': True, # -> reversed at import
36                 'internal_number': True, # -> reference at import
37                 'comment': True,
38                 'reference': True,
39                 'amount_untaxed': True,
40                 'amount_tax': True,
41                 'amount_total': True,
42                 'reconciled': True,
43                 'date_invoice': True,
44                 'date_due': True,
45                 'partner_id': True,
46                 'address_invoice_id': True, #only one address needed
47                 'payment_term': True,
48                 'currency_id': True,
49                 'invoice_line': {
50                         'name': True,
51                         'origin': True,
52                         'uos_id': True,
53                         'product_id': True,
54                         'price_unit': True,
55                         'price_subtotal': True,
56                         'quantity': True,
57                         'discount': True,
58                         'note': True,
59                 },
60                 'tax_line': {
61                         'name': True,
62                         'base': True,
63                         'amount': True,
64                         'manual': True,
65                         'sequence': True,
66                         'base_amount': True,
67                         'tax_amount': True,
68                 },
69                 #'paid': True,
70         }
71         company_pool = self.pool.get('res.company')
72         edi_doc_list = []
73         for invoice in records:
74             # Get EDI doc based on struct. The result will also contain all metadata fields and attachments.
75             edi_doc = super(account_invoice,self).edi_export(cr, uid, [invoice], edi_struct, context)
76             if not edi_doc:
77                 continue
78             edi_doc = edi_doc[0]
79
80             # Add company info and address
81             edi_company_document = company_pool.edi_export_address(cr, uid, [invoice.company_id], context=context)[invoice.company_id.id]
82             edi_doc.update({
83                     'company_address': edi_company_document['company_address'],
84                     #'company_logo': edi_company_document['company_logo'],#TODO
85             })
86             edi_doc_list.append(edi_doc)
87         return edi_doc_list
88
89     def get_invoice_journal(self, cr, uid, invoice_type, context=None):
90         if context is None:
91             context = {}
92         account_journal_pool = self.pool.get('account.journal')
93         journal_context = context.copy()
94         journal_context.update({'type':invoice_type})
95         journal_id = self._get_journal(cr, uid, context=journal_context)
96         journal = False
97         if journal_id:
98             journal = account_journal_pool.browse(cr, uid, journal_id, context=context)
99         return journal
100
101     def get_tax_account(self, cr, uid, invoice_type='out_invoice', context=None):
102         #TOCHECK: should select account of output VAT for Customer Invoice and Input VAT for Supplier Invoice
103         account_pool = self.pool.get('account.account')
104         account_ids = account_pool.search(cr, uid, [('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')])
105         tax_account = False
106         if account_ids:
107             tax_account = account_pool.browse(cr, uid, account_ids[0])
108         return tax_account
109
110     def get_invoice_account(self, cr, uid, partner_id, invoice_type, context=None):
111         partner_pool = self.pool.get('res.partner')
112         partner = partner_pool.browse(cr, uid, partner_id, context=context)
113         if invoice_type in ('out_invoice', 'out_refund'):
114             invoice_account = partner.property_account_receivable
115         else:
116             invoice_account = partner.property_account_payable
117         return invoice_account
118
119     def get_product_account(self, cr, uid, product_id, invoice_type, context=None):
120         product_pool = self.pool.get('product.product')
121         product = product_pool.browse(cr, uid, product_id, context=context)
122         account = False
123         if invoice_type in ('out_invoice','out_refund'):
124             account = product.product_tmpl_id.property_account_income
125             if not account:
126                 account = product.categ_id.property_account_income_categ
127         else:
128             account = product.product_tmpl_id.property_account_expense
129             if not account:
130                 account = product.categ_id.property_account_expense_categ
131         return account
132
133     def edi_import_company(self, cr, uid, edi_document, context=None):
134         partner_address_pool = self.pool.get('res.partner.address')
135         partner_pool = self.pool.get('res.partner')
136         company_pool = self.pool.get('res.company')
137
138         # import company as a new partner, if type==in then supplier=1, else customer=1
139         # company_address data used to add address to new partner
140         invoice_type = edi_document['type']
141         partner_value = {}
142         if invoice_type in ('out_invoice', 'in_refund'):
143             partner_value.update({'customer': True})
144         if invoice_type in ('in_invoice', 'out_refund'):
145             partner_value.update({'supplier': True})
146         partner_id = company_pool.edi_import_as_partner(cr, uid, edi_document, values=partner_value, context=context)
147
148         # partner_id field is modified to point to the new partner
149         res = partner_pool.address_get(cr, uid, [partner_id], ['contact', 'invoice'])
150         address_id = res['invoice']
151         partner = partner_pool.browse(cr, uid, partner_id, context=context)
152         partner_address = partner_address_pool.browse(cr, uid, address_id, context=context)
153         edi_document['partner_id'] = self.edi_m2o(cr, uid, partner, context=context)
154         edi_document['address_invoice_id'] = self.edi_m2o(cr, uid, partner_address, context=context)
155         del edi_document['company_id']
156         return partner_id
157         
158
159     def edi_import(self, cr, uid, edi_document, context=None):
160         """ During import, invoices will import the company that is provided in the invoice as
161             a new partner (e.g. supplier company for a customer invoice will be come a supplier
162             record for the new invoice.
163             Summary of tasks that need to be done:
164                 - import company as a new partner, if type==in then supplier=1, else customer=1
165                 - partner_id field is modified to point to the new partner
166                 - company_address data used to add address to new partner
167                 - change type: out_invoice'<->'in_invoice','out_refund'<->'in_refund'
168                 - reference: should contain the value of the 'internal_number'
169                 - reference_type: 'none'
170                 - internal number: reset to False, auto-generated
171                 - journal_id: should be selected based on type: simply put the 'type' 
172                     in the context when calling create(), will be selected correctly
173                 - payment_term: if set, create a default one based on name...
174                 - for invoice lines, the account_id value should be taken from the
175                     product's default, i.e. from the default category, as it will not
176                     be provided.
177                 - for tax lines, we disconnect from the invoice.line, so all tax lines
178                     will be of type 'manual', and default accounts should be picked based
179                     on the tax config of the DB where it is imported.    
180         """
181         if context is None:
182             context = {}
183         
184         #import company as a new partner
185         partner_id = self.edi_import_company(cr, uid, edi_document, context=context)
186
187         # change type: out_invoice'<->'in_invoice','out_refund'<->'in_refund'
188         invoice_type = edi_document['type']
189         invoice_type = invoice_type.startswith('in_') and invoice_type.replace('in_','out_') or invoice_type.replace('out_','in_')
190         edi_document['type'] = invoice_type
191
192         # Set Account
193         invoice_account = self.get_invoice_account(cr, uid, partner_id, invoice_type, context=context)
194         edi_document['account_id'] = invoice_account and self.edi_m2o(cr, uid, invoice_account, context=context) or False
195
196         # reference: should contain the value of the 'internal_number'
197         edi_document['reference'] = edi_document.get('internal_number', False)
198         # reference_type: 'none'
199         edi_document['reference_type'] = 'none'
200
201         # internal number: reset to False, auto-generated
202         edi_document['internal_number'] = False
203         
204
205         # journal_id: should be selected based on type: simply put the 'type' in the context when calling create(), will be selected correctly
206         journal = self.get_invoice_journal(cr, uid, invoice_type, context=context)
207         edi_document['journal_id'] = journal and  self.edi_m2o(cr, uid, journal, context=context) or False
208
209         # for invoice lines, the account_id value should be taken from the product's default, i.e. from the default category, as it will not be provided.
210         for edi_invoice_line in edi_document.get('invoice_line', []):
211             product_id = edi_invoice_line.get('product_id', False)
212             account = False
213             if product_id:
214                 product_name = product_id and product_id[1]
215                 product_id = self.edi_import_relation(cr, uid, 'product.product', product_name, context=context)
216                 account = self.get_product_account(cr, uid, product_id, invoice_type, context=context)
217             # TODO: add effect of fiscal position 
218             # account = fpos_obj.map_account(cr, uid, fiscal_position_id, account.id)
219             edi_invoice_line['account_id'] = account and self.edi_m2o(cr, uid, account, context=context) or False
220
221         # for tax lines, we disconnect from the invoice.line, so all tax lines will be of type 'manual', and default accounts should be picked based
222         # on the tax config of the DB where it is imported.
223         for edi_tax_line in edi_document.get('tax_line', []):
224             tax_account = self.get_tax_account(cr, uid, context=context)
225             if tax_account:
226                 edi_tax_line['account_id'] = self.edi_m2o(cr, uid, tax_account, context=context) 
227             edi_tax_line['manual'] = True
228
229         # TODO :=> payment_term: if set, create a default one based on name... 
230         return super(account_invoice,self).edi_import(cr, uid, edi_document, context=context)
231       
232 account_invoice()
233
234 class account_invoice_line(osv.osv, ir_edi.edi):
235     _inherit='account.invoice.line'
236
237 account_invoice_line()
238 class account_invoice_tax(osv.osv, ir_edi.edi):
239     _inherit = "account.invoice.tax"
240
241 account_invoice_tax()
242
243