first commit
This commit is contained in:
264
modules/sale_subscription/recurrence.py
Normal file
264
modules/sale_subscription/recurrence.py
Normal file
@@ -0,0 +1,264 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import datetime as dt
|
||||
|
||||
from dateutil.rrule import (
|
||||
DAILY, FR, MO, MONTHLY, SA, SU, TH, TU, WE, WEEKLY, YEARLY, rrule,
|
||||
rruleset)
|
||||
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Button, StateView, Wizard
|
||||
|
||||
from .exceptions import RecurrenceRuleValidationError
|
||||
|
||||
WEEKDAYS = {
|
||||
'MO': MO,
|
||||
'TU': TU,
|
||||
'WE': WE,
|
||||
'TH': TH,
|
||||
'FR': FR,
|
||||
'SA': SA,
|
||||
'SU': SU,
|
||||
}
|
||||
FREQUENCIES = {
|
||||
'yearly': YEARLY,
|
||||
'monthly': MONTHLY,
|
||||
'weekly': WEEKLY,
|
||||
'daily': DAILY,
|
||||
}
|
||||
|
||||
|
||||
class RecurrenceRuleSet(ModelSQL, ModelView):
|
||||
__name__ = 'sale.subscription.recurrence.rule.set'
|
||||
|
||||
name = fields.Char(
|
||||
"Name", required=True, translate=True,
|
||||
help="The main identifier of the rule set.")
|
||||
rules = fields.One2Many(
|
||||
'sale.subscription.recurrence.rule', 'set_', "Rules")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('name', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def default_rules(cls):
|
||||
if Transaction().user == 0:
|
||||
return []
|
||||
return [{}]
|
||||
|
||||
def rruleset(self, dtstart):
|
||||
set_ = rruleset(**self._rruleset())
|
||||
for rule in self.rules:
|
||||
if not rule.exclusive:
|
||||
set_.rrule(rule.rrule(dtstart))
|
||||
else:
|
||||
set_.exrule(rule.rrule(dtstart))
|
||||
return set_
|
||||
|
||||
def _rruleset(self):
|
||||
return {}
|
||||
|
||||
|
||||
class RecurrenceRule(ModelSQL, ModelView):
|
||||
__name__ = 'sale.subscription.recurrence.rule'
|
||||
|
||||
set_ = fields.Many2One(
|
||||
'sale.subscription.recurrence.rule.set', "Set",
|
||||
required=True, ondelete='CASCADE',
|
||||
help="Add the rule below the set.")
|
||||
freq = fields.Selection([
|
||||
('yearly', 'Yearly'),
|
||||
('monthly', 'Monthly'),
|
||||
('weekly', 'Weekly'),
|
||||
('daily', 'Daily'),
|
||||
], "Frequency", sort=False, required=True)
|
||||
interval = fields.Integer("Interval", required=True)
|
||||
byweekday = fields.Char(
|
||||
"By Week Day",
|
||||
help="A comma separated list of integers or weekday (MO, TU etc).")
|
||||
bymonthday = fields.Char(
|
||||
"By Month Day",
|
||||
help="A comma separated list of integers.")
|
||||
byyearday = fields.Char(
|
||||
"By Year Day",
|
||||
help="A comma separated list of integers.")
|
||||
byweekno = fields.Char(
|
||||
"By Week Number",
|
||||
help="A comma separated list of integers (ISO8601).")
|
||||
bymonth = fields.Char(
|
||||
"By Month",
|
||||
help="A comma separated list of integers.")
|
||||
bysetpos = fields.Char(
|
||||
"By Position",
|
||||
help="A comma separated list of integers.")
|
||||
week_start_day = fields.Many2One('ir.calendar.day', "Week Start Day")
|
||||
|
||||
exclusive = fields.Boolean(
|
||||
"Exclusive",
|
||||
help="If checked, dates which are part of this recurrence rule "
|
||||
"will not be generated.")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__access__.add('set_')
|
||||
|
||||
@classmethod
|
||||
def default_interval(cls):
|
||||
return 1
|
||||
|
||||
@classmethod
|
||||
def default_exclusive(cls):
|
||||
return False
|
||||
|
||||
def rrule(self, dtstart):
|
||||
return rrule(**self._rrule(dtstart))
|
||||
|
||||
def _rrule(self, dtstart):
|
||||
return {
|
||||
'dtstart': dtstart,
|
||||
'freq': FREQUENCIES[self.freq],
|
||||
'interval': self.interval,
|
||||
'byweekday': self._byweekday,
|
||||
'bymonthday': self._bymonthday,
|
||||
'byyearday': self._byyearday,
|
||||
'byweekno': self._byweekno,
|
||||
'bymonth': self._bymonth,
|
||||
'bysetpos': self._bysetpos,
|
||||
'wkst': self.week_start_day.index if self.week_start_day else None,
|
||||
}
|
||||
|
||||
@property
|
||||
def _byweekday(self):
|
||||
if not self.byweekday:
|
||||
return None
|
||||
byweekday = []
|
||||
for weekday in self.byweekday.split(','):
|
||||
try:
|
||||
weekday = int(weekday)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if 0 <= weekday <= len(WEEKDAYS):
|
||||
byweekday.append(weekday)
|
||||
continue
|
||||
else:
|
||||
raise ValueError('Invalid weekday')
|
||||
try:
|
||||
cls = WEEKDAYS[weekday[:2]]
|
||||
except KeyError:
|
||||
raise ValueError('Invalid weekday')
|
||||
if not weekday[2:]:
|
||||
byweekday.append(cls)
|
||||
else:
|
||||
byweekday.append(cls(int(weekday[3:-1])))
|
||||
return byweekday
|
||||
|
||||
@property
|
||||
def _bymonthday(self):
|
||||
if not self.bymonthday:
|
||||
return None
|
||||
return [int(md) for md in self.bymonthday.split(',')]
|
||||
|
||||
@property
|
||||
def _byyearday(self):
|
||||
if not self.byyearday:
|
||||
return None
|
||||
return [int(yd) for yd in self.byyearday.split(',')]
|
||||
|
||||
@property
|
||||
def _byweekno(self):
|
||||
if not self.byweekno:
|
||||
return None
|
||||
return [int(wn) for wn in self.byweekno.split(',')]
|
||||
|
||||
@property
|
||||
def _bymonth(self):
|
||||
if not self.bymonth:
|
||||
return None
|
||||
return [int(m) for m in self.bymonth.split(',')]
|
||||
|
||||
@property
|
||||
def _bysetpos(self):
|
||||
if not self.bysetpos:
|
||||
return None
|
||||
positions = []
|
||||
for setpos in self.bysetpos.split(','):
|
||||
setpos = int(setpos)
|
||||
if -366 <= setpos <= 366:
|
||||
positions.append(setpos)
|
||||
else:
|
||||
raise ValueError('Invalid setpos')
|
||||
return positions
|
||||
|
||||
def pre_validate(self):
|
||||
for name in ['byweekday', 'bymonthday', 'byyearday', 'byweekno',
|
||||
'bymonth', 'bysetpos']:
|
||||
self.check_by(name)
|
||||
|
||||
def check_by(self, name):
|
||||
try:
|
||||
getattr(self, '_%s' % name)
|
||||
except ValueError as exception:
|
||||
raise RecurrenceRuleValidationError(
|
||||
gettext('sale_subscription.msg_recurrence_rule_invalid_by',
|
||||
value=getattr(self, name),
|
||||
recurrence_rule=self.rec_name,
|
||||
exception=exception,
|
||||
**self.__names__(name))) from exception
|
||||
|
||||
|
||||
class TestRecurrenceRuleSet(Wizard):
|
||||
__name__ = 'sale.subscription.recurrence.rule.set.test'
|
||||
start_state = 'test'
|
||||
test = StateView(
|
||||
'sale.subscription.recurrence.rule.set.test',
|
||||
'sale_subscription.recurrence_rule_set_test_view_form',
|
||||
[Button("Close", 'end', 'tryton-close', default=True)])
|
||||
|
||||
def default_test(self, fields):
|
||||
default = {}
|
||||
if (self.model and self.model.__name__
|
||||
== 'sale.subscription.recurrence.rule.set'):
|
||||
if self.record:
|
||||
default['recurrence'] = self.record.id
|
||||
return default
|
||||
|
||||
|
||||
class TestRecurrenceRuleSetView(ModelView):
|
||||
__name__ = 'sale.subscription.recurrence.rule.set.test'
|
||||
recurrence = fields.Many2One(
|
||||
'sale.subscription.recurrence.rule.set',
|
||||
"Subscription Recurrence", required=True)
|
||||
start_date = fields.Date("Start Date", required=True)
|
||||
count = fields.Integer("Count", required=True,
|
||||
help="Used to determine how many occurences to compute.")
|
||||
result = fields.One2Many(
|
||||
'sale.subscription.recurrence.rule.set.test.result',
|
||||
None, "Result", readonly=True)
|
||||
|
||||
@classmethod
|
||||
def default_start_date(cls):
|
||||
return Pool().get('ir.date').today()
|
||||
|
||||
@fields.depends('recurrence', 'start_date', 'count', 'result')
|
||||
def on_change_with_result(self):
|
||||
pool = Pool()
|
||||
Result = pool.get('sale.subscription.recurrence.rule.set.test.result')
|
||||
result = []
|
||||
if self.recurrence and self.start_date and self.count:
|
||||
rruleset = self.recurrence.rruleset(self.start_date)
|
||||
datetime = dt.datetime.combine(self.start_date, dt.time())
|
||||
for date in rruleset.xafter(datetime, self.count, inc=True):
|
||||
result.append(Result(date=date.date()))
|
||||
return result
|
||||
|
||||
|
||||
class TestRecurrenceRuleSetViewResult(ModelView):
|
||||
__name__ = 'sale.subscription.recurrence.rule.set.test.result'
|
||||
date = fields.Date("Date", readonly=True)
|
||||
Reference in New Issue
Block a user