1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5.Document.Invoice import Invoice
from Products.ERP5Type.Utils import cartesianProduct
from zLOG import LOG, DEBUG, INFO
#XXX TODO: review naming of new methods
#XXX WARNING: current API naming may change although model should be stable.
class PaySheetTransaction(Invoice):
"""
A paysheet will store data about the salary of an employee
"""
meta_type = 'ERP5 Pay Sheet Transaction'
portal_type = 'Pay Sheet Transaction'
add_permission = Permissions.AddPortalContent
isPortalContent = 1
isRADContent = 1
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Default Properties
property_sheets = ( PropertySheet.Base
, PropertySheet.SimpleItem
, PropertySheet.CategoryCore
, PropertySheet.Task
, PropertySheet.Arrow
, PropertySheet.Delivery
, PropertySheet.PaySheet
, PropertySheet.Movement
, PropertySheet.Amount
, PropertySheet.XMLObject
, PropertySheet.TradeCondition
, PropertySheet.DefaultAnnotationLine
)
# Declarative Interface
__implements__ = ( )
security.declareProtected(Permissions.AccessContentsInformation,
'getRatioQuantityFromReference')
def getRatioQuantityFromReference(self, ratio_reference=None):
"""
return the ratio value correponding to the ratio_reference,
None if ratio_reference not found
"""
# get ratio lines
portal_type_list = ['Pay Sheet Model Ratio Line']
object_ratio_list = self.contentValues(portal_type=portal_type_list)
# look for ratio lines on the paysheet
if object_ratio_list:
for obj in object_ratio_list:
if obj.getReference() == ratio_reference:
return obj.getQuantity()
# if not find in the paysheet, look on dependence tree
sub_object_list = self.getInheritedObjectValueList(portal_type_list)
object_ratio_list = sub_object_list
for object in object_ratio_list:
if object.getReference() == ratio_reference:
return object.getQuantity()
return None
security.declareProtected(Permissions.AccessContentsInformation,
'getRatioQuantityList')
def getRatioQuantityList(self, ratio_reference_list):
"""
Return a list of reference_ratio_list correponding values.
reference_ratio_list is a list of references to the ratio lines
we want to get.
"""
if not isinstance(ratio_reference_list, (list, tuple)):
return [self.getRatioQuantityFromReference(ratio_reference_list)]
return [self.getRatioQuantityFromReference(reference) \
for reference in ratio_reference_list]
security.declareProtected(Permissions.AccessContentsInformation,
'getRatioQuantityFromReference')
def getAnnotationLineFromReference(self, reference=None):
"""Return the annotation line corresponding to the reference.
Returns None if reference not found
"""
# look for annotation lines on the paysheet
annotation_line_list = self.contentValues(portal_type=['Annotation Line'])
if annotation_line_list:
for annotation_line in annotation_line_list:
if annotation_line.getReference() == reference:
return annotation_line
# if not find in the paysheet, look on dependence tree
for annotation_line in self.getInheritedObjectValueList(['Annotation Line']):
if annotation_line.getReference() == reference:
return annotation_line
return None
security.declareProtected(Permissions.AccessContentsInformation,
'getRatioQuantityList')
def getAnnotationLineListList(self, reference_list):
"""Return a list of annotation lines corresponding to the reference_list
reference_list is a list of references to the Annotation Line we want
to get.
"""
if not isinstance(reference_list, (list, tuple)):
return [self.getAnnotationLineFromReference(reference_list)]
return [self.getAnnotationLineFromReference(reference) \
for reference in reference_list]
security.declareProtected(Permissions.AddPortalContent,
'createPaySheetLine')
def createPaySheetLine(self, cell_list, title='', resource='',
description='', base_amount_list=None, int_index=None,
categories=None, **kw):
'''
This function register all paysheet informations in paysheet lines and
cells. Select good cells only
'''
if not resource:
raise ValueError, "Cannot create Pay Sheet Line without resource"
good_cell_list = []
for cell in cell_list:
if cell['quantity'] or cell['price']:
good_cell_list.append(cell)
if len(good_cell_list) == 0:
return
# Get all variation categories used in cell_list
var_cat_list = []
for cell in good_cell_list:
# Don't add a variation category if already in it
for category in cell['category_list']:
if category not in var_cat_list:
var_cat_list.append(category)
resource_value = self.getPortalObject().unrestrictedTraverse(resource)
# Add a new Pay Sheet Line
payline = self.newContent(
portal_type='Pay Sheet Line',
title=title,
description=description,
destination=self.getSourceSection(),
resource_value=resource_value,
destination_section=self.getDestinationSection(),
variation_base_category_list=('tax_category',
'salary_range'),
variation_category_list=var_cat_list,
base_amount_list=base_amount_list,
int_index=int_index,
**kw)
# add cells categories to the Pay Sheet Line
# it's a sort of inheritance of sub-object data
if categories:
categories_list = payline.getCategoryList()
categories_list.extend(categories)
# XXX editing categories directly is wrong !
payline.edit(categories=categories_list)
base_id = 'movement'
a = payline.updateCellRange(base_id=base_id)
# create cell_list
for cell in good_cell_list:
paycell = payline.newCell(base_id=base_id, *cell['category_list'])
paycell.edit(mapped_value_property_list=('price', 'quantity'),
force_update=1,
**cell)
return payline
security.declareProtected(Permissions.AccessContentsInformation,
'getEditableModelLineAsDict')
def getEditableModelLineAsDict(self, listbox, paysheet):
'''
listbox is composed by one line for each slice of editables model_lines
this script will return editable model lines as a dict with the
properties that could/have be modified.
'''
portal = paysheet.getPortalObject()
model_line_dict = {}
for line in listbox:
model_line_url = line['model_line']
model_line = portal.restrictedTraverse(model_line_url)
salary_range_relative_url=line['salary_range_relative_url']
if salary_range_relative_url == '':
salary_range_relative_url='no_slice'
# if this is the first slice of the model_line, create the dict
if not model_line_dict.has_key(model_line_url):
model_line_dict[model_line_url] = {'int_index' :\
model_line.getIntIndex()}
model_line_dict[model_line_url][salary_range_relative_url] = {}
slice_dict = model_line_dict[model_line_url][salary_range_relative_url]
for tax_category in model_line.getTaxCategoryList():
if line.has_key('%s_quantity' % tax_category) and \
line.has_key('%s_price' % tax_category):
slice_dict[tax_category] = dict(
quantity=line['%s_quantity' % tax_category],
price=line['%s_price' % tax_category],)
else:
LOG('ERP5', INFO, 'No attribute %s_quantity or %s_price for model_line %s' %
( tax_category, tax_category, model_line_url ))
return model_line_dict
security.declareProtected(Permissions.AccessContentsInformation,
'getNotEditableModelLineAsDict')
def getNotEditableModelLineAsDict(self, paysheet):
'''
return the not editable lines as dict
'''
model = paysheet.getSpecialiseValue()
def sortByIntIndex(a, b):
return cmp(a.getIntIndex(), b.getIntIndex())
# get model lines
portal_type_list = ['Pay Sheet Model Line']
sub_object_list = paysheet.getInheritedObjectValueList(portal_type_list)
sub_object_list.sort(sortByIntIndex)
model_line_list = sub_object_list
model_line_dict = {}
for model_line in model_line_list:
model_line_url = model_line.getRelativeUrl()
cell_list = model_line.contentValues(portal_type='Pay Sheet Cell')
for cell in cell_list:
salary_range_relative_url = \
cell.getVariationCategoryList(base_category_list='salary_range')
tax_category = cell.getTaxCategory()
if len(salary_range_relative_url):
salary_range_relative_url = salary_range_relative_url[0]
else:
salary_range_relative_url = 'no_slice'
# if this is the first slice of the model_line, create the dict
if not model_line_dict.has_key(model_line_url):
model_line_dict[model_line_url] = {'int_index' :\
model_line.getIntIndex()}
model_line_dict[model_line_url][salary_range_relative_url] = {}
slice_dict = model_line_dict[model_line_url][salary_range_relative_url]
slice_dict[tax_category] = dict(quantity=cell.getQuantity(),
price=cell.getPrice())
return model_line_dict
security.declareProtected(Permissions.ModifyPortalContent,
'createPaySheetLineList')
def createPaySheetLineList(self, listbox=None, batch_mode=0, **kw):
'''Create all Pay Sheet Lines (editable or not)
parameters :
- batch_mode :if batch_mode is enabled (=1) then there is no preview view,
and editable lines are considered as not editable lines.
This is usefull to generate all PaySheet of a company.
Modification values can be made on each paysheet after, by
using the "Calculation of the Pay Sheet Transaction"
action button. (concerned model lines must be editable)
'''
paysheet = self
if not batch_mode and listbox is not None:
model_line_dict = paysheet.getEditableModelLineAsDict(listbox=listbox,
paysheet=paysheet)
# Get Precision
precision = paysheet.getPriceCurrencyValue().getQuantityPrecision()
# in this dictionary will be saved the current amount corresponding to
# the tuple (tax_category, base_amount) :
# current_amount = base_amount_dict[base_amount][share]
base_amount_dict = {}
model = paysheet.getSpecialiseValue()
def sortByIntIndex(a, b):
return cmp(a.getIntIndex(), b.getIntIndex())
# XXX should this be recursive ? then should membership be strict
base_amount_list = paysheet.portal_categories['base_amount'].contentValues()
base_amount_list.sort(sortByIntIndex)
# get model lines
portal_type_list = ['Pay Sheet Model Line']
sub_object_list = paysheet.getInheritedObjectValueList(portal_type_list)
sub_object_list.sort(sortByIntIndex)
model_line_list = sub_object_list
pay_sheet_line_list = []
# main loop : find all informations and create cell and PaySheetLines
for model_line in model_line_list:
cell_list = []
# test with predicate if this model line could be applied
if not model_line.test(paysheet,):
# This model_line should not be applied
LOG('ERP5', DEBUG, 'createPaySheetLineList: Model Line %s (%s) will'
' not be applied, because predicates does not match' %
( model_line.getTitle(), model_line.getRelativeUrl() ))
continue
service = model_line.getResourceValue()
if service is None:
raise ValueError, 'Model Line %s has no resource' % (
model_line.getRelativeUrl())
title = model_line.getTitleOrId()
int_index = model_line.getFloatIndex()
base_amount_list = model_line.getBaseAmountList()
resource = service.getRelativeUrl()
# get the service provider, either on the model line, or using the
# annotation line reference.
source_section = None
source_annotation_line_reference = \
model_line.getSourceAnnotationLineReference()
if model_line.getSource():
source_section = model_line.getSource()
elif source_annotation_line_reference:
for annotation_line in paysheet.contentValues(
portal_type='Annotation Line'):
annotation_line_reference = annotation_line.getReference() \
or annotation_line.getId()
if annotation_line_reference == source_annotation_line_reference \
and annotation_line.getSource():
source_section = annotation_line.getSource()
break
if model_line.getDescription():
desc = model_line.getDescription()
# if the model_line description is empty, the payroll service
# description is used
else:
desc = service.getDescription()
base_category_list = model_line.getVariationBaseCategoryList()
category_list_list = []
for base_cat in base_category_list:
category_list = model_line.getVariationCategoryList(
base_category_list=base_cat)
category_list_list.append(category_list)
cartesian_product = cartesianProduct(category_list_list)
share = None
slice = 'no_slice'
indice = 0
categories = []
for cell_coordinates in cartesian_product:
indice += 1
cell = model_line.getCell(*cell_coordinates)
if cell is None:
LOG('ERP5', INFO, "Can't find the cell corresponding to those cells"
" coordinates : %s" % cell_coordinates)
# XXX is it enough to log ?
continue
if len(cell.getVariationCategoryList(\
base_category_list='tax_category')):
share = cell.getVariationCategoryList(\
base_category_list='tax_category')[0]
if len(cell.getVariationCategoryList(\
base_category_list='salary_range')):
slice = cell.getVariationCategoryList(\
base_category_list='salary_range')[0]
# get the edited values if this model_line is editable
# and replace the original cell values by this ones
if model_line.isEditable() and not batch_mode:
tax_category = cell.getTaxCategory()
# get the dict who contain modified values
line_dict = model_line_dict[model_line.getRelativeUrl()]
def getModifiedCell(cell, slice_dict, tax_category):
'''
return a cell with the modified values (contained in slice_dict)
'''
if slice_dict:
if slice_dict.has_key(tax_category):
if slice_dict[tax_category].has_key('quantity'):
cell = cell.asContext(\
quantity=slice_dict[tax_category]['quantity'])
if slice_dict[tax_category].has_key('price'):
cell = cell.asContext(price=slice_dict[tax_category]['price'])
return cell
cell = getModifiedCell(cell, line_dict[slice], tax_category)
# get the slice :
model_slice = model_line.getParentValue().getCell(slice)
quantity = 0.0
price = 0.0
model_slice_min = 0
model_slice_max = 0
if model_slice is None:
pass # that's not a problem :)
else:
model_slice_min = model_slice.getQuantityRangeMin()
model_slice_max = model_slice.getQuantityRangeMax()
######################
# calculation part : #
######################
# get script in this order
# 1 - model_line script
# 2 - model script
# 3 - get the default calculation script
# get the model line script
script_name = model_line.getCalculationScriptId()
if script_name is None:
# if model line script is None, get the default model script
script_name = model.getDefaultCalculationScriptId()
if script_name is None:
# if no calculation script found, use a default script :
script_name = 'PaySheetTransaction_defaultCalculationScript'
if getattr(paysheet, script_name, None) is None:
raise ValueError, "Unable to find `%s` calculation script" % \
script_name
calculation_script = getattr(paysheet, script_name, None)
quantity=0
price=0
cell_dict = calculation_script(base_amount_dict=base_amount_dict,
cell=cell,)
cell_dict.update({'category_list': cell_coordinates})
if cell_dict.has_key('categories'):
for cat in cell_dict['categories']:
if cat not in categories:
categories.append(cat)
quantity = cell_dict['quantity']
price = cell_dict['price']
if quantity and price:
cell_list.append(cell_dict)
# update the base_participation
base_participation_list = service.getBaseAmountList(base=1)
for base_participation in base_participation_list:
if quantity:
if base_amount_dict.has_key(base_participation) and \
base_amount_dict[base_participation].has_key(share):
old_val = base_amount_dict[base_participation][share]
else:
old_val = 0
new_val = old_val + quantity
if not base_amount_dict.has_key(base_participation):
base_amount_dict[base_participation]={}
if price:
new_val = round((old_val + quantity*price), precision)
base_amount_dict[base_participation][share] = new_val
if cell_list:
# create the PaySheetLine
pay_sheet_line = paysheet.createPaySheetLine(
title=title,
resource=resource,
source_section=source_section,
int_index=int_index,
desc=desc,
base_amount_list=base_amount_list,
cell_list=cell_list,
categories=categories)
pay_sheet_line_list.append(pay_sheet_line)
# this script is used to add a line that permit to have good accounting
# lines
post_calculation_script = paysheet._getTypeBasedMethod('postCalculation')
if post_calculation_script:
post_calculation_script()
return pay_sheet_line_list
def getInheritedObjectValueList(self, portal_type_list, property_list=()):
'''Return a list of all subobjects of the herited model (incuding the
dependencies).
If property_list is provided, only subobjects with at least one of those
properties is defined will be taken into account
'''
model = self.getSpecialiseValue()
model_reference_dict = model.getInheritanceModelReferenceDict(
portal_type_list=portal_type_list,
property_list=property_list)
sub_object_list = []
traverse = self.getPortalObject().unrestrictedTraverse
for model_url, id_list in model_reference_dict.items():
model = traverse(model_url)
sub_object_list.extend([model._getOb(x) for x in id_list])
return sub_object_list