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, )
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)
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"])
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()
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)), )]
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)), ), ]
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")]
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()
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()
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()
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 /> " % ( 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
def __str__(self): return currency(self.value)
def __str__(self): return "%s, %s, %s" % ( self.owned_by.get_full_name(), local_date_format(self.created_at.date()), currency(self.total), )
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", ))
def amount(row): return currency(row["service"].service_cost)