2
# -*- coding: utf-8 -*-
3
##############################################################################
5
# OpenERP, Open Source Management Solution
6
# Copyright (C) 2011 TeMPO Consulting, MSF. All Rights Reserved
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as
10
# published by the Free Software Foundation, either version 3 of the
11
# License, or (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU Affero General Public License for more details.
18
# You should have received a copy of the GNU Affero General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21
##############################################################################
22
from osv import fields, osv
24
from datetime import datetime
25
from dateutil.relativedelta import relativedelta
26
from tools.translate import _
28
class account_model_line(osv.osv):
29
_name = "account.model.line"
30
_inherit = "account.model.line"
32
def _get_distribution_state(self, cr, uid, ids, name, args, context=None):
34
Get state of distribution:
35
- if compatible with the invoice line, then "valid"
36
- if no distribution, take a tour of invoice distribution, if compatible, then "valid"
37
- if no distribution on invoice line and invoice, then "none"
38
- all other case are "invalid"
43
if isinstance(ids, (int, long)):
47
# Browse all given lines
48
for line in self.browse(cr, uid, ids, context=context):
49
res[line.id] = self.pool.get('analytic.distribution')._get_distribution_state(cr, uid, line.analytic_distribution_id.id, line.model_id.analytic_distribution_id.id, line.account_id.id)
52
def _have_analytic_distribution_from_header(self, cr, uid, ids, name, arg, context=None):
54
If model has an analytic distribution, return False, else return True
59
if isinstance(ids, (int, long)):
62
for model in self.browse(cr, uid, ids, context=context):
64
if model.analytic_distribution_id:
68
def _get_is_allocatable(self, cr, uid, ids, name, arg, context=None):
70
If analytic-a-holic account, then this account is allocatable.
72
if isinstance(ids, (int, long)):
75
for model_line in self.browse(cr, uid, ids):
76
res[model_line.id] = True
77
if model_line.account_id and not model_line.account_id.is_analytic_addicted:
78
res[model_line.id] = False
81
def _get_distribution_state_recap(self, cr, uid, ids, name, arg, context=None):
83
Get a recap from analytic distribution state and if it come from header or not.
85
if isinstance(ids, (int, long)):
88
for model_line in self.browse(cr, uid, ids):
89
res[model_line.id] = ''
90
if not model_line.is_allocatable:
93
if model_line.have_analytic_distribution_from_header:
94
from_header = _(' (from header)')
95
ana_distri_state = self.pool.get('ir.model.fields').get_browse_selection(cr, uid, model_line, 'analytic_distribution_state', context)
96
res[model_line.id] = "%s%s" % (ana_distri_state, from_header)
99
def _get_exp_in_line_state(self, cr, uid, ids, name, args, context=None):
103
if isinstance(ids, (int, long)):
105
for line in self.browse(cr, uid, ids, context=context):
106
if line.account_id.user_type.code in ('expense', 'income'):
107
if line.have_analytic_distribution_from_header \
108
and not line.model_id.analytic_distribution_id:
110
res[line.id] = 'no_header'
113
res[line.id] = line.analytic_distribution_state
115
res[line.id] = 'no_exp_in'
119
'analytic_distribution_state': fields.function(_get_distribution_state, method=True, type='selection',
120
selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')],
121
string="Distribution state", help="Informs from distribution state among 'none', 'valid', 'invalid."),
122
'have_analytic_distribution_from_header': fields.function(_have_analytic_distribution_from_header, method=True, type='boolean',
123
string='Header Distrib.?'),
124
'is_allocatable': fields.function(_get_is_allocatable, method=True, type='boolean', string="Is allocatable?", readonly=True, store=False),
125
'analytic_distribution_state_recap': fields.function(_get_distribution_state_recap, method=True, type='char', size=30,
126
string="Distribution",
127
help="Informs you about analaytic distribution state among 'none', 'valid', 'invalid', from header or not, or no analytic distribution"),
128
'sequence': fields.integer('Sequence', readonly=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
129
'analytic_distribution_id': fields.many2one('analytic.distribution', 'Analytic Distribution'),
130
'exp_in_ad_state': fields.function(_get_exp_in_line_state, method=True, type='selection',
131
selection=[('no_exp_in', 'Not expense/income'), ('no_header', 'No header'), ('valid', 'Valid'), ('invalid', 'Invalid')],
132
string='Expense/income line status'), # UFTP-103
136
'have_analytic_distribution_from_header': lambda *a: True,
137
'is_allocatable': lambda *a: True,
138
'analytic_distribution_state_recap': lambda *a: '',
141
def create(self, cr, uid, vals, context=None):
142
model = self.pool.get('account.model').browse(cr, uid, vals['model_id'], context=context)
143
# just add the next line
144
vals['sequence'] = len(model.lines_id) + 1
145
return super(account_model_line, self).create(cr, uid, vals, context=context)
147
def button_analytic_distribution(self, cr, uid, ids, context=None):
149
Launch analytic distribution wizard on an invoice line
154
if isinstance(ids, (int, long)):
157
raise osv.except_osv(_('Error'), _('No model line given. Please save your model line before.'))
158
model_line = self.browse(cr, uid, ids[0], context=context)
160
amount = abs(model_line.debit - model_line.credit)
161
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
162
currency = model_line.model_id and model_line.model_id.currency_id and model_line.model_id.currency_id.id or company_currency
163
# Get analytic distribution id from this line
164
distrib_id = model_line.analytic_distribution_id and model_line.analytic_distribution_id.id or False
165
# Prepare values for wizard
167
'total_amount': amount,
168
'model_line_id': model_line.id,
169
'currency_id': currency or False,
171
'account_id': model_line.account_id.id,
174
vals.update({'distribution_id': distrib_id,})
176
wiz_obj = self.pool.get('analytic.distribution.wizard')
177
wiz_id = wiz_obj.create(cr, uid, vals, context=context)
178
# Update some context values
185
'name': _('Analytic distribution'),
186
'type': 'ir.actions.act_window',
187
'res_model': 'analytic.distribution.wizard',
195
def copy_data(self, cr, uid, id, default=None, context=None):
197
Copy global distribution and give it to new model line
204
# Copy analytic distribution
205
model_line = self.browse(cr, uid, [id], context=context)[0]
206
if model_line.analytic_distribution_id:
207
new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, model_line.analytic_distribution_id.id, {}, context=context)
209
default.update({'analytic_distribution_id': new_distrib_id})
210
return super(account_model_line, self).copy_data(cr, uid, id, default, context)
214
class account_model(osv.osv):
215
_name = "account.model"
216
_inherit = "account.model"
218
def _has_any_bad_ad_line_exp_in(self, cr, uid, ids, name, args, context=None):
222
if isinstance(ids, (int, long)):
224
for model in self.browse(cr, uid, ids, context=context):
225
res[model.id] = False
226
for line in model.lines_id:
227
if line.exp_in_ad_state and line.exp_in_ad_state in ('no_header', 'invalid'):
233
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
234
'analytic_distribution_id': fields.many2one('analytic.distribution', 'Analytic Distribution'),
235
'has_any_bad_ad_line_exp_in': fields.function(_has_any_bad_ad_line_exp_in,
236
method=True, type='boolean',
237
string='Has bad analytic distribution on expense/income lines',
238
help='There is lines with expense or income accounts with invalid analytic distribution or using header AD that is not defined or not compatible.'), # UFTP-103
242
'currency_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id,
243
'has_any_bad_ad_line_exp_in': False,
246
# @@@override@account.account_model.generate()
247
def generate(self, cr, uid, ids, datas={}, context=None):
250
account_move_obj = self.pool.get('account.move')
251
account_move_line_obj = self.pool.get('account.move.line')
252
pt_obj = self.pool.get('account.payment.term')
253
ana_obj = self.pool.get('analytic.distribution')
258
if datas.get('date', False):
259
context.update({'date': datas['date']})
261
period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
263
raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
264
period_id = period_id[0]
266
for model in self.browse(cr, uid, ids, context=context):
267
entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
268
move_id = account_move_obj.create(cr, uid, {
269
'ref': entry['name'],
270
'period_id': period_id,
271
'journal_id': model.journal_id.id,
272
'date': context.get('date',time.strftime('%Y-%m-%d'))
274
move_ids.append(move_id)
275
for line in model.lines_id:
278
'journal_id': model.journal_id.id,
279
'period_id': period_id,
281
if line.account_id.is_analytic_addicted:
282
if line.analytic_distribution_state == 'invalid':
283
raise osv.except_osv(_('Invalid Analytic Distribution !'),_("Please define a valid analytic distribution for the recurring model '%s'!") % (line.name))
284
if not model.journal_id.analytic_journal_id:
285
raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
286
if line.analytic_distribution_id:
287
new_distribution_id = ana_obj.copy(cr, uid, line.analytic_distribution_id.id, {}, context=context)
288
val.update({'analytic_distribution_id': new_distribution_id})
289
elif model.analytic_distribution_id:
290
new_distribution_id = ana_obj.copy(cr, uid, model.analytic_distribution_id.id, {}, context=context)
291
val.update({'analytic_distribution_id': new_distribution_id})
293
date_maturity = time.strftime('%Y-%m-%d')
294
if line.date_maturity == 'partner':
295
if not line.partner_id:
296
raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
297
"\nPlease define partner on it!")%(line.name, model.name))
298
if line.partner_id.property_payment_term:
299
payment_term_id = line.partner_id.property_payment_term.id
300
pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
302
pterm_list = [l[0] for l in pterm_list]
304
date_maturity = pterm_list[-1]
308
'quantity': line.quantity,
309
'debit_currency': line.debit, # UF-1535: set this value as the booking currency, and not functional currency
310
'credit_currency': line.credit, # UF-1535: set this value as the booking currency, and not functional currency
311
'account_id': line.account_id.id,
313
'partner_id': line.partner_id.id,
314
'date': context.get('date',time.strftime('%Y-%m-%d')),
315
'document_date': context.get('date',time.strftime('%Y-%m-%d')),
316
'date_maturity': date_maturity,
317
'currency_id': model.currency_id.id,
318
'is_recurring': True,
321
c.update({'journal_id': model.journal_id.id,'period_id': period_id})
322
account_move_line_obj.create(cr, uid, val, context=c)
326
def button_analytic_distribution(self, cr, uid, ids, context=None):
328
Launch analytic distribution wizard on a model
333
if isinstance(ids, (int, long)):
335
# Prepare some values
336
model = self.browse(cr, uid, ids[0], context=context)
338
# Search elements for currency
339
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
340
currency = model.currency_id and model.currency_id.id or company_currency
341
for line in model.lines_id:
342
amount += (line.debit - line.credit)
344
# Get analytic_distribution_id
345
distrib_id = model.analytic_distribution_id and model.analytic_distribution_id.id
346
# Prepare values for wizard
348
'total_amount': amount,
349
'model_id': model.id,
350
'currency_id': currency or False,
354
vals.update({'distribution_id': distrib_id,})
356
wiz_obj = self.pool.get('analytic.distribution.wizard')
357
wiz_id = wiz_obj.create(cr, uid, vals, context=context)
358
# Update some context values
365
'name': _('Global analytic distribution'),
366
'type': 'ir.actions.act_window',
367
'res_model': 'analytic.distribution.wizard',
375
def button_reset_distribution(self, cr, uid, ids, context=None):
377
Reset analytic distribution on all recurring lines.
378
To do this, just delete the analytic_distribution id link on each recurring line.
382
if isinstance(ids, (int, long)):
384
# Prepare some values
385
recurring_obj = self.pool.get(self._name + '.line')
386
# Search recurring lines
387
to_reset = recurring_obj.search(cr, uid, [('model_id', 'in', ids)])
388
recurring_obj.write(cr, uid, to_reset, {'analytic_distribution_id': False})
392
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: