class TimeEntrySchema(colander.Schema): title = colander.SchemaNode( colander.String(), title=_("Title"), missing="", ) tariff_uid = colander.SchemaNode( colander.String(), title=_("Tariff"), missing="", widget=selectable_tariff_radio, ) created = colander.SchemaNode( LocalDateTime(), missing=None, widget=deform.widget.DateTimeInputWidget(time_options={'interval': 5}), title=_("Start time")) stop_time = colander.SchemaNode( LocalDateTime(), missing=None, widget=deform.widget.DateTimeInputWidget(time_options={'interval': 5}), title=_("End time")) bill_hours = colander.SchemaNode(colander.Decimal(), missing=None, title=_("Bill hours"))
class TariffSchema(colander.Schema): title = colander.SchemaNode(colander.String(), title=_("Title")) price = colander.SchemaNode(colander.Decimal(), title=_("Price"), default=0) currency = colander.SchemaNode(colander.String(), title=_("Currency"), missing="") vat = colander.SchemaNode(colander.Int(), title=_("VAT in integer percentage"), default=0)
class ProjectSchema(BaseOrgSchema): trello_board = colander.SchemaNode( colander.String(), title=_('Trello board '), widget=trello_board_widget, missing="", )
class Project(Content): type_name = "Project" type_title = _("Project") add_permission = "Add %s" % type_name css_icon = "glyphicon glyphicon-road" blob_key = 'image' trello_board = "" def other_used_cards(self, task=None): other_tasks = filter(lambda t: t != task, self.tasks_with_card()) return [t.trello_card for t in other_tasks] @property def used_cards(self): return self.other_used_cards() def tasks_with_card(self): return filter(lambda t: isinstance(t, Task) and t.trello_card, self.values()) @property def image_data(self): blobs = IBlobs(self, None) if blobs: return blobs.formdata_dict(self.blob_key) @image_data.setter def image_data(self, value): IBlobs(self).create_from_formdata(self.blob_key, value) def consumed_hours(self, only_unbilled=True): pass
class TaskSchema(colander.Schema): title = colander.SchemaNode(colander.String(), title=_("Title")) description = colander.SchemaNode( colander.String(), title=_("Description"), widget=deform.widget.TextAreaWidget(rows=3), missing="") trello_card = colander.SchemaNode( colander.String(), title=_('Trello card'), missing='', widget=trello_card_widget, ) tags = colander.SchemaNode(colander.List(), title=_("Tags or subjects"), missing="", widget=tagging_widget)
class BaseOrgSchema(colander.Schema): title = colander.SchemaNode(colander.String(), title=_("Title")) description = colander.SchemaNode( colander.String(), title=_("Description"), widget=deform.widget.TextAreaWidget(rows=3), missing=u"") tags = colander.SchemaNode(colander.List(), title=_("Tags or subjects"), missing="", widget=tagging_widget) image_data = colander.SchemaNode(deform.FileData(), missing=None, title=_(u"Image"), blob_key='image', validator=supported_thumbnail_mimetype, widget=FileAttachmentWidget())
class Tariff(Base): type_name = "Tariff" type_title = _("Tariff") add_permission = "Add %s" % type_name css_icon = "glyphicon glyphicon-usd" title = "" price = None currency = "" vat = None
def selectable_tariff_radio(node, kw): values = [('', _("N/A"))] context = kw['context'] while context.__parent__: if IFolder.providedBy(context): for obj in context.values(): if ITariff.providedBy(obj): values.append((obj.uid, obj.title)) context = context.__parent__ return deform.widget.RadioChoiceWidget(values=values)
class Customer(Content): type_name = "Customer" type_title = _("Customer") add_permission = "Add %s" % type_name css_icon = "glyphicon glyphicon-briefcase" blob_key = 'image' @property def image_data(self): blobs = IBlobs(self, None) if blobs: return blobs.formdata_dict(self.blob_key) @image_data.setter def image_data(self, value): IBlobs(self).create_from_formdata(self.blob_key, value)
class Task(Content): type_name = "Task" type_title = _("Task") add_permission = "Add %s" % type_name css_icon = "glyphicon glyphicon-tasks" trello_card = '' card_estimated_hours = None card_consumed_hours = None def get_time_entries(self, only_unbilled=False): results = [] for obj in self.values(): if ITimeEntry.providedBy(obj) and not (only_unbilled and obj.billed): results.append(obj) return sorted(results, key=lambda x: x.start_time, reverse=True) @property def total_time(self): entries = self.get_time_entries() if entries: total = timedelta() for t in entries: total += t.timedelta return total @property def consumed_hours(self): # Returns time entry hours, rounded to full half hours return entries_to_consumed_hours(self.get_time_entries()) @property def unbilled_hours(self): # Returns time entry hours, rounded to full half hours return entries_to_consumed_hours( self.get_time_entries(only_unbilled=True)) def __str__(self): consumed_hours = self.consumed_hours return ''.join(( self.card_estimated_hours and '({}) '.format(self.card_estimated_hours) or '', self.title, consumed_hours and ' [{}]'.format(consumed_hours) or '', ))
class TimeEntry(Base): type_name = "TimeEntry" type_title = _("Time entry") add_permission = "Add %s" % type_name css_icon = "glyphicon glyphicon-time" title = "" tariff_uid = "" stop_time = None bill_hours = None billed = False @property def creator(self): return getattr(self, '__creator__', ()) @creator.setter def creator(self, value): if value: self.__creator__ = PersistentList(value) else: if hasattr(self, '__creator__'): delattr(self, '__creator__') @property def start_time(self): #Map to created and set via created. It makes sense to link it to that #attribute and use that catalog index. return self.created @property def timedelta(self): # type: () -> timedelta if self.bill_hours: return timedelta(minutes=int(self.bill_hours * 60)) if self.stop_time is None: # TODO Get real timezone, yo return datetime.now(utc) - self.start_time else: return self.stop_time - self.start_time