Пример #1
0
 def __str__(self):
     if self.date_until.year > 3000:
         return _("since %s") % local_date_format(self.date_from)
     return "%s - %s" % (
         local_date_format(self.date_from),
         local_date_format(self.date_until),
     )
Пример #2
0
    def clean(self):
        data = super().clean()

        if data.get("request") and data.get("weeks"):
            outside = [
                week for week in data["weeks"]
                if week < data["request"].earliest_start_on
                or week >= data["request"].completion_requested_on
            ]
            if outside:
                self.add_warning(
                    _("At least one week is outside the requested range of"
                      " %(from)s – %(until)s: %(weeks)s") %
                    {
                        "from":
                        local_date_format(data["request"].earliest_start_on),
                        "until":
                        local_date_format(
                            data["request"].completion_requested_on -
                            dt.timedelta(days=1)),
                        "weeks":
                        ", ".join(local_date_format(week) for week in outside),
                    },
                    code="weeks-outside-request",
                )

        if data.get("offer") and data["offer"].is_declined:
            self.add_warning(_("The selected offer is declined."),
                             code="offer-is-declined")

        return data
Пример #3
0
    def process_invoice(self, invoice):
        title = _("invoice")
        if invoice.type == invoice.DOWN_PAYMENT:
            title = _("down payment invoice")
        elif invoice.type == invoice.CREDIT:
            title = _("credit")

        self.process_services_letter(
            invoice,
            watermark="" if invoice.status in {invoice.SENT, invoice.PAID} else
            str(invoice.get_status_display()),
            details=[
                (
                    title,
                    invoice.code,
                ),
                (
                    _("date"),
                    (local_date_format(invoice.invoiced_on)
                     if invoice.invoiced_on else MarkupParagraph(
                         "<b>%s</b>" %
                         _("NO DATE YET"), style=self.style.bold)),
                ),
                (_("Our reference"), invoice.owned_by.get_full_name()),
                ("MwSt.-Nr.", settings.WORKBENCH.PDF_VAT_NO),
            ],
            footer=(settings.WORKBENCH.PDF_CREDIT if invoice.type == invoice.
                    CREDIT else settings.WORKBENCH.PDF_INVOICE_PAYMENT) % {
                        "code":
                        invoice.code,
                        "due": (local_date_format(invoice.due_on)
                                if invoice.due_on else _("NO DATE YET")),
                    },
        )
Пример #4
0
def list_timestamps(request):
    form = SignedEmailUserForm(request.GET, request=request)
    if not form.is_valid():
        return JsonResponse({"errors": form.errors.as_json()}, status=400)

    user = form.cleaned_data["user"]
    slices = Timestamp.objects.slices(user)
    daily_hours = sum(
        (slice["logged_hours"].hours for slice in slices if slice.get("logged_hours")),
        Z1,
    )
    return JsonResponse(
        {
            "success": True,
            "user": str(user),
            "hours": daily_hours,
            "timestamps": [
                {
                    "timestamp": "{:>5} - {:>5} {:^7} {}".format(
                        local_date_format(slice.get("starts_at"), fmt="H:i") or "?  ",
                        local_date_format(slice.get("ends_at"), fmt="H:i") or "?  ",
                        f"({hours(slice.elapsed_hours, plus_sign=True)})"
                        if slice.elapsed_hours is not None
                        else "?",
                        slice["description"] or "-",
                    ),
                    "elapsed": slice.elapsed_hours,
                    "comment": slice.get("comment", ""),
                }
                for slice in slices
            ],
        }
    )
Пример #5
0
    def ranges(self):
        def _find_ranges(weeks):
            start = weeks[0]
            maybe_end = weeks[0]
            for day in weeks[1:]:
                if (day - maybe_end).days == 7:
                    maybe_end = day
                else:
                    yield start, maybe_end
                    start = maybe_end = day

            yield start, maybe_end

        for from_, until_ in _find_ranges(self.weeks):
            yield {
                "from":
                from_,
                "until":
                until_,
                "pretty":
                "{} – {}".format(
                    local_date_format(from_),
                    local_date_format(until_ + dt.timedelta(days=6)),
                ),
            }
Пример #6
0
    def pretty_status(self):
        d = {
            "invoiced_on": (local_date_format(self.invoiced_on)
                            if self.invoiced_on else None),
            "reminded_on": (local_date_format(self.last_reminded_on)
                            if self.last_reminded_on else None),
            "created_at":
            local_date_format(self.created_at.date()),
            "closed_on":
            (local_date_format(self.closed_on) if self.closed_on else None),
        }

        if self.status == self.IN_PREPARATION:
            return _("In preparation since %(created_at)s") % d
        elif self.status == self.SENT:
            if self.last_reminded_on:
                return _(
                    "Sent on %(invoiced_on)s, reminded on %(reminded_on)s") % d

            if self.due_on and dt.date.today() > self.due_on:
                return _("Sent on %(invoiced_on)s but overdue") % d

            return _("Sent on %(invoiced_on)s") % d
        elif self.status == self.PAID:
            return _("Paid on %(closed_on)s") % d
        else:
            return self.get_status_display()
Пример #7
0
    def test_crud(self):
        """CRUD of deals incl. reopening etc."""
        person = factories.PersonFactory.create(
            organization=factories.OrganizationFactory.create()
        )
        self.client.force_login(person.primary_contact)

        response = self.client.post(
            "/deals/create/",
            {
                "customer": person.organization.id,
                "contact": person.id,
                "title": "Some deal",
                "probability": Deal.NORMAL,
                "owned_by": person.primary_contact_id,
            },
        )
        self.assertEqual(response.status_code, 302)
        deal = Deal.objects.get()
        self.assertIsNone(deal.closed_on)
        self.assertEqual(
            deal.pretty_status,
            "Open since {}".format(local_date_format(dt.date.today())),
        )

        response = self.client.post(
            deal.urls["set_status"] + "?status={}".format(Deal.DECLINED),
            {
                "closing_type": factories.ClosingTypeFactory.create(
                    represents_a_win=False
                ).pk,
            },
        )
        self.assertRedirects(response, deal.urls["detail"])

        response = self.client.get(deal.urls["update"])
        self.assertContains(response, "This deal is already closed.")

        deal.refresh_from_db()
        self.assertEqual(deal.closed_on, dt.date.today())
        self.assertEqual(
            deal.pretty_status,
            "Declined on {}".format(local_date_format(dt.date.today())),
        )

        response = self.client.post(
            deal.urls["set_status"] + "?status={}".format(Deal.OPEN),
        )
        self.assertRedirects(response, deal.urls["detail"])

        deal.refresh_from_db()
        self.assertIsNone(deal.closed_on)
        self.assertEqual(
            deal.pretty_status,
            "Open since {}".format(local_date_format(dt.date.today())),
        )
Пример #8
0
    def _project_record(self, project, offers):
        offers = sorted(
            filter(
                None,
                (self._offer_record(offer, work_list)
                 for offer, work_list in sorted(offers.items())),
            ),
            key=lambda row: (
                row["offer"]["date_from"],
                row["offer"]["date_until"],
                -row["offer"]["planned_hours"],
            ),
        )

        if not offers:
            return None

        date_from = min(rec["offer"]["date_from"] for rec in offers)
        date_until = max(rec["offer"]["date_until"] for rec in offers)
        hours = sum(rec["offer"]["planned_hours"] for rec in offers)

        return {
            "project": {
                "id":
                project.id,
                "title":
                project.title,
                "is_closed":
                bool(project.closed_on),
                "url":
                project.get_absolute_url(),
                "planning":
                project.urls["planning"],
                "creatework":
                project.urls["creatework"],
                "date_from":
                date_from,
                "date_until":
                date_until,
                "range":
                "{} – {}".format(
                    local_date_format(date_from, fmt="d.m."),
                    local_date_format(date_until, fmt="d.m."),
                ),
                "planned_hours":
                hours,
                "worked_hours":
                self._worked_hours_by_project[project.id],
            },
            "by_week":
            [self._by_project_and_week[project][week] for week in self.weeks],
            "offers":
            offers,
        }
Пример #9
0
 def pretty_status(self):
     if self.ends_on:
         return _("%(periodicity)s from %(from)s until %(until)s") % {
             "periodicity": self.get_periodicity_display(),
             "from": local_date_format(self.starts_on),
             "until": local_date_format(self.ends_on),
         }
     return _("%(periodicity)s from %(from)s") % {
         "periodicity": self.get_periodicity_display(),
         "from": local_date_format(self.starts_on),
     }
Пример #10
0
    def pretty_next_period(self):
        start = self.next_period_starts_on or self.starts_on
        if self.ends_on and self.ends_on < start:
            return ""
        create = start + dt.timedelta(days=self.create_invoice_on_day)

        return _(
            "Next period starts on %(start)s, invoice will be created on %(create)s"
        ) % {
            "start": local_date_format(start),
            "create": local_date_format(create)
        }
Пример #11
0
    def add_planning_requests(self, queryset):
        for pr in (queryset.filter(
                Q(earliest_start_on__lte=max(self.weeks)),
                Q(completion_requested_on__gte=min(self.weeks)),
        ).select_related(
                "project__owned_by", "offer__project",
                "offer__owned_by").prefetch_related("receivers")).distinct():
            per_week = (pr.missing_hours / len(pr.weeks)).quantize(Z2)
            for week in pr.weeks:
                self._requested_by_week[week] += per_week

            date_from = min(pr.weeks)
            date_until = max(pr.weeks) + dt.timedelta(days=6)

            self._projects_offers[pr.project][pr.offer].append({
                "work": {
                    "is_request":
                    True,
                    "id":
                    pr.id,
                    "title":
                    pr.title,
                    "text":
                    ", ".join(user.get_short_name()
                              for user in pr.receivers.all()),
                    "requested_hours":
                    pr.requested_hours,
                    "planned_hours":
                    pr.planned_hours,
                    "missing_hours":
                    pr.missing_hours,
                    "url":
                    pr.get_absolute_url(),
                    "date_from":
                    date_from,
                    "date_until":
                    date_until,
                    "range":
                    "{} – {}".format(
                        local_date_format(date_from, fmt="d.m."),
                        local_date_format(date_until, fmt="d.m."),
                    ),
                    "period":
                    _period(self.weeks, min(pr.weeks), max(pr.weeks)),
                    "is_provisional":
                    pr.is_provisional,
                },
                "per_week":
                per_week,
            })

            self._project_ids.add(pr.project.pk)
            self._user_ids |= {user.id for user in pr.receivers.all()}
Пример #12
0
    def add_planned_work(self, queryset):
        for pw in queryset.filter(weeks__overlap=self.weeks).select_related(
                "user", "project__owned_by", "offer__project",
                "offer__owned_by", "request"):
            per_week = (pw.planned_hours / len(pw.weeks)).quantize(Z2)
            for week in pw.weeks:
                self._by_week[week] += per_week
                self._by_project_and_week[pw.project][week] += per_week

            date_from = min(pw.weeks)
            date_until = max(pw.weeks) + dt.timedelta(days=6)

            self._projects_offers[pw.project][pw.offer].append({
                "work": {
                    "is_request":
                    False,
                    "id":
                    pw.id,
                    "request_id":
                    pw.request_id,
                    "title":
                    pw.title,
                    "text":
                    pw.user.get_short_name(),
                    "user":
                    pw.user.get_short_name(),
                    "planned_hours":
                    pw.planned_hours,
                    "url":
                    pw.get_absolute_url(),
                    "date_from":
                    date_from,
                    "date_until":
                    date_until,
                    "range":
                    "{} – {}".format(
                        local_date_format(date_from, fmt="d.m."),
                        local_date_format(date_until, fmt="d.m."),
                    ),
                    "is_provisional":
                    pw.request.is_provisional if pw.request else False,
                },
                "hours_per_week":
                [per_week if week in pw.weeks else Z1 for week in self.weeks],
                "per_week":
                per_week,
            })

            self._project_ids.add(pw.project.pk)
            self._user_ids.add(pw.user.id)
Пример #13
0
    def pretty_status(self):
        d = {
            "created_at": local_date_format(self.created_at.date()),
            "closed_on": self.closed_on and local_date_format(self.closed_on),
            "decision_expected_on": self.decision_expected_on
            and local_date_format(self.decision_expected_on),
            "status": self.get_status_display(),
        }

        if self.status != self.OPEN:
            return _("%(status)s on %(closed_on)s") % d
        if self.decision_expected_on:
            return _("Decision expected on %(decision_expected_on)s") % d
        return _("Open since %(created_at)s") % d
Пример #14
0
 def report(self):
     try:
         this_week_index = self.weeks.index(monday())
     except ValueError:
         this_week_index = None
     return {
         "this_week_index": this_week_index,
         "weeks": [
             {
                 "monday": week,
                 "month": local_date_format(week, fmt="M"),
                 "week": local_date_format(week, fmt="W"),
                 "period": "{}–{}".format(
                     local_date_format(week, fmt="j."),
                     local_date_format(week + dt.timedelta(days=6), fmt="j."),
                 ),
             }
             for week in self.weeks
         ],
         "projects_offers": sorted(
             filter(
                 None,
                 (
                     self._project_record(project, offers)
                     for project, offers in self._projects_offers.items()
                 ),
             ),
             key=lambda row: (
                 row["project"]["date_from"],
                 row["project"]["date_until"],
                 -row["project"]["planned_hours"],
             )
             if row["project"]["date_from"] and row["project"]["date_until"]
             else (),
         ),
         "by_week": [self._by_week[week] for week in self.weeks],
         "by_week_provisional": [
             self._by_week_provisional[week] for week in self.weeks
         ],
         "absences": [
             (str(user), lst) for user, lst in sorted(self._absences.items())
         ],
         "capacity": self.capacity() if self.users else None,
         "service_types": [
             {"id": type.id, "title": type.title, "color": type.color}
             for type in ServiceType.objects.all()
         ],
         "external_view": self.external,
     }
Пример #15
0
    def _offer_record(self, offer, work_list):
        date_from = min(pw["work"]["date_from"] for pw in work_list)
        date_until = max(pw["work"]["date_until"] for pw in work_list)
        hours = sum(pw["work"]["planned_hours"] for pw in work_list)

        if not work_list:
            return None

        for wl in work_list:
            wl.update(
                {
                    "absences": [
                        [a for a in self._absences[user][idx] if h > 0]
                        for idx, h in enumerate(wl["hours_per_week"])
                    ]
                    for user in self._work_ids_users[wl["work"]["id"]]
                }
            )

        return {
            "offer": {
                "date_from": date_from,
                "date_until": date_until,
                "range": "{} – {}".format(
                    local_date_format(date_from, fmt="d.m."),
                    local_date_format(date_until, fmt="d.m."),
                ),
                "planned_hours": hours,
                "worked_hours": Z1,
                **(
                    {
                        "id": offer.id,
                        "title": offer.title,
                        "is_declined": offer.is_declined,
                        "is_accepted": offer.is_accepted,
                        "url": offer.get_absolute_url(),
                        "creatework": offer.project.urls["creatework"]
                        + f"?offer={offer.pk}",
                    }
                    if offer
                    else {}
                ),
            },
            "work_list": sorted(
                work_list,
                key=lambda row: (row["work"]["date_from"], row["work"]["date_until"]),
            ),
        }
Пример #16
0
    def clean(self):
        data = super().clean()
        s_dict = dict(Offer.STATUS_CHOICES)

        if self.instance.closed_on and data["status"] < Offer.ACCEPTED:
            self.add_warning(
                _("You are attempting to set status to '%(to)s',"
                  " but the offer has already been closed on %(closed)s."
                  " Are you sure?") % {
                      "to": s_dict[data["status"]],
                      "closed": local_date_format(self.instance.closed_on),
                  },
                code="status-change-but-already-closed",
            )

        if data["status"] == Offer.DECLINED:
            self.add_warning(
                _("You are setting the offer status to 'Declined'."
                  " However, if you just want to change a few things"
                  " and send the offer to the client again then you"
                  " could just as well put the offer back into preparation."),
                code="yes-please-decline",
            )

        return data
Пример #17
0
def create_recurring_invoices_and_notify():
    by_owner = defaultdict(list)
    for ri in RecurringInvoice.objects.renewal_candidates():
        for invoice in ri.create_invoices():
            by_owner[invoice.owned_by].append((ri, invoice))

    for owner, invoices in by_owner.items():
        invoices = "\n".join(
            TEMPLATE.format(
                invoice=invoice,
                customer=invoice.contact.name_with_organization
                if invoice.contact
                else invoice.customer,
                total=currency(invoice.total),
                invoiced_on=local_date_format(invoice.invoiced_on),
                base_url=settings.WORKBENCH.URL,
                invoice_url=invoice.get_absolute_url(),
                recurring_url=ri.get_absolute_url(),
            )
            for (ri, invoice) in invoices
        )
        mail = EmailMultiAlternatives(
            _("recurring invoices"), invoices, to=[owner.email], bcc=settings.BCC
        )
        mail.send()
Пример #18
0
def field_value_pairs(object, fields=None):
    fields = (fields.split(",")
              if fields else getattr(object, "field_value_pairs", EVERYTHING))
    for field in object._meta.get_fields():
        if (field.one_to_many or field.one_to_one or field.many_to_many
                or field.primary_key or field.name in {"_fts"}
                or field.name not in fields):
            continue

        if field.choices:
            yield (capfirst(field.verbose_name),
                   object._get_FIELD_display(field))

        elif isinstance(field, models.TextField):
            yield (
                capfirst(field.verbose_name),
                linebreaksbr(getattr(object, field.name)),
            )

        else:
            value = getattr(object, field.name)
            if isinstance(value, dt.date):
                value = local_date_format(value)
            elif isinstance(value, bool):
                value = _("yes") if value else _("no")

            yield (capfirst(field.verbose_name), value)
Пример #19
0
    def dunning_letter(self, *, invoices):
        self.init_letter()
        self.p(invoices[-1].postal_address)
        self.next_frame()
        self.p("Zürich, %s" % local_date_format(dt.date.today()))
        self.spacer()
        self.h1("Zahlungserinnerung")
        self.spacer()
        self.mini_html(("""\
<p>Sehr geehrte Damen und Herren</p>
<p>Bei der beiliegenden Rechnung konnten wir leider noch keinen Zahlungseingang
verzeichnen. Wir bitten Sie, den ausstehenden Betrag innerhalb von 10 Tagen auf
das angegebene Konto zu überweisen. Bei allfälligen Unstimmigkeiten setzen Sie
sich bitte mit uns in Verbindung.</p>
<p>Falls sich Ihre Zahlung mit diesem Schreiben kreuzt, bitten wir Sie, dieses
als gegenstandslos zu betrachten.</p>
<p>Freundliche Grüsse</p>
<p>%s</p>""" if len(invoices) == 1 else """\
<p>Sehr geehrte Damen und Herren</p>
<p>Bei den beiliegenden Rechnungen konnten wir leider noch keinen Zahlungseingang
verzeichnen. Wir bitten Sie, die ausstehenden Beträge innerhalb von 10 Tagen auf
das angegebene Konto zu überweisen. Bei allfälligen Unstimmigkeiten setzen Sie
sich bitte mit uns in Verbindung.</p>
<p>Falls sich Ihre Zahlung mit diesem Schreiben kreuzt, bitten wir Sie, dieses
als gegenstandslos zu betrachten.</p>
<p>Freundliche Grüsse</p>
<p>%s</p>""") % settings.WORKBENCH.PDF_COMPANY)
        self.spacer()
        self.table(
            [
                tuple(
                    capfirst(title)
                    for title in (_("invoice"), _("date"), _("total")))
            ] + [(
                MarkupParagraph(invoice.title, self.style.normal),
                local_date_format(invoice.invoiced_on),
                currency(invoice.total),
            ) for invoice in invoices],
            (self.bounds.E - self.bounds.W - 40 * mm, 24 * mm, 16 * mm),
            self.style.tableHead + (("ALIGN", (1, 0), (1, -1), "LEFT"), ),
        )
        self.restart()
        for invoice in invoices:
            self.init_letter(page_fn=self.stationery())
            self.process_invoice(invoice)
            self.restart()
Пример #20
0
    def test_decision_expected_on_status(self):
        """The status of a deal depends on the "decision expected on" field"""
        today = dt.date.today()
        deal = Deal(decision_expected_on=today)
        self.assertEqual(
            deal.pretty_status,
            "Decision expected on {}".format(local_date_format(today)),
        )
        self.assertIn("badge-info", deal.status_badge)

        deal = Deal(decision_expected_on=in_days(-1))

        self.assertEqual(
            deal.pretty_status,
            "Decision expected on {}".format(local_date_format(in_days(-1))),
        )
        self.assertIn("badge-warning", deal.status_badge)
Пример #21
0
 def handle_date(self, values, field):
     value = values.get(field.attname)
     if value is None:
         return _("<no value>")
     dt = dateparse.parse_datetime(value) or dateparse.parse_date(value)
     if dt:
         values[field.attname] = dt
         return local_date_format(dt)
     return value
Пример #22
0
    def offers_pdf(self, *, project, offers):
        self.init_letter()
        self.p(offers[-1].postal_address)
        self.next_frame()
        self.p("Zürich, %s" % local_date_format(dt.date.today()))
        self.spacer()
        self.h1(project.title)
        if project.description:
            self.spacer(2 * mm)
            self.p(project.description)
        self.spacer()
        self.table(
            [
                tuple(
                    capfirst(title)
                    for title in (_("offer"), _("offered on"), _("total")))
            ] + [(
                MarkupParagraph(offer.title, self.style.normal),
                local_date_format(offer.offered_on) if offer.offered_on else
                MarkupParagraph("<b>%s</b>" % _("NO DATE YET"),
                                style=self.style.bold),
                currency(offer.total_excl_tax),
            ) for offer in offers],
            (self.bounds.E - self.bounds.W - 40 * mm, 24 * mm, 16 * mm),
            self.style.tableHead + (("ALIGN", (1, 0), (1, -1), "LEFT"), ),
        )

        total = CalculationModel(
            subtotal=sum((offer.total_excl_tax for offer in offers), Z),
            discount=Z,
            liable_to_vat=offers[0].liable_to_vat,
            tax_rate=offers[0].tax_rate,
        )
        total._calculate_total()
        total.total_title = offers[0].total_title
        self.table_total(total)

        self.restart()
        for offer in offers:
            self.init_letter()
            self.process_offer(offer)
            self.restart()

        self.generate()
Пример #23
0
    def _offer_record(self, offer, work_list):
        date_from = min(pw["work"]["date_from"] for pw in work_list)
        date_until = max(pw["work"]["date_until"] for pw in work_list)
        hours = sum(pw["work"]["planned_hours"] for pw in work_list
                    if not pw["work"]["is_request"])

        work_list = list(self._sort_work_list(work_list))
        if not work_list:
            return None

        return {
            "offer": {
                "date_from":
                date_from,
                "date_until":
                date_until,
                "range":
                "{} – {}".format(
                    local_date_format(date_from, fmt="d.m."),
                    local_date_format(date_until, fmt="d.m."),
                ),
                "planned_hours":
                hours,
                "worked_hours":
                Z1,
                **({
                    "id":
                    offer.id,
                    "title":
                    offer.title,
                    "is_declined":
                    offer.is_declined,
                    "is_accepted":
                    offer.is_accepted,
                    "url":
                    offer.get_absolute_url(),
                    "creatework":
                    offer.project.urls["creatework"] + "?offer={}".format(offer.pk),
                    "worked_hours":
                    self._worked_hours_by_offer[offer.id],
                } if offer else {}),
            },
            "work_list": work_list,
        }
Пример #24
0
    def break_create_url(self):
        if self.has_associated_log:
            return None

        params = [
            ("day", self["day"].isoformat()),
            ("description", self["description"]),
            ("starts_at", local_date_format(self.get("starts_at"), fmt="H:i:s")),
            ("ends_at", local_date_format(self.get("ends_at"), fmt="H:i:s")),
        ]
        if self.get("timestamp_id"):
            params.append(("timestamp", self["timestamp_id"]))
        else:  # No timestamp ID and no associated log -- it's a detected slice!
            params.append(("detected_ends_at", self["ends_at"]))

        return "{}?{}".format(
            reverse("logbook_break_create"),
            urlencode([pair for pair in params if pair[1]]),
        )
Пример #25
0
 def test_status(self):
     """Test various results of the status badge"""
     today = dt.date.today()
     yesterday = in_days(-1)
     fmt = local_date_format(today)
     self.assertEqual(
         Invoice(status=Invoice.IN_PREPARATION).pretty_status,
         "In preparation since {}".format(fmt),
     )
     self.assertEqual(
         Invoice(status=Invoice.SENT, invoiced_on=today).pretty_status,
         "Sent on {}".format(fmt),
     )
     self.assertEqual(
         Invoice(
             status=Invoice.SENT,
             invoiced_on=yesterday,
             due_on=in_days(-5),
         ).pretty_status,
         "Sent on {} but overdue".format(local_date_format(yesterday)),
     )
     self.assertIn(
         "badge-warning",
         Invoice(
             status=Invoice.SENT,
             invoiced_on=yesterday,
             due_on=in_days(-5),
         ).status_badge,
     )
     self.assertEqual(
         Invoice(status=Invoice.SENT,
                 invoiced_on=yesterday,
                 last_reminded_on=today).pretty_status,
         "Sent on {}, reminded on {}".format(local_date_format(yesterday),
                                             fmt),
     )
     self.assertEqual(
         Invoice(status=Invoice.PAID, closed_on=today).pretty_status,
         "Paid on {}".format(fmt),
     )
     self.assertEqual(
         Invoice(status=Invoice.CANCELED).pretty_status, "Canceled")
Пример #26
0
    def pretty_status(self):
        if self.status == self.IN_PREPARATION:
            return _("In preparation since %(created_at)s") % {
                "created_at": local_date_format(self.created_at.date())
            }
        elif self.status == self.OFFERED:
            if self.project.closed_on:
                return _(
                    "Offered on %(offered_on)s, but project closed on %(closed_on)s"
                ) % {
                    "offered_on": local_date_format(self.offered_on),
                    "closed_on": local_date_format(self.project.closed_on),
                }

            if self.valid_until < dt.date.today():
                return _(
                    "Offered on %(offered_on)s, but not valid anymore") % {
                        "offered_on": local_date_format(self.offered_on),
                    }

            return _("Offered on %(offered_on)s") % {
                "offered_on": local_date_format(self.offered_on)
            }
        elif self.status in (self.ACCEPTED, self.DECLINED):
            return _("%(status)s on %(closed_on)s") % {
                "status": self.get_status_display(),
                "closed_on": local_date_format(self.closed_on),
            }
        return self.get_status_display()
Пример #27
0
 def report(self):
     try:
         this_week_index = self.weeks.index(monday())
     except ValueError:
         this_week_index = None
     return {
         "this_week_index":
         this_week_index,
         "weeks": [{
             "month":
             local_date_format(week, fmt="M"),
             "week":
             local_date_format(week, fmt="W"),
             "period":
             "{}–{}".format(
                 local_date_format(week, fmt="j."),
                 local_date_format(week + dt.timedelta(days=6), fmt="j."),
             ),
         } for week in self.weeks],
         "projects_offers":
         sorted(
             filter(
                 None,
                 (self._project_record(project, offers)
                  for project, offers in self._projects_offers.items()),
             ),
             key=lambda row: (
                 row["project"]["date_from"],
                 row["project"]["date_until"],
                 -row["project"]["planned_hours"],
             ),
         ),
         "by_week": [self._by_week[week] for week in self.weeks],
         "requested_by_week":
         [self._requested_by_week[week] for week in self.weeks],
         "absences":
         [(str(user), lst) for user, lst in sorted(self._absences.items())],
         "capacity":
         self.capacity() if self.users else None,
     }
Пример #28
0
    def clean_fields(self, exclude=None):
        super().clean_fields(exclude=exclude)
        errors = {}

        if self.weeks:
            no_mondays = [day for day in self.weeks if day.weekday() != 0]
            if no_mondays:
                errors["weeks"] = _(
                    "Only mondays allowed, but field contains %s.") % (
                        ", ".join(
                            local_date_format(day) for day in no_mondays), )

        raise_if_errors(errors, exclude)
Пример #29
0
 def process_offer(self, offer):
     self.process_services_letter(
         offer,
         watermark="" if offer.status in {offer.OFFERED, offer.ACCEPTED}
         else str(offer.get_status_display()),
         details=[
             (_("offer"), offer.code),
             (
                 _("date"),
                 (local_date_format(offer.offered_on) if offer.offered_on
                  else MarkupParagraph("<b>%s</b>" % _("NO DATE YET"),
                                       style=self.style.bold)),
             ),
             (_("Our reference"), offer.owned_by.get_full_name()),
             (
                 capfirst(_("valid until")),
                 (local_date_format(offer.valid_until) if offer.valid_until
                  else MarkupParagraph("<b>%s</b>" % _("NO DATE YET"),
                                       style=self.style.bold)),
             ),
         ],
         footer=settings.WORKBENCH.PDF_OFFER_TERMS,
     )
Пример #30
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        assert not self.instance.closed_on, "Must be in preparation"
        self.fields["is_closed"] = forms.BooleanField(
            label=_("is closed"),
            help_text=_("Once an expense report is closed it stays that way."),
            required=False,
        )

        if self.instance.pk:
            self.fields["expenses"] = forms.ModelMultipleChoiceField(
                queryset=LoggedCost.objects.expenses(
                    user=self.instance.owned_by).filter(
                        Q(expense_report=self.instance)
                        | Q(expense_report__isnull=True)),
                label=_("expenses"),
                widget=forms.CheckboxSelectMultiple,
            )
            self.fields[
                "expenses"].initial = self.instance.expenses.values_list(
                    "id", flat=True)
        else:
            self.instance.created_by = self.request.user
            self.instance.owned_by = self.request.user

            expenses = LoggedCost.objects.expenses(
                user=self.request.user).filter(expense_report__isnull=True)
            self.fields["expenses"] = forms.ModelMultipleChoiceField(
                queryset=expenses,
                label=_("expenses"),
                widget=forms.CheckboxSelectMultiple,
            )
            self.fields["expenses"].initial = expenses.values_list("id",
                                                                   flat=True)

        self.fields["expenses"].choices = [(
            cost.id,
            format_html(
                "{}<br>{}: {}<br>{}<br>{}{}",
                local_date_format(cost.rendered_on),
                cost.service.project,
                cost.service,
                cost.description,
                currency(cost.third_party_costs),
                " ({} {})".format(cost.expense_currency, cost.expense_cost)
                if cost.expense_cost else "",
            ),
        ) for cost in self.fields["expenses"].queryset.select_related(
            "service__project__owned_by").order_by("rendered_on", "pk")]