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

« back to all changes in this revision

Viewing changes to msf_homere_interface/hr_payroll.py

  • Committer: Quentin THEURET
  • Date: 2011-12-12 08:02:59 UTC
  • mto: This revision was merged to the branch mainline in revision 724.
  • Revision ID: qt@tempo-consulting.fr-20111212080259-oul1f0g37hcpubyc
UF-641 [ADD] Added the empty purchase_followup module

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
# -*- coding: utf-8 -*-
3
 
##############################################################################
4
 
#
5
 
#    OpenERP, Open Source Management Solution
6
 
#    Copyright (C) 2012 TeMPO Consulting, MSF. All Rights Reserved
7
 
#    Developer: Olivier DOSSMANN
8
 
#
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.
13
 
#
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.
18
 
#
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/>.
21
 
#
22
 
##############################################################################
23
 
 
24
 
from osv import osv
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 _
30
 
 
31
 
class hr_payroll(osv.osv):
32
 
    _name = 'hr.payroll.msf'
33
 
    _description = 'Payroll'
34
 
 
35
 
    def _get_analytic_state(self, cr, uid, ids, name, args, context=None):
36
 
        """
37
 
        Get state of distribution:
38
 
         - if compatible with the line, then "valid"
39
 
         - all other case are "invalid"
40
 
        """
41
 
        if isinstance(ids, (int, long)):
42
 
            ids = [ids]
43
 
        # Prepare some values
44
 
        res = {}
45
 
        # Search MSF Private Fund element, because it's valid with all accounts
46
 
        try:
47
 
            fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 
48
 
            'analytic_account_msf_private_funds')[1]
49
 
        except ValueError:
50
 
            fp_id = 0
51
 
        # Browse all given lines to check analytic distribution validity
52
 
        ## TO CHECK:
53
 
        # A/ if no CC
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
 
        # F/ Check posting date with cost center and destination if exists
59
 
        # G/ Check document date with funding pool
60
 
        ## CASES where FP is filled in (or not) and/or DEST is filled in (or not).
61
 
        ## CC is mandatory, so always available:
62
 
        # 1/ no FP, no DEST => Distro = valid
63
 
        # 2/ FP, no DEST => Check D except B
64
 
        # 3/ no FP, DEST => Check E
65
 
        # 4/ FP, DEST => Check C, D except B, E
66
 
        ## 
67
 
        for line in self.browse(cr, uid, ids, context=context):
68
 
            res[line.id] = 'valid' # by default
69
 
            #### SOME CASE WHERE DISTRO IS OK
70
 
            # if account is not analytic-a-holic, so it's valid
71
 
            if line.account_id and not line.account_id.is_analytic_addicted:
72
 
                continue
73
 
            # Date checks
74
 
            # F Check
75
 
            if line.cost_center_id:
76
 
                cc = self.pool.get('account.analytic.account').browse(cr, uid, line.cost_center_id.id, context={'date': line.date})
77
 
                if cc and cc.filter_active is False:
78
 
                    res[line.id] = 'invalid'
79
 
                    continue
80
 
            if line.destination_id:
81
 
                dest = self.pool.get('account.analytic.account').browse(cr, uid, line.destination_id.id, context={'date': line.date})
82
 
                if dest and dest.filter_active is False:
83
 
                    res[line.id] = 'invalid'
84
 
                    continue
85
 
            # G Check
86
 
            if line.funding_pool_id:
87
 
                fp = self.pool.get('account.analytic.account').browse(cr, uid, line.funding_pool_id.id, context={'date': line.document_date})
88
 
                if fp and fp.filter_active is False:
89
 
                    res[line.id] = 'invalid'
90
 
                    continue
91
 
            # if just a cost center, it's also valid! (CASE 1/)
92
 
            if not line.funding_pool_id and not line.destination_id:
93
 
                continue
94
 
            # if FP is MSF Private Fund and no destination_id, then all is OK.
95
 
            if line.funding_pool_id and line.funding_pool_id.id == fp_id and not line.destination_id:
96
 
                continue
97
 
            #### END OF CASES
98
 
            # if no cost center, distro is invalid (CASE A/)
99
 
            if not line.cost_center_id:
100
 
                    res[line.id] = 'invalid'
101
 
                    continue
102
 
            if line.funding_pool_id and not line.destination_id: # CASE 2/
103
 
                # D Check, except B check
104
 
                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:
105
 
                    res[line.id] = 'invalid'
106
 
                    continue
107
 
            elif not line.funding_pool_id and line.destination_id: # CASE 3/
108
 
                # E Check
109
 
                account = self.pool.get('account.account').browse(cr, uid, line.account_id.id)
110
 
                if line.destination_id.id not in [x.id for x in account.destination_ids]:
111
 
                    res[line.id] = 'invalid'
112
 
                    continue
113
 
            else: # CASE 4/
114
 
                # C Check, except B
115
 
                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:
116
 
                    res[line.id] = 'invalid'
117
 
                    continue
118
 
                # D Check, except B check
119
 
                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:
120
 
                    res[line.id] = 'invalid'
121
 
                    continue
122
 
                # E Check
123
 
                account = self.pool.get('account.account').browse(cr, uid, line.account_id.id)
124
 
                if line.destination_id.id not in [x.id for x in account.destination_ids]:
125
 
                    res[line.id] = 'invalid'
126
 
                    continue
127
 
        return res
128
 
 
129
 
    def _get_third_parties(self, cr, uid, ids, field_name=None, arg=None, context=None):
130
 
        """
131
 
        Get "Third Parties" following other fields
132
 
        """
133
 
        res = {}
134
 
        for line in self.browse(cr, uid, ids):
135
 
            if line.employee_id:
136
 
                res[line.id] = {'third_parties': 'hr.employee,%s' % line.employee_id.id}
137
 
                res[line.id] = 'hr.employee,%s' % line.employee_id.id
138
 
            elif line.journal_id:
139
 
                res[line.id] = 'account.journal,%s' % line.transfer_journal_id.id
140
 
            elif line.partner_id:
141
 
                res[line.id] = 'res.partner,%s' % line.partner_id.id
142
 
            else:
143
 
                res[line.id] = False
144
 
        return res
145
 
 
146
 
    def _get_employee_identification_id(self, cr, uid, ids, field_name=None, arg=None, context=None):
147
 
        """
148
 
        Get employee identification number if employee id is given
149
 
        """
150
 
        res = {}
151
 
        for line in self.browse(cr, uid, ids):
152
 
            res[line.id] = ''
153
 
            if line.employee_id:
154
 
                res[line.id] = line.employee_id.identification_id
155
 
        return res
156
 
 
157
 
    def _get_trigger_state_ana(self, cr, uid, ids, context=None):
158
 
        if isinstance(ids, (int, long)):
159
 
            ids = [ids]
160
 
 
161
 
        fp = [0]
162
 
        cc = [0]
163
 
        dest = [0]
164
 
        for ana_account in self.read(cr, uid, ids, ['category']):
165
 
            if ana_account['category'] == 'OC':
166
 
                cc.append(ana_account['id'])
167
 
            elif ana_account['category'] == 'DEST':
168
 
                dest.append(ana_account['id'])
169
 
            elif ana_account['category'] == 'FUNDING':
170
 
                fp.append(ana_account['id'])
171
 
        if len(fp) > 1 or len(cc) > 1 or len(dest) > 1:
172
 
            return self.pool.get('hr.payroll.msf').search(cr, uid, [('state', '=', 'draft'), '|', '|', ('funding_pool_id', 'in', fp), ('cost_center_id','in', cc), ('destination_id','in', dest)])
173
 
 
174
 
        return []
175
 
    
176
 
    def _get_trigger_state_account(self, cr, uid, ids, context=None):
177
 
        pay_obj = self.pool.get('hr.payroll.msf')
178
 
        return pay_obj.search(cr, uid, [('state', '=', 'draft'), ('account_id', 'in', ids)])
179
 
    
180
 
    def _get_trigger_state_dest_link(self, cr, uid, ids, context=None):
181
 
        if isinstance(ids, (int, long)):
182
 
            ids = [ids]
183
 
        to_update = []
184
 
        pay_obj = self.pool.get('hr.payroll.msf')
185
 
        for dest_link in self.read(cr, uid, ids, ['account_id', 'destination_id', 'funding_pool_ids']):
186
 
            to_update += pay_obj.search(cr, uid, [
187
 
                    ('state', '=', 'draft'),
188
 
                    ('account_id', '=', dest_link['account_id'][0]),
189
 
                    ('destination_id', '=', dest_link['destination_id'][0]),
190
 
                    ('funding_pool_id', 'in', dest_link['funding_pool_ids'])
191
 
            ])
192
 
        return to_update
193
 
 
194
 
    _columns = {
195
 
        'date': fields.date(string='Date', required=True, readonly=True),
196
 
        'document_date': fields.date(string='Document Date', required=True, readonly=True),
197
 
        'account_id': fields.many2one('account.account', string="Account", required=True, readonly=True),
198
 
        'period_id': fields.many2one('account.period', string="Period", required=True, readonly=True),
199
 
        'employee_id': fields.many2one('hr.employee', string="Employee", readonly=True, ondelete="restrict"),
200
 
        'partner_id': fields.many2one('res.partner', string="Partner", readonly=True, ondelete="restrict"),
201
 
        'journal_id': fields.many2one('account.journal', string="Journal", readonly=True, ondelete="restrict"),
202
 
        'employee_id_number': fields.function(_get_employee_identification_id, method=True, type='char', size=255, string='Employee ID', readonly=True),
203
 
        'name': fields.char(string='Description', size=255, readonly=True),
204
 
        'ref': fields.char(string='Reference', size=255, readonly=True),
205
 
        'amount': fields.float(string='Amount', digits_compute=get_precision('Account'), readonly=True),
206
 
        'currency_id': fields.many2one('res.currency', string="Currency", required=True, readonly=True),
207
 
        'state': fields.selection([('draft', 'Draft'), ('valid', 'Validated')], string="State", required=True, readonly=True),
208
 
        'cost_center_id': fields.many2one('account.analytic.account', string="Cost Center", required=False, domain="[('category','=','OC'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
209
 
        'funding_pool_id': fields.many2one('account.analytic.account', string="Funding Pool", domain="[('category', '=', 'FUNDING'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
210
 
        'free1_id': fields.many2one('account.analytic.account', string="Free 1", domain="[('category', '=', 'FREE1'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
211
 
        'free2_id': fields.many2one('account.analytic.account', string="Free 2", domain="[('category', '=', 'FREE2'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
212
 
        'destination_id': fields.many2one('account.analytic.account', string="Destination", domain="[('category', '=', 'DEST'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
213
 
        'analytic_state': fields.function(_get_analytic_state, type='selection', method=True, readonly=True, string="Distribution State",
214
 
            selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')], help="Give analytic distribution state",
215
 
            store={
216
 
                'hr.payroll.msf': (lambda self, cr, uid, ids, c=None: ids, ['account_id', 'cost_center_id', 'funding_pool_id', 'destination_id'], 10),
217
 
                'account.account': (_get_trigger_state_account, ['user_type_code', 'destination_ids'], 20),
218
 
                'account.analytic.account': (_get_trigger_state_ana, ['date', 'date_start', 'cost_center_ids', 'tuple_destination_account_ids'], 20),
219
 
                'account.destination.link': (_get_trigger_state_dest_link, ['account_id', 'destination_id'], 30),
220
 
            }
221
 
        ),
222
 
        'partner_type': fields.function(_get_third_parties, type='reference', method=True, string="Third Parties", readonly=True,
223
 
            selection=[('res.partner', 'Partner'), ('account.journal', 'Journal'), ('hr.employee', 'Employee')]),
224
 
        'field': fields.char(string='Field', readonly=True, size=255, help="Field this line come from in Homère."),
225
 
    }
226
 
 
227
 
    _order = 'employee_id, date desc'
228
 
 
229
 
    _defaults = {
230
 
        'date': lambda *a: strftime('%Y-%m-%d'),
231
 
        'document_date': lambda *a: strftime('%Y-%m-%d'),
232
 
        'state': lambda *a: 'draft',
233
 
    }
234
 
 
235
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
236
 
        """
237
 
        Change funding pool domain in order to include MSF Private fund
238
 
        """
239
 
        if not context:
240
 
            context = {}
241
 
        view = super(hr_payroll, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
242
 
        if view_type in ['tree', 'form']:
243
 
            form = etree.fromstring(view['arch'])
244
 
            data_obj = self.pool.get('ir.model.data')
245
 
            try:
246
 
                oc_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project')[1]
247
 
            except ValueError:
248
 
                oc_id = 0
249
 
            # Change OC field
250
 
            fields = form.xpath('//field[@name="cost_center_id"]')
251
 
            for field in fields:
252
 
                field.set('domain', "[('category', '=', 'OC'), ('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
253
 
            # Change FP field
254
 
            try:
255
 
                fp_id = data_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
256
 
            except ValueError:
257
 
                fp_id = 0
258
 
            fp_fields = form.xpath('//field[@name="funding_pool_id"]')
259
 
            for field in fp_fields:
260
 
                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)
261
 
            # Change Destination field
262
 
            dest_fields = form.xpath('//field[@name="destination_id"]')
263
 
            for field in dest_fields:
264
 
                field.set('domain', "[('type', '!=', 'view'), ('state', '=', 'open'), ('category', '=', 'DEST'), ('destination_ids', '=', account_id)]")
265
 
            # Apply changes
266
 
            view['arch'] = etree.tostring(form)
267
 
        return view
268
 
 
269
 
    def onchange_destination(self, cr, uid, ids, destination_id=False, funding_pool_id=False, account_id=False):
270
 
        """
271
 
        Check given funding pool with destination
272
 
        """
273
 
        # Prepare some values
274
 
        res = {}
275
 
        # If all elements given, then search FP compatibility
276
 
        if destination_id and funding_pool_id and account_id:
277
 
            fp_line = self.pool.get('account.analytic.account').browse(cr, uid, funding_pool_id)
278
 
            # Search MSF Private Fund element, because it's valid with all accounts
279
 
            try:
280
 
                fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 
281
 
                'analytic_account_msf_private_funds')[1]
282
 
            except ValueError:
283
 
                fp_id = 0
284
 
            # Delete funding_pool_id if not valid with tuple "account_id/destination_id".
285
 
            # but do an exception for MSF Private FUND analytic account
286
 
            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:
287
 
                res = {'value': {'funding_pool_id': False}}
288
 
        # If no destination, do nothing
289
 
        elif not destination_id:
290
 
            res = {}
291
 
        # Otherway: delete FP
292
 
        else:
293
 
            res = {'value': {'funding_pool_id': False}}
294
 
        # If destination given, search if given 
295
 
        return res
296
 
 
297
 
    def create(self, cr, uid, vals, context=None):
298
 
        """
299
 
        Raise an error if creation don't become from an import or a YAML.
300
 
        Add default analytic distribution for those that doesn't have anyone.
301
 
        """
302
 
        if not context:
303
 
            context = {}
304
 
        if not context.get('from', False) and not context.get('from') in ['yaml', 'csv_import']:
305
 
            raise osv.except_osv(_('Error'), _('You are not able to create payroll entries.'))
306
 
        if not vals.get('funding_pool_id', False):
307
 
            try:
308
 
                fp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_msf_private_funds')[1]
309
 
            except ValueError:
310
 
                fp_id = 0
311
 
            if fp_id:
312
 
                vals.update({'funding_pool_id': fp_id,})
313
 
        return super(osv.osv, self).create(cr, uid, vals, context)
314
 
 
315
 
hr_payroll()
316
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: