~tempo-openerp/+junk/loewert-prod

« back to all changes in this revision

Viewing changes to addons/account/edi/invoice.py

  • Committer: jbe at tempo-consulting
  • Date: 2013-08-21 08:48:11 UTC
  • Revision ID: jbe@tempo-consulting.fr-20130821084811-913uo4l7b5ayxq8m
[NEW] Création de la branche trunk Loewert

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    OpenERP, Open Source Business Applications
 
5
#    Copyright (c) 2011-2012 OpenERP S.A. <http://openerp.com>
 
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
from openerp.osv import osv, fields
 
22
from openerp.addons.edi import EDIMixin
 
23
 
 
24
from urllib import urlencode
 
25
 
 
26
INVOICE_LINE_EDI_STRUCT = {
 
27
    'name': True,
 
28
    'origin': True,
 
29
    'uos_id': True,
 
30
    'product_id': True,
 
31
    'price_unit': True,
 
32
    'quantity': True,
 
33
    'discount': True,
 
34
 
 
35
    # fields used for web preview only - discarded on import
 
36
    'price_subtotal': True,
 
37
}
 
38
 
 
39
INVOICE_TAX_LINE_EDI_STRUCT = {
 
40
    'name': True,
 
41
    'base': True,
 
42
    'amount': True,
 
43
    'manual': True,
 
44
    'sequence': True,
 
45
    'base_amount': True,
 
46
    'tax_amount': True,
 
47
}
 
48
 
 
49
INVOICE_EDI_STRUCT = {
 
50
    'name': True,
 
51
    'origin': True,
 
52
    'company_id': True, # -> to be changed into partner
 
53
    'type': True, # -> reversed at import
 
54
    'internal_number': True, # -> reference at import
 
55
    'comment': True,
 
56
    'date_invoice': True,
 
57
    'date_due': True,
 
58
    'partner_id': True,
 
59
    'payment_term': True,
 
60
    #custom: currency_id
 
61
    'invoice_line': INVOICE_LINE_EDI_STRUCT,
 
62
    'tax_line': INVOICE_TAX_LINE_EDI_STRUCT,
 
63
 
 
64
    # fields used for web preview only - discarded on import
 
65
    #custom: 'partner_ref'
 
66
    'amount_total': True,
 
67
    'amount_untaxed': True,
 
68
    'amount_tax': True,
 
69
}
 
70
 
 
71
class account_invoice(osv.osv, EDIMixin):
 
72
    _inherit = 'account.invoice'
 
73
 
 
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')
 
79
        edi_doc_list = []
 
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]
 
84
            edi_doc.update({
 
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,
 
90
            })
 
91
            edi_doc_list.append(edi_doc)
 
92
        return edi_doc_list
 
93
 
 
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')])
 
98
        tax_account = False
 
99
        if account_ids:
 
100
            tax_account = account_pool.browse(cr, uid, account_ids[0])
 
101
        return tax_account
 
102
 
 
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
 
108
        else:
 
109
            invoice_account = partner.property_account_payable
 
110
        return invoice_account
 
111
 
 
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
 
117
        else:
 
118
            account = product.property_account_expense or product.categ_id.property_account_expense_categ
 
119
        return account
 
120
 
 
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
 
125
 
 
126
        self._edi_requires_attributes(('company_id','company_address','type'), edi_document)
 
127
        res_partner = self.pool.get('res.partner')
 
128
 
 
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')
 
138
 
 
139
        invoice_type = edi_document['type']
 
140
        if invoice_type.startswith('out_'):
 
141
            company_address_edi['customer'] = True
 
142
        else:
 
143
            company_address_edi['supplier'] = True
 
144
        partner_id = res_partner.edi_import(cr, uid, company_address_edi, context=context)
 
145
 
 
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!
 
151
 
 
152
        return partner_id
 
153
 
 
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
 
171
                    be provided.
 
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.
 
175
        """
 
176
        if context is None:
 
177
            context = {}
 
178
        self._edi_requires_attributes(('company_id','company_address','type','invoice_line','currency'), edi_document)
 
179
 
 
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)
 
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
        # import company as a new partner
 
193
        partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
 
194
 
 
195
        # Set Account
 
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
 
198
 
 
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'
 
203
 
 
204
        # internal number: reset to False, auto-generated
 
205
        edi_document['internal_number'] = False
 
206
 
 
207
        # discard web preview fields, if present
 
208
        edi_document.pop('partner_ref', None)
 
209
 
 
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)
 
212
 
 
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
 
222
 
 
223
            # discard web preview fields, if present
 
224
            edi_invoice_line.pop('price_subtotal', None)
 
225
 
 
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
 
233
 
 
234
        return super(account_invoice,self).edi_import(cr, uid, edi_document, context=context)
 
235
 
 
236
 
 
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``.
 
240
 
 
241
           :param int id: database ID of record to display
 
242
           :return: action definition dict
 
243
        """
 
244
        action = super(account_invoice,self)._edi_record_display_action(cr, uid, id, context=context)
 
245
        try:
 
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'
 
250
            else:
 
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')])
 
257
        except ValueError:
 
258
            # ignore if views are missing
 
259
            pass
 
260
        return action
 
261
 
 
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:
 
266
                params = {
 
267
                    "cmd": "_xclick",
 
268
                    "business": inv.company_id.paypal_account,
 
269
                    "item_name": inv.company_id.name + " Invoice " + inv.number,
 
270
                    "invoice": inv.number,
 
271
                    "amount": inv.residual,
 
272
                    "currency_code": inv.currency_id.name,
 
273
                    "button_subtype": "services",
 
274
                    "no_note": "1",
 
275
                    "bn": "OpenERP_Invoice_PayNow_" + inv.currency_id.name,
 
276
                }
 
277
                res[inv.id] = "https://www.paypal.com/cgi-bin/webscr?" + urlencode(params)
 
278
        return res
 
279
 
 
280
    _columns = {
 
281
        'paypal_url': fields.function(_edi_paypal_url, type='char', string='Paypal Url'),
 
282
    }
 
283
 
 
284
 
 
285
class account_invoice_line(osv.osv, EDIMixin):
 
286
    _inherit='account.invoice.line'
 
287
 
 
288
class account_invoice_tax(osv.osv, EDIMixin):
 
289
    _inherit = "account.invoice.tax"
 
290
 
 
291
 
 
292
 
 
293
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: