20
20
##############################################################################
22
22
from osv import fields, osv
23
24
from tools.translate import _
25
26
class account_move_line(osv.osv):
26
27
_inherit = 'account.move.line'
28
def _display_analytic_button(self, cr, uid, ids, name, args, context=None):
30
Return True for all element that correspond to some criteria:
31
- The journal entry state is draft (unposted)
32
- The account is analytic-a-holic
35
for ml in self.browse(cr, uid, ids, context=context):
37
# False if account not anlaytic-a-holic
38
if not ml.account_id.is_analytic_addicted:
42
def _get_distribution_state(self, cr, uid, ids, name, args, context=None):
44
Get state of distribution:
45
- if compatible with the move line, then "valid"
46
- if no distribution, take a tour of move distribution, if compatible, then "valid"
47
- if no distribution on move line and move, then "none"
48
- all other case are "invalid"
53
if isinstance(ids, (int, long)):
57
distrib_obj = self.pool.get('analytic.distribution')
59
SELECT aml.id, aml.analytic_distribution_id AS distrib_id, m.analytic_distribution_id AS move_distrib_id, aml.account_id
60
FROM account_move_line AS aml, account_move AS m
61
WHERE aml.move_id = m.id
64
cr.execute(sql, (tuple(ids),))
65
for line in cr.fetchall():
66
res[line[0]] = distrib_obj._get_distribution_state(cr, uid, line[1], line[2], line[3])
69
def _have_analytic_distribution_from_header(self, cr, uid, ids, name, arg, context=None):
71
If move have an analytic distribution, return False, else return True
76
if isinstance(ids, (int, long)):
79
for ml in self.browse(cr, uid, ids, context=context):
81
if ml.analytic_distribution_id:
85
def _get_distribution_state_recap(self, cr, uid, ids, name, arg, context=None):
87
Get a recap from analytic distribution state and if it come from header or not.
89
if isinstance(ids, (int, long)):
92
get_sel = self.pool.get('ir.model.fields').get_browse_selection
93
for ml in self.browse(cr, uid, ids):
96
if ml.have_analytic_distribution_from_header:
97
from_header = _(' (from header)')
98
d_state = get_sel(cr, uid, ml, 'analytic_distribution_state', context)
99
res[ml.id] = "%s%s" % (d_state, from_header)
100
# Do not show any recap for non analytic-a-holic accounts
101
if ml.account_id and not ml.account_id.is_analytic_addicted:
106
30
'analytic_distribution_id': fields.many2one('analytic.distribution', 'Analytic Distribution'),
107
'display_analytic_button': fields.function(_display_analytic_button, method=True, string='Display analytic button?', type='boolean', readonly=True,
108
help="This informs system that we can display or not an analytic button", store=False),
109
'analytic_distribution_state': fields.function(_get_distribution_state, method=True, type='selection',
110
selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')],
111
string="Distribution state", help="Informs from distribution state among 'none', 'valid', 'invalid."),
112
'have_analytic_distribution_from_header': fields.function(_have_analytic_distribution_from_header, method=True, type='boolean',
113
string='Header Distrib.?'),
114
'analytic_distribution_state_recap': fields.function(_get_distribution_state_recap, method=True, type='char', size=30,
115
string="Distribution",
116
help="Informs you about analaytic distribution state among 'none', 'valid', 'invalid', from header or not, or no analytic distribution"),
119
33
def create_analytic_lines(self, cr, uid, ids, context=None):
121
Create analytic lines on analytic-a-holic accounts that have an analytical distribution.
126
34
acc_ana_line_obj = self.pool.get('account.analytic.line')
127
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
131
'analytic_distribution_id',
145
for obj_line in self.read(cr, uid, ids, obj_fields, context=context):
146
# Prepare some values
147
amount = obj_line.get('debit_currency', 0.0) - obj_line.get('credit_currency', 0.0)
148
journal = self.pool.get('account.journal').read(cr, uid, obj_line.get('journal_id', [False])[0], ['analytic_journal_id', 'name'], context=context)
149
move = self.pool.get('account.move').read(cr, uid, obj_line.get('move_id', [False])[0], ['analytic_distribution_id', 'status', 'line_id'], context=context)
150
account = self.pool.get('account.account').read(cr, uid, obj_line.get('account_id', [False])[0], ['is_analytic_addicted'], context=context)
151
aal_obj = self.pool.get('account.analytic.line')
152
line_distrib_id = (obj_line.get('analytic_distribution_id', False) and obj_line.get('analytic_distribution_id')[0]) or (move.get('analytic_distribution_id', False) and move.get('analytic_distribution_id')[0]) or False
153
# When you create a journal entry manually, we should not have analytic lines if ONE line is invalid!
154
other_lines_are_ok = True
155
result = self.search(cr, uid, [('move_id', '=', move.get('id', False)), ('move_id.status', '=', 'manu'), ('state', '!=', 'valid')], count=1)
156
if result and result > 0 and move.get('status', False) == 'manu':
157
other_lines_are_ok = False
158
# Check that line have analytic-a-holic account and have a distribution
159
if line_distrib_id and account.get('is_analytic_addicted', False) and other_lines_are_ok:
160
ana_state = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, line_distrib_id, {}, account.get('id'))
161
# For manual journal entries, do not raise an error. But delete all analytic distribution linked to other_lines because if one line is invalid, all lines should not create analytic lines
162
if ana_state == 'invalid' and move.get('status', '') == 'manu':
163
ana_line_ids = acc_ana_line_obj.search(cr, uid, [('move_id', 'in', move.get('line_id', []))])
164
acc_ana_line_obj.unlink(cr, uid, ana_line_ids)
166
elif ana_state == 'invalid':
167
raise osv.except_osv(_('Warning'), _('Invalid analytic distribution.'))
168
if not journal.get('analytic_journal_id', False):
169
raise osv.except_osv(_('Warning'),_("No Analytic Journal! You have to define an analytic journal on the '%s' journal!") % (journal.get('name', ''), ))
170
distrib_obj = self.pool.get('analytic.distribution').browse(cr, uid, line_distrib_id, context=context)
35
for obj_line in self.browse(cr, uid, ids, context=context):
36
if obj_line.analytic_distribution_id and obj_line.account_id.user_type_code == 'expense':
37
if not obj_line.journal_id.analytic_journal_id:
38
raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (obj_line.journal_id.name, ))
39
distrib_obj = self.pool.get('analytic.distribution').browse(cr, uid, obj_line.analytic_distribution_id.id, context=context)
172
for distrib_lines in [distrib_obj.funding_pool_lines, distrib_obj.free_1_lines, distrib_obj.free_2_lines]:
173
aji_greater_amount = {
178
dl_total_amount_rounded = 0.
41
for distrib_lines in [distrib_obj.cost_center_lines, distrib_obj.funding_pool_lines, distrib_obj.free_1_lines, distrib_obj.free_2_lines]:
179
42
for distrib_line in distrib_lines:
180
context.update({'date': obj_line.get('source_date', False) or obj_line.get('date', False)})
181
anal_amount = distrib_line.percentage*amount/100
182
dl_total_amount_rounded += round(anal_amount, 2)
183
if anal_amount > aji_greater_amount['amount']:
184
# US-119: breakdown by fp line or free 1, free2
185
# register the aji that will have the greatest amount
186
aji_greater_amount['amount'] = anal_amount
187
aji_greater_amount['is'] = True
189
aji_greater_amount['is'] = False
191
'name': obj_line.get('name', ''),
192
'date': obj_line.get('date', False),
193
'ref': obj_line.get('ref', ''),
194
'journal_id': journal.get('analytic_journal_id', [False])[0],
195
'amount': -1 * self.pool.get('res.currency').compute(cr, uid, obj_line.get('currency_id', [False])[0], company_currency,
196
anal_amount, round=False, context=context),
197
'amount_currency': -1 * anal_amount,
44
'name': obj_line.name,
45
'date': obj_line.date,
47
'journal_id': obj_line.journal_id.analytic_journal_id.id,
48
'amount': distrib_line.amount,
198
49
'account_id': distrib_line.analytic_id.id,
199
'general_account_id': account.get('id'),
200
'move_id': obj_line.get('id'),
201
'distribution_id': distrib_obj.id,
203
'currency_id': obj_line.get('currency_id', [False])[0],
204
'distrib_line_id': '%s,%s'%(distrib_line._name, distrib_line.id),
205
'document_date': obj_line.get('document_date', False),
206
'source_date': obj_line.get('source_date', False) or obj_line.get('date', False), # UFTP-361 source_date from date if not any (posting date)
50
'general_account_id': obj_line.account_id.id,
51
'move_id': obj_line.id,
52
'distribution_id': obj_line.analytic_distribution_id.id,
208
# Update values if we come from a funding pool
209
if distrib_line._name == 'funding.pool.distribution.line':
210
destination_id = distrib_line.destination_id and distrib_line.destination_id.id or False
211
line_vals.update({'cost_center_id': distrib_line.cost_center_id and distrib_line.cost_center_id.id or False,
212
'destination_id': destination_id,})
213
# Update value if we come from a write-off
214
if obj_line.get('is_write_off', False):
215
line_vals.update({'from_write_off': True,})
216
# Add source_date value for account_move_line that are a correction of another account_move_line
217
if obj_line.get('corrected_line_id', False) and obj_line.get('source_date', False):
218
line_vals.update({'source_date': obj_line.get('source_date', False)})
219
aji_id = aal_obj.create(cr, uid, line_vals, context=context)
220
if aji_greater_amount['is']:
221
aji_greater_amount['id'] = aji_id
223
if amount > 0. and dl_total_amount_rounded > 0.:
224
if abs(dl_total_amount_rounded - amount) > 0.001 and \
225
aji_greater_amount['id']:
226
# US-119 deduce the rounding gap and apply it
227
# to the AJI of greater amount
228
# http://jira.unifield.org/browse/US-119?focusedCommentId=38217&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-38217
229
fixed_amount = aji_greater_amount['amount'] - (dl_total_amount_rounded - amount)
230
fixed_amount_vals = {
231
'amount': -1 * self.pool.get('res.currency').compute(cr, uid, obj_line.get('currency_id', [False])[0], company_currency,
232
fixed_amount, round=False, context=context),
233
'amount_currency': -1 * fixed_amount,
235
aal_obj.write(cr, uid, [aji_greater_amount['id']],
236
fixed_amount_vals, context=context)
55
self.pool.get('account.analytic.line').create(cr, uid, line_vals, context=context)
240
def unlink(self, cr, uid, ids, context=None, check=True):
242
Delete analytic lines before unlink move lines.
243
Update Manual Journal Entries.
249
# Search manual moves to revalidate
252
FROM account_move_line AS ml, account_move AS m
253
WHERE ml.move_id = m.id
254
AND m.status = 'manu'
258
cr.execute(sql, (tuple(ids),))
259
move_ids += [x and x[0] for x in cr.fetchall()]
260
# Search analytic lines
261
ana_ids = self.pool.get('account.analytic.line').search(cr, uid, [('move_id', 'in', ids)], context=context)
262
self.pool.get('account.analytic.line').unlink(cr, uid, ana_ids, context=context)
263
res = super(account_move_line, self).unlink(cr, uid, ids, context=context, check=check) #ITWG-84: Pass also the check flag to the super!
265
self.pool.get('account.move').validate(cr, uid, move_ids, context=context)
268
def button_analytic_distribution(self, cr, uid, ids, context=None):
270
Launch analytic distribution wizard on an move line
275
if isinstance(ids, (int, long)):
278
raise osv.except_osv(_('Error'), _('No journal item given. Please save your line before.'))
279
# Prepare some values
280
ml = self.browse(cr, uid, ids[0], context=context)
281
amount = ml.debit_currency - ml.credit_currency
282
# Search elements for currency
283
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
284
currency = ml.currency_id and ml.currency_id.id or company_currency
285
# Get analytic distribution id from this line
286
distrib_id = ml and ml.analytic_distribution_id and ml.analytic_distribution_id.id or False
287
# Prepare values for wizard
289
'total_amount': amount,
290
'move_line_id': ml.id,
291
'currency_id': currency or False,
293
'account_id': ml.account_id and ml.account_id.id or False,
294
'posting_date': ml.date,
295
'document_date': ml.document_date,
298
vals.update({'distribution_id': distrib_id,})
300
wiz_obj = self.pool.get('analytic.distribution.wizard')
301
wiz_id = wiz_obj.create(cr, uid, vals, context=context)
302
# Update some context values
59
def button_analytic_distribution(self, cr, uid, ids, context={}):
60
# we get the analytical distribution object linked to this line
62
move_line_obj = self.browse(cr, uid, ids[0], context=context)
63
amount = abs(move_line_obj.amount_currency)
64
if move_line_obj.analytic_distribution_id:
65
distrib_id = move_line_obj.analytic_distribution_id.id
67
raise osv.except_osv(_('No Analytic Distribution !'),_("You have to define an analytic distribution on the move line!"))
68
wiz_obj = self.pool.get('wizard.costcenter.distribution')
69
wiz_id = wiz_obj.create(cr, uid, {'total_amount': amount, 'distribution_id': distrib_id}, context=context)
309
'name': _('Analytic distribution'),
310
72
'type': 'ir.actions.act_window',
311
'res_model': 'analytic.distribution.wizard',
73
'res_model': 'wizard.costcenter.distribution',
312
74
'view_type': 'form',
313
75
'view_mode': 'form',
315
77
'res_id': [wiz_id],
319
def _check_employee_analytic_distribution(self, cr, uid, ids, context=None):
321
Check that analytic distribution could be retrieved from given employee.
322
If not employee, return True.
326
if isinstance(ids, (int, long)):
328
for l in self.browse(cr, uid, ids):
329
# Next line if this one comes from a non-manual move (journal entry)
330
if l.move_id.status != 'manu':
332
# Do not continue if no employee or no cost center (could not be invented)
333
if not l.employee_id or not l.employee_id.cost_center_id:
335
if l.account_id and l.account_id.is_analytic_addicted:
336
vals = {'cost_center_id': l.employee_id.cost_center_id.id}
337
if l.employee_id.destination_id:
338
if l.employee_id.destination_id.id in [x and x.id for x in l.account_id.destination_ids]:
339
vals.update({'destination_id': l.employee_id.destination_id.id})
341
vals.update({'destination_id': l.account_id.default_destination_id.id})
342
if l.employee_id.funding_pool_id:
343
vals.update({'analytic_id': l.employee_id.funding_pool_id.id})
344
if vals.get('cost_center_id') not in l.employee_id.funding_pool_id.cost_center_ids:
345
# Fetch default funding pool: MSF Private Fund
347
msf_fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
350
vals.update({'analytic_id': msf_fp_id})
351
# Create analytic distribution
352
if 'cost_center_id' in vals and 'analytic_id' in vals and 'destination_id' in vals:
353
distrib_id = self.pool.get('analytic.distribution').create(cr, uid, {'name': 'check_employee_analytic_distribution'})
354
vals.update({'distribution_id': distrib_id, 'percentage': 100.0, 'currency_id': l.currency_id.id})
355
# Create funding pool lines
356
self.pool.get('funding.pool.distribution.line').create(cr, uid, vals)
357
# Then cost center lines
358
vals.update({'analytic_id': vals.get('cost_center_id'),})
359
self.pool.get('cost.center.distribution.line').create(cr, uid, vals)
360
# finally free1 and free2
361
if l.employee_id.free1_id:
362
self.pool.get('free.1.distribution.line').create(cr, uid, {'distribution_id': distrib_id, 'percentage': 100.0, 'currency_id': l.currency_id.id, 'analytic_id': l.employee_id.free1_id.id})
363
if l.employee_id.free2_id:
364
self.pool.get('free.2.distribution.line').create(cr, uid, {'distribution_id': distrib_id, 'percentage': 100.0, 'currency_id': l.currency_id.id, 'analytic_id': l.employee_id.free2_id.id})
365
if context.get('from_write', False):
366
return {'analytic_distribution_id': distrib_id,}
367
# Write analytic distribution on the move line
368
self.pool.get('account.move.line').write(cr, uid, [l.id], {'analytic_distribution_id': distrib_id}, check=False, update_check=False)
373
def create(self, cr, uid, vals, context=None, check=True):
375
Check analytic distribution for employee (if given)
377
res = super(account_move_line, self).create(cr, uid, vals, context, check)
378
self._check_employee_analytic_distribution(cr, uid, res, context)
381
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
383
Check line if we come from web (from_web_menu)
387
if isinstance(ids, (int, long)):
389
if context.get('from_web_menu', False):
391
for ml in self.browse(cr, uid, ids):
392
distrib_state = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, ml.analytic_distribution_id.id, ml.move_id and ml.move_id.analytic_distribution_id and ml.move_id.analytic_distribution_id.id or False, vals.get('account_id') or ml.account_id.id)
393
if distrib_state in ['invalid', 'none']:
394
vals.update({'state': 'draft'})
395
# Add account_id because of an error with account_activable module for checking date
396
if not 'account_id' in vals and 'date' in vals:
397
vals.update({'account_id': ml.account_id and ml.account_id.id or False})
398
check = self._check_employee_analytic_distribution(cr, uid, [ml.id], context={'from_write': True})
399
if check and isinstance(check, dict):
401
tmp_res = super(account_move_line, self).write(cr, uid, [ml.id], vals, context, False, False)
404
res = super(account_move_line, self).write(cr, uid, ids, vals, context, check, update_check)
407
def copy(self, cr, uid, aml_id, default=None, context=None):
409
Copy analytic_distribution
417
res = super(account_move_line, self).copy(cr, uid, aml_id, default, context)
418
# Update analytic distribution
420
c = self.browse(cr, uid, res, context=context)
421
if res and c.analytic_distribution_id:
422
new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, c.analytic_distribution_id.id, {}, context=context)
424
self.write(cr, uid, [res], {'analytic_distribution_id': new_distrib_id}, context=context)
427
def get_analytic_move_lines(self, cr, uid, ids, context=None):
429
Return FP analytic lines attached to move lines
434
if 'active_ids' in context:
435
ids = context.get('active_ids')
436
if isinstance(ids, (int, long)):
439
domain = [('move_id', 'in', ids), ('account_id.category', '=', 'FUNDING')]
440
context.update({'display_fp': True})
442
'name': _('Analytic lines (FP) from Journal Items'),
443
'type': 'ir.actions.act_window',
444
'res_model': 'account.analytic.line',
446
'view_mode': 'tree,form',
452
def get_analytic_move_free1_lines(self, cr, uid, ids, context=None):
454
Return FREE1 analytic lines attached to move lines
459
if 'active_ids' in context:
460
ids = context.get('active_ids')
461
if isinstance(ids, (int, long)):
464
domain = [('move_id', 'in', ids), ('account_id.category', '=', 'FREE1')]
465
context.update({'display_fp': False, 'categ': 'FREE1'})
467
'name': _('Analytic Lines (Free 1) from Journal Items'),
468
'type': 'ir.actions.act_window',
469
'res_model': 'account.analytic.line',
471
'view_mode': 'tree,form',
477
def get_analytic_move_free2_lines(self, cr, uid, ids, context=None):
479
Return FREE2 analytic lines attached to move lines
484
if 'active_ids' in context:
485
ids = context.get('active_ids')
486
if isinstance(ids, (int, long)):
489
domain = [('move_id', 'in', ids), ('account_id.category', '=', 'FREE2')]
490
context.update({'display_fp': False, 'categ': 'FREE2'})
492
'name': _('Analytic Lines (Free 2) from Journal Items'),
493
'type': 'ir.actions.act_window',
494
'res_model': 'account.analytic.line',
496
'view_mode': 'tree,form',
81
'wizard_ids': {'cost_center': wiz_id}
502
85
account_move_line()
503
87
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: