feat: implement swiss invoice component

master
Katja Lutz 2 years ago
parent 60638bcbdf
commit 74739a2a7a

@ -0,0 +1,221 @@
import { Component, FlowComponent, JSX, Show } from "solid-js";
import Address, { AddressData } from "./Address";
import SwissQrCode from "./SwissQrCode";
export type InvoiceData = {
iban: string;
amount: number;
amountBeforeTax: number;
tax: number;
currency: string;
message?: string;
reference?: string;
referenceType?: "QRR" | "SCOR" | "NON";
creditor: AddressData;
debtor?: AddressData;
};
export const formatAmount = (amount: number) => {
return amount
.toLocaleString("de-CH", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
.replaceAll("", " ");
};
const spaceEveryX = (text: string, x = 4, reverse = false) => {
text = text.replaceAll(" ", "");
let result: string[] = [];
let chars = text.split("");
if (reverse) {
chars = chars.reverse();
}
chars.forEach(function (v, i) {
if (i % x === 0) {
result.push(" ");
}
result.push(v);
});
if (reverse) {
result = result.reverse();
}
return result.join("");
};
const encodeSwissQrInvoice = (
invoiceData: InvoiceData,
{ type = "SPC" } = {}
) => {
const VERSION = "0220"; // 2.20
const CODING = 1; // utf-8
const END_INDICATOR = "EPD";
const creditorType = invoiceData.creditor.type || "S";
const debtorType = invoiceData.debtor?.type || "S";
const header: string[] = [type, VERSION, CODING.toString()];
const creditor: string[] = [
invoiceData.iban.replaceAll(" ", ""),
creditorType,
invoiceData.creditor.name,
invoiceData.creditor.line1 || "",
invoiceData.creditor.line2 || "",
invoiceData.creditor.type === "S"
? (invoiceData.creditor.zip || 0).toString()
: "",
creditorType ? invoiceData.creditor.city || "" : "",
invoiceData.creditor.country,
];
const ultimateCreditor: string[] = [
"", // type
"", // name
"", // line 1
"", // line 2
"", // zip
"", // city
"", // country
];
const amount: string[] = [
invoiceData.amount.toFixed(2),
invoiceData.currency,
];
const debtor: string[] = [
debtorType ? invoiceData.debtor?.type || "" : "",
invoiceData.debtor?.name || "",
invoiceData.debtor?.line1 || "",
invoiceData.debtor?.line2 || "",
(invoiceData.debtor?.zip || "").toString(),
invoiceData.debtor?.city || "",
invoiceData.debtor?.country || "",
];
const referenceType = !invoiceData.reference
? "NON"
: invoiceData.referenceType ||
(invoiceData.reference.startsWith("RF") ? "SCOR" : "QRR");
const reference: string[] = [
referenceType,
(invoiceData.reference || "").replaceAll(" ", ""),
];
const invoice = [
header,
creditor,
ultimateCreditor,
amount,
debtor,
reference,
invoiceData.message || "",
END_INDICATOR,
]
.flat()
.join("\n");
return invoice;
};
const SwissInvoice: Component<
{ value: InvoiceData } & JSX.HTMLAttributes<HTMLElement>
> = (props) => {
const HeaderEz: FlowComponent = (props) => (
<div class="text-lg leading-normal font-bold mb-3">{props.children}</div>
);
const HeaderZ: FlowComponent = (props) => (
<div class="text-sm leading-normal font-bold">{props.children}</div>
);
const HeaderE: FlowComponent = (props) => (
<div class="text-xs leading-normal font-bold">{props.children}</div>
);
return (
<div class="break-inside-avoid">
<div class="swissinvoice hyphenate break-normal break-all -max-w-[1200px] aspect-[2/1] print:w-full flex-shrink-0 text-left print:text-black text-sm bg-white border-y border-black border-dashed grid grid-flow-col grid-cols-[29.5%_26.5%_auto] grid-rows-[auto_auto_1fr]">
<div class="col-span-1 row-span-1 p-10 pb-0 border-r border-black border-dashed">
<HeaderEz>Empfangsschein</HeaderEz>
<div class="leading-tight flex flex-col gap-4">
<div>
<HeaderE>Konto / Zahlbar an</HeaderE>
<div>{spaceEveryX(props.value.iban)}</div>
<Address address={props.value.creditor} />
</div>
<Show when={props.value.reference}>
<div>
<HeaderE>Referenz</HeaderE>
<div>{spaceEveryX(props.value.reference!)}</div>
</div>
</Show>
<Show when={props.value.debtor}>
<div>
<HeaderE>Zahlbar durch</HeaderE>
<Address address={props.value.debtor!} />
</div>
</Show>
</div>
</div>
<div class="col-span-1 row-span-2 p-10 pt-10 border-r border-black border-dashed">
<div class="grid grid-cols-2 gap-x-2">
<HeaderE>Währung</HeaderE>
<HeaderE>Betrag</HeaderE>
<div>{props.value.currency}</div>
<div>{formatAmount(props.value.amount)}</div>
</div>
<div class="text-right text-xs font-bold mt-10 leading-tight">
Annahmestelle
</div>
</div>
<div class="col-span-1 row-span-1 pt-10 pl-10">
<HeaderEz>Zahlteil</HeaderEz>
<SwissQrCode
value={encodeSwissQrInvoice(props.value)}
class="w-full aspect-square"
/>
</div>
<div class="col-span-1 row-span-1 pt-10 pl-10 pb-10">
<div class="grid grid-cols-2 gap-x-2">
<HeaderZ>Währung</HeaderZ>
<HeaderZ>Betrag</HeaderZ>
<div>{props.value.currency}</div>
<div>{formatAmount(props.value.amount)}</div>
</div>
</div>
<div class="col-span-2 pl-10"></div>
<div class="leading-tight text-base col-span-1 row-span-2 p-10 flex flex-col gap-4">
<div>
<HeaderZ>Konto / Zahlbar an</HeaderZ>
<div>{spaceEveryX(props.value.iban)}</div>
<Address address={props.value.creditor} />
</div>
<Show when={props.value.reference}>
<div>
<HeaderZ>Referenz</HeaderZ>
<div>{spaceEveryX(props.value.reference!)}</div>
</div>
</Show>
<Show when={props.value.message}>
<div>
<HeaderZ>Zusätzliche Informationen</HeaderZ>
<div>{props.value.message}</div>
</div>
</Show>
<Show when={props.value.debtor}>
<div>
<HeaderE>Zahlbar durch</HeaderE>
<Address address={props.value.debtor!} />
</div>
</Show>
</div>
</div>
</div>
);
};
export default SwissInvoice;
Loading…
Cancel
Save