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

634.5.3 by Olivier DOSSMANN
UF-822 [ADD] Payroll entries
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
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
28
from lxml import etree
634.5.17 by Olivier DOSSMANN
UF-822 [ADD] Block payroll entries creation
29
from tools.translate import _
634.5.3 by Olivier DOSSMANN
UF-822 [ADD] Payroll entries
30
31
class hr_payroll(osv.osv):
32
    _name = 'hr.payroll.msf'
33
    _description = 'Payroll'
34
726 by jf
uF-822 UF-824 [DEV] Homere interface
35
    def _get_analytic_state(self, cr, uid, ids, name, args, context=None):
634.5.11 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll lines
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
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
51
        # Browse all given lines to check analytic distribution validity
52
        ## TO CHECK:
1193.12.3 by Olivier DOSSMANN
UF-1469 [DEL] Dummy CC from Unifield
53
        # A/ if no CC
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
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
1380.3.22 by Olivier DOSSMANN
UF-1678 [ADD] Check date and document date for Payrolls
58
        # F/ Check posting date with cost center and destination if exists
59
        # G/ Check document date with funding pool
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
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
        ## 
634.5.11 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll lines
67
        for line in self.browse(cr, uid, ids, context=context):
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
68
            res[line.id] = 'valid' # by default
69
            #### SOME CASE WHERE DISTRO IS OK
1874.4.9 by Olivier DOSSMANN
UTP-944 [ADD] Parce code to find "expense" word in which it needs changes. Do it for:
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:
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
72
                continue
1380.3.22 by Olivier DOSSMANN
UF-1678 [ADD] Check date and document date for Payrolls
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
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
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
1193.12.3 by Olivier DOSSMANN
UF-1469 [DEL] Dummy CC from Unifield
98
            # if no cost center, distro is invalid (CASE A/)
1193.12.1 by Olivier DOSSMANN
[WIP] Delete the use of dummy CC in employee update AND payroll import
99
            if not line.cost_center_id:
100
                    res[line.id] = 'invalid'
101
                    continue
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
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
634.5.11 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll lines
127
        return res
128
726 by jf
uF-822 UF-824 [DEV] Homere interface
129
    def _get_third_parties(self, cr, uid, ids, field_name=None, arg=None, context=None):
634.5.21 by Olivier DOSSMANN
UF-822 [ADD] Third parties for payroll entries
130
        """
131
        Get "Third Parties" following other fields
132
        """
133
        res = {}
634.5.24 by Olivier DOSSMANN
UF-822 [ADD] Improve Employee identification id display for payroll entries
134
        for line in self.browse(cr, uid, ids):
634.5.21 by Olivier DOSSMANN
UF-822 [ADD] Third parties for payroll entries
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
726 by jf
uF-822 UF-824 [DEV] Homere interface
146
    def _get_employee_identification_id(self, cr, uid, ids, field_name=None, arg=None, context=None):
634.5.24 by Olivier DOSSMANN
UF-822 [ADD] Improve Employee identification id display for payroll entries
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
1837.1.2 by jf
UTP-252 [FIX] Triggers to recompute distrib state on payroll
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
634.5.3 by Olivier DOSSMANN
UF-822 [ADD] Payroll entries
194
    _columns = {
634.5.14 by Olivier DOSSMANN
UF-822 [ADD] All fields for payroll entries are readonly except analytic fields
195
        'date': fields.date(string='Date', required=True, readonly=True),
891.1.20 by Olivier DOSSMANN
UF-1089 [ADD] document date on payroll import and validation
196
        'document_date': fields.date(string='Document Date', required=True, readonly=True),
634.5.14 by Olivier DOSSMANN
UF-822 [ADD] All fields for payroll entries are readonly except analytic fields
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),
1282.5.1 by Olivier DOSSMANN
UF-1592 [FIX] Don't permit to delete partner, employee, journal or register if it's used in a register, a journal item, an accrual or a payroll
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"),
634.5.24 by Olivier DOSSMANN
UF-822 [ADD] Improve Employee identification id display for payroll entries
202
        'employee_id_number': fields.function(_get_employee_identification_id, method=True, type='char', size=255, string='Employee ID', readonly=True),
634.5.14 by Olivier DOSSMANN
UF-822 [ADD] All fields for payroll entries are readonly except analytic fields
203
        'name': fields.char(string='Description', size=255, readonly=True),
204
        'ref': fields.char(string='Reference', size=255, readonly=True),
