hr_leave_accrual_plan_level.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import datetime
  4. import calendar
  5. from dateutil.relativedelta import relativedelta
  6. from odoo import _, api, fields, models
  7. from odoo.tools.date_utils import get_timedelta
  8. DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
  9. MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
  10. # Used for displaying the days and reversing selection -> integer
  11. DAY_SELECT_VALUES = [str(i) for i in range(1, 29)] + ['last']
  12. DAY_SELECT_SELECTION_NO_LAST = tuple(zip(DAY_SELECT_VALUES, (str(i) for i in range(1, 29))))
  13. def _get_selection_days(self):
  14. return DAY_SELECT_SELECTION_NO_LAST + (("last", _("last day")),)
  15. class AccrualPlanLevel(models.Model):
  16. _name = "hr.leave.accrual.level"
  17. _description = "Accrual Plan Level"
  18. _order = 'sequence asc'
  19. sequence = fields.Integer(
  20. string='sequence', compute='_compute_sequence', store=True,
  21. help='Sequence is generated automatically by start time delta.')
  22. accrual_plan_id = fields.Many2one('hr.leave.accrual.plan', "Accrual Plan", required=True)
  23. start_count = fields.Integer(
  24. "Start after",
  25. help="The accrual starts after a defined period from the allocation start date. This field defines the number of days, months or years after which accrual is used.", default="1")
  26. start_type = fields.Selection(
  27. [('day', 'day(s)'),
  28. ('month', 'month(s)'),
  29. ('year', 'year(s)')],
  30. default='day', string=" ", required=True,
  31. help="This field defines the unit of time after which the accrual starts.")
  32. is_based_on_worked_time = fields.Boolean("Based on worked time",
  33. help="If checked, the rate will be prorated on time off type where type is set on Working Time in the configuration.")
  34. # Accrue of
  35. added_value = fields.Float(
  36. "Rate", digits=(16, 5), required=True,
  37. help="The number of hours/days that will be incremented in the specified Time Off Type for every period")
  38. added_value_type = fields.Selection(
  39. [('days', 'Days'),
  40. ('hours', 'Hours')],
  41. default='days', required=True)
  42. frequency = fields.Selection([
  43. ('daily', 'Daily'),
  44. ('weekly', 'Weekly'),
  45. ('bimonthly', 'Twice a month'),
  46. ('monthly', 'Monthly'),
  47. ('biyearly', 'Twice a year'),
  48. ('yearly', 'Yearly'),
  49. ], default='daily', required=True, string="Frequency")
  50. week_day = fields.Selection([
  51. ('mon', 'Monday'),
  52. ('tue', 'Tuesday'),
  53. ('wed', 'Wednesday'),
  54. ('thu', 'Thursday'),
  55. ('fri', 'Friday'),
  56. ('sat', 'Saturday'),
  57. ('sun', 'Sunday'),
  58. ], default='mon', required=True, string="Allocation on")
  59. first_day = fields.Integer(default=1)
  60. first_day_display = fields.Selection(
  61. _get_selection_days, compute='_compute_days_display', inverse='_inverse_first_day_display')
  62. second_day = fields.Integer(default=15)
  63. second_day_display = fields.Selection(
  64. _get_selection_days, compute='_compute_days_display', inverse='_inverse_second_day_display')
  65. first_month_day = fields.Integer(default=1)
  66. first_month_day_display = fields.Selection(
  67. _get_selection_days, compute='_compute_days_display', inverse='_inverse_first_month_day_display')
  68. first_month = fields.Selection([
  69. ('jan', 'January'),
  70. ('feb', 'February'),
  71. ('mar', 'March'),
  72. ('apr', 'April'),
  73. ('may', 'May'),
  74. ('jun', 'June'),
  75. ], default="jan")
  76. second_month_day = fields.Integer(default=1)
  77. second_month_day_display = fields.Selection(
  78. _get_selection_days, compute='_compute_days_display', inverse='_inverse_second_month_day_display')
  79. second_month = fields.Selection([
  80. ('jul', 'July'),
  81. ('aug', 'August'),
  82. ('sep', 'September'),
  83. ('oct', 'October'),
  84. ('nov', 'November'),
  85. ('dec', 'December')
  86. ], default="jul")
  87. yearly_month = fields.Selection([
  88. ('jan', 'January'),
  89. ('feb', 'February'),
  90. ('mar', 'March'),
  91. ('apr', 'April'),
  92. ('may', 'May'),
  93. ('jun', 'June'),
  94. ('jul', 'July'),
  95. ('aug', 'August'),
  96. ('sep', 'September'),
  97. ('oct', 'October'),
  98. ('nov', 'November'),
  99. ('dec', 'December')
  100. ], default="jan")
  101. yearly_day = fields.Integer(default=1)
  102. yearly_day_display = fields.Selection(
  103. _get_selection_days, compute='_compute_days_display', inverse='_inverse_yearly_day_display')
  104. maximum_leave = fields.Float(
  105. 'Limit to', required=False, default=100,
  106. help="Choose a cap for this accrual. 0 means no cap.")
  107. parent_id = fields.Many2one(
  108. 'hr.leave.accrual.level', string="Previous Level",
  109. help="If this field is empty, this level is the first one.")
  110. action_with_unused_accruals = fields.Selection(
  111. [('postponed', 'Transferred to the next year'),
  112. ('lost', 'Lost')],
  113. string="At the end of the calendar year, unused accruals will be",
  114. default='postponed', required='True')
  115. postpone_max_days = fields.Integer("Maximum amount of accruals to transfer",
  116. help="Set a maximum of days an allocation keeps at the end of the year. 0 for no limit.")
  117. _sql_constraints = [
  118. ('check_dates',
  119. "CHECK( (frequency = 'daily') or"
  120. "(week_day IS NOT NULL AND frequency = 'weekly') or "
  121. "(first_day > 0 AND second_day > first_day AND first_day <= 31 AND second_day <= 31 AND frequency = 'bimonthly') or "
  122. "(first_day > 0 AND first_day <= 31 AND frequency = 'monthly')or "
  123. "(first_month_day > 0 AND first_month_day <= 31 AND second_month_day > 0 AND second_month_day <= 31 AND frequency = 'biyearly') or "
  124. "(yearly_day > 0 AND yearly_day <= 31 AND frequency = 'yearly'))",
  125. "The dates you've set up aren't correct. Please check them."),
  126. ('start_count_check', "CHECK( start_count >= 0 )", "You can not start an accrual in the past."),
  127. ('added_value_greater_than_zero', 'CHECK(added_value > 0)', 'You must give a rate greater than 0 in accrual plan levels.')
  128. ]
  129. @api.depends('start_count', 'start_type')
  130. def _compute_sequence(self):
  131. # Not 100% accurate because of odd months/years, but good enough
  132. start_type_multipliers = {
  133. 'day': 1,
  134. 'month': 30,
  135. 'year': 365,
  136. }
  137. for level in self:
  138. level.sequence = level.start_count * start_type_multipliers[level.start_type]
  139. @api.depends('first_day', 'second_day', 'first_month_day', 'second_month_day', 'yearly_day')
  140. def _compute_days_display(self):
  141. days_select = _get_selection_days(self)
  142. for level in self:
  143. level.first_day_display = days_select[min(level.first_day - 1, 28)][0]
  144. level.second_day_display = days_select[min(level.second_day - 1, 28)][0]
  145. level.first_month_day_display = days_select[min(level.first_month_day - 1, 28)][0]
  146. level.second_month_day_display = days_select[min(level.second_month_day - 1, 28)][0]
  147. level.yearly_day_display = days_select[min(level.yearly_day - 1, 28)][0]
  148. def _inverse_first_day_display(self):
  149. for level in self:
  150. if level.first_day_display == 'last':
  151. level.first_day = 31
  152. else:
  153. level.first_day = DAY_SELECT_VALUES.index(level.first_day_display) + 1
  154. def _inverse_second_day_display(self):
  155. for level in self:
  156. if level.second_day_display == 'last':
  157. level.second_day = 31
  158. else:
  159. level.second_day = DAY_SELECT_VALUES.index(level.second_day_display) + 1
  160. def _inverse_first_month_day_display(self):
  161. for level in self:
  162. if level.first_month_day_display == 'last':
  163. level.first_month_day = 31
  164. else:
  165. level.first_month_day = DAY_SELECT_VALUES.index(level.first_month_day_display) + 1
  166. def _inverse_second_month_day_display(self):
  167. for level in self:
  168. if level.second_month_day_display == 'last':
  169. level.second_month_day = 31
  170. else:
  171. level.second_month_day = DAY_SELECT_VALUES.index(level.second_month_day_display) + 1
  172. def _inverse_yearly_day_display(self):
  173. for level in self:
  174. if level.yearly_day_display == 'last':
  175. level.yearly_day = 31
  176. else:
  177. level.yearly_day = DAY_SELECT_VALUES.index(level.yearly_day_display) + 1
  178. def _get_next_date(self, last_call):
  179. """
  180. Returns the next date with the given last call
  181. """
  182. self.ensure_one()
  183. if self.frequency == 'daily':
  184. return last_call + relativedelta(days=1)
  185. elif self.frequency == 'weekly':
  186. daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
  187. weekday = daynames.index(self.week_day)
  188. return last_call + relativedelta(days=1, weekday=weekday)
  189. elif self.frequency == 'bimonthly':
  190. first_date = last_call + relativedelta(day=self.first_day)
  191. second_date = last_call + relativedelta(day=self.second_day)
  192. if last_call < first_date:
  193. return first_date
  194. elif last_call < second_date:
  195. return second_date
  196. else:
  197. return last_call + relativedelta(months=1, day=self.first_day)
  198. elif self.frequency == 'monthly':
  199. date = last_call + relativedelta(day=self.first_day)
  200. if last_call < date:
  201. return date
  202. else:
  203. return last_call + relativedelta(months=1, day=self.first_day)
  204. elif self.frequency == 'biyearly':
  205. first_month = MONTHS.index(self.first_month) + 1
  206. second_month = MONTHS.index(self.second_month) + 1
  207. first_date = last_call + relativedelta(month=first_month, day=self.first_month_day)
  208. second_date = last_call + relativedelta(month=second_month, day=self.second_month_day)
  209. if last_call < first_date:
  210. return first_date
  211. elif last_call < second_date:
  212. return second_date
  213. else:
  214. return last_call + relativedelta(years=1, month=first_month, day=self.first_month_day)
  215. elif self.frequency == 'yearly':
  216. month = MONTHS.index(self.yearly_month) + 1
  217. date = last_call + relativedelta(month=month, day=self.yearly_day)
  218. if last_call < date:
  219. return date
  220. else:
  221. return last_call + relativedelta(years=1, month=month, day=self.yearly_day)
  222. else:
  223. return False
  224. def _get_previous_date(self, last_call):
  225. """
  226. Returns the date a potential previous call would have been at
  227. For example if you have a monthly level giving 16/02 would return 01/02
  228. Contrary to `_get_next_date` this function will return the 01/02 if that date is given
  229. """
  230. self.ensure_one()
  231. if self.frequency == 'daily':
  232. return last_call
  233. elif self.frequency == 'weekly':
  234. daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
  235. weekday = daynames.index(self.week_day)
  236. return last_call + relativedelta(days=-6, weekday=weekday)
  237. elif self.frequency == 'bimonthly':
  238. second_date = last_call + relativedelta(day=self.second_day)
  239. first_date = last_call + relativedelta(day=self.first_day)
  240. if last_call >= second_date:
  241. return second_date
  242. elif last_call >= first_date:
  243. return first_date
  244. else:
  245. return last_call + relativedelta(months=-1, day=self.second_day)
  246. elif self.frequency == 'monthly':
  247. date = last_call + relativedelta(day=self.first_day)
  248. if last_call >= date:
  249. return date
  250. else:
  251. return last_call + relativedelta(months=-1, day=self.first_day)
  252. elif self.frequency == 'biyearly':
  253. first_month = MONTHS.index(self.first_month) + 1
  254. second_month = MONTHS.index(self.second_month) + 1
  255. first_date = last_call + relativedelta(month=first_month, day=self.first_month_day)
  256. second_date = last_call + relativedelta(month=second_month, day=self.second_month_day)
  257. if last_call >= second_date:
  258. return second_date
  259. elif last_call >= first_date:
  260. return first_date
  261. else:
  262. return last_call + relativedelta(years=-1, month=second_month, day=self.second_month_day)
  263. elif self.frequency == 'yearly':
  264. month = MONTHS.index(self.yearly_month) + 1
  265. year_date = last_call + relativedelta(month=month, day=self.yearly_day)
  266. if last_call >= year_date:
  267. return year_date
  268. else:
  269. return last_call + relativedelta(years=-1, month=month, day=self.yearly_day)
  270. else:
  271. return False