Fix isPaidImmediately's exact-match regression, add unpaid Vorkasse instructions
Validate e-invoices / mustang (push) Successful in 41s

isPaidImmediately() matched paymentMethodTitle with an exact !== check
against the literal "Überweisung" — the consuming shop renamed its
Vorkasse row to "Überweisung (Vorkasse)" the same day, which would have
silently made every unpaid Vorkasse invoice show "✓ Bereits beglichen".
Changed to startsWith("Überweisung") — same design, tolerant of a
suffix qualifier.

Also adds an explicit instruction for the unpaid case (previously the
absence of the paid confirmation was the only signal, no actual text):
pay to the listed bank details, order processed after payment received
(~1-2 business days).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-25 16:11:44 +00:00
parent ceaa437724
commit 8dc56c9783
5 changed files with 42 additions and 12 deletions
+2
View File
@@ -29,6 +29,8 @@ One canonical implementation, consumed by both repos, makes this class of drift
**2026-07-24, Kleinunternehmerregelung (§19 UStG).** `InvoiceOrder`/`CorrectionInvoiceOrder` gained optional `kleinunternehmer`, a third order-level VAT treatment alongside `vatExempt` — see "Kleinunternehmerregelung" below. Takes precedence over `vatExempt` wherever both would otherwise apply (a Kleinunternehmer never charges VAT to begin with, so there's nothing left to "exempt" via the separate intra-community rule).
**2026-07-25, Vorkasse fixes (v0.2.3).** `isPaidImmediately()` matched `paymentMethodTitle` with an exact `!==` check against the literal string `"Überweisung"` — the consuming shop renamed its Vorkasse row to `"Überweisung (Vorkasse)"` the same day (to make room for a possible future *automated* bank-transfer method, e.g. "Sofortüberweisung", routed through a real payment gateway), which would have silently made every unpaid Vorkasse invoice show "✓ Bereits beglichen". Changed to `startsWith("Überweisung")` — same "only Überweisung is the named exception" design, tolerant of a suffix qualifier on that one title. Also added an explicit instruction in the unpaid case (previously the *absence* of the paid confirmation was the only signal, no actual text): "Bitte überweisen Sie den Rechnungsbetrag unter Angabe der Bestellnummer … auf die unten stehende Bankverbindung. Die Bestellung wird nach Zahlungseingang bearbeitet (in der Regel innerhalb von 12 Werktagen)." — same plain-text treatment as the paid confirmation (`unpaidNoticeText`, muted rather than green — an unpaid Vorkasse invoice isn't a problem, just an expected pending state).
## How this is consumed
Not published to npm — installed as a git dependency:
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@einfach-produktiv/invoicing",
"version": "0.1.0",
"version": "0.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@einfach-produktiv/invoicing",
"version": "0.1.0",
"version": "0.2.2",
"dependencies": {
"@e-invoice-eu/core": "^3.1.1"
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@einfach-produktiv/invoicing",
"version": "0.2.2",
"version": "0.2.3",
"private": true,
"description": "Shared invoice / correction-invoice (Stornorechnung, Gutschrift) PDF generation and VAT-breakdown math, consumed as a git dependency by both the einfach-produktiv frontend and the payload backend — not published to npm.",
"type": "module",
+8 -1
View File
@@ -42,10 +42,17 @@ describe("isPaidImmediately", () => {
expect(isPaidImmediately("PayPal")).toBe(true);
});
it("is false only for Überweisung", () => {
it("is false for Überweisung", () => {
expect(isPaidImmediately("Überweisung")).toBe(false);
});
it("is false for a suffixed Überweisung title too (startsWith, not exact match)", () => {
// Regression: the shop renamed its Vorkasse row to this exact title
// (2026-07-25) — an exact `!==` match would have silently started
// showing "✓ Bereits beglichen" on unpaid invoices.
expect(isPaidImmediately("Überweisung (Vorkasse)")).toBe(false);
});
it("defaults to true for any future/unknown payment method (only Überweisung is the named exception)", () => {
expect(isPaidImmediately("Sofortüberweisung")).toBe(true);
expect(isPaidImmediately("Klarna")).toBe(true);
+29 -8
View File
@@ -50,6 +50,12 @@ const styles = StyleSheet.create({
metaLabel: { fontSize: 7, color: TEXT_MUTED, textTransform: "uppercase", marginBottom: 2 },
metaValue: { fontSize: 10, fontFamily: "Helvetica-Bold" },
paidBadgeText: { fontSize: 10, fontFamily: "Helvetica-Bold", color: SUCCESS },
// Same plain-text treatment as paidBadgeText (no pill/box, see that
// style's own comment) — muted, not an error/warning color, since an
// unpaid Vorkasse invoice isn't a problem, just a pending, expected
// state. Right-aligned + a max width so the two-sentence instruction
// wraps under the summary card instead of running the full page width.
unpaidNoticeText: { fontSize: 9, color: TEXT_MUTED, textAlign: "right", maxWidth: 220 },
table: { borderRadius: 6, overflow: "hidden", borderWidth: 1, borderColor: BORDER, marginTop: 8, marginBottom: 16 },
tableHeader: { flexDirection: "row", backgroundColor: BG_MUTED, paddingVertical: 8, paddingHorizontal: 10 },
// Every item row shares this same tinted background now — no more
@@ -222,14 +228,23 @@ export const SAMPLE_INVOICE_ORDER: InvoiceOrder = {
total: 37.7,
};
// "Überweisung" (bank transfer) is the only payment method on this shop
// that ISN'T settled immediately — Kreditkarte/PayPal both capture at
// checkout. Rather than hardcode a list of "immediate" method titles
// (fragile the moment a new one is added in Payload's payment-methods
// collection), the only method that's ever NOT immediate is named
// explicitly — everything else defaults to "paid already".
// "Überweisung" (bank transfer, Vorkasse) is the only payment method on
// this shop that ISN'T settled immediately — Kreditkarte/PayPal both
// capture at checkout. Rather than hardcode a list of "immediate" method
// titles (fragile the moment a new one is added in Payload's
// payment-methods collection), the only method that's ever NOT immediate
// is named explicitly — everything else defaults to "paid already".
//
// `startsWith`, not an exact `!==` match — the consuming shop renamed its
// Vorkasse row to "Überweisung (Vorkasse)" (2026-07-25, to distinguish it
// from a possible future *automated* bank-transfer method routed through
// Stripe, e.g. "Sofortüberweisung" — see the test below asserting that one
// stays `true`). An exact-match check would have silently started showing
// "✓ Bereits beglichen" on unpaid Vorkasse invoices the moment that title
// changed. `startsWith` keeps the same "only Überweisung is the named
// exception" design while tolerating a suffix qualifier on that one title.
function isPaidImmediately(paymentMethodTitle: string): boolean {
return paymentMethodTitle !== "Überweisung";
return !paymentMethodTitle.startsWith("Überweisung");
}
// Distributes the order-level discount/shipping proportionally across each
@@ -448,10 +463,16 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
</View>
)}
</View>
{paid && (
{paid ? (
<View style={styles.paidBadgeRow}>
<Text style={styles.paidBadgeText}> Bereits beglichen ({order.paymentMethodTitle})</Text>
</View>
) : (
<View style={styles.paidBadgeRow}>
<Text style={styles.unpaidNoticeText}>
Bitte überweisen Sie den Rechnungsbetrag unter Angabe der Bestellnummer {order.orderNumber} auf die unten stehende Bankverbindung. Die Bestellung wird nach Zahlungseingang bearbeitet (in der Regel innerhalb von 12 Werktagen).
</Text>
</View>
)}
</View>
</View>