634.5.7 by Olivier DOSSMANN
UF-822 [ADD] Change some payroll entry fields in order not to be modified by users
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),
1193.12.1 by Olivier DOSSMANN
[WIP] Delete the use of dummy CC in employee update AND payroll import
208
        'cost_center_id': fields.many2one('account.analytic.account', string="Cost Center", required=False, domain="[('category','=','OC'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
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')]"),
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
212
        'destination_id': fields.many2one('account.analytic.account', string="Destination", domain="[('category', '=', 'DEST'), ('type', '!=', 'view'), ('state', '=', 'open')]"),
634.5.11 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll lines
213
        'analytic_state': fields.function(_get_analytic_state, type='selection', method=True, readonly=True, string="Distribution State",
1837.1.2 by jf
UTP-252 [FIX] Triggers to recompute distrib state on payroll
214
            selection=[('none', 'None'), ('valid', 'Valid'), ('invalid', 'Invalid')], help="Give analytic distribution state",
1814.6.10 by Olivier DOSSMANN
UTP-252 [ADD] Point 25 - New Invalid button to only display lines that have an invalid analytic distribution
215
            store={
1837.1.2 by jf
UTP-252 [FIX] Triggers to recompute distrib state on payroll
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),
1814.6.10 by Olivier DOSSMANN
UTP-252 [ADD] Point 25 - New Invalid button to only display lines that have an invalid analytic distribution
220
            }
221
        ),
634.5.21 by Olivier DOSSMANN
UF-822 [ADD] Third parties for payroll entries
222
        'partner_type': fields.function(_get_third_parties, type='reference', method=True, string="Third Parties", readonly=True,
1453.9.1 by Olivier DOSSMANN
UF-1706 [DEL] register_id from 3RD parties on register lines and account_move_line
223
            selection=[('res.partner', 'Partner'), ('account.journal', 'Journal'), ('hr.employee', 'Employee')]),
634.5.93 by Olivier DOSSMANN
UF-822 [ADD] Permit to import payroll from some fields
224
        'field': fields.char(string='Field', readonly=True, size=255, help="Field this line come from in Homère."),
634.5.3 by Olivier DOSSMANN
UF-822 [ADD] Payroll entries
225
    }
226
227
    _order = 'employee_id, date desc'
228
229
    _defaults = {
634.5.21 by Olivier DOSSMANN
UF-822 [ADD] Third parties for payroll entries
230
        'date': lambda *a: strftime('%Y-%m-%d'),
891.1.20 by Olivier DOSSMANN
UF-1089 [ADD] document date on payroll import and validation
231
        'document_date': lambda *a: strftime('%Y-%m-%d'),
634.5.3 by Olivier DOSSMANN
UF-822 [ADD] Payroll entries
232
        'state': lambda *a: 'draft',
233
    }
234
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
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)
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
242
        if view_type in ['tree', 'form']:
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
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
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
250
            fields = form.xpath('//field[@name="cost_center_id"]')
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
251
            for field in fields:
850.6.19 by Olivier DOSSMANN
UF-1022 [FIX] Problem of domain for hr.employee
252
                field.set('domain', "[('category', '=', 'OC'), ('type', '!=', 'view'), ('state', '=', 'open'), ('id', 'child_of', [%s])]" % oc_id)
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
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
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
258
            fp_fields = form.xpath('//field[@name="funding_pool_id"]')
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
259
            for field in fp_fields:
827.8.87 by Olivier DOSSMANN
UF-1106 [IMP] Improve HQ Entries views
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)
827.8.44 by Olivier DOSSMANN
UF-1106 [ADD] Analytic distribution on Payrolls
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
634.5.10 by Olivier DOSSMANN
UF-822 [ADD] Add analytic distribution on payroll entries
266
            view['arch'] = etree.tostring(form)
267
        return view
268
827.8.46 by Olivier DOSSMANN
UF-1106 [ADD] Onchange on Destination for payroll entries
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
726 by jf
uF-822 UF-824 [DEV] Homere interface
297
    def create(self, cr, uid, vals, context=None):
634.5.15 by Olivier DOSSMANN
UF-822 [ADD] Create a description for expense account with "Salary Month Year"
298
        """
634.5.43 by Olivier DOSSMANN
UF-822 [ADD] Delete some useless code
299
        Raise an error if creation don't become from an import or a YAML.
634.5.45 by Olivier DOSSMANN
UF-822 [ADD] Default analytic distribution for new payroll entries
300
        Add default analytic distribution for those that doesn't have anyone.
634.5.15 by Olivier DOSSMANN
UF-822 [ADD] Create a description for expense account with "Salary Month Year"
301
        """
302
        if not context:
303
            context = {}
634.5.17 by Olivier DOSSMANN
UF-822 [ADD] Block payroll entries creation
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.'))
634.5.45 by Olivier DOSSMANN
UF-822 [ADD] Default analytic distribution for new 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]
726 by jf
uF-822 UF-824 [DEV] Homere interface
309
            except ValueError:
634.5.45 by Olivier DOSSMANN
UF-822 [ADD] Default analytic distribution for new payroll entries
310
                fp_id = 0
311
            if fp_id:
312
                vals.update({'funding_pool_id': fp_id,})
634.5.15 by Olivier DOSSMANN
UF-822 [ADD] Create a description for expense account with "Salary Month Year"
313
        return super(osv.osv, self).create(cr, uid, vals, context)
314
634.5.3 by Olivier DOSSMANN
UF-822 [ADD] Payroll entries
315
hr_payroll()
316
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: