4
4
from pytz import utc
5
5
6
6
from odoo .tools import float_utils
7
-
8
- from odoo .addons .resource .models .resource import ResourceCalendar
9
- from odoo .addons .resource .models .resource_mixin import ROUNDING_FACTOR , ResourceMixin
10
-
11
-
12
- def post_load_hook ():
13
- if not hasattr (ResourceMixin , "get_work_days_data_original" ):
14
- ResourceMixin .get_work_days_data_original = ResourceMixin .get_work_days_data
15
- if not hasattr (ResourceCalendar , "get_work_hours_count_original" ):
16
- ResourceCalendar .get_work_hours_count_original = (
17
- ResourceCalendar .get_work_hours_count
18
- )
19
-
20
- def __new_get_work_days_data (
21
- self ,
22
- from_datetime ,
23
- to_datetime ,
24
- compute_leaves = True ,
25
- calendar = None ,
26
- domain = None ,
27
- ):
28
- """
29
- By default the resource calendar is used, but it can be
30
- changed using the `calendar` argument.
31
-
32
- `domain` is used in order to recognise the leaves to take,
33
- None means default value ('time_type', '=', 'leave')
34
-
35
- Returns a dict {'days': n, 'hours': h} containing the
36
- quantity of working time expressed as days and as hours.
37
- """
38
- resource = self .resource_id
39
- calendar = calendar or self .resource_calendar_id
40
-
41
- # naive datetimes are made explicit in UTC
42
- if not from_datetime .tzinfo :
43
- from_datetime = from_datetime .replace (tzinfo = utc )
44
- if not to_datetime .tzinfo :
45
- to_datetime = to_datetime .replace (tzinfo = utc )
46
-
47
- # total hours per day: retrieve attendances with one extra day margin,
48
- # in order to compute the total hours on the first and last days
49
- from_full = from_datetime - timedelta (days = 1 )
50
- to_full = to_datetime + timedelta (days = 1 )
51
- intervals = calendar ._attendance_intervals (from_full , to_full , resource )
52
- day_total = defaultdict (float )
53
- for start , stop , meta in intervals :
54
- day_total [start .date ()] += self ._get_work_hours (start , stop , meta )
55
-
56
- # actual hours per day
57
- if compute_leaves :
58
- intervals = calendar ._work_intervals (
59
- from_datetime , to_datetime , resource , domain
60
- )
61
- else :
62
- intervals = calendar ._attendance_intervals (
63
- from_datetime , to_datetime , resource
64
- )
7
+ from odoo .tools .float_utils import float_round
8
+
9
+ from odoo .addons .resource .models .resource import (
10
+ ROUNDING_FACTOR ,
11
+ ResourceCalendar ,
12
+ make_aware ,
13
+ partial ,
14
+ )
15
+ from odoo .addons .resource .models .resource_mixin import ResourceMixin
16
+
17
+
18
+ def post_load_hook (): # noqa: C901
19
+
20
+ # Functions to override
21
+ resource_functions = [
22
+ (ResourceCalendar , "_get_days_data" ),
23
+ (ResourceCalendar , "_get_resources_day_total" ),
24
+ (ResourceCalendar , "get_work_hours_count" ),
25
+ (ResourceCalendar , "plan_hours" ),
26
+ (ResourceCalendar , "_compute_hours_per_day" ),
27
+ (ResourceMixin , "list_work_time_per_day" ),
28
+ (ResourceMixin , "list_leaves" ),
29
+ ]
30
+
31
+ for func in resource_functions :
32
+ if not hasattr (func [0 ], func [1 ] + "_original" ):
33
+ old_function = getattr (func [0 ], func [1 ])
34
+ setattr (func [0 ], func [1 ] + "_original" , old_function )
35
+
36
+ def __new__get_days_data (self , intervals , day_total ):
65
37
day_hours = defaultdict (float )
66
38
for start , stop , meta in intervals :
67
- day_hours [start .date ()] += self ._get_work_hours (start , stop , meta )
39
+ day_hours [start .date ()] += self ._get_work_hours_interval (start , stop , meta )
68
40
69
41
# compute number of days as quarters
70
42
days = sum (
@@ -77,32 +49,154 @@ def __new_get_work_days_data(
77
49
"hours" : sum (day_hours .values ()),
78
50
}
79
51
80
- def __new_get_work_hours_count (
81
- self , start_dt , end_dt , compute_leaves = True , domain = None
52
+ def __new__get_resources_day_total (
53
+ self , from_datetime , to_datetime , resources = None
82
54
):
83
- """
84
- `compute_leaves` controls whether or not this method is taking into
85
- account the global leaves.
55
+ self .ensure_one ()
56
+ resources = self .env ["resource.resource" ] if not resources else resources
57
+ resources_list = list (resources ) + [self .env ["resource.resource" ]]
58
+ # total hours per day: retrieve attendances with one extra day margin,
59
+ # in order to compute the total hours on the first and last days
60
+ from_full = from_datetime - timedelta (days = 1 )
61
+ to_full = to_datetime + timedelta (days = 1 )
62
+ intervals = self ._attendance_intervals_batch (
63
+ from_full , to_full , resources = resources
64
+ )
86
65
87
- `domain` controls the way leaves are recognized.
88
- None means default value ('time_type', '=', 'leave')
66
+ result = defaultdict (lambda : defaultdict (float ))
67
+ for resource in resources_list :
68
+ day_total = result [resource .id ]
69
+ for start , stop , meta in intervals [resource .id ]:
70
+ day_total [start .date ()] += self ._get_work_hours_interval (
71
+ start , stop , meta
72
+ )
73
+ return result
89
74
90
- Counts the number of work hours between two datetimes.
91
- """
75
+ def __new_get_work_hours_count (
76
+ self , start_dt , end_dt , compute_leaves = True , domain = None
77
+ ):
92
78
# Set timezone in UTC if no timezone is explicitly given
93
79
if not start_dt .tzinfo :
94
80
start_dt = start_dt .replace (tzinfo = utc )
95
81
if not end_dt .tzinfo :
96
82
end_dt = end_dt .replace (tzinfo = utc )
97
83
98
84
if compute_leaves :
99
- intervals = self ._work_intervals (start_dt , end_dt , domain = domain )
85
+ intervals = self ._work_intervals_batch (start_dt , end_dt , domain = domain )[
86
+ False
87
+ ]
100
88
else :
101
- intervals = self ._attendance_intervals (start_dt , end_dt )
89
+ intervals = self ._attendance_intervals_batch (start_dt , end_dt )[ False ]
102
90
103
91
return sum (
104
- self ._get_work_hours (start , stop , meta ) for start , stop , meta in intervals
92
+ self ._get_work_hours_interval (start , stop , meta )
93
+ for start , stop , meta in intervals
105
94
)
106
95
107
- ResourceMixin .get_work_days_data = __new_get_work_days_data
108
- ResourceCalendar .get_work_hours_count = __new_get_work_hours_count
96
+ def __new_plan_hours (
97
+ self , hours , day_dt , compute_leaves = False , domain = None , resource = None
98
+ ):
99
+ day_dt , revert = make_aware (day_dt )
100
+
101
+ # which method to use for retrieving intervals
102
+ if compute_leaves :
103
+ get_intervals = partial (
104
+ self ._work_intervals , domain = domain , resource = resource
105
+ )
106
+ else :
107
+ get_intervals = self ._attendance_intervals
108
+
109
+ if hours >= 0 :
110
+ delta = timedelta (days = 14 )
111
+ for n in range (100 ):
112
+ dt = day_dt + delta * n
113
+ for start , stop , meta in get_intervals (dt , dt + delta ):
114
+ interval_hours = self ._get_work_hours_interval (start , stop , meta )
115
+ if hours <= interval_hours :
116
+ return revert (start + timedelta (hours = hours ))
117
+ hours -= interval_hours
118
+ return False
119
+ else :
120
+ hours = abs (hours )
121
+ delta = timedelta (days = 14 )
122
+ for n in range (100 ):
123
+ dt = day_dt - delta * n
124
+ for start , stop , meta in reversed (get_intervals (dt - delta , dt )):
125
+ interval_hours = self ._get_work_hours_interval (start , stop , meta )
126
+ if hours <= interval_hours :
127
+ return revert (stop - timedelta (hours = hours ))
128
+ hours -= interval_hours
129
+ return False
130
+
131
+ def __new__compute_hours_per_day (self , attendances ):
132
+ if not attendances :
133
+ return 0
134
+
135
+ hour_count = 0.0
136
+ for attendance in attendances :
137
+ hour_count += self ._get_work_hours_attendance (attendance )
138
+
139
+ if self .two_weeks_calendar :
140
+ number_of_days = len (
141
+ set (
142
+ attendances .filtered (lambda cal : cal .week_type == "1" ).mapped (
143
+ "dayofweek"
144
+ )
145
+ )
146
+ )
147
+ number_of_days += len (
148
+ set (
149
+ attendances .filtered (lambda cal : cal .week_type == "0" ).mapped (
150
+ "dayofweek"
151
+ )
152
+ )
153
+ )
154
+ else :
155
+ number_of_days = len (set (attendances .mapped ("dayofweek" )))
156
+
157
+ return float_round (hour_count / float (number_of_days ), precision_digits = 2 )
158
+
159
+ def __new_list_work_time_per_day (
160
+ self , from_datetime , to_datetime , calendar = None , domain = None
161
+ ):
162
+ resource = self .resource_id
163
+ calendar = calendar or self .resource_calendar_id
164
+
165
+ # naive datetimes are made explicit in UTC
166
+ if not from_datetime .tzinfo :
167
+ from_datetime = from_datetime .replace (tzinfo = utc )
168
+ if not to_datetime .tzinfo :
169
+ to_datetime = to_datetime .replace (tzinfo = utc )
170
+
171
+ intervals = calendar ._work_intervals_batch (
172
+ from_datetime , to_datetime , resource , domain
173
+ )[resource .id ]
174
+ result = defaultdict (float )
175
+ for start , stop , meta in intervals :
176
+ result [start .date ()] += self ._get_work_hours_interval (start , stop , meta )
177
+ return sorted (result .items ())
178
+
179
+ def __new_list_leaves (self , from_datetime , to_datetime , calendar = None , domain = None ):
180
+ resource = self .resource_id
181
+ calendar = calendar or self .resource_calendar_id
182
+
183
+ # naive datetimes are made explicit in UTC
184
+ if not from_datetime .tzinfo :
185
+ from_datetime = from_datetime .replace (tzinfo = utc )
186
+ if not to_datetime .tzinfo :
187
+ to_datetime = to_datetime .replace (tzinfo = utc )
188
+
189
+ attendances = calendar ._attendance_intervals_batch (
190
+ from_datetime , to_datetime , resource
191
+ )[resource .id ]
192
+ leaves = calendar ._leave_intervals_batch (
193
+ from_datetime , to_datetime , resource , domain
194
+ )[resource .id ]
195
+ result = []
196
+ for start , stop , leave in leaves & attendances :
197
+ hours = self ._get_work_hours_interval (start , stop , leave )
198
+ result .append ((start .date (), hours , leave ))
199
+ return result
200
+
201
+ for func in resource_functions :
202
+ setattr (func [0 ], func [1 ], locals ()["__new_" + func [1 ]])
0 commit comments