~unifield-team/unifield-wm/us-826

« back to all changes in this revision

Viewing changes to unifield_tests/tests/finance.py

  • Committer: Quentin THEURET
  • Date: 2016-03-04 12:15:00 UTC
  • Revision ID: qt@tempo-consulting.fr-20160304121500-u2ay8zrf83ih9fu3
US-826 [IMP] Change the way to check if products is not consistent on add multiple line wizard

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf8 -*-
 
3
 
 
4
#
 
5
# FINANCE UNIT TESTS TOOLS
 
6
# Developer: Vincent GREINER
 
7
#
 
8
 
 
9
from __future__ import print_function
 
10
from unifield_test import UnifieldTestException
 
11
from unifield_test import UnifieldTest
 
12
from datetime import datetime
 
13
from datetime import timedelta
 
14
from time import strftime
 
15
from time import sleep
 
16
from random import randint
 
17
from random import randrange
 
18
from oerplib import error
 
19
 
 
20
 
 
21
FINANCE_TEST_MASK = {
 
22
    'register': "%s %s",
 
23
    'register_line': "[%s] %s",  # "[tag] uuid"
 
24
    'je': "JE %s",
 
25
    'ji': "JI %s",
 
26
    'ad': "AD %s",
 
27
    'cheque_number': "cheque %s",
 
28
    # invoice line name: "[tag] (invoice id)/L(line number) (account)"
 
29
    'invoice_line': "[%s] %d/L%03d %s",
 
30
}
 
31
 
 
32
AMOUNT_TOTAL_DIFF_DELTA = 0.01
 
33
 
 
34
CHECK_IS_CORRIGIBLE = False
 
35
 
 
36
 
 
37
class FinanceTestException(UnifieldTestException):
 
38
    pass
 
39
 
 
40
 
 
41
class FinanceTest(UnifieldTest):
 
42
 
 
43
    def __init__(self, *args, **kwargs):
 
44
        '''
 
45
        Include some finance data in the databases (except sync one).
 
46
        Include/create them only if they have not been already created.
 
47
        To know this, we use the key: "finance_test_class"
 
48
        '''
 
49
        super(FinanceTest, self).__init__(*args, **kwargs)
 
50
 
 
51
    def _hook_db_process(self, name, database):
 
52
        '''
 
53
        Check that finance data are loaded into the given database
 
54
        '''
 
55
        keyword = 'finance_test_class'
 
56
        colors = self.colors
 
57
        # If no one, do some changes on DBs
 
58
        if not self.is_keyword_present(database, keyword):
 
59
            # 00 Open periods from january to today's one
 
60
            """
 
61
            FIXME
 
62
            temporay bypass this dataset as done in cor testing
 
63
            and since no other tests performed for finance area
 
64
 
 
65
            month = strftime('%m')
 
66
            today = strftime('%Y-%m-%d')
 
67
            fy_obj = database.get('account.fiscalyear')
 
68
            period_obj = database.get('account.period')
 
69
            # Fiscal years
 
70
            fy_ids = fy_obj.search([
 
71
                ('date_start', '<=', today),
 
72
                ('date_stop', '>=', today)
 
73
            ])
 
74
            self.assert_(fy_ids != False, 'No fiscalyear found!')
 
75
 
 
76
            # Sort periods by number
 
77
            periods = period_obj.search([
 
78
                ('fiscalyear_id', 'in', fy_ids),
 
79
                ('number', '<=', month),
 
80
                ('state', '=', 'created')
 
81
            ], 0, 16, 'number')
 
82
            for period in periods:
 
83
                try:
 
84
                    period_obj.action_set_state(period, {'state': 'draft'})
 
85
                except error.RPCError as e:
 
86
                    print(e.oerp_traceback)
 
87
                    print(e.message)
 
88
                except Exception, e:
 
89
                    raise Exception('error', str(e))
 
90
            """
 
91
            # Write the fact that data have been loaded
 
92
            database.get(self.test_module_obj_name).create({
 
93
                'name': keyword,
 
94
                'active': True
 
95
            })
 
96
            print (database.colored_name + ' [' + colors.BGreen + \
 
97
                'OK'.center(4) + colors.Color_Off + '] %s: Data loaded' % (
 
98
                    keyword, ))
 
99
        else:
 
100
            print (database.colored_name + ' [' + colors.BYellow + \
 
101
                'WARN'.center(4) + colors.Color_Off \
 
102
                + '] %s: Data already exists' % (keyword, ))
 
103
        return super(FinanceTest, self)._hook_db_process(name, database)
 
104
 
 
105
    def get_journal_ids(self, db, journal_type, is_of_instance=False,
 
106
        is_analytic=False):
 
107
        model = 'account.analytic.journal' if is_analytic \
 
108
            else 'account.journal'
 
109
        domain = [('type', '=', journal_type)]
 
110
        if is_of_instance:
 
111
            domain.append(('is_current_instance', '=', True))
 
112
 
 
113
        ids = db.get(model).search(domain)
 
114
        self.assert_(
 
115
            ids != False,
 
116
            "no %s journal(s) found" % (journal_type, )
 
117
        )
 
118
        return ids
 
119
 
 
120
    def get_account_from_code(self, db, code, is_analytic=False):
 
121
        model = 'account.analytic.account' if is_analytic \
 
122
            else 'account.account'
 
123
        ids = db.get(model).search([('code', '=', code)])
 
124
        return ids and ids[0] or False
 
125
 
 
126
    def get_account_code(self, db, id, is_analytic=False):
 
127
        model = 'account.analytic.account' if is_analytic \
 
128
            else 'account.account'
 
129
        return db.get(model).browse(id).code
 
130
 
 
131
    def get_random_amount(self, is_expense=False):
 
132
        amount = float(randrange(100, 10000))
 
133
        if is_expense:
 
134
            amount *= -1.
 
135
        return amount
 
136
 
 
137
    def journal_create(self, db, name, code, journal_type,
 
138
        analytic_journal_id=False, account_code=False, currency_name=False,
 
139
        bank_journal_id=False):
 
140
        """
 
141
        create journal
 
142
        if of type bank/cash/cheque: account_code and currency_name needed.
 
143
 
 
144
        :type db: oerplib object
 
145
        :param name: journal name
 
146
        :param code: journal code
 
147
        :param journal_type: journal type. available types::
 
148
         * accrual
 
149
         * bank
 
150
         * cash
 
151
         * cheque
 
152
         * correction
 
153
         * cur_adj
 
154
         * depreciation
 
155
         * general
 
156
         * hq
 
157
         * hr
 
158
         * inkind
 
159
         * intermission
 
160
         * migration
 
161
         * extra
 
162
         * situation
 
163
         * purchase
 
164
         * purchase_refund
 
165
         * revaluation
 
166
         * sale
 
167
         * sale_refund
 
168
         * stock
 
169
        :param analytic_journal_id: (optional) linked analytic journal id
 
170
            default attempt to search an analytic journal that have the same
 
171
            journal_type
 
172
        :param account_code: (mandatory for bank/cash/cheque) account
 
173
            code that will be used in debit/credit for the journal
 
174
        :param currency_name: (mandatory for bank/cash/cheque) journal
 
175
            currency name
 
176
        :param bank_journal_id: (mandatory for cheque) linked bank journal
 
177
        :return: journal id
 
178
        :rtype: int
 
179
        """
 
180
        # checks
 
181
        self.assert_(
 
182
            name and code and journal_type,
 
183
            "name or/and code or/and journal type missing"
 
184
        )
 
185
        # bank/cash/cheque
 
186
        if journal_type in ('bank', 'cheque', 'cash', ):
 
187
            self.assert_(
 
188
                account_code and currency_name,
 
189
                "bank/cash/cheque: account code and currency required." \
 
190
                    " account: '%s', currency: '%s'" % (
 
191
                        account_code or '', currency_name or '', )
 
192
            )
 
193
        # cheque journal
 
194
        if journal_type == 'cheque':
 
195
            self.assert_(
 
196
                bank_journal_id != False,
 
197
                "bank journal mandatory for cheque journal"
 
198
            )
 
199
 
 
200
        aaj_obj = db.get('account.analytic.journal')
 
201
        aa_obj = db.get('account.account')
 
202
        ccy_obj =  db.get('res.currency')
 
203
        aj_obj = db.get('account.journal')
 
204
 
 
205
        # analytic journal
 
206
        if not analytic_journal_id:
 
207
            analytic_journal_type = journal_type
 
208
            if journal_type in ('bank', 'cheque', ):
 
209
                analytic_journal_type = 'cash'
 
210
            aaj_ids = aaj_obj.search([('type', '=', analytic_journal_type)])
 
211
            self.assert_(
 
212
                aaj_ids != False,
 
213
                "no analytic journal found with this type: %s" % (
 
214
                    journal_type, )
 
215
            )
 
216
            analytic_journal_id = aaj_ids[0]
 
217
 
 
218
        # prepare values
 
219
        vals = {
 
220
            'name': name,
 
221
            'code': code,
 
222
            'type': journal_type,
 
223
            'analytic_journal_id': analytic_journal_id,
 
224
        }
 
225
        if account_code:
 
226
            a_ids = aa_obj.search([('code', '=', account_code)])
 
227
            self.assert_(
 
228
                a_ids != False,
 
229
                "no account found for the given code: %s" % (account_code, )
 
230
            )
 
231
            account_id = a_ids[0]
 
232
            vals.update({
 
233
                'default_debit_account_id': account_id,
 
234
                'default_credit_account_id': account_id,
 
235
            })
 
236
        if currency_name:
 
237
            c_ids = ccy_obj.search([('name', '=', currency_name)])
 
238
            self.assert_(
 
239
                c_ids != False,
 
240
                "currency not found: %s" % (currency_name, )
 
241
            )
 
242
            vals.update({'currency': c_ids[0]})
 
243
        if bank_journal_id:
 
244
            vals['bank_journal_id'] = bank_journal_id
 
245
        # create the journal
 
246
        return aj_obj.create(vals)
 
247
 
 
248
    def register_create(self, db, name, code, register_type, account_code,
 
249
        currency_name, bank_journal_id=False):
 
250
        """
 
251
        create a register in the current period.
 
252
        (use journal_create)
 
253
 
 
254
        :type db: oerplib object
 
255
        :param name: register name (used as journal's name)
 
256
        :param code: register's code (used as journal's code)
 
257
        :param register_type: register available types::
 
258
         * bank
 
259
         * cash
 
260
         * cheque
 
261
        :param account_code: account code used for debit/credit account
 
262
            at journal creation. (so used by the register)
 
263
        :param currency_name: name of currency to use(must exists)
 
264
        :param bank_journal_id: (mandatory for cheque) linked bank journal
 
265
        :return: register_id and journal_id
 
266
        :rtype: tuple (registed id, journal id)
 
267
        """
 
268
        aaj_obj = db.get('account.analytic.journal')
 
269
        abs_obj = db.get('account.bank.statement')
 
270
 
 
271
        analytic_journal_code_map = {
 
272
            'cash': 'CAS',
 
273
            'bank': 'BNK',
 
274
            'cheque': 'CHK',
 
275
        }
 
276
        aaj_code = analytic_journal_code_map[register_type]
 
277
        aaj_ids = aaj_obj.search([('code', '=', aaj_code)])
 
278
        self.assert_(
 
279
            aaj_ids != False,
 
280
            "analytic journal code %s not found" % (aaj_code, )
 
281
        )
 
282
 
 
283
        j_id = self.journal_create(db, name, code, register_type,
 
284
            account_code=account_code, currency_name=currency_name,
 
285
            bank_journal_id=bank_journal_id,
 
286
            analytic_journal_id=aaj_ids[0])
 
287
        # search the register (should be created by journal creation)
 
288
        reg_ids = abs_obj.search([('journal_id', '=', j_id)])
 
289
        return reg_ids and reg_ids[0] or False, j_id
 
290
 
 
291
    def register_create_line(self, db, regbr_or_id, account_code_or_id, amount,
 
292
            ad_breakdown_data=False,
 
293
            date=False, document_date=False,
 
294
            third_partner_id=False, third_employee_id=False,
 
295
            third_journal_id=False,
 
296
            do_temp_post=False, do_hard_post=False,
 
297
            tag="UNIT_TEST"):
 
298
        """
 
299
        create a register line in the given register
 
300
 
 
301
        :type db: oerplib object
 
302
        :param regbr_or_id: parent register browsed object or id
 
303
        :type regbr_or_id: object/int/long
 
304
        :param account_code_or_id: account code to search or account_id
 
305
        :type code_or_id: str/int/long
 
306
        :param amount: > 0 amount IN, < 0 amount OUT
 
307
        :param ad_breakdown_data: (optional) see analytic_distribution_create
 
308
            breakdown_data param help
 
309
        :param datetime date: posting date
 
310
        :param datetime document_date: document date
 
311
        :param third_partner_id: partner id
 
312
        :param third_employee_id: emp id (operational advance)
 
313
        :param third_journal_id: journal id (internal transfer)
 
314
        :return: register line id and AD id and target ji for correction
 
315
            (if hard host or temp post)
 
316
        :rtype: tuple (register_line_id, ad_id/False, ji_id)
 
317
        """
 
318
        # register
 
319
        self.assert_(regbr_or_id != False, "register missing")
 
320
        self.assert_(
 
321
            not (do_hard_post and do_temp_post) ,
 
322
            "you can not temp post and hard post at the same time"
 
323
        )
 
324
 
 
325
        abs_obj = db.get('account.bank.statement')
 
326
        absl_obj = db.get('account.bank.statement.line')
 
327
        aa_obj = db.get('account.account')
 
328
 
 
329
        if isinstance(regbr_or_id, (int, long)):
 
330
            register_br = abs_obj.browse(regbr_or_id)
 
331
        else:
 
332
            register_br = regbr_or_id
 
333
 
 
334
        # general account
 
335
        if isinstance(account_code_or_id, (str, unicode)):
 
336
            # check account code
 
337
            code_ids = aa_obj.search([
 
338
                '|',
 
339
                ('name', 'ilike', account_code_or_id),
 
340
                ('code', 'ilike', account_code_or_id)]
 
341
            )
 
342
            self.assert_(
 
343
                code_ids != False,
 
344
                "error searching for this account code: %s" % (
 
345
                    account_code_or_id, )
 
346
            )
 
347
            self.assert_(
 
348
                len(code_ids) == 1,
 
349
                "error more than 1 account with code: %s" % (
 
350
                    account_code_or_id, )
 
351
            )
 
352
            account_id = code_ids[0]
 
353
        else:
 
354
            account_id = account_code_or_id
 
355
        account_br = aa_obj.browse(account_id)
 
356
 
 
357
        # check dates
 
358
        if not date:
 
359
            date_start = register_br.period_id.date_start or False
 
360
            date_stop = register_br.period_id.date_stop or False
 
361
            self.assert_(
 
362
                date_start and date_stop,
 
363
                "no date found for the period %s" % (
 
364
                    register_br.period_id.name, )
 
365
            )
 
366
            random_date = self.random_date(
 
367
                datetime.strptime(str(date_start), '%Y-%m-%d'),
 
368
                datetime.strptime(str(date_stop), '%Y-%m-%d')
 
369
            )
 
370
            date = datetime.strftime(random_date, '%Y-%m-%d')
 
371
        if not document_date:
 
372
            document_date = date
 
373
 
 
374
        # vals
 
375
        name = FINANCE_TEST_MASK['register_line'] % (tag, self.get_uuid(), )
 
376
        vals = {
 
377
            'statement_id': register_br.id,
 
378
            'account_id': account_id,
 
379
            'document_date': document_date,
 
380
            'date': date,
 
381
            'amount': amount,
 
382
            'name': name,
 
383
        }
 
384
        if third_partner_id:
 
385
            vals['partner_id'] = third_partner_id
 
386
        if third_employee_id:
 
387
            vals['employee_id'] = third_employee_id
 
388
        if third_journal_id:
 
389
            vals['transfer_journal_id'] = third_journal_id
 
390
        if register_br.journal_id.type == 'cheque':
 
391
            vals['cheque_number'] = FINANCE_TEST_MASK['cheque_number'] % (
 
392
                self.proxy.get_uuid(), )
 
393
 
 
394
        # create
 
395
        regl_id = absl_obj.create(vals)
 
396
 
 
397
        # optional AD
 
398
        if ad_breakdown_data and account_br.is_analytic_addicted:
 
399
            distrib_id = self.analytic_distribution_create(db,
 
400
                breakdown_data=ad_breakdown_data)
 
401
            absl_obj.write([regl_id],
 
402
                {'analytic_distribution_id': distrib_id}, {'fake': 1, })
 
403
        else:
 
404
            distrib_id = False
 
405
 
 
406
        target_ji_id = False
 
407
        if do_temp_post:
 
408
            self.register_line_temp_post(db, regl_id)
 
409
        if do_hard_post:
 
410
            self.register_line_hard_post(db, regl_id)
 
411
        if do_temp_post or do_hard_post:
 
412
            target_ji_id = self.register_line_get_target_ji(db, regl_id,
 
413
                account_br.code, account_br.is_analytic_addicted)
 
414
 
 
415
        return (regl_id, distrib_id, target_ji_id, )
 
416
 
 
417
    def register_line_temp_post(self, db, regl_id):
 
418
        if isinstance(regl_id, (int, long, )):
 
419
            regl_id = [regl_id]
 
420
        db.get('account.bank.statement.line').button_temp_posting(regl_id,
 
421
            {'fake': 1, })
 
422
 
 
423
    def register_line_hard_post(self, db, regl_id):
 
424
        if isinstance(regl_id, (int, long, )):
 
425
            regl_id = [regl_id]
 
426
        db.get('account.bank.statement.line').button_hard_posting(regl_id,
 
427
            {'fake': 1, })
 
428
 
 
429
    def register_line_get_target_ji(self, db, regl_id, account_code,
 
430
        is_expense_account):
 
431
        """
 
432
        return regline related target ji for corrections
 
433
        (the reg line need to be temp or hard posted: journal items here)
 
434
        """
 
435
        absl_obj = db.get('account.bank.statement.line')
 
436
        aml_obj = db.get('account.move.line')
 
437
 
 
438
        domain = [
 
439
            ('name', '=', absl_obj.browse(regl_id).name),
 
440
            ('account_id.code', '=', account_code),
 
441
        ]
 
442
        if is_expense_account:
 
443
            domain.append(('account_id.is_analytic_addicted', '=', 'True'))
 
444
 
 
445
        aml_ids = aml_obj.search(domain)
 
446
        return aml_ids and aml_ids[0] or False
 
447
 
 
448
    def register_close(self, db, ids):
 
449
        if isinstance(ids, (int, long, )):
 
450
            ids = [ids]
 
451
        abs_obj = db.get('account.bank.statement')
 
452
        wcb_obj = db.get('wizard.confirm.bank')
 
453
 
 
454
        for abs_br in abs_obj.browse(ids):
 
455
            abs_ids = [abs_br.id]
 
456
            # set fake balance amount...
 
457
            abs_obj.write(abs_ids, {
 
458
                'balance_end_real': abs_br.balance_end,
 
459
            })
 
460
            abs_obj.button_confirm_closing_bank_balance(abs_ids)
 
461
 
 
462
            # ...then close
 
463
            abs_obj.write(abs_ids, {
 
464
                'state': 'confirm',
 
465
                'closing_date': self.get_orm_date_now(),
 
466
            })
 
467
 
 
468
    def register_reopen(self, db, ids):
 
469
        if isinstance(ids, (int, long, )):
 
470
            ids = [ids]
 
471
        db.get('account.bank.statement').write(ids, {
 
472
                'state': 'open',
 
473
                'closing_date': False,
 
474
            })
 
475
 
 
476
    def analytic_distribution_set_fp_account_dest(self, db,
 
477
        fp_name, account_code, dest_code):
 
478
        """
 
479
        add account/dest tuple to FP
 
480
        """
 
481
        if fp_name and fp_name == 'PF':
 
482
            return  # nothing to do
 
483
        self.assert_(
 
484
            fp_name and account_code and dest_code,
 
485
            "you must give fp name and account/dest codes"
 
486
        )
 
487
 
 
488
        fp_id = self.get_account_from_code(db, fp_name, is_analytic=True)
 
489
        self.assert_(
 
490
            fp_id != False,
 
491
            "FP '%s' not found" % (fp_name, )
 
492
        )
 
493
        account_id = self.get_account_from_code(db, account_code,
 
494
            is_analytic=False)
 
495
        self.assert_(
 
496
            account_id != False,
 
497
            "account '%s' not found" % (account_code, )
 
498
        )
 
499
        dest_id = self.get_account_from_code(db, dest_code, is_analytic=True)
 
500
        self.assert_(
 
501
            dest_id != False,
 
502
            "dest '%s' not found" % (dest_code, )
 
503
        )
 
504
 
 
505
        aaa_obj = db.get('account.analytic.account')
 
506
        fp_br = aaa_obj.browse(fp_id)
 
507
 
 
508
        # search account/dest tuple in FP ?
 
509
        for tda in fp_br.tuple_destination_account_ids:
 
510
            if tda.account_id.id == account_id and \
 
511
                tda.destination_id.id == dest_id:
 
512
                    return  # account/dest tuple already in FP
 
513
 
 
514
        tuple_dest_obj = db.get('account.destination.link')
 
515
        tuple_id = tuple_dest_obj.search([('account_id', '=', account_id) , ('destination_id', '=', dest_id)])
 
516
        if not tuple_id:
 
517
            tuple_id = tuple_dest_obj.create({'account_id': account_id, 'destination_id': dest_id})
 
518
        else:
 
519
            tuple_id = tuple_id[0]
 
520
        aaa_obj.write(fp_id, {'tuple_destination_account_ids': [(4, tuple_id)]})
 
521
 
 
522
    def analytic_distribution_create(self, db,
 
523
        breakdown_data=[(100., 'OPS', False, False)]):
 
524
        """
 
525
        create analytic distribution
 
526
 
 
527
        :type db: oerplib object
 
528
        :param account_id: related account._id (if not set search for a random
 
529
            destination)
 
530
        :type account_id: int
 
531
        :param breakdown_data: [(purcent, dest, cc, fp)]
 
532
            - for breakdown of lines list: percent, dest code, cc code, fp code
 
533
            - False cc for default company top cost center
 
534
            - False FP for PF
 
535
        :return: ad id
 
536
        """
 
537
        comp_obj = db.get('res.company')
 
538
        aaa_obj = db.get('account.analytic.account')
 
539
        ad_obj = db.get('analytic.distribution')
 
540
 
 
541
        company = comp_obj.browse(comp_obj.search([])[0])
 
542
        funding_pool_pf_id = self.get_record_id_from_xmlid(db,
 
543
            'analytic_distribution', 'analytic_account_msf_private_funds', )
 
544
        self.assert_(funding_pool_pf_id != False, 'PF funding pool not found')
 
545
 
 
546
        # DEST/CC/FP
 
547
        if not breakdown_data:
 
548
            breakdown_data  = [(100., 'OPS', 'HT101', 'PF', ), ]
 
549
 
 
550
        # create ad
 
551
        name = FINANCE_TEST_MASK['ad'] % (self.get_uuid(), )
 
552
        distrib_id = ad_obj.create({'name': name})
 
553
 
 
554
        for purcent, dest, cc, fp in breakdown_data:
 
555
            dest_id = aaa_obj.search([
 
556
                ('category', '=', 'DEST'),
 
557
                ('type', '=', 'normal'),
 
558
                ('code', '=', dest),
 
559
            ])
 
560
            self.assert_(
 
561
                dest_id != False,
 
562
                'no destination found %s' % (dest, )
 
563
            )
 
564
            dest_id = dest_id[0]
 
565
 
 
566
            if cc:
 
567
                cost_center_id = aaa_obj.search([
 
568
                    ('category', '=', 'OC'),
 
569
                    ('type', '=', 'normal'),
 
570
                    ('code', '=', cc),
 
571
                ])
 
572
                self.assert_(
 
573
                    cost_center_id != False,
 
574
                    'no cost center found %s' % (cc, )
 
575
                )
 
576
                cost_center_id = cost_center_id[0]
 
577
            else:
 
578
                cost_center_id = company.instance_id.top_cost_center_id \
 
579
                    and company.instance_id.top_cost_center_id.id or False
 
580
                self.assert_(
 
581
                    cost_center_id != False,
 
582
                    'no top cost center found for instance %s' % (
 
583
                        company.name or '', )
 
584
                )
 
585
            if fp:
 
586
                funding_pool_id = aaa_obj.search([
 
587
                    ('category', '=', 'FUNDING'),
 
588
                    ('type', '=', 'normal'),
 
589
                    ('code', '=', fp),
 
590
                ])
 
591
                self.assert_(
 
592
                    funding_pool_id != False,
 
593
                    'no funding pool found %s' % (fp, )
 
594
                )
 
595
                funding_pool_id = funding_pool_id[0]
 
596
            else:
 
597
                funding_pool_id = funding_pool_pf_id  # default PF
 
598
 
 
599
            # relating ad line dimension distribution lines (1 cc, 1fp)
 
600
            data = [
 
601
                ('cost.center.distribution.line', cost_center_id, False),
 
602
                ('funding.pool.distribution.line', funding_pool_id,
 
603
                    cost_center_id),
 
604
            ]
 
605
            for ad_dim_analytic_obj, val, fpdim_cc_id in data:
 
606
                vals = {
 
607
                    'distribution_id': distrib_id,
 
608
                    'name': name,
 
609
                    'analytic_id': val,
 
610
                    'cost_center_id': fpdim_cc_id,
 
611
                    'percentage': purcent,
 
612
                    'currency_id': company.currency_id.id,
 
613
                    'destination_id': dest_id,
 
614
                }
 
615
                db.get(ad_dim_analytic_obj).create(vals)
 
616
        return distrib_id
 
617
 
 
618
    def simulation_correction_wizard(self,
 
619
        db,
 
620
        ji_to_correct_id,
 
621
        cor_date=False,
 
622
        new_account_code=False,
 
623
        new_ad_breakdown_data=False,
 
624
        ad_replace_data=False):
 
625
        """
 
626
        :param new_account_code: new account code for a G/L correction
 
627
        :param new_ad_breakdown_data: new ad lines to replace all ones (delete)
 
628
        :param ad_replace_data: { percent_key: {'dest/cc/fp/per': new_val, }
 
629
            ad_replace_data: percent_key to identify the line where to replace
 
630
            examples:
 
631
                ad_replace_data={ 100.: {'dest': new_dest, } },
 
632
                ad_replace_data={
 
633
                            60.: {'per': 70., },
 
634
                            40.: {'per': 30., },
 
635
                        },
 
636
        choose between delete and recreate AD with new_ad_breakdown_data
 
637
        or to replace dest/cc/fp/percentage values with ad_replace_data
 
638
        """
 
639
        wizard_cor_obj = db.get('wizard.journal.items.corrections')
 
640
        wizard_corl_obj = db.get('wizard.journal.items.corrections.lines')
 
641
        wizard_ad_obj = db.get('analytic.distribution.wizard')
 
642
        wizard_adl_obj = db.get('analytic.distribution.wizard.lines')
 
643
        wizard_adfpl_obj = db.get('analytic.distribution.wizard.fp.lines')
 
644
 
 
645
        aa_obj = db.get('account.account')
 
646
        aml_obj = db.get('account.move.line')
 
647
        aaa_obj = db.get('account.analytic.account')
 
648
 
 
649
        # check valid correction
 
650
        self.assert_(
 
651
            new_account_code or new_ad_breakdown_data or ad_replace_data,
 
652
            'no correction changes: required G/L or AD or both'
 
653
        )
 
654
        # check valid correction
 
655
        self.assert_(
 
656
            not (new_ad_breakdown_data and ad_replace_data),
 
657
            'you can not both redefine full AD and replace attributes'
 
658
        )
 
659
 
 
660
        # get new account id for a G/L correction
 
661
        new_account_id = False
 
662
        if new_account_code:
 
663
            account_ids = aa_obj.search([('code', '=', new_account_code)])
 
664
            self.assert_(
 
665
                account_ids != False,
 
666
                'account %s for a G/L correction not found' % (
 
667
                    new_account_code, )
 
668
            )
 
669
            new_account_id = account_ids[0]
 
670
 
 
671
        # get ji and checks
 
672
        ji_br = aml_obj.browse(ji_to_correct_id)
 
673
        self.assert_(
 
674
            ji_br != False,
 
675
            'journal item not found'
 
676
        )
 
677
        if new_account_code:
 
678
            self.assert_(
 
679
                ji_br.account_id.code != new_account_code,
 
680
                'you can not do a G/L correction with same account code'
 
681
            )
 
682
        if CHECK_IS_CORRIGIBLE:
 
683
            self.assert_(
 
684
                ji_br.is_corrigible,
 
685
                "JI item '%s' is not corrigible (and should be)" % (
 
686
                    ji_br.name or '', )
 
687
            )
 
688
        old_account_id = ji_br.account_id and ji_br.account_id.id or False
 
689
        ji_amount = ji_br.debit_currency and ji_br.debit_currency * -1 or \
 
690
            ji_br.credit_currency
 
691
 
 
692
        if not cor_date:
 
693
            cor_date = ji_br.date
 
694
        if not isinstance(cor_date, str):
 
695
            cor_date = self.date2orm(cor_date)
 
696
 
 
697
        # set wizard header (will generate in create the correction lines)
 
698
        vals = {
 
699
            'date': cor_date,
 
700
            'move_line_id': ji_to_correct_id,
 
701
            'state': 'draft',
 
702
            'from_donation': False,
 
703
        }
 
704
        wiz_br = wizard_cor_obj.browse(wizard_cor_obj.create(vals))
 
705
 
 
706
        # set the generated correction line
 
707
        wiz_cor_line = wiz_br.to_be_corrected_ids.next()
 
708
        self.assert_(
 
709
            wiz_cor_line != False,
 
710
            'error generating a correction line'
 
711
        )
 
712
 
 
713
        vals = {}
 
714
        if new_account_id:  # G/L correction
 
715
            wizard_corl_obj.write([wiz_cor_line.id],
 
716
                {'account_id' : new_account_id})
 
717
 
 
718
        if new_ad_breakdown_data or ad_replace_data:
 
719
            # AD correction
 
720
            action = wizard_corl_obj.button_analytic_distribution(
 
721
                [wiz_cor_line.id], {'fake': 1})
 
722
            # read the AD wizard
 
723
            wizard_ad_id = action['res_id'][0]
 
724
            wizard_ad_br = wizard_ad_obj.browse(wizard_ad_id)
 
725
            self.assert_(
 
726
                wizard_ad_br != False,
 
727
                "error getting AD wizard record from action: %s" % (
 
728
                    str(action), )
 
729
            )
 
730
 
 
731
            total_amount = 0.
 
732
            if ad_replace_data:
 
733
                fields = [
 
734
                    'percentage',
 
735
                    'cost_center_id',
 
736
                    'destination_id',
 
737
                    'analytic_id',
 
738
                ]
 
739
 
 
740
                if wizard_ad_br.line_ids:
 
741
                    # CC lines: 'cost_center_id' False, 'destination_id' dest,
 
742
                    # 'analytic_id' <=> CC
 
743
                    # as rpc browse failed here: dirty workaround with read
 
744
                    line_ids = [ l.id for l in wizard_ad_br.line_ids ]
 
745
                    for adwl_r in wizard_adl_obj.read(line_ids, fields):
 
746
                        percent = adwl_r['percentage']
 
747
 
 
748
                        if percent in ad_replace_data:
 
749
                            # line is target of replace
 
750
                            ad_line_vals = {}
 
751
                            replace_vals = ad_replace_data[percent]
 
752
 
 
753
                            # destination replace
 
754
                            if 'dest' in replace_vals:
 
755
                                ad_line_vals['destination_id'] = \
 
756
                                    self.get_account_from_code(db,
 
757
                                        replace_vals['dest'],
 
758
                                        is_analytic=True)
 
759
 
 
760
                            # cost center replace
 
761
                            if 'cc' in replace_vals:
 
762
                                ad_line_vals['analytic_id'] = \
 
763
                                    self.get_account_from_code(db,
 
764
                                        replace_vals['cc'],
 
765
                                        is_analytic=True)
 
766
 
 
767
                            # percentage replace
 
768
                            if 'per' in replace_vals:
 
769
                                percent = replace_vals['per']
 
770
 
 
771
                            if ad_line_vals or 'per' in replace_vals:
 
772
                                # (always needed percentage in vals)
 
773
                                ad_line_vals['percentage'] = percent
 
774
                                ad_line_vals['is_percentage_amount_touched'] = True
 
775
                                wizard_adl_obj.write([adwl_r['id']],
 
776
                                    ad_line_vals)
 
777
 
 
778
                    # supply update amount from cc lines
 
779
                    # NOTE: for finance (state != 'cc') the amount is to be
 
780
                    # computed from amount of fp lines
 
781
                    if line_ids and wizard_ad_br.state != 'cc':
 
782
                        for adwl_r \
 
783
                            in wizard_adl_obj.read(line_ids, ['amount']):
 
784
                            total_amount += adwl_r['amount']
 
785
 
 
786
                if wizard_ad_br.fp_line_ids:
 
787
                    # FP LINES: 'cost_center_id', 'destination_id',
 
788
                    # 'analytic_id' <=> FP
 
789
                    # as rpc browse failed here: dirty workaround with read
 
790
                    fp_line_ids = [ l.id for l in wizard_ad_br.fp_line_ids ]
 
791
                    for adwl_r in wizard_adfpl_obj.read(fp_line_ids, fields):
 
792
                        percent = adwl_r['percentage']
 
793
 
 
794
                        if percent in ad_replace_data:
 
795
                            # line is target of replace
 
796
                            ad_line_vals = {}
 
797
                            replace_vals = ad_replace_data[percent]
 
798
 
 
799
                            # destination replace
 
800
                            if 'dest' in replace_vals:
 
801
                                ad_line_vals['destination_id'] = \
 
802
                                    self.get_account_from_code(db,
 
803
                                        replace_vals['dest'],
 
804
                                        is_analytic=True)
 
805
 
 
806
                            # cost center replace
 
807
                            if 'cc' in replace_vals:
 
808
                                ad_line_vals['cost_center_id'] = \
 
809
                                    self.get_account_from_code(db,
 
810
                                        replace_vals['cc'],
 
811
                                        is_analytic=True)
 
812
 
 
813
                            # fp replace
 
814
                            if 'fp' in replace_vals:
 
815
                                ad_line_vals['analytic_id'] = \
 
816
                                    self.get_account_from_code(db,
 
817
                                        replace_vals['fp'],
 
818
                                        is_analytic=True)
 
819
 
 
820
                            # percentage replace
 
821
                            if 'per' in replace_vals:
 
822
                                percent = replace_vals['per']
 
823
 
 
824
                            if ad_line_vals or 'per' in replace_vals:
 
825
                                # (always needed percentage in vals)
 
826
                                ad_line_vals['percentage'] = percent
 
827
                                ad_line_vals['is_percentage_amount_touched'] = True
 
828
                                wizard_adfpl_obj.write([adwl_r['id']],
 
829
                                    ad_line_vals)
 
830
 
 
831
                    # finance update amount from fp lines
 
832
                    # NOTE: for supply (state == 'cc') the amount is to be
 
833
                    # computed from amount of cc lines
 
834
                    if fp_line_ids and wizard_ad_br.state != 'cc':
 
835
                        for adwl_r in wizard_adfpl_obj.read(fp_line_ids,
 
836
                            ['amount']):
 
837
                            total_amount += adwl_r['amount']
 
838
            # end replace data
 
839
            elif new_ad_breakdown_data:
 
840
                # replace full AD
 
841
 
 
842
                # delete previous AD
 
843
                """del_vals = {}
 
844
                if wizard_ad_br.line_ids:
 
845
                    # get del vals
 
846
                    del_vals['line_ids'] = [ (2, l.id, ) \
 
847
                        for l in wizard_ad_br.line_ids ]
 
848
                if wizard_ad_br.fp_line_ids:
 
849
                    # get del vals
 
850
                    del_vals['fp_line_ids'] = [ (2, l.id, ) \
 
851
                        for l in wizard_ad_br.fp_line_ids ]
 
852
                if del_vals:
 
853
                    wizard_ad_obj.write([wizard_ad_id], del_vals)"""
 
854
                todel_ids = wizard_adl_obj.search(
 
855
                    [('wizard_id', '=', wizard_ad_br.id)])
 
856
                if todel_ids:
 
857
                    wizard_adl_obj.unlink(todel_ids)
 
858
                todel_ids = wizard_adfpl_obj.search(
 
859
                    [('wizard_id', '=', wizard_ad_br.id)])
 
860
                if todel_ids:
 
861
                    wizard_adfpl_obj.unlink(todel_ids)
 
862
 
 
863
                # set the new AD
 
864
                ccy_id = self.get_company(db).currency_id.id
 
865
                ad_line_vals = []
 
866
                ad_fp_line_vals = []
 
867
                for percent, dest, cc, fp in new_ad_breakdown_data:
 
868
                    dest_id = self.get_account_from_code(db, dest,
 
869
                        is_analytic=True)
 
870
                    cc_id = self.get_account_from_code(db, cc,
 
871
                        is_analytic=True)
 
872
                    fp_id = self.get_account_from_code(db, fp,
 
873
                        is_analytic=True)
 
874
 
 
875
                    total_amount += (ji_amount * percent) / 100.
 
876
 
 
877
                    # set cc line
 
878
                    # 'destination_id' dest, # 'analytic_id' <=> CC
 
879
                    # not set amount here as will be auto computed from percent
 
880
                    ad_line_vals.append({
 
881
                        'wizard_id': wizard_ad_br.id,
 
882
                        'analytic_id': cc_id,
 
883
                        'percentage': percent,
 
884
                        'currency_id': ccy_id,
 
885
                        'destination_id': dest_id,
 
886
                        'is_percentage_amount_touched': True,
 
887
                    })
 
888
 
 
889
                    # set fp line
 
890
                    # FP LINES: 'cost_center_id', 'destination_id',
 
891
                    # 'analytic_id' <=> FP
 
892
                    # not set amount here as will be auto computed from percent
 
893
                    ad_fp_line_vals.append({
 
894
                        'wizard_id': wizard_ad_br.id,
 
895
                        'analytic_id': fp_id,
 
896
                        'percentage': percent,
 
897
                        'currency_id': ccy_id,
 
898
                        'destination_id': dest_id,
 
899
                        'cost_center_id': cc_id,
 
900
                        'is_percentage_amount_touched': True,
 
901
                    })
 
902
 
 
903
                if ad_line_vals and ad_fp_line_vals:
 
904
                    for new_vals in ad_line_vals:
 
905
                        wizard_adl_obj.create(new_vals)
 
906
                    for new_vals in ad_fp_line_vals:
 
907
                        wizard_adfpl_obj.create(new_vals)
 
908
            # end new ad breakdown
 
909
 
 
910
            # will validate cor wizard too
 
911
            if total_amount and (ad_replace_data or new_ad_breakdown_data):
 
912
                # set wizard header vals to update
 
913
                ad_wiz_vals = {
 
914
                    'amount': total_amount,
 
915
                    'state': 'correction',
 
916
                }
 
917
                if new_account_id:
 
918
                    # needed for AD wizard to process directly from it a G/L
 
919
                    # account change
 
920
                    ad_wiz_vals.update({
 
921
                        'old_account_id': old_account_id,
 
922
                        'account_id': new_account_id,
 
923
                    })
 
924
                wizard_ad_obj.write([wizard_ad_id], ad_wiz_vals)
 
925
 
 
926
                # confirm the wizard with adoc context values to process a
 
927
                # correction
 
928
                context = {
 
929
                    'from': 'wizard.journal.items.corrections',
 
930
                    'from_list_grid': 1,
 
931
                    'wiz_id': wiz_br.id,
 
932
                }
 
933
                wizard_ad_obj.button_confirm([wizard_ad_id], context)
 
934
                return  # G/L account change already processed line above
 
935
 
 
936
        if new_account_id:
 
937
            # G/L correction without AD correction: confirm wizard
 
938
            # (with an AD correction, cor is confirmed by AD wizard)
 
939
            # action_confirm(ids, context=None, distrib_id=False)
 
940
            wizard_cor_obj.action_confirm([wiz_br.id], {}, False)
 
941
 
 
942
    def check_sequence_number(self, model_obj, entries_ids, is_analytic=False,
 
943
        db_name=''):
 
944
        """
 
945
        check sequence number consistency
 
946
        :param model_obj: oerplib model object
 
947
        :param entries_ids: entries ids
 
948
        :type entries_ids:  int/long/list
 
949
        :param analytic: is analytic entry ?
 
950
        :type analytic: bool
 
951
        :param db_name: database name
 
952
        :type db_name: str
 
953
        """
 
954
        def get_entry_sequence(entry_br):
 
955
            return is_analytic and entry_br.entry_sequence \
 
956
                    or entry_br.move_id.name
 
957
 
 
958
        if isinstance(entries_ids, (int, long, )):
 
959
            entries_ids = [ entries_ids ]
 
960
 
 
961
        entries_seq_number = [ get_entry_sequence(e_br) for e_br \
 
962
            in model_obj.browse(entries_ids) ]
 
963
 
 
964
        assert_pattern_header = db_name \
 
965
            and "sequence number mismatch :: %s: %s" \
 
966
            or "sequence number mismatch: %s"
 
967
        self.assert_(
 
968
            len(set(entries_seq_number)) == 1,
 
969
            assert_pattern_header % (db_name, ', '.join(entries_seq_number)),
 
970
        )
 
971
 
 
972
 
 
973
    def check_ji_correction(self, db, ji_id,
 
974
        account_code, new_account_code=False,
 
975
        expected_ad=False, expected_ad_rev=False, expected_ad_cor=False,
 
976
        expected_cor_rev_ajis_total_func_amount=False,
 
977
        cor_level=1, ji_origin_id=False,
 
978
        check_sequence_number=False):
 
979
        """
 
980
        ji_origin_id: 1st ji corrected for cor cascade
 
981
        cor_level: cor level for cor cascade
 
982
        """
 
983
        def get_rev_cor_amount_and_field(base_amount, is_rev):
 
984
            if is_rev:
 
985
                base_amount *= -1.
 
986
            amount_field = 'credit_currency' \
 
987
                    if base_amount > 0 else 'debit_currency'
 
988
            return (base_amount, amount_field, )
 
989
 
 
990
        aml_obj = db.get('account.move.line')
 
991
        aal_obj = db.get('account.analytic.line')
 
992
 
 
993
        od_journal_ids = self.get_journal_ids(db, 'correction',
 
994
            is_of_instance=False, is_analytic=False)
 
995
        aod_journal_ids = self.get_journal_ids(db, 'correction',
 
996
            is_of_instance=False, is_analytic=True)
 
997
 
 
998
        account_id = self.get_account_from_code(db, account_code,
 
999
            is_analytic=False)
 
1000
        new_account_id = False
 
1001
        if new_account_code:
 
1002
            new_account_id = self.get_account_from_code(db, new_account_code,
 
1003
                is_analytic=False)
 
1004
            self.assert_(
 
1005
                new_account_id != False,
 
1006
                "new account '%s' not found" % (new_account_id, )
 
1007
            )
 
1008
 
 
1009
        ji_br = aml_obj.browse(ji_id)
 
1010
        ji_seq_num = ji_br.move_id.name
 
1011
        ji_origin = ji_origin_id and aml_obj.browse(ji_origin_id).name or \
 
1012
            ji_br.name
 
1013
        ji_amount = ji_br.debit_currency and ji_br.debit_currency * -1 or \
 
1014
            ji_br.credit_currency
 
1015
 
 
1016
        if new_account_code and new_account_code != account_code:
 
1017
            # CHECK JI REV COR
 
1018
 
 
1019
            # check JI REV
 
1020
            cor_rev_amount, cor_rev_amount_field = get_rev_cor_amount_and_field(
 
1021
                ji_amount, True)
 
1022
            domain = [
 
1023
                ('journal_id', 'in', od_journal_ids),
 
1024
                ('reversal_line_id', '=', ji_id),
 
1025
                ('account_id', '=', account_id),
 
1026
                (cor_rev_amount_field, '=', abs(cor_rev_amount)),
 
1027
            ]
 
1028
            rev_ids = aml_obj.search(domain)
 
1029
            self.assert_(
 
1030
                rev_ids != False,
 
1031
                "no JI REV found for %s %s %f:: %s" % (account_code,
 
1032
                    ji_br.name, ji_amount, db.colored_name, )
 
1033
            )
 
1034
            if check_sequence_number:
 
1035
                # check sequence number
 
1036
                self.check_sequence_number(aml_obj, rev_ids, is_analytic=False,
 
1037
                    db_name=db.colored_name)
 
1038
 
 
1039
            # check JI COR
 
1040
            cor_rev_amount, cor_rev_amount_field = get_rev_cor_amount_and_field(
 
1041
                ji_amount, False)
 
1042
            domain = [
 
1043
                ('journal_id', 'in', od_journal_ids),
 
1044
                ('corrected_line_id', '=', ji_id),
 
1045
                ('account_id', '=', new_account_id),
 
1046
                (cor_rev_amount_field, '=', abs(cor_rev_amount)),
 
1047
            ]
 
1048
            cor_ids = aml_obj.search(domain)
 
1049
            self.assert_(
 
1050
                cor_ids != False,
 
1051
                "no JI COR found for %s %s %f:: %s" % (new_account_code,
 
1052
                    ji_br.name, ji_amount, db.colored_name, )
 
1053
            )
 
1054
            if check_sequence_number:
 
1055
                # check sequence number
 
1056
                self.check_sequence_number(aml_obj, cor_ids, is_analytic=False,
 
1057
                    db_name=db.colored_name)
 
1058
 
 
1059
        # ids of AJIs not rev/cor (not in correction journal)
 
1060
        base_aji_ids = aal_obj.search([
 
1061
            ('move_id', '=', ji_id),
 
1062
            ('journal_id', 'not in', aod_journal_ids),
 
1063
            ('general_account_id', '=', account_id),
 
1064
        ]) or []
 
1065
        # FIXME way of truely getting AJIs when cor of cor
 
1066
 
 
1067
        # FIXME remove and not cor_level > 1
 
1068
        if expected_ad and not cor_level > 1:
 
1069
            # check AJIs
 
1070
            self.assert_(
 
1071
                len(base_aji_ids) == len(expected_ad),
 
1072
                "expected AJIs count do not match for JI %s %s %f:: %s" % (
 
1073
                    new_account_code or account_code,
 
1074
                    ji_br.name, ji_amount, db.colored_name, )
 
1075
            )
 
1076
 
 
1077
            match_count = 0
 
1078
            for aal_br in aal_obj.browse(base_aji_ids):
 
1079
                for percent, dest, cc, fp in expected_ad:
 
1080
                    if aal_br.general_account_id.id == account_id and \
 
1081
                        aal_br.destination_id.code == dest and \
 
1082
                        aal_br.cost_center_id.code == cc and \
 
1083
                        aal_br.account_id.code == fp and \
 
1084
                        aal_br.amount_currency == ((ji_amount*percent) / 100.):
 
1085
                        # check with percent match
 
1086
                        match_count += 1
 
1087
                        break
 
1088
 
 
1089
            self.assert_(
 
1090
                len(base_aji_ids) == match_count,
 
1091
                "expected AJIs do not match for JI %s %f:: %s" % (
 
1092
                    ji_br.name, ji_amount, db.colored_name, )
 
1093
            )
 
1094
 
 
1095
        if expected_ad_rev:
 
1096
            # check REV AJIs
 
1097
            domain = [
 
1098
                ('journal_id', 'in', aod_journal_ids),
 
1099
                ('general_account_id', '=', account_id),
 
1100
            ]
 
1101
            if cor_level == 1:
 
1102
                domain.append(('reversal_origin', 'in', base_aji_ids))
 
1103
            else:
 
1104
                domain.append(('name', '=', "REV - %s" % (ji_br.name, )))
 
1105
            ids = aal_obj.search(domain)
 
1106
            self.assert_(
 
1107
                len(ids) == len(expected_ad_rev),
 
1108
                "expected REV AJIs count do not match for JI %s %s %f:: %s" % (
 
1109
                    new_account_code or account_code,
 
1110
                    ji_br.name, ji_amount, db.colored_name, )
 
1111
            )
 
1112
 
 
1113
            match_count = 0
 
1114
            total_func_amount = 0
 
1115
            for aal_br in aal_obj.browse(ids):
 
1116
                total_func_amount += aal_br.amount
 
1117
                for percent, dest, cc, fp in expected_ad_rev:
 
1118
                    if aal_br.general_account_id.id == account_id and \
 
1119
                        aal_br.destination_id.code == dest and \
 
1120
                        aal_br.cost_center_id.code == cc and \
 
1121
                        aal_br.account_id.code == fp and \
 
1122
                        aal_br.amount_currency == \
 
1123
                            (((ji_amount * percent) / 100.) * -1):
 
1124
                        match_count += 1
 
1125
                        break
 
1126
 
 
1127
            self.assert_(
 
1128
                len(ids) == match_count,
 
1129
                "expected REV AJIs do not match for JI %s %s %f:: %s" % (
 
1130
                    new_account_code or account_code,
 
1131
                    ji_br.name, ji_amount, db.colored_name, )
 
1132
            )
 
1133
            if expected_cor_rev_ajis_total_func_amount:
 
1134
                amount_diff = abs(expected_cor_rev_ajis_total_func_amount) \
 
1135
                    - abs(total_func_amount)
 
1136
                self.assert_(
 
1137
                    0 <= amount_diff <= AMOUNT_TOTAL_DIFF_DELTA,
 
1138
                    "expected REV AJIs total func amount %f not found:: %s" % (
 
1139
                        expected_cor_rev_ajis_total_func_amount,
 
1140
                        db.colored_name, )
 
1141
                )
 
1142
            if check_sequence_number:
 
1143
                # check sequence number
 
1144
                self.check_sequence_number(aal_obj, ids, is_analytic=True,
 
1145
                    db_name=db.colored_name)
 
1146
 
 
1147
        if expected_ad_cor:
 
1148
            # check COR AJIs
 
1149
            ids = aal_obj.search([
 
1150
                #('last_corrected_id', 'in', base_aji_ids),
 
1151
                ('journal_id', 'in', aod_journal_ids),
 
1152
                ('general_account_id', '=', new_account_id or account_id),
 
1153
                ('name', '=', "COR%d - %s" % (cor_level, ji_origin, )),
 
1154
            ])
 
1155
            self.assert_(
 
1156
                len(ids) == len(expected_ad_cor),
 
1157
                "expected COR AJIs count do not match for JI %s %s %f:: %s" % (
 
1158
                    new_account_code or account_code,
 
1159
                    ji_br.name, ji_amount, db.colored_name, )
 
1160
            )
 
1161
 
 
1162
            match_count = 0
 
1163
            total_func_amount = 0
 
1164
            for aal_br in aal_obj.browse(ids):
 
1165
                total_func_amount += aal_br.amount
 
1166
                for percent, dest, cc, fp in expected_ad_cor:
 
1167
                    # COR with new account
 
1168
                    gl_account_id = new_account_id or account_id
 
1169
                    if aal_br.general_account_id.id == gl_account_id and \
 
1170
                        aal_br.destination_id.code == dest and \
 
1171
                        aal_br.cost_center_id.code == cc and \
 
1172
                        aal_br.account_id.code == fp and \
 
1173
                        aal_br.amount_currency == \
 
1174
                            ((ji_amount * percent) / 100.):  # percent match ?
 
1175
                        match_count += 1
 
1176
                        break
 
1177
 
 
1178
            self.assert_(
 
1179
                len(ids) == match_count,
 
1180
                "expected COR AJIs do not match for JI %s %s %f:: %s" % (
 
1181
                    new_account_code or account_code,
 
1182
                    ji_br.name, ji_amount, db.colored_name, )
 
1183
            )
 
1184
            if expected_cor_rev_ajis_total_func_amount:
 
1185
                amount_diff = abs(expected_cor_rev_ajis_total_func_amount) \
 
1186
                    - abs(total_func_amount)
 
1187
                """
 
1188
                FIXME: diff of AMOUNT_TOTAL_DIFF_DELTA raises the assert
 
1189
                self.assert_(
 
1190
                    0 <= amount_diff <= AMOUNT_TOTAL_DIFF_DELTA,
 
1191
                    "expected COR AJIs total func amount %f not found:: %s" % (
 
1192
                        expected_cor_rev_ajis_total_func_amount,
 
1193
                        db.colored_name, )
 
1194
                )"""
 
1195
            if check_sequence_number:
 
1196
                # check sequence number
 
1197
                self.check_sequence_number(aal_obj, ids, is_analytic=True,
 
1198
                    db_name=db.colored_name)
 
1199
 
 
1200
    def journal_create_entry(self, database):
 
1201
        '''
 
1202
        Create a journal entry (account.move) with 2 lines:
 
1203
          - an expense one (with an analytic distribution)
 
1204
          - a counterpart one
 
1205
        Return the move ID, expense line ID, then counterpart ID
 
1206
        '''
 
1207
        # Prepare some values
 
1208
        move_obj = database.get('account.move')
 
1209
        aml_obj = database.get('account.move.line')
 
1210
        period_obj = database.get('account.period')
 
1211
        journal_obj = database.get('account.journal')
 
1212
        partner_obj = database.get('res.partner')
 
1213
        account_obj = database.get('account.account')
 
1214
        distrib_obj = database.get('analytic.distribution')
 
1215
        curr_date = strftime('%Y-%m-%d')
 
1216
        # Search journal
 
1217
        journal_ids = journal_obj.search([('type', '=', 'purchase')])
 
1218
        self.assert_(journal_ids != [], "No purchase journal found!")
 
1219
        # Search period
 
1220
        period_ids = period_obj.get_period_from_date(curr_date)
 
1221
        # Search partner
 
1222
        partner_ids = partner_obj.search([('partner_type', '=', 'external')])
 
1223
        # Create a random amount
 
1224
        random_amount = randint(100, 10000)
 
1225
        # Create a move
 
1226
        move_vals = {
 
1227
            'journal_id': journal_ids[0],
 
1228
            'period_id': period_ids[0],
 
1229
            'date': curr_date,
 
1230
            'document_date': curr_date,
 
1231
            'partner_id': partner_ids[0],
 
1232
            'status': 'manu',
 
1233
        }
 
1234
        move_id = move_obj.create(move_vals)
 
1235
        self.assert_(
 
1236
            move_id != False,
 
1237
            "Move creation failed with these values: %s" % move_vals
 
1238
        )
 
1239
        # Create some move lines
 
1240
        account_ids = account_obj.search([
 
1241
            ('is_analytic_addicted', '=', True),
 
1242
            ('code', '=', '6101-expense-test')]
 
1243
        )
 
1244
        random_account = randint(0, len(account_ids) - 1)
 
1245
        vals = {
 
1246
            'move_id': move_id,
 
1247
            'account_id': account_ids[random_account],
 
1248
            'name': 'fp_changes expense',
 
1249
            'amount_currency': random_amount,
 
1250
        }
 
1251
        # Search analytic distribution
 
1252
        distribution_ids = distrib_obj.search([('name', '=', 'DISTRIB 1')])
 
1253
        distribution_id = distrib_obj.copy(distribution_ids[0],
 
1254
            {'name': 'distribution-test'})
 
1255
        vals.update({'analytic_distribution_id': distribution_id})
 
1256
        aml_expense_id = aml_obj.create(vals)
 
1257
        counterpart_ids = account_obj.search([
 
1258
            ('is_analytic_addicted', '=', False),
 
1259
            ('code', '=', '401-supplier-test'), ('type', '!=', 'view')])
 
1260
        random_counterpart = randint(0, len(counterpart_ids) - 1)
 
1261
        vals.update({
 
1262
            'account_id': counterpart_ids[random_counterpart],
 
1263
            'amount_currency': -1 * random_amount,
 
1264
            'name': 'fp_changes counterpart',
 
1265
            'analytic_distribution_id': False,
 
1266
        })
 
1267
        aml_counterpart_id = aml_obj.create(vals)
 
1268
        # Validate the journal entry
 
1269
        # WARNING: we use button_validate so that it check the analytic
 
1270
        # distribution validity/presence
 
1271
        move_obj.button_validate([move_id])
 
1272
        return move_id, aml_expense_id, aml_counterpart_id
 
1273
 
 
1274
    def get_period_id(self, db, month, year=0):
 
1275
        year = datetime.now().year if year == 0 else year
 
1276
        period_obj = db.get('account.period')
 
1277
 
 
1278
        ids = period_obj.search([
 
1279
            ('date_start', '=', "%04d-%02d-01" % (year, month, )),
 
1280
        ])
 
1281
        return ids and ids[0] or False
 
1282
 
 
1283
    def period_close(self, db, level, month, year=0):
 
1284
        """
 
1285
        :param level: 'f', 'm' or 'h' for 'field', 'mission' or 'hq'
 
1286
        """
 
1287
        self._period_close_reopen(db, level, month, year=year, reopen=False)
 
1288
 
 
1289
    def period_reopen(self, db, level, month, year=0):
 
1290
        """
 
1291
        :param level: 'f', 'm' or 'h' for 'field', 'mission' or 'hq'
 
1292
        """
 
1293
        self._period_close_reopen(db, level, month, year=year, reopen=True)
 
1294
 
 
1295
    def _period_close_reopen(self, db, level, month, year=0, reopen=None):
 
1296
        """
 
1297
        close/reopen period at given level
 
1298
        :param level: 'f', 'm' or 'h' for 'field', 'mission' or 'hq'
 
1299
        """
 
1300
        self.assert_(
 
1301
            level in ('f', 'm', 'h', ),
 
1302
            "invalid level parameter 'f', 'm' or 'h' expected"
 
1303
        )
 
1304
 
 
1305
        self.assert_(
 
1306
            reopen is not None and isinstance(reopen, bool),
 
1307
            "invalid reopen parameter: bool expected"
 
1308
        )
 
1309
 
 
1310
        period_id = self.get_period_id(db, month, year=year)
 
1311
        self.assert_(
 
1312
            period_id != False,
 
1313
            "period %02d/%04d not found" % (year, month, )
 
1314
        )
 
1315
 
 
1316
        period_obj = db.get('account.period')
 
1317
        period_br = period_obj.browse(period_id)
 
1318
 
 
1319
        # check current state
 
1320
        state = period_br.state
 
1321
        if reopen:
 
1322
            if level == 'f':
 
1323
                if state == 'draft':
 
1324
                    return  # already open
 
1325
            elif level == 'm':
 
1326
                if state in ('draft', 'field-closed', ):
 
1327
                    return  # already mission reopen
 
1328
            elif level == 'h':
 
1329
                if state in ('draft', 'field-closed', 'mission-closed', ):
 
1330
                    return   # already hq reopen
 
1331
        else:
 
1332
            if level == 'f':
 
1333
                if state in ('field-closed', 'mission-closed', 'done', ):
 
1334
                    return  # already field closed
 
1335
            elif level == 'm':
 
1336
                if state in ('mission-closed', 'done', ):
 
1337
                    return  # already mission closed
 
1338
            elif level == 'h':
 
1339
                if state == 'done':
 
1340
                    return   # already hq closed
 
1341
 
 
1342
        # reopen/close related registers
 
1343
        abs_obj = db.get('account.bank.statement')
 
1344
        reg_ids = abs_obj.search([
 
1345
                ('state', '!=', 'open' if reopen else 'confirm'),
 
1346
                ('period_id', '=', period_id),
 
1347
            ])
 
1348
        if reg_ids:
 
1349
            for id in reg_ids:
 
1350
                if reopen:
 
1351
                    self.register_reopen(db, id)
 
1352
                else:
 
1353
                    self.register_close(db, id)
 
1354
 
 
1355
        if level =='f':
 
1356
            if reopen:
 
1357
                period_obj.action_reopen_field([period_id])
 
1358
            else:
 
1359
                period_obj.action_close_field([period_id])
 
1360
        elif level == 'm':
 
1361
            if reopen:
 
1362
                period_obj.action_reopen_mission([period_id])
 
1363
            else:
 
1364
                period_obj.action_close_mission([period_id])
 
1365
        elif level == 'h':
 
1366
            if reopen:
 
1367
                period_obj.action_open_period([period_id])
 
1368
            else:
 
1369
                period_obj.action_close_hq([period_id])
 
1370
 
 
1371
    def invoice_create_supplier_invoice(self, db, ccy_code=False,
 
1372
        is_refund=False, date=False, partner_id=False,
 
1373
        ad_header_breakdown_data=False,
 
1374
        lines_accounts=[], lines_breakdown_data=False,
 
1375
        tag="UNIT_TEST"):
 
1376
        """
 
1377
        create a supplier invoice or
 
1378
        :param ccy_code: ccy code (partner ccy if not set)
 
1379
        :param is_refund: is a refund ? False for a regular invoice
 
1380
        :param date: today if False
 
1381
        :param partner_id: Local Market if False
 
1382
        :param ad_header_breakdown_data: see analytic_distribution_create
 
1383
        :param lines_accounts: list of account codes for each line to generate
 
1384
        :param lines_breakdown_data: {index: [(percent, dest, cc, fp), ]}
 
1385
            index (start by 1) refer to lines_accounts order
 
1386
            the index key allow to pass some lines with AD at line level
 
1387
            keeping using AD at header level for others lines
 
1388
        :return : id of invoice
 
1389
        """
 
1390
        res = {}
 
1391
 
 
1392
        ai_obj = db.get('account.invoice')
 
1393
 
 
1394
        # simulate menu context
 
1395
        context = { 'type': 'in_invoice', 'journal_type': 'purchase', }
 
1396
 
 
1397
        # vals
 
1398
        itype = 'in_refund' if is_refund else 'in_invoice'
 
1399
        date = date or self.get_orm_date_now()
 
1400
        vals = {
 
1401
            'type': itype,
 
1402
 
 
1403
            'is_direct_invoice': False,
 
1404
            'is_inkind_donation': False,
 
1405
            'is_debit_note': False,
 
1406
            'is_intermission': False,
 
1407
 
 
1408
            'date_invoice': date,  # posting date
 
1409
            'document_date': date,
 
1410
        }
 
1411
 
 
1412
        # company
 
1413
        # via on_change: will set journal
 
1414
        # company_id = db.get('res.users').browse(cr, uid, [uid]).company_id.id
 
1415
        company_id = db.user.company_id.id
 
1416
        vals['company_id'] = company_id
 
1417
        res = ai_obj.onchange_company_id(
 
1418
            False,  # ids
 
1419
            company_id,
 
1420
            False,  # partner id
 
1421
            itype,  # invoice type
 
1422
            False,  # invoice line,
 
1423
            False)  # ccy id
 
1424
        if res and res['value']:
 
1425
            vals.update(res['value'])
 
1426
 
 
1427
        # partner
 
1428
        # via on_change: will set account_id, currency_id
 
1429
        if not partner_id:
 
1430
            domain = [
 
1431
                ('supplier', '=', True),
 
1432
                ('name', '=', 'Local Market'),
 
1433
            ]
 
1434
            partner_id = db.get('res.partner').search(domain)
 
1435
            self.assert_(
 
1436
                partner_id != False,
 
1437
                "Partner %s not found" % (str(domain), )
 
1438
            )
 
1439
            partner_id = partner_id[0]
 
1440
            vals['partner_id'] = partner_id
 
1441
 
 
1442
            res = ai_obj.onchange_partner_id(
 
1443
                False,  # ids
 
1444
                itype,  # invoice type
 
1445
                partner_id,
 
1446
                False,  # date_invoice
 
1447
                False,  # payment_term
 
1448
                False,  # partner_bank_id
 
1449
                False,  # company_id
 
1450
                False,  #  is_inkind_donation
 
1451
                False,  # is_intermission
 
1452
                False,  # is_debit_note
 
1453
                False)  # is_direct_invoice
 
1454
            if res and res['value']:
 
1455
                vals.update(res['value'])
 
1456
 
 
1457
        if ccy_code:
 
1458
            # specific ccy instead of partner one
 
1459
            ccy_ids = db.get('res.currency').search([('name', '=', ccy_code)])
 
1460
            self.assert_(
 
1461
                ccy_ids != False,
 
1462
                "'%s' currency not found" % (ccy_code, )
 
1463
            )
 
1464
            vals['currency_id'] = ccy_ids[0]
 
1465
 
 
1466
        # header ad
 
1467
        if ad_header_breakdown_data:
 
1468
            vals['analytic_distribution_id'] = \
 
1469
                self.analytic_distribution_create(db,
 
1470
                    breakdown_data=ad_header_breakdown_data)
 
1471
 
 
1472
        # save header
 
1473
        id = ai_obj.create(vals, context)
 
1474
 
 
1475
        # save lines
 
1476
        if lines_accounts:
 
1477
            line_vals = [
 
1478
                (0, 0, {
 
1479
                    'account_id': self.get_account_from_code(db, a),
 
1480
                    'name': FINANCE_TEST_MASK['invoice_line'] % (tag, id, i + 1,
 
1481
                        a, ),
 
1482
                    'price_unit': float(randrange(1, 10)),
 
1483
                    'quantity': float(randrange(10, 100)),
 
1484
                }) for i, a in list(enumerate(lines_accounts))
 
1485
            ]
 
1486
 
 
1487
            if lines_breakdown_data:
 
1488
                for i in lines_breakdown_data:
 
1489
                    line_vals[i-1][2]['analytic_distribution_id'] = \
 
1490
                        self.analytic_distribution_create(db,
 
1491
                            breakdown_data=lines_breakdown_data[i])
 
1492
 
 
1493
            ai_obj.write([id], {'invoice_line': line_vals}, context)
 
1494
 
 
1495
        return id
 
1496
 
 
1497
    def invoice_validate(self, db, id, out=None):
 
1498
        """
 
1499
        validate the invoice and return its expense JIs ids list
 
1500
        the invoice must be DRAT
 
1501
        :param out: optional result, if provided filled with:
 
1502
            { 'ji_counterpart_id': id, 'ji_counterpart_sdref': 'sdref', }
 
1503
        :type out: dict/none
 
1504
        :return : [ji_id, ...]
 
1505
        """
 
1506
        if not isinstance(id, (int, long, )):
 
1507
            raise FinanceTestException("id parameter expected to be unique id")
 
1508
 
 
1509
        res = []
 
1510
 
 
1511
        ai_model_name = 'account.invoice'
 
1512
        ai_obj = db.get(ai_model_name)
 
1513
        aml_model = 'account.move.line'
 
1514
        aml_obj = db.get(aml_model)
 
1515
 
 
1516
        ai = ai_obj.browse(id)
 
1517
        if ai.state != 'draft':
 
1518
            raise FinanceTestException(
 
1519
                "You can not validate non draft invoice")
 
1520
 
 
1521
        # - open it
 
1522
        # - force doc date to posting date (as by default to cur date)
 
1523
        vals = {
 
1524
            'document_date': self.date2orm(ai.date_invoice),
 
1525
        }
 
1526
        if not ai.check_total:
 
1527
            vals['check_total'] = ai.amount_total
 
1528
        ai_obj.write([ai.id], vals)
 
1529
        db.exec_workflow(ai_model_name, 'invoice_open', ai.id)
 
1530
 
 
1531
        # rebrowse it to refresh after validate to get accounting entries
 
1532
        ai = ai_obj.browse(id)
 
1533
 
 
1534
        # get invoice EXPENSE JIs
 
1535
        ji_ids = [ ji.id for ji in ai.move_id.line_id \
 
1536
            if ji.account_id.is_analytic_addicted
 
1537
        ]
 
1538
        res = ji_ids or []
 
1539
 
 
1540
        if out is not None:
 
1541
            # optional results
 
1542
 
 
1543
            # get counterpart JI
 
1544
            ji_ids = [ ji.id for ji in ai.move_id.line_id \
 
1545
                if not ji.account_id.is_analytic_addicted
 
1546
            ]
 
1547
            if ji_ids:
 
1548
                out['ji_counterpart_id'] = ji_ids[0]
 
1549
                out['ji_counterpart_sdref'] = self.get_record_sdref_from_id(
 
1550
                    aml_model, db, ji_ids[0])
 
1551
 
 
1552
        return res
 
1553
 
 
1554
    def get_jis_by_account(self, db, ji_ids):
 
1555
        """
 
1556
        get JIs id/sdref breakdown by account code
 
1557
        :param ji_ids: JI ids
 
1558
        :return { 'account_code': [(id, sdref), ], }
 
1559
        """
 
1560
        if not ji_ids:
 
1561
            return False
 
1562
        if isinstance(ji_ids, (int, long, )):
 
1563
            ji_ids = [ji_ids]
 
1564
        res = {}
 
1565
 
 
1566
        for ji_br in db.get('account.move.line').browse(ji_ids):
 
1567
            if not ji_br.account_id.code in res:
 
1568
                res[ji_br.account_id.code] = []
 
1569
            res[ji_br.account_id.code].append((ji_br.id,
 
1570
                self.get_record_sdref_from_id(
 
1571
                    'account.move.line', db, ji_br.id)))
 
1572
 
 
1573
        return res
 
1574
 
 
1575
    def get_ji_ajis_by_account(self, db, ji_ids, account_code_filter=False,
 
1576
        cc_code_filter=False):
 
1577
        """
 
1578
        get base JIs'AJIs id/sdref breakdown by account code
 
1579
        (use for sync cases, REV/COR AJIs are not get, only base AJIs)
 
1580
        :param ji_ids: JI ids
 
1581
        :param account_code_filter: filter results by account code
 
1582
        :param cc_code_filter: filter results by CC code
 
1583
        :return { 'account_code': [(id, sdref), ], } or [(id, sdref), ] if
 
1584
            cc_code_filter is used
 
1585
        """
 
1586
        if not ji_ids:
 
1587
            return False
 
1588
        if isinstance(ji_ids, (int, long, )):
 
1589
            ji_ids = [ji_ids]
 
1590
        res = [] if account_code_filter else {}
 
1591
        get_ajis = []
 
1592
 
 
1593
        for ji_br in db.get('account.move.line').browse(ji_ids):
 
1594
            if ji_br.analytic_lines:
 
1595
                for aji in ji_br.analytic_lines:
 
1596
                    if account_code_filter and \
 
1597
                        aji.general_account_id.code != account_code_filter:
 
1598
                        continue
 
1599
                    if cc_code_filter and \
 
1600
                        aji.cost_center_id.code != cc_code_filter:
 
1601
                            continue
 
1602
                    if 'COR' in aji.name or 'REV' in aji.name:
 
1603
                        # DO NOT GET REV/COR AJIs
 
1604
                        continue
 
1605
 
 
1606
                    if aji.id in get_ajis:
 
1607
                        continue
 
1608
                    get_ajis.append(aji.id)
 
1609
 
 
1610
                    res_item = (
 
1611
                            aji.id,
 
1612
                            self.get_record_sdref_from_id(
 
1613
                                'account.analytic.line',
 
1614
                                db, aji.id),
 
1615
                    )
 
1616
                    if account_code_filter:
 
1617
                        res.append(res_item)
 
1618
                    else:
 
1619
                        if not ji_br.account_id.code in res:
 
1620
                            res[ji_br.account_id.code] = []
 
1621
                        res[ji_br.account_id.code].append(res_item)
 
1622
 
 
1623
        return res
 
1624
 
 
1625
    def get_aji_revs(self, db, aji, cor_level=1):
 
1626
        """
 
1627
        get aji REVs entries
 
1628
        :param aji: aji id or sdref
 
1629
        :type aji: int/str
 
1630
        :param cor_level: cor level
 
1631
        :type cor_level: int
 
1632
        :return [(id, sdref), ]
 
1633
        """
 
1634
        if isinstance(aji, str):
 
1635
            aji = self.get_record_id_from_sdref(db, aji)
 
1636
            if not aji:
 
1637
                raise FinanceTestException("aji not found from sdref '%s'" % (
 
1638
                    aji, ))
 
1639
        elif not isinstance(aji, (int, long, )):
 
1640
            raise FinanceTestException('invalid arg aji: int or str expected')
 
1641
 
 
1642
        res = []
 
1643
        od_journal_ids = self.get_journal_ids(db, 'correction',
 
1644
            is_of_instance=False, is_analytic=True)
 
1645
        aal_model = 'account.analytic.line'
 
1646
        aal_obj = db.get(aal_model)
 
1647
        aal_br = aal_obj.browse(aji)
 
1648
 
 
1649
        domain = [
 
1650
            ('journal_id', 'in', od_journal_ids),
 
1651
            ('general_account_id', '=', aal_br.general_account_id.id),
 
1652
        ]
 
1653
        if cor_level == 1:
 
1654
            domain.append(('reversal_origin', '=', aji))
 
1655
        else:
 
1656
            domain.append(('name', '=', "REV - %s" % (aal_br.name, )))
 
1657
        ids = aal_obj.search(domain)
 
1658
 
 
1659
        if ids:
 
1660
            res = [
 
1661
                (id, self.get_record_sdref_from_id(aal_model, db, id)) \
 
1662
                    for id in ids
 
1663
            ]
 
1664
        return res
 
1665
 
 
1666
    def get_aji_cors(self, db, aji, new_account=False, cor_level=1):
 
1667
        """
 
1668
        get aji CORs entries
 
1669
        :param aji: aji id or sdref
 
1670
        :type aji: int/str
 
1671
        :param new_account: new GL account code expected if was corrected
 
1672
        :type new_account: str/False
 
1673
        :param cor_level: cor level
 
1674
        :type cor_level: int
 
1675
        :return [(id, sdref), ]
 
1676
        """
 
1677
        if isinstance(aji, str):
 
1678
            aji = self.get_record_id_from_sdref(db, aji)
 
1679
            if not aji:
 
1680
                raise FinanceTestException("aji not found from sdref '%s'" % (
 
1681
                    aji, ))
 
1682
        elif not isinstance(aji, (int, long, )):
 
1683
            raise FinanceTestException('invalid arg aji: int or str expected')
 
1684
 
 
1685
        res = []
 
1686
        od_journal_ids = self.get_journal_ids(db, 'correction',
 
1687
            is_of_instance=False, is_analytic=True)
 
1688
        aal_model = 'account.analytic.line'
 
1689
        aal_obj = db.get(aal_model)
 
1690
        aal_br = aal_obj.browse(aji)
 
1691
 
 
1692
        if new_account:
 
1693
            account_id = self.get_account_from_code(db, new_account,
 
1694
                is_analytic=False)
 
1695
        else:
 
1696
            account_id = aal_br.general_account_id.id
 
1697
 
 
1698
        ids = aal_obj.search([
 
1699
                #('last_corrected_id', '=', aji),
 
1700
                ('journal_id', 'in', od_journal_ids),
 
1701
                ('general_account_id', '=', account_id),
 
1702
                ('name', '=', "COR%d - %s" % (cor_level, aal_br.name, )),
 
1703
            ])
 
1704
        if ids:
 
1705
            res = [
 
1706
                (id, self.get_record_sdref_from_id(aal_model, db, id)) \
 
1707
                    for id in ids
 
1708
            ]
 
1709
        return res
 
1710
 
 
1711
    def analytic_account_activate_since(self, db, date):
 
1712
        aaa_obj = db.get('account.analytic.account')
 
1713
        aaa_ids = aaa_obj.search([('parent_id', '!=', False)])
 
1714
        for aaa_br in aaa_obj.browse(aaa_ids):
 
1715
            aaa_obj.write(aaa_br.id, {
 
1716
                'parent_id': aaa_br.parent_id.id,
 
1717
                'date_start': date,
 
1718
            })
 
1719
 
 
1720
    def check_ji_record_sync_push_pulled(self,
 
1721
        push_db=None,
 
1722
        push_expected=[],
 
1723
        push_not_expected=[],
 
1724
        push_should_deleted=[],
 
1725
        pull_db=None,
 
1726
        assert_report=True):
 
1727
        """
 
1728
        JI wrapper for check_records_sync_push_pulled
 
1729
        see unifield_test.py check_records_sync_push_pulled for parameters help
 
1730
        """
 
1731
        fields = False
 
1732
        fields_m2o = False
 
1733
        if push_expected:
 
1734
            # check fields of expected pulled records
 
1735
 
 
1736
            # regular fields
 
1737
            fields = [
 
1738
                'date',
 
1739
                'document_date',
 
1740
 
 
1741
                'debit_currency',
 
1742
                'debit',
 
1743
                'credit_currency',
 
1744
                'credit',
 
1745
            ]
 
1746
 
 
1747
            # m2o fields
 
1748
            model_ccy = 'res.currency'
 
1749
            model_account = 'account.account'
 
1750
            fields_m2o = [
 
1751
                ('account.move', 'move_id'),
 
1752
 
 
1753
                (model_ccy, 'currency_id'),
 
1754
                (model_ccy, 'functional_currency_id'),
 
1755
 
 
1756
                (model_account, 'account_id'),
 
1757
            ]
 
1758
 
 
1759
        return self.check_records_sync_push_pulled(
 
1760
            model='account.move.line',
 
1761
            push_db=push_db,
 
1762
            push_expected=push_expected,
 
1763
            push_not_expected=push_not_expected,
 
1764
            push_should_deleted=push_should_deleted,
 
1765
            pull_db=pull_db,
 
1766
            fields=fields, fields_m2o=fields_m2o,
 
1767
            assert_report=assert_report,
 
1768
            report_fields=[ 'account_id', 'date', 'document_date', ]
 
1769
        )
 
1770
 
 
1771
    def check_aji_record_sync_push_pulled(self,
 
1772
        push_db=None,
 
1773
        push_expected=[],
 
1774
        push_not_expected=[],
 
1775
        push_should_deleted=[],
 
1776
        pull_db=None,
 
1777
        assert_report=True):
 
1778
        """
 
1779
        AJI wrapper for check_records_sync_push_pulled
 
1780
        see unifield_test.py check_records_sync_push_pulled for parameters help
 
1781
        """
 
1782
        fields = False
 
1783
        fields_m2o = False
 
1784
        if push_expected:
 
1785
            # check fields of expected pulled records
 
1786
 
 
1787
            # regular fields
 
1788
            fields = [
 
1789
                'entry_sequence',
 
1790
                'date',
 
1791
                'document_date',
 
1792
 
 
1793
                'amount_currency',
 
1794
                'amount',
 
1795
            ]
 
1796
 
 
1797
            # m2o fields
 
1798
            model_ccy = 'res.currency'
 
1799
            model_account = 'account.account'
 
1800
            model_analytic_account = 'account.analytic.account'
 
1801
            fields_m2o = [
 
1802
                (model_ccy, 'currency_id'),
 
1803
                (model_ccy, 'functional_currency_id'),
 
1804
 
 
1805
                (model_account, 'general_account_id'),
 
1806
 
 
1807
                (model_analytic_account, 'destination_id'),
 
1808
                (model_analytic_account, 'cost_center_id'),
 
1809
                (model_analytic_account, 'account_id'),  # funding pool
 
1810
            ]
 
1811
 
 
1812
        return self.check_records_sync_push_pulled(
 
1813
            model='account.analytic.line',
 
1814
            push_db=push_db,
 
1815
            push_expected=push_expected,
 
1816
            push_not_expected=push_not_expected,
 
1817
            push_should_deleted=push_should_deleted,
 
1818
            pull_db=pull_db,
 
1819
            fields=fields, fields_m2o=fields_m2o,
 
1820
            assert_report=assert_report,
 
1821
            report_fields=[
 
1822
                'general_account_id', 'cost_center_id', 'account_id',
 
1823
                'date', 'document_date', ]
 
1824
        )
 
1825
 
 
1826
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: