Esempio n. 1
0
    def table_total(self, instance):
        total = [(_("subtotal"), currency(instance.subtotal.quantize(Z)))]
        if instance.discount:
            total.append(
                (_("discount"), currency(-instance.discount.quantize(Z))))
        if getattr(instance, "down_payment_total", None):
            for invoice in instance.down_payment_invoices.all():
                total.append((
                    MarkupParagraph(
                        "%s: %s (%s)" % (
                            _("Down payment"),
                            Truncator(invoice).chars(60),
                            invoice.invoiced_on.strftime("%d.%m.%Y")
                            if invoice.invoiced_on else _("NO DATE YET"),
                        ),
                        self.style.normal,
                    ),
                    currency(-invoice.total_excl_tax.quantize(Z)),
                ))
        if instance.tax_amount:
            total.append((
                "%0.1f%% %s" % (instance.tax_rate, _("tax")),
                currency(instance.tax_amount.quantize(Z)),
            ))

        if len(total) > 1:
            self.table(total, self.style.tableColumns, self.style.tableHead)
            self.spacer(0.7 * mm)

        self.table(
            [(instance.total_title, currency(instance.total.quantize(Z)))],
            self.style.tableColumns,
            self.style.tableHeadLine,
        )
Esempio n. 2
0
 def pretty_total_excl(self):
     parts = [gettext("%s excl. tax") % currency(self.total_excl_tax)]
     if self.discount:
         parts.append(
             " ({} {})".format(currency(self.discount), gettext("discount"))
         )
     return "".join(parts)
Esempio n. 3
0
 def amount(row):
     if row["service"].effort_rate is not None:
         return currency(row["not_archived"])
     elif row["logged_hours"]:
         return format_html(
             '{} <small class="bg-warning px-1">{}</small>',
             currency(row["not_archived"]),
             _("%s logged but no hourly rate defined.") %
             hours(row["logged_hours"]),
         )
     return currency(row["logged_cost"])
Esempio n. 4
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()
Esempio n. 5
0
 def services_row(self, service):
     is_optional = getattr(service, "is_optional", False)
     return [(
         MarkupParagraph(
             "<b>%s</b> %s<br/>%s" % (
                 sanitize(service.title),
                 _("(optional)") if is_optional else "",
                 sanitize(service.description),
             ),
             self.style.normal,
         ),
         MarkupParagraph(
             "<i>%s</i>" % currency(service.service_cost.quantize(Z)),
             self.style.right,
         ) if is_optional else "",
         "" if is_optional else currency(service.service_cost.quantize(Z)),
     )]
Esempio n. 6
0
 def services_row_with_details(self, service):
     is_optional = getattr(service, "is_optional", False)
     return [
         (
             MarkupParagraph(
                 "<b>%s</b> %s<br/>%s" % (
                     sanitize(service.title),
                     _("(optional)") if is_optional else "",
                     sanitize(service.description),
                 ),
                 self.style.normal,
             ),
             "",
             "",
         ),
         (
             MarkupParagraph(
                 ", ".join(
                     filter(
                         None,
                         [
                             ("%s %s à %s/h" % (
                                 hours(service.effort_hours),
                                 service.effort_type,
                                 currency(service.effort_rate),
                             )) if service.effort_hours
                             and service.effort_rate else "",
                             ("{} {}".format(currency(service.cost),
                                             _("fixed costs")))
                             if service.cost else "",
                         ],
                     )),
                 self.style.normalWithExtraLeading,
             ),
             MarkupParagraph(
                 "<i>%s</i>" % currency(service.service_cost.quantize(Z)),
                 self.style.right,
             ) if is_optional else "",
             "" if is_optional else currency(
                 service.service_cost.quantize(Z)),
         ),
     ]
Esempio n. 7
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")]
Esempio n. 8
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()
Esempio n. 9
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()
Esempio n. 10
0
def send_unsent_projected_invoices_reminders():
    today = dt.date.today()
    next_month = next_valid_day(today.year, today.month, 99)
    if (next_month - today).days != 3:  # third-last day of month
        return

    upi = unsent_projected_invoices(next_month)
    by_user = defaultdict(list)

    for project in upi:
        by_user[project["project"].owned_by].append(project)

    for user, projects in by_user.items():
        body = "\n\n".join(
            PROJECTED_INVOICES_TEMPLATE.format(
                project=project["project"],
                base_url=settings.WORKBENCH.URL,
                project_url=project["project"].get_absolute_url(),
                unsent=currency(project["unsent"]),
            )
            for project in projects
        )
        eom = local_date_format(next_month - dt.timedelta(days=1))
        EmailMultiAlternatives(
            "{} ({})".format(_("Unsent projected invoices"), eom),
            f"""\
Hallo {user}

Das geplante Rechnungstotal per {eom} wurde bei
folgenden Projekten noch nicht erreicht:

{body}

Wenn Du die Rechnungen nicht stellen kannst, aktualisiere bitte
die geplanten Rechnungen oder schliesse das Projekt.
""",
            to=[user.email],
            cc=["*****@*****.**"],
        ).send()
Esempio n. 11
0
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()

        if not self.object.closed_on:
            messages.warning(
                request,
                _(
                    "Please close the expense report first. Generating PDFs"
                    " for open expense reports isn't allowed."
                ),
            )
            return redirect(self.object)

        pdf, response = pdf_response(
            self.object.code,
            as_attachment=request.GET.get("disposition") == "attachment",
        )
        pdf.init_report()
        pdf.h1(_("expense report"))
        pdf.spacer(2 * mm)
        pdf.table(
            [
                (capfirst(_("of")), self.object.owned_by.get_full_name()),
                (capfirst(_("created at")), local_date_format(self.object.created_at)),
                (capfirst(_("status")), capfirst(self.object.pretty_status)),
            ],
            pdf.style.tableColumnsLeft,
            pdf.style.table,
        )
        pdf.spacer(5 * mm)

        counter = count(1)
        expenses = OrderedDict()
        for cost in self.object.expenses.select_related(
            "service__project__owned_by"
        ).order_by("rendered_on", "pk"):
            expenses.setdefault(cost.expense_currency, []).append(cost)

        for currency_code, sublist in sorted(expenses.items()):
            pdf.table(
                [(_("receipt"), "", "")]
                + [
                    (
                        "%d." % (next(counter),),
                        MarkupParagraph(
                            "%s<br />%s: %s<br />%s<br />&nbsp;"
                            % (
                                local_date_format(cost.rendered_on),
                                cost.service.project,
                                cost.service,
                                cost.description,
                            ),
                            pdf.style.normal,
                        ),
                        currency(cost.third_party_costs)
                        if cost.expense_cost is None
                        else currency(cost.expense_cost),
                    )
                    for cost in sublist
                ],
                (10 * mm, pdf.bounds.E - pdf.bounds.W - 10 * mm - 16 * mm, 16 * mm),
                pdf.style.tableHead,
            )

            pdf.spacer(0.7 * mm)
            total_cost = reduce(
                operator.add,
                (cost.expense_cost or cost.third_party_costs for cost in sublist),
                Z2,
            )
            pdf.table(
                [
                    (
                        "%s %s"
                        % (
                            capfirst(_("total")),
                            currency_code or settings.WORKBENCH.CURRENCY,
                        ),
                        currency(total_cost),
                    )
                ],
                pdf.style.tableColumns,
                pdf.style.tableHeadLine,
            )
            pdf.spacer()

        pdf.generate()

        return response
Esempio n. 12
0
 def __str__(self):
     return currency(self.value)
Esempio n. 13
0
 def __str__(self):
     return "%s, %s, %s" % (
         self.owned_by.get_full_name(),
         local_date_format(self.created_at.date()),
         currency(self.total),
     )
Esempio n. 14
0
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop("request")

        super().__init__(*args, **kwargs)

        self.entries = []
        for entry in CreditEntry.objects.reverse().filter(invoice__isnull=True,
                                                          notes="")[:20]:
            self.fields[f"entry_{entry.pk}_invoice"] = forms.TypedChoiceField(
                label=format_html(
                    '<a href="{}" target="_blank"'
                    ' rel="noopener noreferrer">{}, {}: {}</a>',
                    entry.get_absolute_url(),
                    entry.total,
                    local_date_format(entry.value_date),
                    entry.payment_notice,
                ),
                choices=[(None, "----------")] + [(
                    invoice.id,
                    mark_safe(" ".join((
                        format_html('<span title="{}">', invoice.description),
                        format_html(
                            "<strong>{}</strong>" if re.search(
                                r"\b" + invoice.code + r"\b",
                                entry.payment_notice,
                            ) else "{}",
                            invoice,
                        ),
                        invoice.status_badge,
                        "<br>",
                        format_html(
                            "{}",
                            invoice.contact.name_with_organization
                            if invoice.contact else invoice.customer,
                        ),
                        "<br>",
                        format_html(
                            "{} {}",
                            _("invoiced on"),
                            local_date_format(invoice.invoiced_on),
                        ) if invoice.invoiced_on else gettext("NO DATE YET"),
                        "<br>",
                        currency(invoice.total),
                        format_html(
                            "<br><span style='color:darkred'>{}: {}</span>",
                            _("third party costs"),
                            currency(invoice.third_party_costs),
                        ) if invoice.third_party_costs else "",
                        "</span>",
                    ))),
                ) for invoice in Invoice.objects.open().filter(
                    total=entry.total).select_related("contact__organization",
                                                      "customer", "owned_by",
                                                      "project")[:100]],
                coerce=int,
                required=False,
                widget=forms.RadioSelect,
            )

            self.fields[f"entry_{entry.pk}_notes"] = forms.CharField(
                widget=Textarea({"rows": 1}), label=_("notes"), required=False)

            self.entries.append((
                entry,
                f"entry_{entry.pk}_invoice",
                f"entry_{entry.pk}_notes",
            ))
Esempio n. 15
0
 def amount(row):
     return currency(row["service"].service_cost)