1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Business Applications
5 # Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
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.
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.
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/>.
20 ##############################################################################
21 from openerp.osv import osv, fields
22 from openerp.addons.edi import EDIMixin
24 from urllib import urlencode
26 INVOICE_LINE_EDI_STRUCT = {
35 # fields used for web preview only - discarded on import
36 'price_subtotal': True,
39 INVOICE_TAX_LINE_EDI_STRUCT = {
49 INVOICE_EDI_STRUCT = {
52 'company_id': True, # -> to be changed into partner
53 'type': True, # -> reversed at import
54 'internal_number': True, # -> reference at import
61 'invoice_line': INVOICE_LINE_EDI_STRUCT,
62 'tax_line': INVOICE_TAX_LINE_EDI_STRUCT,
64 # fields used for web preview only - discarded on import
65 #custom: 'partner_ref'
67 'amount_untaxed': True,
71 class account_invoice(osv.osv, EDIMixin):
72 _inherit = 'account.invoice'
74 def edi_export(self, cr, uid, records, edi_struct=None, context=None):
75 """Exports a supplier or customer invoice"""
76 edi_struct = dict(edi_struct or INVOICE_EDI_STRUCT)
77 res_company = self.pool.get('res.company')
78 res_partner = self.pool.get('res.partner')
80 for invoice in records:
81 # generate the main report
82 self._edi_generate_report_attachment(cr, uid, invoice, context=context)
83 edi_doc = super(account_invoice,self).edi_export(cr, uid, [invoice], edi_struct, context)[0]
85 'company_address': res_company.edi_export_address(cr, uid, invoice.company_id, context=context),
86 'company_paypal_account': invoice.company_id.paypal_account,
87 'partner_address': res_partner.edi_export(cr, uid, [invoice.partner_id], context=context)[0],
88 'currency': self.pool.get('res.currency').edi_export(cr, uid, [invoice.currency_id], context=context)[0],
89 'partner_ref': invoice.reference or False,
91 edi_doc_list.append(edi_doc)
94 def _edi_tax_account(self, cr, uid, invoice_type='out_invoice', context=None):
95 #TODO/FIXME: should select proper Tax Account
96 account_pool = self.pool.get('account.account')
97 account_ids = account_pool.search(cr, uid, [('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')])
100 tax_account = account_pool.browse(cr, uid, account_ids[0])
103 def _edi_invoice_account(self, cr, uid, partner_id, invoice_type, context=None):
104 res_partner = self.pool.get('res.partner')
105 partner = res_partner.browse(cr, uid, partner_id, context=context)
106 if invoice_type in ('out_invoice', 'out_refund'):
107 invoice_account = partner.property_account_receivable
109 invoice_account = partner.property_account_payable
110 return invoice_account
112 def _edi_product_account(self, cr, uid, product_id, invoice_type, context=None):
113 product_pool = self.pool.get('product.product')
114 product = product_pool.browse(cr, uid, product_id, context=context)
115 if invoice_type in ('out_invoice','out_refund'):
116 account = product.property_account_income or product.categ_id.property_account_income_categ
118 account = product.property_account_expense or product.categ_id.property_account_expense_categ
121 def _edi_import_company(self, cr, uid, edi_document, context=None):
122 # TODO: for multi-company setups, we currently import the document in the
123 # user's current company, but we should perhaps foresee a way to select
124 # the desired company among the user's allowed companies
126 self._edi_requires_attributes(('company_id','company_address','type'), edi_document)
127 res_partner = self.pool.get('res.partner')
129 xid, company_name = edi_document.pop('company_id')
130 # Retrofit address info into a unified partner info (changed in v7 - used to keep them separate)
131 company_address_edi = edi_document.pop('company_address')
132 company_address_edi['name'] = company_name
133 company_address_edi['is_company'] = True
134 company_address_edi['__import_model'] = 'res.partner'
135 company_address_edi['__id'] = xid # override address ID, as of v7 they should be the same anyway
136 if company_address_edi.get('logo'):
137 company_address_edi['image'] = company_address_edi.pop('logo')
139 invoice_type = edi_document['type']
140 if invoice_type.startswith('out_'):
141 company_address_edi['customer'] = True
143 company_address_edi['supplier'] = True
144 partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
146 # modify edi_document to refer to new partner
147 partner = res_partner.browse(cr, uid, partner_id, context=context)
148 partner_edi_m2o = self.edi_m2o(cr, uid, partner, context=context)
149 edi_document['partner_id'] = partner_edi_m2o
150 edi_document.pop('partner_address', None) # ignored, that's supposed to be our own address!
154 def edi_import(self, cr, uid, edi_document, context=None):
155 """ During import, invoices will import the company that is provided in the invoice as
156 a new partner (e.g. supplier company for a customer invoice will be come a supplier
157 record for the new invoice.
158 Summary of tasks that need to be done:
159 - import company as a new partner, if type==in then supplier=1, else customer=1
160 - partner_id field is modified to point to the new partner
161 - company_address data used to add address to new partner
162 - change type: out_invoice'<->'in_invoice','out_refund'<->'in_refund'
163 - reference: should contain the value of the 'internal_number'
164 - reference_type: 'none'
165 - internal number: reset to False, auto-generated
166 - journal_id: should be selected based on type: simply put the 'type'
167 in the context when calling create(), will be selected correctly
168 - payment_term: if set, create a default one based on name...
169 - for invoice lines, the account_id value should be taken from the
170 product's default, i.e. from the default category, as it will not
172 - for tax lines, we disconnect from the invoice.line, so all tax lines
173 will be of type 'manual', and default accounts should be picked based
174 on the tax config of the DB where it is imported.
178 self._edi_requires_attributes(('company_id','company_address','type','invoice_line','currency'), edi_document)
180 # extract currency info
181 res_currency = self.pool.get('res.currency')
182 currency_info = edi_document.pop('currency')
183 currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
184 currency = res_currency.browse(cr, uid, currency_id)
185 edi_document['currency_id'] = self.edi_m2o(cr, uid, currency, context=context)
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
192 # import company as a new partner
193 partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
196 invoice_account = self._edi_invoice_account(cr, uid, partner_id, invoice_type, context=context)
197 edi_document['account_id'] = invoice_account and self.edi_m2o(cr, uid, invoice_account, context=context) or False
199 # reference: should contain the value of the 'internal_number'
200 edi_document['reference'] = edi_document.get('internal_number', False)
201 # reference_type: 'none'
202 edi_document['reference_type'] = 'none'
204 # internal number: reset to False, auto-generated
205 edi_document['internal_number'] = False
207 # discard web preview fields, if present
208 edi_document.pop('partner_ref', None)
210 # journal_id: should be selected based on type: simply put the 'type' in the context when calling create(), will be selected correctly
211 context.update(type=invoice_type)
213 # 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.
214 for edi_invoice_line in edi_document['invoice_line']:
215 product_info = edi_invoice_line['product_id']
216 product_id = self.edi_import_relation(cr, uid, 'product.product', product_info[1],
217 product_info[0], context=context)
218 account = self._edi_product_account(cr, uid, product_id, invoice_type, context=context)
219 # TODO: could be improved with fiscal positions perhaps
220 # account = fpos_obj.map_account(cr, uid, fiscal_position_id, account.id)
221 edi_invoice_line['account_id'] = self.edi_m2o(cr, uid, account, context=context) if account else False
223 # discard web preview fields, if present
224 edi_invoice_line.pop('price_subtotal', None)
226 # 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
227 # on the tax config of the DB where it is imported.
228 tax_account = self._edi_tax_account(cr, uid, context=context)
229 tax_account_info = self.edi_m2o(cr, uid, tax_account, context=context)
230 for edi_tax_line in edi_document.get('tax_line', []):
231 edi_tax_line['account_id'] = tax_account_info
232 edi_tax_line['manual'] = True
234 return super(account_invoice,self).edi_import(cr, uid, edi_document, context=context)
237 def _edi_record_display_action(self, cr, uid, id, context=None):
238 """Returns an appropriate action definition dict for displaying
239 the record with ID ``rec_id``.
241 :param int id: database ID of record to display
242 :return: action definition dict
244 action = super(account_invoice,self)._edi_record_display_action(cr, uid, id, context=context)
246 invoice = self.browse(cr, uid, id, context=context)
247 if 'out_' in invoice.type:
248 view_ext_id = 'invoice_form'
249 journal_type = 'sale'
251 view_ext_id = 'invoice_supplier_form'
252 journal_type = 'purchase'
253 ctx = "{'type': '%s', 'journal_type': '%s'}" % (invoice.type, journal_type)
254 action.update(context=ctx)
255 view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', view_ext_id)[1]
256 action.update(views=[(view_id,'form'), (False, 'tree')])
258 # ignore if views are missing
262 def _edi_paypal_url(self, cr, uid, ids, field, arg, context=None):
263 res = dict.fromkeys(ids, False)
264 for inv in self.browse(cr, uid, ids, context=context):
265 if inv.type == 'out_invoice' and inv.company_id.paypal_account:
268 "business": inv.company_id.paypal_account,
269 "item_name": "%s Invoice %s" % (inv.company_id.name, inv.number or ''),
270 "invoice": inv.number,
271 "amount": inv.residual,
272 "currency_code": inv.currency_id.name,
273 "button_subtype": "services",
275 "bn": "OpenERP_Invoice_PayNow_" + inv.currency_id.name,
277 res[inv.id] = "https://www.paypal.com/cgi-bin/webscr?" + urlencode(params)
281 'paypal_url': fields.function(_edi_paypal_url, type='char', string='Paypal Url'),
285 class account_invoice_line(osv.osv, EDIMixin):
286 _inherit='account.invoice.line'
288 class account_invoice_tax(osv.osv, EDIMixin):
289 _inherit = "account.invoice.tax"
293 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: