~unifield-team/unifield-wm/us-671-homere

« back to all changes in this revision

Viewing changes to account_override/invoice.py

  • Committer: chloups208
  • Date: 2012-11-21 11:15:15 UTC
  • mto: This revision was merged to the branch mainline in revision 1340.
  • Revision ID: chloups208@chloups208-laptop-20121121111515-myqv282h6xmgh053
utp-171 modification of fields po, po line, product, so, so line

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
from osv import fields
27
27
from time import strftime
28
28
from tools.translate import _
29
 
from lxml import etree
30
 
import re
31
 
import netsvc
32
 
 
33
 
 
34
 
import decimal_precision as dp
 
29
import logging
 
30
import datetime
35
31
 
36
32
class account_invoice(osv.osv):
37
33
    _name = 'account.invoice'
38
34
    _inherit = 'account.invoice'
39
35
 
40
 
    def _get_invoice_report_name(self, cr, uid, ids, context=None):
41
 
        '''
42
 
        Returns the name of the invoice according to its type
43
 
        '''
44
 
        if isinstance(ids, list):
45
 
            ids = ids[0]
46
 
 
47
 
        inv = self.browse(cr, uid, ids, context=context)
48
 
        inv_name = inv.number or inv.name or 'No_description'
49
 
        prefix = 'STV_'
50
 
 
51
 
        if inv.type == 'out_refund': # Customer refund
52
 
            prefix = 'CR_'
53
 
        elif inv.type == 'in_refund': # Supplier refund
54
 
            prefix = 'SR_'
55
 
        elif inv.type == 'out_invoice':
56
 
            # Stock transfer voucher
57
 
            prefix = 'STV_'
58
 
            # Debit note
59
 
            if inv.is_debit_note and not inv.is_inkind_donation and not inv.is_intermission:
60
 
                prefix = 'DN_'
61
 
            # Intermission voucher OUT
62
 
            elif not inv.is_debit_note and not inv.is_inkind_donation and inv.is_intermission:
63
 
                prefix = 'IMO_'
64
 
        elif inv.type == 'in_invoice':
65
 
            # Supplier invoice
66
 
            prefix = 'SI_'
67
 
            # Intermission voucher IN
68
 
            if not inv.is_debit_note and not inv.is_inkind_donation and inv.is_intermission:
69
 
                prefix = 'IMI_'
70
 
            # Direct invoice
71
 
            elif inv.is_direct_invoice:
72
 
                prefix = 'DI_'
73
 
            # In-kind donation
74
 
            elif not inv.is_debit_note and inv.is_inkind_donation:
75
 
                prefix = 'DON_'
76
 
        return '%s%s' % (prefix, inv_name)
77
 
 
 
36
    
78
37
    def _get_journal(self, cr, uid, context=None):
79
38
        """
80
39
        WARNING: This method has been taken from account module from OpenERP
95
54
        journal_obj = self.pool.get('account.journal')
96
55
        res = journal_obj.search(cr, uid, args, limit=1)
97
56
        return res and res[0] or False
98
 
 
99
 
    def _get_fake(self, cr, uid, ids, field_name=None, arg=None, context=None):
100
 
        """
101
 
        Fake method for 'ready_for_import_in_debit_note' field
102
 
        """
103
 
        res = {}
104
 
        for i in ids:
105
 
            res[i] = False
106
 
        return res
107
 
 
108
 
    def _search_ready_for_import_in_debit_note(self, cr, uid, obj, name, args, context=None):
109
 
        if not args:
110
 
            return []
111
 
        account_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.import_invoice_default_account and \
112
 
            self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.import_invoice_default_account.id or False
113
 
        if not account_id:
114
 
            raise osv.except_osv(_('Error'), _('No default account for import invoice on Debit Note!'))
115
 
        dom1 = [
116
 
            ('account_id','=',account_id),
117
 
            ('reconciled','=',False),
118
 
            ('state', '=', 'open'),
119
 
            ('type', '=', 'out_invoice'),
120
 
            ('journal_id.type', 'not in', ['migration']),
121
 
            ('partner_id.partner_type', '=', 'section'),
122
 
        ]
123
 
        return dom1+[('is_debit_note', '=', False)]
124
 
 
125
 
    def _get_fake_m2o_id(self, cr, uid, ids, field_name=None, arg=None, context=None):
126
 
        """
127
 
        Get many2one field content
128
 
        """
129
 
        res = {}
130
 
        name = field_name.replace("fake_", '')
131
 
        for i in self.browse(cr, uid, ids):
132
 
            if context and context.get('is_intermission', False):
133
 
                res[i.id] = False
134
 
                if name == 'account_id':
135
 
                    user = self.pool.get('res.users').browse(cr, uid, [uid], context=context)
136
 
                    if user[0].company_id.intermission_default_counterpart:
137
 
                        res[i.id] = user[0].company_id.intermission_default_counterpart.id
138
 
                elif name == 'journal_id':
139
 
                    int_journal_id = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'intermission')], context=context)
140
 
                    if int_journal_id:
141
 
                        if isinstance(int_journal_id, (int, long)):
142
 
                            int_journal_id = [int_journal_id]
143
 
                        res[i.id] = int_journal_id[0]
144
 
                elif name == 'currency_id':
145
 
                    user = self.pool.get('res.users').browse(cr, uid, [uid], context=context)
146
 
                    if user[0].company_id.currency_id:
147
 
                        res[i.id] = user[0].company_id.currency_id.id
148
 
            else:
149
 
                res[i.id] = getattr(i, name, False) and getattr(getattr(i, name, False), 'id', False) or False
150
 
        return res
151
 
 
152
 
    def _get_have_donation_certificate(self, cr, uid, ids, field_name=None, arg=None, context=None):
153
 
        """
154
 
        If this invoice have a stock picking in which there is a Certificate of Donation, return True. Otherwise return False.
155
 
        """
156
 
        res = {}
157
 
        for i in self.browse(cr, uid, ids):
158
 
            res[i.id] = False
159
 
            if i.picking_id:
160
 
                a_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', 'stock.picking'), ('res_id', '=', i.picking_id.id), ('description', '=', 'Certificate of Donation')])
161
 
                if a_ids:
162
 
                    res[i.id] = True
163
 
        return res
164
 
 
165
 
    def _get_virtual_fields(self, cr, uid, ids, field_name=None, arg=None, context=None):
166
 
        """
167
 
        Get fields in order to transform them into 'virtual fields" (kind of field duplicity):
168
 
         - currency_id
169
 
         - account_id
170
 
         - supplier
171
 
        """
172
 
        res = {}
173
 
        for inv in self.browse(cr, uid, ids, context=context):
174
 
            res[inv.id] = {'virtual_currency_id': inv.currency_id.id or False, 'virtual_account_id': inv.account_id.id or False,
175
 
            'virtual_partner_id': inv.partner_id.id or False}
176
 
        return res
177
 
 
178
 
    def _get_vat_ok(self, cr, uid, ids, field_name, args, context=None):
179
 
        '''
180
 
        Return True if the system configuration VAT management is set to True
181
 
        '''
182
 
        vat_ok = self.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok
183
 
        res = {}
184
 
        for id in ids:
185
 
            res[id] = vat_ok
186
 
 
187
 
        return res
188
 
 
189
 
    def _get_can_merge_lines(self, cr, uid, ids, field_name, args,
190
 
        context=None):
191
 
        res = {}
192
 
        if not ids:
193
 
            return res
194
 
        if isinstance(ids, (int, long, )):
195
 
            ids = [ids]
196
 
 
197
 
        for inv_br in self.browse(cr, uid, ids, context=context):
198
 
            # US-357: allow merge of line only for draft SI
199
 
            res[inv_br.id] = inv_br.state and inv_br.state == 'draft' \
200
 
                and inv_br.invoice_line \
201
 
                and inv_br.type == 'in_invoice' \
202
 
                and not inv_br.is_direct_invoice \
203
 
                and not inv_br.is_inkind_donation \
204
 
                and not inv_br.is_debit_note \
205
 
                and not inv_br.is_intermission \
206
 
                or False
207
 
 
 
57
    
 
58
    def onchange_company_id(self, cr, uid, ids, company_id, part_id, type, invoice_line, currency_id):
 
59
        """
 
60
        This is a method to redefine the journal_id domain with the current_instance taken into account
 
61
        """
 
62
        res = super(account_invoice, self).onchange_company_id(cr, uid, ids, company_id, part_id, type, invoice_line, currency_id)
 
63
        if 'domain' in res and 'journal_id' in res['domain']:
 
64
            journal_domain = res['domain']['journal_id']
 
65
            journal_domain.append(('is_current_instance','=',True))
 
66
            new_journal_ids = self.pool.get('account.journal').search(cr, uid, journal_domain)
 
67
            res['domain']['journal_id'] = [('id','in',new_journal_ids)]
208
68
        return res
209
69
 
210
70
    _columns = {
211
71
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
212
72
        'sequence_id': fields.many2one('ir.sequence', string='Lines Sequence', ondelete='cascade',
213
73
            help="This field contains the information related to the numbering of the lines of this order."),
214
 
        'date_invoice': fields.date('Posting Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)],
215
 
            'close':[('readonly',True)]}, select=True),
216
 
        'document_date': fields.date('Document Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)],
217
 
            'close':[('readonly',True)]}, select=True),
218
 
        'is_debit_note': fields.boolean(string="Is a Debit Note?"),
219
 
        'is_inkind_donation': fields.boolean(string="Is an In-kind Donation?"),
220
 
        'is_intermission': fields.boolean(string="Is an Intermission Voucher?"),
221
 
        'ready_for_import_in_debit_note': fields.function(_get_fake, fnct_search=_search_ready_for_import_in_debit_note, type="boolean",
222
 
            method=True, string="Can be imported as invoice in a debit note?",),
223
 
        'imported_invoices': fields.one2many('account.invoice.line', 'import_invoice_id', string="Imported invoices", readonly=True),
224
 
        'partner_move_line': fields.one2many('account.move.line', 'invoice_partner_link', string="Partner move line", readonly=True),
225
 
        'fake_account_id': fields.function(_get_fake_m2o_id, method=True, type='many2one', relation="account.account", string="Account", readonly="True"),
226
 
        'fake_journal_id': fields.function(_get_fake_m2o_id, method=True, type='many2one', relation="account.journal", string="Journal", readonly="True"),
227
 
        'fake_currency_id': fields.function(_get_fake_m2o_id, method=True, type='many2one', relation="res.currency", string="Currency", readonly="True"),
228
 
        'have_donation_certificate': fields.function(_get_have_donation_certificate, method=True, type='boolean', string="Have a Certificate of donation?"),
229
 
        'purchase_list': fields.boolean(string='Purchase List ?', help='Check this box if the invoice comes from a purchase list', readonly=True, states={'draft':[('readonly',False)]}),
230
 
        'virtual_currency_id': fields.function(_get_virtual_fields, method=True, store=False, multi='virtual_fields', string="Currency",
231
 
            type='many2one', relation="res.currency", readonly=True),
232
 
        'virtual_account_id': fields.function(_get_virtual_fields, method=True, store=False, multi='virtual_fields', string="Account",
233
 
            type='many2one', relation="account.account", readonly=True),
234
 
        'virtual_partner_id': fields.function(_get_virtual_fields, method=True, store=False, multi='virtual_fields', string="Supplier",
235
 
            type='many2one', relation="res.partner", readonly=True),
236
 
        'register_line_ids': fields.one2many('account.bank.statement.line', 'invoice_id', string="Register Lines"),
237
 
        'is_direct_invoice': fields.boolean("Is direct invoice?", readonly=True),
238
 
        'address_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=False,
239
 
            states={'draft':[('readonly',False)]}),
240
 
        'register_posting_date': fields.date(string="Register posting date for Direct Invoice", required=False),
241
 
        'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False, readonly=True),
242
 
        'st_lines': fields.one2many('account.bank.statement.line', 'invoice_id', string="Register lines", readonly=True, help="Register lines that have a link to this invoice."),
243
 
        'can_merge_lines': fields.function(_get_can_merge_lines, method=True, type='boolean', string='Can merge lines ?'),
244
 
        'is_merged_by_account': fields.boolean("Is merged by account"),
 
74
        'date_invoice': fields.date('Posting Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 
 
75
            'close':[('readonly',True)]}, select=True),
 
76
        'document_date': fields.date('Document Date', states={'paid':[('readonly',True)], 'open':[('readonly',True)], 
 
77
            'close':[('readonly',True)]}, select=True),
245
78
    }
246
79
 
247
80
    _defaults = {
248
81
        'journal_id': _get_journal,
249
82
        'from_yml_test': lambda *a: False,
250
83
        'date_invoice': lambda *a: strftime('%Y-%m-%d'),
251
 
        'is_debit_note': lambda obj, cr, uid, c: c.get('is_debit_note', False),
252
 
        'is_inkind_donation': lambda obj, cr, uid, c: c.get('is_inkind_donation', False),
253
 
        'is_intermission': lambda obj, cr, uid, c: c.get('is_intermission', False),
254
 
        'is_direct_invoice': lambda *a: False,
255
 
        'vat_ok': lambda obj, cr, uid, context: obj.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok,
256
 
        'can_merge_lines': lambda *a: False,
257
 
        'is_merged_by_account': lambda *a: False,
258
84
    }
259
85
 
260
 
    def onchange_company_id(self, cr, uid, ids, company_id, part_id, ctype, invoice_line, currency_id):
261
 
        """
262
 
        This is a method to redefine the journal_id domain with the current_instance taken into account
263
 
        """
264
 
        res = super(account_invoice, self).onchange_company_id(cr, uid, ids, company_id, part_id, ctype, invoice_line, currency_id)
265
 
        if company_id and ctype:
266
 
            res.setdefault('domain', {})
267
 
            res.setdefault('value', {})
268
 
            ass = {
269
 
                'out_invoice': 'sale',
270
 
                'in_invoice': 'purchase',
271
 
                'out_refund': 'sale_refund',
272
 
                'in_refund': 'purchase_refund',
273
 
            }
274
 
            journal_ids = self.pool.get('account.journal').search(cr, uid, [
275
 
                ('company_id','=',company_id), ('type', '=', ass.get(ctype, 'purchase')), ('is_current_instance', '=', True)
276
 
            ])
277
 
            if not journal_ids:
278
 
                raise osv.except_osv(_('Configuration Error !'), _('Can\'t find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Financial Accounting\Accounts\Journals.') % (ass.get(type, 'purchase'), ))
279
 
            res['value']['journal_id'] = journal_ids[0]
280
 
            # TODO: it's very bad to set a domain by onchange method, no time to rewrite UniField !
281
 
            res['domain']['journal_id'] = [('id', 'in', journal_ids)]
282
 
        return res
283
 
 
284
 
    def onchange_partner_id(self, cr, uid, ids, ctype, partner_id,\
285
 
        date_invoice=False, payment_term=False, partner_bank_id=False, company_id=False, is_inkind_donation=False, is_intermission=False, is_debit_note=False, is_direct_invoice=False):
286
 
        """
287
 
        Update fake_account_id field regarding account_id result.
288
 
        Get default donation account for Donation invoices.
289
 
        Get default intermission account for Intermission Voucher IN/OUT invoices.
290
 
        Get default currency from partner if this one is linked to a pricelist.
291
 
        Ticket utp917 - added code to avoid currency cd change if a direct invoice
292
 
        """
293
 
        res = super(account_invoice, self).onchange_partner_id(cr, uid, ids, ctype, partner_id, date_invoice, payment_term, partner_bank_id, company_id)
294
 
        if is_inkind_donation and partner_id:
295
 
            partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
296
 
            account_id = partner and partner.donation_payable_account and partner.donation_payable_account.id or False
297
 
            res['value']['account_id'] = account_id
298
 
        if is_intermission and partner_id:
299
 
            intermission_default_account = self.pool.get('res.users').browse(cr, uid, uid).company_id.intermission_default_counterpart
300
 
            account_id = intermission_default_account and intermission_default_account.id or False
301
 
            if not account_id:
302
 
                raise osv.except_osv(_('Error'), _('Please configure a default intermission account in Company configuration.'))
303
 
            res['value']['account_id'] = account_id
304
 
        if res.get('value', False) and 'account_id' in res['value']:
305
 
            res['value'].update({'fake_account_id': res['value'].get('account_id')})
306
 
        if partner_id and ctype:
307
 
            p = self.pool.get('res.partner').browse(cr, uid, partner_id)
308
 
            ai_direct_invoice = False
309
 
            if ids: #utp917
310
 
                ai = self.browse(cr, uid, ids)[0]
311
 
                ai_direct_invoice = ai.is_direct_invoice
312
 
            if p:
313
 
                c_id = False
314
 
                if ctype in ['in_invoice', 'out_refund'] and p.property_product_pricelist_purchase:
315
 
                    c_id = p.property_product_pricelist_purchase.currency_id.id
316
 
                elif ctype in ['out_invoice', 'in_refund'] and p.property_product_pricelist:
317
 
                    c_id = p.property_product_pricelist.currency_id.id
318
 
                # UFTP-121: regarding UTP-917, we have to change currency when changing partner, but not for direct invoices
319
 
                if c_id and (not is_direct_invoice and not ai_direct_invoice):
320
 
                    if not res.get('value', False):
321
 
                        res['value'] = {'currency_id': c_id}
322
 
                    else:
323
 
                        res['value'].update({'currency_id': c_id})
324
 
        # UFTP-168: If debit note, set account to False value
325
 
        if is_debit_note:
326
 
            res['value'].update({'account_id': False, 'fake_account_id': False})
327
 
        return res
328
 
 
329
 
    def _check_document_date(self, cr, uid, ids):
330
 
        """
331
 
        Check that document's date is done BEFORE posting date
332
 
        """
333
 
        if isinstance(ids, (int, long)):
334
 
            ids = [ids]
335
 
        for i in self.browse(cr, uid, ids):
336
 
            self.pool.get('finance.tools').check_document_date(cr, uid,
337
 
                i.document_date, i.date_invoice)
338
 
        return True
339
 
 
340
 
    def _check_invoice_merged_lines(self, cr, uid, ids, context=None):
341
 
        """
342
 
        US-357:
343
 
            merge of lines by account break lines descriptions (required field)
344
 
            => before next workflow stage from draft (validate, split)
345
 
               check that user has entered description on each line
346
 
               (force user to enter a custom description)
347
 
        """
348
 
        for self_br in self.browse(cr, uid, ids, context=context):
349
 
            if self_br.is_merged_by_account:
350
 
                if not all([ l.name for l in self_br.invoice_line ]):
351
 
                    raise osv.except_osv(
352
 
                        _('Error'),
353
 
                        _('Please enter a description in each merged line' \
354
 
                            ' before invoice validation')
355
 
                    )
356
 
 
357
 
    def _refund_cleanup_lines(self, cr, uid, lines):
358
 
        """
359
 
        Remove useless fields
360
 
        """
361
 
        for line in lines:
362
 
            if line.get('move_lines',False):
363
 
                del line['move_lines']
364
 
            if line.get('import_invoice_id',False):
365
 
                del line['import_invoice_id']
366
 
        res = super(account_invoice, self)._refund_cleanup_lines(cr, uid, lines)
367
 
        return res
368
 
 
369
 
    def check_po_link(self, cr, uid, ids, context=None):
370
 
        """
371
 
        Check that invoice (only supplier invoices) has no link with a PO. This is because of commitments presence.
372
 
        """
373
 
        if not context:
374
 
            context = {}
375
 
        if isinstance(ids, (int, long)):
376
 
            ids = [ids]
377
 
        purchase_obj = self.pool.get('purchase.order')
378
 
        commitment_obj = self.pool.get('account.commitment')
379
 
        for inv in self.read(cr, uid, ids, ['purchase_ids', 'type', 'is_inkind_donation', 'is_debit_note', 'state']):
380
 
            if inv.get('type', '') == 'in_invoice' and not inv.get('is_inkind_donation', False) and not inv.get('is_debit_note', False):
381
 
                if inv.get('purchase_ids', False):
382
 
                    # UTP-808: Allow draft invoice deletion. If commitment exists, set them as done.
383
 
                    if inv.get('state', '') != 'draft':
384
 
                        raise osv.except_osv(_('Warning'), _('You cannot cancel or delete a supplier invoice linked to a PO.'))
385
 
                    else:
386
 
                        for purchase in purchase_obj.browse(cr, uid, inv.get('purchase_ids', []), context=context):
387
 
                            commitment_obj.action_commitment_done(cr, uid, [x.id for x in purchase.commitment_ids])
388
 
        return True
389
 
 
390
 
    def _hook_period_id(self, cr, uid, inv, context=None):
391
 
        """
392
 
        Give matches period that are not draft and not HQ-closed from given date.
393
 
        Do not use special periods as period 13, 14 and 15.
394
 
        """
395
 
        # Some verifications
396
 
        if not context:
397
 
            context = {}
398
 
        if not inv:
399
 
            return False
400
 
        # NB: there is some period state. So we define that we choose only open period (so not draft and not done)
401
 
        res = self.pool.get('account.period').search(cr, uid, [('date_start','<=',inv.date_invoice or strftime('%Y-%m-%d')),
402
 
            ('date_stop','>=',inv.date_invoice or strftime('%Y-%m-%d')), ('state', 'not in', ['created', 'done']),
403
 
            ('company_id', '=', inv.company_id.id), ('special', '=', False)], context=context, order="date_start ASC, name ASC")
404
 
        return res
405
 
 
406
 
    def __hook_lines_before_pay_and_reconcile(self, cr, uid, lines):
407
 
        """
408
 
        Add document date to account_move_line before pay and reconcile
409
 
        """
410
 
        for line in lines:
411
 
            if line[2] and 'date' in line[2] and not line[2].get('document_date', False):
412
 
                line[2].update({'document_date': line[2].get('date')})
413
 
        return lines
414
 
 
415
 
    def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
416
 
        """
417
 
        Rename Supplier/Customer to "Donor" if view_type == tree
418
 
        """
419
 
        if not context:
420
 
            context = {}
421
 
        res = super(account_invoice, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
422
 
        if view_type == 'tree' and (context.get('journal_type', False) == 'inkind' or context.get('journal_type', False) == 'intermission'):
423
 
            doc = etree.XML(res['arch'])
424
 
            nodes = doc.xpath("//field[@name='partner_id']")
425
 
            name = _('Donor')
426
 
            if context.get('journal_type') == 'intermission':
427
 
                name = _('Partner')
428
 
            for node in nodes:
429
 
                node.set('string', name)
430
 
            res['arch'] = etree.tostring(doc)
431
 
        elif view_type in ('tree', 'search') and context.get('type') in ['out_invoice', 'out_refund']:
432
 
            doc = etree.XML(res['arch'])
433
 
            nodes = doc.xpath("//field[@name='supplier_reference']")
434
 
            for node in nodes:
435
 
                node.getparent().remove(node)
436
 
            res['arch'] = etree.tostring(doc)
437
 
        return res
438
 
 
439
 
    def default_get(self, cr, uid, fields, context=None):
440
 
        """
441
 
        Fill in fake account and fake currency for intermission invoice (in context).
442
 
        """
443
 
        defaults = super(account_invoice, self).default_get(cr, uid, fields, context=context)
444
 
        if context and context.get('is_intermission', False):
445
 
            intermission_type = context.get('intermission_type', False)
446
 
            if intermission_type in ('in', 'out'):
447
 
                # UF-2270: manual intermission (in or out)
448
 
                if defaults is None:
449
 
                    defaults = {}
450
 
                user = self.pool.get('res.users').browse(cr, uid, [uid], context=context)
451
 
                if user and user[0] and user[0].company_id:
452
 
                    # get company default currency
453
 
                    if user[0].company_id.currency_id:
454
 
                        defaults['fake_currency_id'] = user[0].company_id.currency_id.id
455
 
                        defaults['currency_id'] = defaults['fake_currency_id']
456
 
                    # get 'intermission counter part' account
457
 
                    if user[0].company_id.intermission_default_counterpart:
458
 
                        defaults['fake_account_id'] = user[0].company_id.intermission_default_counterpart.id
459
 
                        defaults['account_id'] = defaults['fake_account_id']
460
 
                    else:
461
 
                        raise osv.except_osv("Error","Company Intermission Counterpart Account must be set")
462
 
                # 'INT' intermission journal
463
 
                int_journal_id = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'intermission')], context=context)
464
 
                if int_journal_id:
465
 
                    if isinstance(int_journal_id, (int, long)):
466
 
                        int_journal_id = [int_journal_id]
467
 
                    defaults['fake_journal_id'] = int_journal_id[0]
468
 
                    defaults['journal_id'] = defaults['fake_journal_id']
469
 
        return defaults
470
 
 
471
 
    def copy(self, cr, uid, inv_id, default=None, context=None):
472
 
        """
473
 
        Delete period_id from invoice.
474
 
        Check context for splitting invoice.
475
 
        Reset register_line_ids.
476
 
        """
477
 
        # Some checks
478
 
        if context is None:
479
 
            context = {}
480
 
        if default is None:
481
 
            default = {}
482
 
        default.update({
483
 
            'period_id': False,
484
 
            'purchase_ids': False,  # UFTP-24 do not copy linked POs
485
 
            'purchase_list': False,  # UFTP-24 do not copy linked: reset of potential purchase list flag (from a PO direct purchase)
486
 
            'partner_move_line': False,
487
 
            'imported_invoices': False
488
 
        })
489
 
        # Reset register_line_ids if not given in default
490
 
        if 'register_line_ids' not in default:
491
 
            default['register_line_ids'] = []
492
 
        # US-267: Reset st_lines if not given in default, otherwise a new line in Reg will be added
493
 
        if 'st_lines' not in default:
494
 
            default['st_lines'] = []
495
 
        # Default behaviour
496
 
        new_id = super(account_invoice, self).copy(cr, uid, inv_id, default, context)
497
 
        # Case where you split an invoice
498
 
        if 'split_it' in context:
499
 
            purchase_obj = self.pool.get('purchase.order')
500
 
            sale_obj = self.pool.get('sale.order')
501
 
            if purchase_obj:
502
 
                # attach new invoice to PO
503
 
                purchase_ids = purchase_obj.search(cr, uid, [('invoice_ids', 'in', [inv_id])], context=context)
504
 
                if purchase_ids:
505
 
                    purchase_obj.write(cr, uid, purchase_ids, {'invoice_ids': [(4, new_id)]}, context=context)
506
 
            if sale_obj:
507
 
                # attach new invoice to SO
508
 
                sale_ids = sale_obj.search(cr, uid, [('invoice_ids', 'in', [inv_id])], context=context)
509
 
                if sale_ids:
510
 
                    sale_obj.write(cr, uid, sale_ids, {'invoice_ids': [(4, new_id)]}, context=context)
511
 
        return new_id
512
 
 
513
 
    def create(self, cr, uid, vals, context=None):
514
 
        """
515
 
        Filled in 'from_yml_test' to True if we come from tests
516
 
        """
517
 
        if not context:
518
 
            context = {}
519
 
        if 'document_date' in vals and 'date_invoice' in vals:
520
 
            self.pool.get('finance.tools').check_document_date(cr, uid,
521
 
                vals['document_date'], vals['date_invoice'], context=context)
522
 
 
523
 
        # Create a sequence for this new invoice
524
 
        res_seq = self.create_sequence(cr, uid, vals, context)
525
 
        vals.update({'sequence_id': res_seq,})
526
 
 
527
 
        # UTP-317 # Check that no inactive partner have been used to create this invoice
528
 
        if 'partner_id' in vals:
529
 
            partner_id = vals.get('partner_id')
530
 
            if isinstance(partner_id, (str)):
531
 
                partner_id = int(partner_id)
532
 
            partner = self.pool.get('res.partner').browse(cr, uid, [partner_id])
533
 
            if partner and partner[0] and not partner[0].active:
534
 
                raise osv.except_osv(_('Warning'), _("Partner '%s' is not active.") % (partner[0] and partner[0].name or '',))
535
 
 
536
 
        return super(account_invoice, self).create(cr, uid, vals, context)
537
 
 
538
 
    def write(self, cr, uid, ids, vals, context=None):
539
 
        """
540
 
        Check document_date
541
 
        """
542
 
        if context is None:
543
 
            context = {}
544
 
        if isinstance(ids, (int, long)):
545
 
            ids = [ids]
546
 
 
547
 
        # US_286: Forbit possibility to add include price tax
548
 
        # in bottom left corner
549
 
        if 'tax_line' in vals:
550
 
            tax_obj = self.pool.get('account.tax')
551
 
            for tax_line in vals['tax_line']:
552
 
                if tax_line[2]:
553
 
                    if 'account_tax_id' in tax_line[2]:
554
 
                        args = [('price_include', '=', '1'),
555
 
                                ('id', '=', tax_line[2]['account_tax_id'])]
556
 
                        tax_ids = tax_obj.search(cr, uid, args, limit=1,
557
 
                                order='NO_ORDER', context=context)
558
 
                        if tax_ids:
559
 
                            raise osv.except_osv(_('Error'),
560
 
                                                 _('Tax included in price can not be tied to the whole invoice.'))
561
 
 
562
 
        res = super(account_invoice, self).write(cr, uid, ids, vals, context=context)
563
 
        self._check_document_date(cr, uid, ids)
564
 
        return res
565
 
 
566
 
    def unlink(self, cr, uid, ids, context=None):
567
 
        """
568
 
        Delete register line if this invoice is a Direct Invoice.
569
 
        Don't delete an invoice that is linked to a PO. This is only for supplier invoices.
570
 
        """
571
 
        if not context:
572
 
            context = {}
573
 
        if isinstance(ids, (int, long)):
574
 
            ids = [ids]
575
 
        # Check register lines
576
 
        for inv in self.browse(cr, uid, ids):
577
 
            if inv.is_direct_invoice and inv.register_line_ids:
578
 
                if not context.get('from_register', False):
579
 
                    self.pool.get('account.bank.statement.line').unlink(cr, uid, [x.id for x in inv.register_line_ids], {'from_direct_invoice': True})
580
 
        # Check PO
581
 
        self.check_po_link(cr, uid, ids)
582
 
        return super(account_invoice, self).unlink(cr, uid, ids, context)
583
 
 
584
86
    def create_sequence(self, cr, uid, vals, context=None):
585
87
        """
586
88
        Create new entry sequence for every new invoice
605
107
        }
606
108
        return seq_pool.create(cr, uid, seq)
607
109
 
608
 
    def log(self, cr, uid, inv_id, message, secondary=False, context=None):
609
 
        """
610
 
        Change first "Invoice" word from message into "Debit Note" if this invoice is a debit note.
611
 
        Change it to "In-kind donation" if this invoice is an In-kind donation.
612
 
        """
613
 
        if not context:
614
 
            context = {}
615
 
        local_ctx = context.copy()
616
 
        # Prepare some values
617
 
        # Search donation view and return it
618
 
        try:
619
 
            # try / except for runbot
620
 
            debit_res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_override', 'view_debit_note_form')
621
 
            inkind_res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_override', 'view_inkind_donation_form')
622
 
            intermission_res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_override', 'view_intermission_form')
623
 
            supplier_invoice_res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
624
 
            customer_invoice_res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form')
625
 
            supplier_direct_invoice_res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'register_accounting', 'direct_supplier_invoice_form')
626
 
        except ValueError, e:
627
 
            return super(account_invoice, self).log(cr, uid, inv_id, message, secondary, context)
628
 
        debit_view_id = debit_res and debit_res[1] or False
629
 
        debit_note_ctx = {'view_id': debit_view_id, 'type':'out_invoice', 'journal_type': 'sale', 'is_debit_note': True}
630
 
        # Search donation view and return it
631
 
        inkind_view_id = inkind_res and inkind_res[1] or False
632
 
        inkind_ctx = {'view_id': inkind_view_id, 'type':'in_invoice', 'journal_type': 'inkind', 'is_inkind_donation': True}
633
 
        # Search intermission view
634
 
        intermission_view_id = intermission_res and intermission_res[1] or False
635
 
        intermission_ctx = {'view_id': intermission_view_id, 'journal_type': 'intermission', 'is_intermission': True}
636
 
        customer_view_id = customer_invoice_res[1] or False
637
 
        customer_ctx = {'view_id': customer_view_id, 'type': 'out_invoice', 'journal_type': 'sale'}
638
 
        message_changed = False
639
 
        pattern = re.compile('^(Invoice)')
640
 
        for el in [('is_debit_note', 'Debit Note', debit_note_ctx), ('is_inkind_donation', 'In-kind Donation', inkind_ctx), ('is_intermission', 'Intermission Voucher', intermission_ctx)]:
641
 
            if self.read(cr, uid, inv_id, [el[0]]).get(el[0], False) is True:
642
 
                m = re.match(pattern, message)
643
 
                if m and m.groups():
644
 
                    message = re.sub(pattern, el[1], message, 1)
645
 
                    message_changed = True
646
 
                local_ctx.update(el[2])
647
 
        # UF-1112: Give all customer invoices a name as "Stock Transfer Voucher".
648
 
        if not message_changed and self.read(cr, uid, inv_id, ['type']).get('type', False) == 'out_invoice':
649
 
            message = re.sub(pattern, 'Stock Transfer Voucher', message, 1)
650
 
 
651
 
            local_ctx.update(customer_ctx)
652
 
        # UF-1307: for supplier invoice log (from the incoming shipment), the context was not
653
 
        # filled with all the information; this leaded to having a "Sale" journal in the supplier
654
 
        # invoice if it was saved after coming from this link. Here's the fix.
655
 
        if local_ctx.get('type', False) == 'in_invoice':
656
 
            if not local_ctx.get('journal_type', False):
657
 
                supplier_view_id = supplier_invoice_res and supplier_invoice_res[1] or False
658
 
                local_ctx.update({'journal_type': 'purchase',
659
 
                                'view_id': supplier_view_id})
660
 
            elif local_ctx.get('direct_invoice_view', False): # UFTP-166: The wrong context saved in log
661
 
                supplier_view_id = supplier_direct_invoice_res and supplier_direct_invoice_res[1] or False
662
 
                local_ctx = {'journal_type': 'purchase',
663
 
                             'view_id': supplier_view_id}
664
 
        return super(account_invoice, self).log(cr, uid, inv_id, message, secondary, local_ctx)
665
 
 
666
 
    def invoice_open(self, cr, uid, ids, context=None):
667
 
        """
668
 
        No longer fills the date automatically, but requires it to be set
669
 
        """
670
 
        # Some verifications
671
 
        if not context:
672
 
            context = {}
673
 
        self._check_invoice_merged_lines(cr, uid, ids, context=context)
674
 
 
675
 
        # Prepare workflow object
676
 
        wf_service = netsvc.LocalService("workflow")
677
 
        for inv in self.browse(cr, uid, ids):
678
 
            values = {}
679
 
            curr_date = strftime('%Y-%m-%d')
680
 
            if not inv.date_invoice and not inv.document_date:
681
 
                values.update({'date': curr_date, 'document_date': curr_date, 'state': 'date'})
682
 
            elif not inv.date_invoice:
683
 
                values.update({'date': curr_date, 'document_date': inv.document_date, 'state': 'date'})
684
 
            elif not inv.document_date:
685
 
                values.update({'date': inv.date_invoice, 'document_date': curr_date, 'state': 'date'})
686
 
            if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
687
 
                state = values and 'both' or 'amount'
688
 
                values.update({'check_total': inv.check_total , 'amount_total': inv.amount_total, 'state': state})
689
 
            if values:
690
 
                values['invoice_id'] = inv.id
691
 
                wiz_id = self.pool.get('wizard.invoice.date').create(cr, uid, values, context)
692
 
                return {
693
 
                    'name': "Missing Information",
694
 
                    'type': 'ir.actions.act_window',
695
 
                    'res_model': 'wizard.invoice.date',
696
 
                    'target': 'new',
697
 
                    'view_mode': 'form',
698
 
                    'view_type': 'form',
699
 
                    'res_id': wiz_id,
700
 
                    }
701
 
 
702
 
            wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_open', cr)
703
 
        return True
704
 
 
705
 
    def action_reconcile_imported_invoice(self, cr, uid, ids, context=None):
706
 
        """
707
 
        Reconcile each imported invoice with its attached invoice line
708
 
        """
709
 
        # some verifications
 
110
    def create(self, cr, uid, vals, context=None):
 
111
        """
 
112
        Filled in 'from_yml_test' to True if we come from tests
 
113
        """
 
114
        if not context:
 
115
            context = {}
 
116
        if context.get('update_mode') in ['init', 'update']:
 
117
            logging.getLogger('init').info('INV: set from yml test to True')
 
118
            vals['from_yml_test'] = True
 
119
        # Create a sequence for this new invoice
 
120
        res_seq = self.create_sequence(cr, uid, vals, context)
 
121
        vals.update({'sequence_id': res_seq,})
 
122
        return super(account_invoice, self).create(cr, uid, vals, context)
 
123
 
 
124
    def _check_document_date(self, cr, uid, ids):
 
125
        """
 
126
        Check that document's date is done BEFORE posting date
 
127
        """
710
128
        if isinstance(ids, (int, long)):
711
129
            ids = [ids]
712
 
        # browse all given invoices
713
 
        for inv in self.browse(cr, uid, ids):
714
 
            for invl in inv.invoice_line:
715
 
                if not invl.import_invoice_id:
716
 
                    continue
717
 
                imported_invoice = invl.import_invoice_id
718
 
                # reconcile partner line from import invoice with this invoice line attached move line
719
 
                import_invoice_partner_move_lines = self.pool.get('account.move.line').search(cr, uid, [('invoice_partner_link', '=', imported_invoice.id)])
720
 
                invl_move_lines = [x.id or None for x in invl.move_lines]
721
 
                rec = self.pool.get('account.move.line').reconcile_partial(cr, uid, [import_invoice_partner_move_lines[0], invl_move_lines[0]], 'auto', context=context)
722
 
                if not rec:
723
 
                    return False
724
 
        return True
725
 
 
726
 
    def action_reconcile_direct_invoice(self, cr, uid, inv, context=None):
727
 
        """
728
 
        Reconcile move line if invoice is a Direct Invoice
729
 
        NB: In order to define that an invoice is a Direct Invoice, we need to have register_line_ids not null
730
 
        """
731
 
        # Verify that this invoice is linked to a register line and have a move
732
 
        if not inv:
733
 
            return False
734
 
        if inv.move_id and inv.register_line_ids:
735
 
            ml_obj = self.pool.get('account.move.line')
736
 
            # First search move line that becomes from invoice
737
 
            res_ml_ids = ml_obj.search(cr, uid, [
738
 
                ('move_id', '=', inv.move_id.id),
739
 
                ('account_id', '=', inv.account_id.id),
740
 
                ('invoice_line_id', '=', False),  # US-254: do not seek invoice line's JIs (if same account as header)
741
 
            ])
742
 
            if len(res_ml_ids) > 1:
743
 
                raise osv.except_osv(_('Error'), _('More than one journal items found for this invoice.'))
744
 
            invoice_move_line_id = res_ml_ids[0]
745
 
            # Then search move line that corresponds to the register line
746
 
            reg_line = inv.register_line_ids[0]
747
 
            reg_ml_ids = ml_obj.search(cr, uid, [('move_id', '=', reg_line.move_ids[0].id), ('account_id', '=', reg_line.account_id.id)])
748
 
            if len(reg_ml_ids) > 1:
749
 
                raise osv.except_osv(_('Error'), _('More than one journal items found for this register line.'))
750
 
            register_move_line_id = reg_ml_ids[0]
751
 
            # Finally do reconciliation
752
 
            ml_obj.reconcile_partial(cr, uid, [invoice_move_line_id, register_move_line_id])
753
 
        return True
754
 
 
755
 
    def action_cancel(self, cr, uid, ids, *args):
756
 
        """
757
 
        Reverse move if this object is a In-kind Donation. Otherwise do normal job: cancellation.
758
 
        Don't delete an invoice that is linked to a PO. This is only for supplier invoices.
759
 
        """
760
 
        to_cancel = []
761
130
        for i in self.browse(cr, uid, ids):
762
 
            if i.is_inkind_donation:
763
 
                move_id = i.move_id.id
764
 
                tmp_res = self.pool.get('account.move').reverse(cr, uid, [move_id], strftime('%Y-%m-%d'))
765
 
                # If success change invoice to cancel and detach move_id
766
 
                if tmp_res:
767
 
                    # Change invoice state
768
 
                    self.write(cr, uid, [i.id], {'state': 'cancel', 'move_id':False})
769
 
                continue
770
 
            to_cancel.append(i.id)
771
 
        # Check PO link
772
 
        self.check_po_link(cr, uid, ids)
773
 
        return super(account_invoice, self).action_cancel(cr, uid, to_cancel, args)
 
131
            if i.document_date and i.date_invoice and i.date_invoice < i.document_date:
 
132
                raise osv.except_osv(_('Error'), _('Posting date should be later than Document Date.'))
 
133
        return True
774
134
 
775
135
    def action_date_assign(self, cr, uid, ids, *args):
776
136
        """
777
137
        Check Document date.
 
138
        Add it if we come from a YAML test.
778
139
        """
779
 
        # Prepare some values
780
 
        period_obj = self.pool.get('account.period')
781
140
        # Default behaviour to add date
782
141
        res = super(account_invoice, self).action_date_assign(cr, uid, ids, args)
783
142
        # Process invoices
785
144
            if not i.date_invoice:
786
145
                self.write(cr, uid, i.id, {'date_invoice': strftime('%Y-%m-%d')})
787
146
                i = self.browse(cr, uid, i.id) # This permit to refresh the browse of this element
788
 
            if not i.document_date:
 
147
            if not i.document_date and i.from_yml_test:
 
148
                self.write(cr, uid, i.id, {'document_date': i.date_invoice})
 
149
            if not i.document_date and not i.from_yml_test:
789
150
                raise osv.except_osv(_('Warning'), _('Document Date is a mandatory field for validation!'))
790
 
            # UFTP-105: Search period and raise an exeception if this one is not open
791
 
            period_ids = period_obj.get_period_from_date(cr, uid, i.date_invoice)
792
 
            if not period_ids:
793
 
                raise osv.except_osv(_('Error'), _('No period found for this posting date: %s') % (i.date_invoice))
794
 
            for period in period_obj.browse(cr, uid, period_ids):
795
 
                if period.state != 'draft':
796
 
                    raise osv.except_osv(_('Warning'), _('You cannot validate this document in the given period: %s because it\'s not open. Change the date of the document or open the period.') % (period.name))
797
151
        # Posting date should not be done BEFORE document date
798
152
        self._check_document_date(cr, uid, ids)
799
153
        return res
804
158
        """
805
159
        if not context:
806
160
            context = {}
807
 
        if isinstance(ids, (int, long)):
808
 
            ids = [ids]
809
161
        if not self.action_date_assign(cr, uid, ids, context, args):
810
162
            return False
811
163
        if not self.action_move_create(cr, uid, ids, context, args):
812
164
            return False
813
165
        if not self.action_number(cr, uid, ids, context):
814
166
            return False
815
 
        if not self.action_reconcile_imported_invoice(cr, uid, ids, context):
816
 
            return False
817
167
        return True
818
168
 
819
 
    def line_get_convert(self, cr, uid, x, part, date, context=None):
820
 
        """
821
 
        Add these field into invoice line:
822
 
        - invoice_line_id
823
 
        """
 
169
    def _hook_period_id(self, cr, uid, inv, context=None):
 
170
        """
 
171
        Give matches period that are not draft and not HQ-closed from given date
 
172
        """
 
173
        # Some verifications
824
174
        if not context:
825
175
            context = {}
826
 
        res = super(account_invoice, self).line_get_convert(cr, uid, x, part, date, context)
827
 
        res.update({'invoice_line_id': x.get('invoice_line_id', False)})
 
176
        if not inv:
 
177
            return False
 
178
        # NB: there is some period state. So we define that we choose only open period (so not draft and not done)
 
179
        res = self.pool.get('account.period').search(cr, uid, [('date_start','<=',inv.date_invoice or strftime('%Y-%m-%d')),
 
180
            ('date_stop','>=',inv.date_invoice or strftime('%Y-%m-%d')), ('state', 'not in', ['created', 'done']), 
 
181
            ('company_id', '=', inv.company_id.id)], context=context, order="date_start ASC, name ASC")
828
182
        return res
829
183
 
830
184
    def finalize_invoice_move_lines(self, cr, uid, inv, line):
831
185
        """
832
186
        Hook that changes move line data before write them.
833
 
        Add a link between partner move line and invoice.
834
187
        Add invoice document date to data.
835
188
        """
836
 
        def is_partner_line(dico):
837
 
            if isinstance(dico, dict):
838
 
                if dico:
839
 
                    # In case where no amount_currency filled in, then take debit - credit for amount comparison
840
 
                    amount = dico.get('amount_currency', False) or (dico.get('debit', 0.0) - dico.get('credit', 0.0))
841
 
                    if amount == inv.amount_total and dico.get('partner_id', False) == inv.partner_id.id:
842
 
                        return True
843
 
            return False
 
189
        res = super(account_invoice, self).finalize_invoice_move_lines(cr, uid, inv, line)
844
190
        new_line = []
845
191
        for el in line:
846
192
            if el[2]:
847
193
                el[2].update({'document_date': inv.document_date})
848
 
            if el[2] and is_partner_line(el[2]):
849
 
                el[2].update({'invoice_partner_link': inv.id})
850
 
                new_line.append((el[0], el[1], el[2]))
851
 
            else:
852
 
                new_line.append(el)
853
 
        return super(account_invoice, self).finalize_invoice_move_lines(cr, uid, inv, new_line)
854
 
 
855
 
    def button_debit_note_import_invoice(self, cr, uid, ids, context=None):
856
 
        """
857
 
        Launch wizard that permits to import invoice on a debit note
858
 
        """
859
 
        # Some verifications
 
194
        return res
 
195
 
 
196
    def copy(self, cr, uid, id, default={}, context=None):
 
197
        """
 
198
        Delete period_id from invoice
 
199
        """
 
200
        if default is None:
 
201
            default = {}
 
202
        default.update({'period_id': False,})
 
203
        return super(account_invoice, self).copy(cr, uid, id, default, context)
 
204
 
 
205
    def __hook_lines_before_pay_and_reconcile(self, cr, uid, lines):
 
206
        """
 
207
        Add document date to account_move_line before pay and reconcile
 
208
        """
 
209
        for line in lines:
 
210
            if line[2] and 'date' in line[2] and not line[2].get('document_date', False):
 
211
                line[2].update({'document_date': line[2].get('date')})
 
212
        return lines
 
213
 
 
214
    def write(self, cr, uid, ids, vals, context=None):
 
215
        """
 
216
        Check document_date
 
217
        """
860
218
        if not context:
861
219
            context = {}
862
220
        if isinstance(ids, (int, long)):
863
221
            ids = [ids]
864
 
        # Browse all given invoices
865
 
        for inv in self.browse(cr, uid, ids):
866
 
            if inv.type != 'out_invoice' or inv.is_debit_note == False:
867
 
                raise osv.except_osv(_('Error'), _('You can only do import invoice on a Debit Note!'))
868
 
            w_id = self.pool.get('debit.note.import.invoice').create(cr, uid, {'invoice_id': inv.id, 'currency_id': inv.currency_id.id,
869
 
                'partner_id': inv.partner_id.id}, context=context)
870
 
            context.update({
871
 
                'active_id': inv.id,
872
 
                'active_ids': ids,
873
 
            })
874
 
            return {
875
 
                'type': 'ir.actions.act_window',
876
 
                'res_model': 'debit.note.import.invoice',
877
 
                'name': 'Import invoice',
878
 
                'view_type': 'form',
879
 
                'view_mode': 'form',
880
 
                'res_id': w_id,
881
 
                'context': context,
882
 
                'target': 'new',
883
 
            }
884
 
 
885
 
    def button_split_invoice(self, cr, uid, ids, context=None):
886
 
        """
887
 
        Launch the split invoice wizard to split an invoice in two elements.
888
 
        """
889
 
        # Some verifications
890
 
        if not context:
891
 
            context={}
892
 
        if isinstance(ids, (int, long)):
893
 
            ids = [ids]
894
 
        self._check_invoice_merged_lines(cr, uid, ids, context=context)
895
 
 
896
 
        # Prepare some value
897
 
        wiz_lines_obj = self.pool.get('wizard.split.invoice.lines')
898
 
        inv_lines_obj = self.pool.get('account.invoice.line')
899
 
        # Creating wizard
900
 
        wizard_id = self.pool.get('wizard.split.invoice').create(cr, uid, {'invoice_id': ids[0]}, context=context)
901
 
        # Add invoices_lines into the wizard
902
 
        invoice_line_ids = self.pool.get('account.invoice.line').search(cr, uid, [('invoice_id', '=', ids[0])], context=context)
903
 
        # Some other verifications
904
 
        if not len(invoice_line_ids):
905
 
            raise osv.except_osv(_('Error'), _('No invoice line in this invoice or not enough elements'))
906
 
        for invl in inv_lines_obj.browse(cr, uid, invoice_line_ids, context=context):
907
 
            wiz_lines_obj.create(cr, uid, {'invoice_line_id': invl.id, 'product_id': invl.product_id.id, 'quantity': invl.quantity,
908
 
                'price_unit': invl.price_unit, 'description': invl.name, 'wizard_id': wizard_id}, context=context)
909
 
        # Return wizard
910
 
        if wizard_id:
911
 
            return {
912
 
                'name': "Split Invoice",
913
 
                'type': 'ir.actions.act_window',
914
 
                'res_model': 'wizard.split.invoice',
915
 
                'target': 'new',
916
 
                'view_mode': 'form,tree',
917
 
                'view_type': 'form',
918
 
                'res_id': [wizard_id],
919
 
                'context':
920
 
                {
921
 
                    'active_id': ids[0],
922
 
                    'active_ids': ids,
923
 
                    'wizard_id': wizard_id,
924
 
                }
925
 
            }
926
 
        return False
927
 
 
928
 
    def button_donation_certificate(self, cr, uid, ids, context=None):
929
 
        """
930
 
        Open a view containing a list of all donation certificates linked to the given invoice.
931
 
        """
932
 
        for inv in self.browse(cr, uid, ids):
933
 
            pick_id = inv.picking_id and inv.picking_id.id or ''
934
 
            domain = "[('res_model', '=', 'stock.picking'), ('res_id', '=', " + str(pick_id) + "), ('description', '=', 'Certificate of Donation')]"
935
 
            view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_override', 'view_attachment_tree_2')
936
 
            view_id = view_id and view_id[1] or False
937
 
            search_view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_override', 'view_attachment_search_2')
938
 
            search_view_id = search_view_id and search_view_id[1] or False
939
 
            return {
940
 
                'name': "Certificate of Donation",
941
 
                'type': 'ir.actions.act_window',
942
 
                'res_model': 'ir.attachment',
943
 
                'view_type': 'form',
944
 
                'view_mode': 'tree,form',
945
 
                'view_id': [view_id],
946
 
                'search_view_id': search_view_id,
947
 
                'domain': domain,
948
 
                'context': context,
949
 
                'target': 'current',
950
 
            }
951
 
        return False
952
 
 
953
 
    def button_dummy_compute_total(self, cr, uid, ids, context=None):
954
 
        return True
955
 
 
956
 
    def button_merge_lines(self, cr, uid, ids, context=None):
957
 
        # US-357 merge lines (by account) button for draft SIs
958
 
        def check(inv_br):
959
 
            if not inv_br.can_merge_lines:
960
 
                raise osv.except_osv(_('Error'),
961
 
                    _("Invoice not eligible for lines merging"))
962
 
 
963
 
            account_iterations = {}
964
 
            for l in inv_br.invoice_line:
965
 
                account_iterations[l.account_id.id] = \
966
 
                    account_iterations.setdefault(l.account_id.id, 0) + 1
967
 
 
968
 
            any_to_merge = False
969
 
            if account_iterations:
970
 
                for a in account_iterations:
971
 
                    if account_iterations[a] > 1:
972
 
                        any_to_merge = True
973
 
                        break
974
 
 
975
 
            if not any_to_merge:
976
 
                raise osv.except_osv(_('Error'),
977
 
                    _("Invoice has no line to merge by account"))
978
 
 
979
 
        def compute_merge(inv_br):
980
 
            """
981
 
            :result:
982
 
                - A: lines vals by line number
983
 
                - B: and list of inv id to keep (1 line by account (not merged))
984
 
            :rtype : [dict, list]
985
 
 
986
 
            NOTES:
987
 
            - no impact on 'import_invoice_id', 'is_corrected' as the 
988
 
              invoice is draft so not imported, and no accounting entries
989
 
            - for order_line_id and sale_order_line_id these m2o are used
990
 
              for AD at line level but when merging we keep only AD from header
991
 
            """
992
 
            index = 1
993
 
            vals_template = {
994
 
                '_index_': index,  # internal merged line index
995
 
 
996
 
                'account_id': False,
997
 
                'company_id': inv_br.company_id.id,
998
 
                'discount': 0.,
999
 
                'invoice_id': inv_br.id,
1000
 
                'invoice_line_tax_id': None,  # m2m (None to distinguished False)
1001
 
                'name': '',
1002
 
                'partner_id': inv_br.partner_id.id,
1003
 
                'price_unit': 0.,
1004
 
                'quantity': 1.,
1005
 
            }
1006
 
 
1007
 
            by_account_vals = {}  # key: account_id
1008
 
            for l in inv_br.invoice_line:
1009
 
                # get current merge vals for account or create new
1010
 
                if l.account_id.id in by_account_vals:
1011
 
                    vals = by_account_vals[l.account_id.id]
1012
 
                else:
1013
 
                    # new account to merge
1014
 
                    vals = vals_template.copy()
1015
 
                    vals.update({
1016
 
                        '_index_': index,
1017
 
                        'account_id': l.account_id.id,
1018
 
                    })
1019
 
                    index += 1
1020
 
 
1021
 
                # merge line
1022
 
                vals['price_unit'] += l.price_subtotal  # qty 1 and price
1023
 
                if vals['invoice_line_tax_id'] is None:
1024
 
                    vals['invoice_line_tax_id'] = l.invoice_line_tax_id \
1025
 
                        and [ t.id for t in l.invoice_line_tax_id ] or False
1026
 
                else:
1027
 
                    # get rid of the product tax line if <> between merged lines
1028
 
                    if vals['invoice_line_tax_id'] is None:
1029
 
                        # first tax line browsed for the account
1030
 
                        if l.invoice_line_tax_id:
1031
 
                            vals['invoice_line_tax_id'] = [ 
1032
 
                                t.id for t in l.invoice_line_tax_id ]
1033
 
                        else:
1034
 
                            vals['invoice_line_tax_id'] = False
1035
 
                    elif vals['invoice_line_tax_id'] and l.invoice_line_tax_id:
1036
 
                        # track <> tax lines, if the case abort tax(es) in merge
1037
 
                        tax_ids = [ t.id for t in l.invoice_line_tax_id ]
1038
 
                        if cmp(vals['invoice_line_tax_id'], tax_ids) != 0:
1039
 
                            vals['invoice_line_tax_id'] = False
1040
 
                    else:
1041
 
                        # no tax(es) for this line,  abort tax(es) in merge
1042
 
                        vals['invoice_line_tax_id'] = False
1043
 
 
1044
 
                # update merge line
1045
 
                by_account_vals[l.account_id.id] = vals
1046
 
 
1047
 
                # internal merged lines ids
1048
 
                if not '_ids_' in by_account_vals[l.account_id.id]:
1049
 
                    by_account_vals[l.account_id.id]['_ids_'] = []
1050
 
                by_account_vals[l.account_id.id]['_ids_'].append(l.id)
1051
 
 
1052
 
            # result by index
1053
 
            res = [{}, []]
1054
 
            for a in by_account_vals:
1055
 
                if len(by_account_vals[a]['_ids_']) > 1:
1056
 
                    # more than 1 inv line by account
1057
 
                    index = by_account_vals[a]['_index_']
1058
 
                    del by_account_vals[a]['_index_']
1059
 
                    del by_account_vals[a]['_ids_']
1060
 
                    res[0][index] = by_account_vals[a]
1061
 
                else:
1062
 
                    res[1].append(by_account_vals[a]['_ids_'][0])
1063
 
            return res
1064
 
 
1065
 
        def delete_lines(inv_br, skip_ids):
1066
 
            # get ids to delete
1067
 
            ad_to_del_ids = []
1068
 
            line_to_del_ids = []
1069
 
 
1070
 
            for l in inv_br.invoice_line:
1071
 
                if l.id in skip_ids:
1072
 
                    continue  # line not to del (1 by account)
1073
 
                # delete AD
1074
 
                if l.analytic_distribution_id \
1075
 
                    and not l.analytic_distribution_id.id in ad_to_del_ids:
1076
 
                    ad_to_del_ids.append(l.analytic_distribution_id.id)
1077
 
                line_to_del_ids.append(l.id)
1078
 
 
1079
 
            # delete ADs
1080
 
            if ad_to_del_ids:
1081
 
                ad_obj.unlink(cr, uid, ad_to_del_ids, context=context)
1082
 
 
1083
 
            # delete lines
1084
 
            if line_to_del_ids:
1085
 
                ail_obj.unlink(cr, uid, line_to_del_ids, context=context)
1086
 
 
1087
 
        def do_merge(inv_br, lines_vals, not_merged_ids):
1088
 
            """
1089
 
            :param lines_vals: lines vals in order
1090
 
            :type lines_vals: dict
1091
 
            """
1092
 
            # the invoice is reviewed with merge lines
1093
 
            # => reset the line number sequence from 1
1094
 
            if inv_br.sequence_id:
1095
 
                inv_br.sequence_id.write({'number_next': 1}, context=context)
1096
 
 
1097
 
            # create merge lines
1098
 
            for ln in sorted(lines_vals.keys()):
1099
 
                vals = lines_vals[ln]
1100
 
 
1101
 
                # post encode tax m2m
1102
 
                vals['invoice_line_tax_id'] = vals['invoice_line_tax_id'] \
1103
 
                    and [(6, 0, vals['invoice_line_tax_id'])] or False
1104
 
 
1105
 
                # create merge line
1106
 
                if not self.pool.get('account.invoice.line').create(cr, uid,
1107
 
                    vals, context=context):
1108
 
                    break
1109
 
 
1110
 
            # recompute seq number for not merged lines
1111
 
            ail_obj = self.pool.get('account.invoice.line')
1112
 
            if not_merged_ids:
1113
 
                for lid in not_merged_ids:
1114
 
                    ln = inv_br.sequence_id.get_id(code_or_id='id')
1115
 
                    ail_obj.write(cr, uid, [lid], {
1116
 
                        'line_number': ln,
1117
 
                    })
1118
 
 
1119
 
        def merge_invoice(inv_br):
1120
 
            check(inv_br)
1121
 
            merge_res = compute_merge(inv_br)
1122
 
            delete_lines(inv_br, merge_res[1])
1123
 
            do_merge(inv_br, merge_res[0], merge_res[1])
1124
 
 
1125
 
            # set merged flag
1126
 
            inv_br.write({'is_merged_by_account': True}, context=context)
1127
 
 
1128
 
            # recompute taxes (reset not manual ones)
1129
 
            self.button_reset_taxes(cr, uid, [inv_br.id], context=context)
1130
 
 
1131
 
        def post_merge(inv_br):
1132
 
            inv_br.write({
1133
 
                # update check total for accurate check amount at validation
1134
 
                'check_total':
1135
 
                    inv_br.amount_total or inv_br.check_amount or 0.,
1136
 
            }, context=context)
1137
 
 
1138
 
        res = {}
1139
 
        if not ids:
1140
 
            return False
1141
 
        if isinstance(ids, (int, long, )):
1142
 
            ids = [ids]
1143
 
 
1144
 
        ail_obj = self.pool.get('account.invoice.line')
1145
 
        ad_obj = self.pool.get('analytic.distribution')
1146
 
 
1147
 
        # merging
1148
 
        for inv_br in self.browse(cr, uid, ids, context=context):
1149
 
            merge_invoice(inv_br)
1150
 
 
1151
 
        # post processing (reload invoices)
1152
 
        for inv_br in self.browse(cr, uid, ids, context=context):
1153
 
            post_merge(inv_br)
1154
 
 
 
222
        res = super(account_invoice, self).write(cr, uid, ids, vals, context=context)
 
223
        self._check_document_date(cr, uid, ids)
1155
224
        return res
1156
225
 
1157
226
account_invoice()
1160
229
    _name = 'account.invoice.line'
1161
230
    _inherit = 'account.invoice.line'
1162
231
 
1163
 
    def _uom_constraint(self, cr, uid, ids, context=None):
1164
 
        for obj in self.browse(cr, uid, ids, context=context):
1165
 
            if not self.pool.get('uom.tools').check_uom(cr, uid, obj.product_id.id, obj.uos_id.id, context):
1166
 
                raise osv.except_osv(_('Error'), _('You have to select a product UOM in the same category than the purchase UOM of the product !'))
1167
 
        return True
1168
 
 
1169
 
    _constraints = [(_uom_constraint, 'Constraint error on Uom', [])]
1170
 
 
1171
 
    def _have_been_corrected(self, cr, uid, ids, name, args, context=None):
1172
 
        """
1173
 
        Return True if ALL elements are OK:
1174
 
         - a journal items is linked to this invoice line
1175
 
         - the journal items is linked to an analytic line that have been reallocated
1176
 
        """
1177
 
        if context is None:
1178
 
            context = {}
1179
 
        res = {}
1180
 
 
1181
 
        def has_ana_reallocated(move):
1182
 
            for ml in move.move_lines or []:
1183
 
                for al in ml.analytic_lines or []:
1184
 
                    if al.is_reallocated:
1185
 
                        return True
1186
 
            return False
1187
 
 
1188
 
        for il in self.browse(cr, uid, ids, context=context):
1189
 
            res[il.id] = has_ana_reallocated(il)
1190
 
        return res
1191
 
 
1192
 
    def _get_product_code(self, cr, uid, ids, field_name=None, arg=None, context=None):
1193
 
        """
1194
 
        Give product code for each invoice line
1195
 
        """
1196
 
        res = {}
1197
 
        for inv_line in self.browse(cr, uid, ids, context=context):
1198
 
            res[inv_line.id] = ''
1199
 
            if inv_line.product_id:
1200
 
                res[inv_line.id] = inv_line.product_id.default_code
1201
 
 
1202
 
        return res
1203
 
    def _get_vat_ok(self, cr, uid, ids, field_name, args, context=None):
1204
 
        '''
1205
 
        Return True if the system configuration VAT management is set to True
1206
 
        '''
1207
 
        vat_ok = self.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok
1208
 
        res = {}
1209
 
        for id in ids:
1210
 
            res[id] = vat_ok
1211
 
 
1212
 
        return res
1213
 
 
1214
232
    _columns = {
1215
233
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
1216
234
        'line_number': fields.integer(string='Line Number'),
1217
 
        'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Account Computation')),
1218
 
        'import_invoice_id': fields.many2one('account.invoice', string="From an import invoice", readonly=True),
1219
 
        'move_lines':fields.one2many('account.move.line', 'invoice_line_id', string="Journal Item", readonly=True),
1220
 
        'is_corrected': fields.function(_have_been_corrected, method=True, string="Have been corrected?", type='boolean',
1221
 
            readonly=True, help="This informs system if this item have been corrected in analytic lines. Criteria: the invoice line is linked to a journal items that have analytic item which is reallocated.",
1222
 
            store=False),
1223
 
        'product_code': fields.function(_get_product_code, method=True, store=False, string="Product Code", type='char'),
1224
 
        'reference': fields.char(string="Reference", size=64),
1225
 
        'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False, readonly=True),
1226
235
    }
1227
236
 
1228
237
    _defaults = {
1229
 
        'price_unit': lambda *a: 0.00,
1230
238
        'from_yml_test': lambda *a: False,
1231
 
        'is_corrected': lambda *a: False,
1232
 
        'vat_ok': lambda obj, cr, uid, context: obj.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok,
1233
239
    }
1234
240
 
1235
241
    _order = 'line_number'
1236
242
 
1237
243
    def create(self, cr, uid, vals, context=None):
1238
244
        """
 
245
        Filled in 'from_yml_test' to True if we come from tests.
1239
246
        Give a line_number to invoice line.
1240
247
        NB: This appends only for account invoice line and not other object (for an example direct invoice line)
1241
 
        If invoice is a Direct Invoice and is in draft state:
1242
 
         - compute total amount (check_total field)
1243
 
         - write total to the register line
1244
248
        """
1245
249
        if not context:
1246
250
            context = {}
 
251
        if context.get('update_mode') in ['init', 'update']:
 
252
            logging.getLogger('init').info('INV: set from yml test to True')
 
253
            vals['from_yml_test'] = True
1247
254
        # Create new number with invoice sequence
1248
255
        if vals.get('invoice_id') and self._name in ['account.invoice.line']:
1249
256
            invoice = self.pool.get('account.invoice').browse(cr, uid, vals['invoice_id'])
1250
257
            if invoice and invoice.sequence_id:
1251
258
                sequence = invoice.sequence_id
1252
 
                line = sequence.get_id(code_or_id='id', context=context)
 
259
                line = sequence.get_id(test='id', context=context)
1253
260
                vals.update({'line_number': line})
1254
261
        return super(account_invoice_line, self).create(cr, uid, vals, context)
1255
262
 
1257
264
        """
1258
265
        Give a line_number in invoice_id in vals
1259
266
        NB: This appends only for account invoice line and not other object (for an example direct invoice line)
1260
 
        If invoice is a Direct Invoice and is in draft state:
1261
 
         - compute total amount (check_total field)
1262
 
         - write total to the register line
1263
267
        """
1264
 
 
1265
268
        if not context:
1266
269
            context = {}
1267
270
        if isinstance(ids, (int, long)):
1270
273
            for il in self.browse(cr, uid, ids):
1271
274
                if not il.line_number and il.invoice_id.sequence_id:
1272
275
                    sequence = il.invoice_id.sequence_id
1273
 
                    il_number = sequence.get_id(code_or_id='id', context=context)
 
276
                    il_number = sequence.get_id(test='id', context=context)
1274
277
                    vals.update({'line_number': il_number})
1275
 
        res = super(account_invoice_line, self).write(cr, uid, ids, vals, context)
1276
 
        for invl in self.browse(cr, uid, ids):
1277
 
            if invl.invoice_id and invl.invoice_id.is_direct_invoice and invl.invoice_id.state == 'draft':
1278
 
                amount = 0.0
1279
 
                for l in invl.invoice_id.invoice_line:
1280
 
                    amount += l.price_subtotal
1281
 
                self.pool.get('account.invoice').write(cr, uid, [invl.invoice_id.id], {'check_total': amount}, context)
1282
 
                self.pool.get('account.bank.statement.line').write(cr, uid, [x.id for x in invl.invoice_id.register_line_ids], {'amount': -1 * amount}, context)
1283
 
        return res
1284
 
 
1285
 
    def copy(self, cr, uid, inv_id, default=None, context=None):
1286
 
        """
1287
 
        Check context to see if we come from a split. If yes, we create the link between invoice and PO/FO.
1288
 
        """
1289
 
        if not context:
1290
 
            context = {}
1291
 
        if not default:
1292
 
            default = {}
1293
 
 
1294
 
        new_id = super(account_invoice_line, self).copy(cr, uid, inv_id, default, context)
1295
 
 
1296
 
        if 'split_it' in context:
1297
 
            purchase_lines_obj = self.pool.get('purchase.order.line')
1298
 
            sale_lines_obj = self.pool.get('sale.order.line')
1299
 
 
1300
 
            if purchase_lines_obj:
1301
 
                purchase_line_ids = purchase_lines_obj.search(cr, uid,
1302
 
                        [('invoice_lines', 'in', [inv_id])], order='NO_ORDER')
1303
 
                if purchase_line_ids:
1304
 
                    purchase_lines_obj.write(cr, uid, purchase_line_ids, {'invoice_lines': [(4, new_id)]})
1305
 
 
1306
 
            if sale_lines_obj:
1307
 
                sale_lines_ids =  sale_lines_obj.search(cr, uid,
1308
 
                        [('invoice_lines', 'in', [inv_id])], order='NO_ORDER')
1309
 
                if sale_lines_ids:
1310
 
                    sale_lines_obj.write(cr, uid,  sale_lines_ids, {'invoice_lines': [(4, new_id)]})
1311
 
 
1312
 
        return new_id
1313
 
 
1314
 
    def copy_data(self, cr, uid, inv_id, default=None, context=None):
1315
 
        """
1316
 
        Copy an invoice line without its move lines
1317
 
        """
1318
 
        if default is None:
1319
 
            default = {}
1320
 
        default.update({'move_lines': False,})
1321
 
        return super(account_invoice_line, self).copy_data(cr, uid, inv_id, default, context)
1322
 
 
1323
 
    def unlink(self, cr, uid, ids, context=None):
1324
 
        """
1325
 
        If invoice is a Direct Invoice and is in draft state:
1326
 
         - compute total amount (check_total field)
1327
 
         - write total to the register line
1328
 
        """
1329
 
        if not context:
1330
 
            context = {}
1331
 
        if isinstance(ids, (int, long)):
1332
 
            ids = [ids]
1333
 
        # Fetch all invoice_id to check
1334
 
        direct_invoice_ids = []
1335
 
        abst_obj = self.pool.get('account.bank.statement.line')
1336
 
        for invl in self.browse(cr, uid, ids):
1337
 
            if invl.invoice_id and invl.invoice_id.is_direct_invoice and invl.invoice_id.state == 'draft':
1338
 
                direct_invoice_ids.append(invl.invoice_id.id)
1339
 
                # find account_bank_statement_lines and used this to delete the account_moves and associated records
1340
 
                absl_ids = abst_obj.search(cr, uid,
1341
 
                        [('invoice_id','=',invl.invoice_id.id)],
1342
 
                        order='NO_ORDER')
1343
 
                if absl_ids:
1344
 
                    abst_obj.unlink_moves(cr, uid, absl_ids, context)
1345
 
        # Normal behaviour
1346
 
        res = super(account_invoice_line, self).unlink(cr, uid, ids, context)
1347
 
        # See all direct invoice
1348
 
        for inv in self.pool.get('account.invoice').browse(cr, uid, direct_invoice_ids):
1349
 
            amount = 0.0
1350
 
            for l in inv.invoice_line:
1351
 
                amount += l.price_subtotal
1352
 
            self.pool.get('account.invoice').write(cr, uid, [inv.id], {'check_total': amount}, context)
1353
 
            self.pool.get('account.bank.statement.line').write(cr, uid, [x.id for x in inv.register_line_ids], {'amount': -1 * amount}, context)
1354
 
        return res
1355
 
 
1356
 
    def move_line_get_item(self, cr, uid, line, context=None):
1357
 
        """
1358
 
        Add a link between move line and its invoice line
1359
 
        """
1360
 
        # some verification
1361
 
        if not context:
1362
 
            context = {}
1363
 
        # update default dict with invoice line ID
1364
 
        res = super(account_invoice_line, self).move_line_get_item(cr, uid, line, context=context)
1365
 
        res.update({'invoice_line_id': line.id})
1366
 
        return res
1367
 
 
1368
 
    def button_open_analytic_lines(self, cr, uid, ids, context=None):
1369
 
        """
1370
 
        Return analytic lines linked to this invoice line.
1371
 
        First we takes all journal items that are linked to this invoice line.
1372
 
        Then for all journal items, we take all analytic journal items.
1373
 
        Finally we display the result for "button_open_analytic_corrections" of analytic lines
1374
 
        """
1375
 
        # Some checks
1376
 
        if not context:
1377
 
            context = {}
1378
 
        # Prepare some values
1379
 
        al_ids = []
1380
 
        # Browse give invoice lines
1381
 
        for il in self.browse(cr, uid, ids, context=context):
1382
 
            if il.move_lines:
1383
 
                for ml in il.move_lines:
1384
 
                    if ml.analytic_lines:
1385
 
                        al_ids += [x.id for x in ml.analytic_lines]
1386
 
        return self.pool.get('account.analytic.line').button_open_analytic_corrections(cr, uid, al_ids, context=context)
 
278
        return super(account_invoice_line, self).write(cr, uid, ids, vals, context)
1387
279
 
1388
280
account_invoice_line()
1389
 
 
1390
 
 
1391
 
class res_partner(osv.osv):
1392
 
    _description='Partner'
1393
 
    _inherit = "res.partner"
1394
 
 
1395
 
    def _get_fake(self, cr, uid, ids, name, args, context=None):
1396
 
        res = {}
1397
 
        if not ids:
1398
 
            return res
1399
 
        if isinstance(ids, (int, long)):
1400
 
            ids = [ids]
1401
 
        for id in ids:
1402
 
            res[id] = False
1403
 
        return res
1404
 
 
1405
 
    def _get_search_by_invoice_type(self, cr, uid, obj, name, args,
1406
 
        context=None):
1407
 
        res = []
1408
 
        if not len(args):
1409
 
            return res
1410
 
        if context is None:
1411
 
            context = {}
1412
 
        if len(args) != 1:
1413
 
            msg = _("Domain %s not suported") % (str(args), )
1414
 
            raise osv.except_osv(_('Error'), msg)
1415
 
        if args[0][1] != '=':
1416
 
            msg = _("Operator '%s' not suported") % (args[0][1], )
1417
 
            raise osv.except_osv(_('Error'), msg)
1418
 
        if not args[0][2]:
1419
 
            return res
1420
 
 
1421
 
        invoice_type = context.get('type', False)
1422
 
        if invoice_type:
1423
 
            if invoice_type in ('in_invoice', 'in_refund', ):
1424
 
                # in invoices: only supplier partner
1425
 
                res = [('supplier', '=', True)]
1426
 
            elif invoice_type in ('out_invoice', 'out_refund', ):
1427
 
                # out invoices: only customer partner
1428
 
                res = [('customer', '=', True)]
1429
 
 
1430
 
        return res
1431
 
 
1432
 
    _columns = {
1433
 
        'by_invoice_type': fields.function(_get_fake, type='boolean',
1434
 
            fnct_search=_get_search_by_invoice_type, method=True),
1435
 
    }
1436
 
 
1437
 
    def name_search(self, cr, uid, name='', args=None, operator='ilike',
1438
 
        context=None, limit=100):
1439
 
        # BKLG-50: IN/OUT invoice/refund partner autocompletion filter
1440
 
        # regarding supplier/customer
1441
 
        if context is None:
1442
 
            context = {}
1443
 
 
1444
 
        alternate_domain = False
1445
 
        invoice_type = context.get('type', False)
1446
 
        if invoice_type:
1447
 
            if invoice_type in ('in_invoice', 'in_refund', ):
1448
 
                alternate_domain = [('supplier', '=', True)]
1449
 
            elif invoice_type in ('out_invoice', 'out_refund', ):
1450
 
                alternate_domain = [('customer', '=', True)]
1451
 
        if alternate_domain:
1452
 
            args += alternate_domain
1453
 
 
1454
 
        return super(res_partner, self).name_search(cr, uid, name=name,
1455
 
            args=args, operator=operator, context=context, limit=limit)
1456
 
 
1457
 
res_partner()
1458
 
 
1459
281
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: