2
# -*- coding: utf-8 -*-
3
##############################################################################
5
# OpenERP, Open Source Management Solution
6
# Copyright (C) 2012 TeMPO Consulting, MSF. All Rights Reserved
7
# Developer: Olivier DOSSMANN
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU Affero General Public License as
11
# published by the Free Software Foundation, either version 3 of the
12
# License, or (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU Affero General Public License for more details.
19
# You should have received a copy of the GNU Affero General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
##############################################################################
25
from osv import fields
26
from decimal_precision import get_precision
27
from time import strftime
28
from lxml import etree
29
from tools.translate import _
31
class hr_payroll(osv.osv):
32
_name = 'hr.payroll.msf'
33
_description = 'Payroll'
35
def _get_analytic_state(self, cr, uid, ids, name, args, context=None):
37
Get state of distribution:
38
- if compatible with the line, then "valid"
39
- all other case are "invalid"
41
if isinstance(ids, (int, long)):
45
# Search MSF Private Fund element, because it's valid with all accounts
47
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
48
'analytic_account_msf_private_funds')[1]
51
# Browse all given lines to check analytic distribution validity
54
# B/ if FP = MSF Private FUND
55
# C/ (account/DEST) in FP except B
56
# D/ CC in FP except when B
57
# E/ DEST in list of available DEST in ACCOUNT
58
## CASES where FP is filled in (or not) and/or DEST is filled in (or not).
59
## CC is mandatory, so always available:
60
# 1/ no FP, no DEST => Distro = valid
61
# 2/ FP, no DEST => Check D except B
62
# 3/ no FP, DEST => Check E
63
# 4/ FP, DEST => Check C, D except B, E
65
for line in self.browse(cr, uid, ids, context=context):
66
res[line.id] = 'valid' # by default
67
#### SOME CASE WHERE DISTRO IS OK
68
# if account is not expense, so it's valid
69
if line.account_id and line.account_id.user_type_code and line.account_id.user_type_code != 'expense':
71
# if just a cost center, it's also valid! (CASE 1/)
72
if not line.funding_pool_id and not line.destination_id:
74
# if FP is MSF Private Fund and no destination_id, then all is OK.
75
if line.funding_pool_id and line.funding_pool_id.id == fp_id and not line.destination_id:
78
if line.funding_pool_id and not line.destination_id: # CASE 2/
79
# D Check, except B check
80
if line.cost_center_id.id not in [x.id for x in line.funding_pool_id.cost_center_ids] and line.funding_pool_id.id != fp_id:
81
res[line.id] = 'invalid'
83
elif not line.funding_pool_id and line.destination_id: # CASE 3/
85
account = self.pool.get('account.account').browse(cr, uid, line.account_id.id)
86
if line.destination_id.id not in [x.id for x in account.destination_ids]:
87
res[line.id] = 'invalid'
91
if (line.account_id.id, 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 line.funding_pool_id.tuple_destination_account_ids] and line.funding_pool_id.id != fp_id:
92
res[line.id] = 'invalid'
94
# D Check, except B check
95
if line.cost_center_id.id not in [x.id for x in line.funding_pool_id.cost_center_ids] and line.funding_pool_id.id != fp_id:
96
res[line.id] = 'invalid'
99
account = self.pool.get('account.account').browse(cr, uid, line.account_id.id)
100
if line.destination_id.id not in [x.id for x in account.destination_ids]:
101
res[line.id] = 'invalid'
105
def _get_third_parties(self, cr, uid, ids, field_name=None, arg=None, context=None):
107
Get "Third Parties" following other fields
110
for line in self.browse(cr, uid, ids):
112
res[line.id] = {'third_parties': 'hr.employee,%s' % line.employee_id.id}
113
res[line.id] = 'hr.employee,%s' % line.employee_id.id
114
elif line.journal_id:
115
res[line.id] = 'account.journal,%s' % line.transfer_journal_id.id
116
elif line.partner_id:
117
res[line.id] = 'res.partner,%s' % line.partner_id.id
122
def _get_employee_identification_id(self, cr, uid, ids, field_name=None, arg=None, context=None):
124
Get employee identification number if employee id is given
127
for line in self.browse(cr, uid, ids):
130
res[line.id] = line.employee_id.identification_id
134
'date': fields.date(string='Date', required=True, readonly=True),
135
'document_date': fields.date(string='Document Date', required=True, readonly=True),
136
'account_id': fields.many2one('account.account', string="Account", required=True, readonly=True),
137
'period_id': fields.many2one('account.period', string="Period", required=True, readonly=True),
138
'employee_id': fields.many2one('hr.employee', string="Employee", readonly=True),
139
'partner_id': fields.many2one('res.partner', string="Partner", readonly=True),
140
'journal_id': fields.many2one('account.journal', string="Journal", readonly=True),
141
'employee_id_number': fields.function(_get_employee_identification_id, method=True, type='char', size=255, string='Employee ID', readonly=True),
142
'name': fields.char(string='Description', size=255, readonly=True),
143
'ref': fields.char(string='Reference', size=255, readonly=True),
144
'amount': fields.float(string='Amount', digits_compute=get_precision('Account'), readonly=True),
145
'currency_id': fields.many2one('res.currency', string="Currency", required=True, readonly=True),
146
'state': fields.selection([('draft', 'Draft'), ('valid', 'Validated')], string="State", required=True, readonly=True),
147
'cost_center_id': fields.many2one('account.analytic.account', string="Cost Center", required=True, domain="[('category','=','OC'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
148
'funding_pool_id': fields.many2one('account.analytic.account', string="Funding Pool", domain="[('category', '=', 'FUNDING'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
149
'free1_id': fields.many2one('account.analytic.account', string="Free 1", domain="[('category', '=', 'FREE1'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
150
'free2_id': fields.many2one('account.analytic.account', string="Free 2", domain="[('category', '=', 'FREE2'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
151
'destination_id': fields.many2one('account.analytic.account', string="Destination", domain="[('category', '=', 'DEST'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
152
'analytic_state': fields.function(_get_analytic_state, type='selection', method=True, readonly=True, string="Distribution State",
153
selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')], help="Give analytic distribution state"),
154
'partner_type': fields.function(_get_third_parties, type='reference', method=True, string="Third Parties", readonly=True,
155
selection=[('res.partner', 'Partner'), ('account.journal', 'Journal'), ('hr.employee', 'Employee'), ('account.bank.statement', 'Register')]),
156
'field': fields.char(string='Field', readonly=True, size=255, help="Field this line come from in Homère."),
159
_order = 'employee_id, date desc'
162
'date': lambda *a: strftime('%Y-%m-%d'),
163
'document_date': lambda *a: strftime('%Y-%m-%d'),
164
'state': lambda *a: 'draft',
167
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
169
Change funding pool domain in order to include MSF Private fund
173
view = super(hr_payroll, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
174
if view_type in ['tree', 'form']:
175
form = etree.fromstring(view['arch'])
176
data_obj = self.pool.get('ir.model.data')
178
oc_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
182
fields = form.xpath('//field[@name="cost_center_id"]')
184
field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
187
fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
190
fp_fields = form.xpath('//field[@name="funding_pool_id"]')
191
for field in fp_fields:
192
field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'FUNDING'), '|', '&', ('cost_center_ids', '=', cost_center_id), ('tuple_destination', '=', (account_id, destination_id)), ('id', '=', %s)]" % fp_id)
193
# Change Destination field
194
dest_fields = form.xpath('//field[@name="destination_id"]')
195
for field in dest_fields:
196
field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'DEST'), ('destination_ids', '=', account_id)]")
198
view['arch'] = etree.tostring(form)
201
def onchange_destination(self, cr, uid, ids, destination_id=False, funding_pool_id=False, account_id=False):
203
Check given funding pool with destination
205
# Prepare some values
207
# If all elements given, then search FP compatibility
208
if destination_id and funding_pool_id and account_id:
209
fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
210
# Search MSF Private Fund element, because it's valid with all accounts
212
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
213
'analytic_account_msf_private_funds')[1]
216
# Delete funding_pool_id if not valid with tuple "account_id/destination_id".
217
# but do an exception for MSF Private FUND analytic account
218
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_line.tuple_destination_account_ids] and funding_pool_id != fp_id:
219
res = {'value': {'funding_pool_id': False}}
220
# If no destination, do nothing
221
elif not destination_id:
223
# Otherway: delete FP
225
res = {'value': {'funding_pool_id': False}}
226
# If destination given, search if given
229
def create(self, cr, uid, vals, context=None):
231
Raise an error if creation don't become from an import or a YAML.
232
Add default analytic distribution for those that doesn't have anyone.
236
if not context.get('from', False) and not context.get('from') in ['yaml', 'csv_import']:
237
raise osv.except_osv(_('Error'), _('You are not able to create payroll entries.'))
238
if not vals.get('cost_center_id', False):
240
dummy_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project_dummy')[1]
244
vals.update({'cost_center_id': dummy_id,})
245
if not vals.get('funding_pool_id', False):
247
fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
251
vals.update({'funding_pool_id': fp_id,})
252
return super(osv.osv, self).create(cr, uid, vals, context)
255
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: