20
20
##############################################################################
22
from osv import fields, osv
23
import decimal_precision as dp
24
from tools.misc import flatten
25
from time import strftime
27
24
from tools.translate import _
29
class analytic_distribution1(osv.osv):
30
_name = "analytic.distribution"
33
'analytic_lines': fields.one2many('account.analytic.line', 'distribution_id', 'Analytic Lines'),
34
'invoice_ids': fields.one2many('account.invoice', 'analytic_distribution_id', string="Invoices"),
35
'invoice_line_ids': fields.one2many('account.invoice.line', 'analytic_distribution_id', string="Invoice Lines"),
36
'register_line_ids': fields.one2many('account.bank.statement.line', 'analytic_distribution_id', string="Register Lines"),
37
'move_line_ids': fields.one2many('account.move.line', 'analytic_distribution_id', string="Move Lines"),
38
'commitment_ids': fields.one2many('account.commitment', 'analytic_distribution_id', string="Commitments voucher"),
39
'commitment_line_ids': fields.one2many('account.commitment.line', 'analytic_distribution_id', string="Commitment voucher lines"),
42
def copy(self, cr, uid, id, default=None, context=None):
44
Copy an analytic distribution without the one2many links
49
'analytic_lines': False,
51
'invoice_line_ids': False,
52
'register_line_ids': False,
53
'move_line_ids': False,
54
'commitment_ids': False,
55
'commitment_line_ids': False,
57
return super(osv.osv, self).copy(cr, uid, id, default, context=context)
59
def _get_distribution_state(self, cr, uid, id, parent_id, account_id, context=None):
26
class analytic_distribution(osv.osv):
27
_name = 'analytic.distribution'
28
_inherit = 'analytic.distribution'
30
def _get_distribution_state(self, cr, uid, distrib_id, parent_id, account_id, context=None):
61
32
Return distribution state
63
34
if context is None:
65
# Have an analytic distribution on another account than expense account make no sense. So their analytic distribution is valid
36
# Have an analytic distribution on another account than analytic-a-holic account make no sense. So their analytic distribution is valid
66
37
logger = netsvc.Logger()
68
account = self.pool.get('account.account').browse(cr, uid, account_id)
69
if account and account.user_type and account.user_type.code != 'expense':
39
account = self.pool.get('account.account').read(cr, uid, account_id, ['is_analytic_addicted'])
40
if account and not account.get('is_analytic_addicted', False):
73
44
return self._get_distribution_state(cr, uid, parent_id, False, account_id, context)
74
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: NONE!") % (id or ''))
45
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: NONE!") % (distrib_id or ''))
76
distrib = self.browse(cr, uid, id)
47
distrib = self.browse(cr, uid, distrib_id)
77
48
# Search MSF Private Fund element, because it's valid with all accounts
79
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
50
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
80
51
'analytic_account_msf_private_funds')[1]
83
account = self.pool.get('account.account').browse(cr, uid, account_id)
54
account = self.pool.get('account.account').read(cr, uid, account_id, ['destination_ids'])
84
55
# Check Cost Center lines with destination/account link
85
56
for cc_line in distrib.cost_center_lines:
86
if cc_line.destination_id.id not in [x.id for x in account.destination_ids]:
87
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, destination not compatible with G/L account in CC lines") % (id or ''))
57
if cc_line.destination_id.id not in account.get('destination_ids', []):
58
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, destination not compatible with G/L account in CC lines") % (distrib_id or ''))
89
60
# Check Funding pool lines regarding:
90
61
# - destination / account
91
62
# - If analytic account is MSF Private funds
92
63
# - Cost center and funding pool compatibility
93
64
for fp_line in distrib.funding_pool_lines:
94
if fp_line.destination_id.id not in [x.id for x in account.destination_ids]:
95
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, destination not compatible with G/L account for FP lines") % (id or ''))
65
if fp_line.destination_id.id not in account.get('destination_ids', []):
66
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, destination not compatible with G/L account for FP lines") % (distrib_id or ''))
97
68
# If fp_line is MSF Private Fund, all is ok
98
69
if fp_line.analytic_id.id == fp_id:
100
71
if (account_id, fp_line.destination_id.id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in fp_line.analytic_id.tuple_destination_account_ids]:
101
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, account/destination tuple not compatible with given FP analytic account") % (id or ''))
72
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, account/destination tuple not compatible with given FP analytic account") % (distrib_id or ''))
103
74
if fp_line.cost_center_id.id not in [x.id for x in fp_line.analytic_id.cost_center_ids]:
104
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, CC is not compatible with given FP analytic account") % (id or ''))
75
logger.notifyChannel("analytic distribution", netsvc.LOG_WARNING, _("%s: Error, CC is not compatible with given FP analytic account") % (distrib_id or ''))
108
analytic_distribution1()
110
class distribution_line(osv.osv):
111
_name = "distribution.line"
114
'name': fields.char('Name', size=64),
115
"distribution_id": fields.many2one('analytic.distribution', 'Associated Analytic Distribution', ondelete='cascade'),
116
"analytic_id": fields.many2one('account.analytic.account', 'Analytical Account'),
117
"amount": fields.float('Amount', digits_compute=dp.get_precision('Account')),
118
"percentage": fields.float('Percentage', digits=(16,4)),
119
"currency_id": fields.many2one('res.currency', 'Currency', required=True),
120
"date": fields.date(string="Date"),
121
"source_date": fields.date(string="Source Date", help="This date is for source_date for analytic lines"),
125
'name': 'Distribution Line',
126
'date': lambda *a: strftime('%Y-%m-%d'),
127
'source_date': lambda *a: strftime('%Y-%m-%d'),
130
def _check_percentage(self, cr, uid, ids, context=None):
132
Do not allow 0.0 percentage value
134
for l in self.browse(cr, uid, ids):
135
if l.percentage == 0.0:
140
(_check_percentage, '0 is not allowed as percentage value!', ['percentage']),
143
def create_analytic_lines(self, cr, uid, ids, move_line_id, date, document_date, source_date=False, name=False, context=None):
145
Creates an analytic lines from a distribution line and an account.move.line
147
if isinstance(ids, (int, long)):
151
move_line = self.pool.get('account.move.line').browse(cr, uid, move_line_id)
152
company_currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
154
for line in self.browse(cr, uid, ids):
155
amount_cur = (move_line.credit_currency - move_line.debit_currency) * line.percentage / 100
156
ctx = {'date': source_date or date}
157
amount = self.pool.get('res.currency').compute(cr, uid, move_line.currency_id.id, company_currency_id, amount_cur, round=False, context=ctx)
159
'account_id': line.analytic_id.id,
160
'amount_currency': amount_cur,
162
'currency_id': move_line.currency_id.id,
163
'general_account_id': move_line.account_id.id,
165
'source_date': source_date,
166
'document_date': document_date,
167
'journal_id': move_line.journal_id and move_line.journal_id.analytic_journal_id and move_line.journal_id.analytic_journal_id.id or False,
168
'move_id': move_line.id,
169
'name': name or move_line.name,
170
'distrib_id': line.distribution_id.id,
171
'distrib_line_id': '%s,%s'%(self._name, line.id),
173
if self._name == 'funding.pool.distribution.line':
175
'destination_id': line.destination_id and line.destination_id.id or False,
176
'cost_center_id': line.cost_center_id and line.cost_center_id.id or False,
178
ret[line.id] = self.pool.get('account.analytic.line').create(cr, uid, vals)
185
class cost_center_distribution_line(osv.osv):
186
_name = "cost.center.distribution.line"
187
_inherit = "distribution.line"
189
"destination_id": fields.many2one('account.analytic.account', 'Destination', domain="[('type', '!=', 'view'), ('category', '=', 'DEST')]", required=True),
192
cost_center_distribution_line()
194
class funding_pool_distribution_line(osv.osv):
195
_name = "funding.pool.distribution.line"
196
_inherit = "distribution.line"
198
"cost_center_id": fields.many2one('account.analytic.account', 'Cost Center Account', required=True),
199
"destination_id": fields.many2one('account.analytic.account', 'Destination', domain="[('type', '!=', 'view'), ('category', '=', 'DEST')]", required=True),
202
funding_pool_distribution_line()
204
class free_1_distribution_line(osv.osv):
205
_name = "free.1.distribution.line"
206
_inherit = "distribution.line"
208
"destination_id": fields.many2one('account.analytic.account', 'Destination', domain="[('type', '!=', 'view'), ('category', '=', 'DEST')]", required=False),
211
free_1_distribution_line()
213
class free_2_distribution_line(osv.osv):
214
_name = "free.2.distribution.line"
215
_inherit = "distribution.line"
217
"destination_id": fields.many2one('account.analytic.account', 'Destination', domain="[('type', '!=', 'view'), ('category', '=', 'DEST')]", required=False),
220
free_2_distribution_line()
222
class analytic_distribution(osv.osv):
223
_name = 'analytic.distribution'
224
_inherit = "analytic.distribution"
226
def _get_lines_count(self, cr, uid, ids, name=False, args=False, context=None):
228
Get count of each analytic distribution lines type.
229
Example: with an analytic distribution with 2 cost center, 3 funding pool and 1 Free 1:
230
2 CC; 3 FP; 1 F1; 0 F2;
231
(Number of chars: 20 chars + 4 x some lines number)
236
# Prepare some values
240
if isinstance(ids, (int, long)):
242
# Browse given invoices
243
for distrib in self.browse(cr, uid, ids, context=context):
245
txt += str(len(distrib.cost_center_lines) or '0') + ' CC; '
246
txt += str(len(distrib.funding_pool_lines) or '0') + ' FP; '
247
txt += str(len(distrib.free_1_lines) or '0') + ' F1; '
248
txt += str(len(distrib.free_2_lines) or '0') + ' F2'
251
res[distrib.id] = txt
255
'cost_center_lines': fields.one2many('cost.center.distribution.line', 'distribution_id', 'Cost Center Distribution'),
256
'funding_pool_lines': fields.one2many('funding.pool.distribution.line', 'distribution_id', 'Funding Pool Distribution'),
257
'free_1_lines': fields.one2many('free.1.distribution.line', 'distribution_id', 'Free 1 Distribution'),
258
'free_2_lines': fields.one2many('free.2.distribution.line', 'distribution_id', 'Free 2 Distribution'),
259
'name': fields.function(_get_lines_count, method=True, type='char', size=256, string="Name", readonly=True, store=False),
262
def update_distribution_line_amount(self, cr, uid, ids, amount=False, context=None):
264
Update amount on distribution lines for given distribution (ids)
269
if isinstance(ids, (int, long)):
273
# Process distributions
274
for distrib_id in ids:
275
for dl_name in ['cost.center.distribution.line', 'funding.pool.distribution.line', 'free.1.distribution.line', 'free.2.distribution.line']:
276
dl_obj = self.pool.get(dl_name)
277
dl_ids = dl_obj.search(cr, uid, [('distribution_id', '=', distrib_id)], context=context)
278
for dl in dl_obj.browse(cr, uid, dl_ids, context=context):
280
'amount': round(dl.percentage * amount) / 100.0,
282
dl_obj.write(cr, uid, [dl.id], dl_vals, context=context)
285
def update_distribution_line_account(self, cr, uid, line_ids, account_id, context=None):
287
Update account on distribution line
292
if isinstance(line_ids, (int, long)):
293
line_ids = [line_ids]
296
# Prepare some values
297
account = self.pool.get('account.analytic.account').browse(cr, uid, [account_id], context=context)[0]
299
if account.category == 'OC':
300
vals = {'cost_center_id': account_id}
302
vals = {'analytic_id': account_id}
303
return self.pool.get('funding.pool.distribution.line').write(cr, uid, line_ids, vals)
305
def create_funding_pool_lines(self, cr, uid, ids, account_id=False, context=None):
307
Create funding pool lines regarding cost_center_lines from analytic distribution.
308
If funding_pool_lines exists, then nothing appends.
309
By default, add funding_pool_lines with MSF Private Fund element (written in an OpenERP demo file).
310
For destination axis, get those from account_id default configuration (default_destination_id).
315
if isinstance(ids, (int, long)):
317
# Prepare some values
319
# Browse distributions
320
for distrib in self.browse(cr, uid, ids, context=context):
321
if distrib.funding_pool_lines:
322
res[distrib.id] = False
324
# Browse cost center lines
325
for line in distrib.cost_center_lines:
326
# Search MSF Private Fund
328
pf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
329
'analytic_account_msf_private_funds')[1]
334
'analytic_id': pf_id,
335
'amount': line.amount or 0.0,
336
'percentage': line.percentage or 0.0,
337
'currency_id': line.currency_id and line.currency_id.id or False,
338
'distribution_id': distrib.id or False,
339
'cost_center_id': line.analytic_id and line.analytic_id.id or False,
340
'destination_id': line.destination_id and line.destination_id.id or False,
342
# Search default destination if no one given
343
if account_id and not vals.get('destination_id'):
344
account = self.pool.get('account.account').browse(cr, uid, account_id)
345
if account and account.user_type and account.user_type.code == 'expense':
346
vals.update({'destination_id': account.default_destination_id and account.default_destination_id.id or False})
347
new_pf_line_id = self.pool.get('funding.pool.distribution.line').create(cr, uid, vals, context=context)
348
res[distrib.id] = True
351
def create_analytic_lines(self, cr, uid, ids, name, date, amount, journal_id, currency_id, document_date=False, ref=False, source_date=False, general_account_id=False, \
352
move_id=False, invoice_line_id=False, commitment_line_id=False, context=None):
354
Create analytic lines from given elements:
358
- journal_id (analytic_journal_id)
361
- source_date (optional)
362
- general_account_id (optional)
364
- invoice_line_id (optional)
365
- commitment_line_id (optional)
366
Return all created ids, otherwise return false (or [])
371
if isinstance(ids, (int, long)):
373
if not name or not date or not amount or not journal_id or not currency_id:
375
if not document_date:
377
# Prepare some values
381
'date': source_date or date,
382
'document_date': document_date,
384
'journal_id': journal_id,
385
'general_account_id': general_account_id or False,
386
'move_id': move_id or False,
387
'invoice_line_id': invoice_line_id or False,
389
'currency_id': currency_id,
390
'source_date': source_date or False,
391
'commitment_line_id': commitment_line_id or False,
393
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
394
# Browse distribution(s)
395
for distrib in self.browse(cr, uid, ids, context=context):
396
vals.update({'distribution_id': distrib.id,})
398
for distrib_lines in [distrib.funding_pool_lines, distrib.free_1_lines, distrib.free_2_lines]:
399
for distrib_line in distrib_lines:
400
context.update({'date': source_date or date}) # for amount computing
401
anal_amount = (distrib_line.percentage * amount) / 100
403
'amount': -1 * self.pool.get('res.currency').compute(cr, uid, currency_id, company_currency,
404
anal_amount, round=False, context=context),
405
'amount_currency': -1 * anal_amount,
406
'account_id': distrib_line.analytic_id.id,
407
'cost_center_id': False,
408
'destination_id': False,
409
'distrib_line_id': '%s,%s'%(distrib_line._name, distrib_line.id),
411
# Update values if we come from a funding pool
412
if distrib_line._name == 'funding.pool.distribution.line':
413
vals.update({'cost_center_id': distrib_line.cost_center_id and distrib_line.cost_center_id.id or False,
414
'destination_id': distrib_line.destination_id and distrib_line.destination_id.id or False,})
415
# create analytic line
416
al_id = self.pool.get('account.analytic.line').create(cr, uid, vals, context=context)
79
def analytic_state_from_info(self, cr, uid, account_id, destination_id, cost_center_id, analytic_id, context=None):
81
Give analytic state from the given information.
82
Return result and some info if needed.
90
ana_obj = self.pool.get('account.analytic.account')
91
account = self.pool.get('account.account').browse(cr, uid, account_id, context=context)
92
fp = ana_obj.browse(cr, uid, analytic_id, context=context)
94
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
97
is_private_fund = False
98
if analytic_id == fp_id:
99
is_private_fund = True
100
# DISTRIBUTION VERIFICATION
101
# Check account user_type
102
if account.user_type_code != 'expense':
103
return res, _('Not an expense account')
104
# Check that destination is compatible with account
105
if destination_id not in [x.id for x in account.destination_ids]:
106
return 'invalid', _('Destination not compatible with account')
107
if not is_private_fund:
108
# Check that cost center is compatible with FP (except if FP is MSF Private Fund)
109
if cost_center_id not in [x.id for x in fp.cost_center_ids]:
110
return 'invalid', _('Cost Center not compatible with FP')
111
# Check that tuple account/destination is compatible with FP (except if FP is MSF Private Fund):
112
if (account_id, destination_id) not in [x.account_id and x.destination_id and (x.account_id.id, x.destination_id.id) for x in fp.tuple_destination_account_ids]:
113
return 'invalid', _('account/destination tuple not compatible with given FP analytic account')
420
116
analytic_distribution()
421
117
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: