Compare commits

..

2 Commits

Author SHA1 Message Date
667617296b Phase7: Void/Refund logic
All checks were successful
Build and Push / build (push) Successful in 1m43s
2026-05-25 22:59:18 +02:00
1c61111da3 Phase7: Void/Refund logic
All checks were successful
Build and Push / build (push) Successful in 1m46s
2026-05-25 22:52:54 +02:00
5 changed files with 43 additions and 50 deletions

View File

@@ -462,34 +462,34 @@ export function InvoiceDetailView({ detail, creditNotes = [] }: Props) {
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead className="text-xs text-text-muted text-left"> <thead className="text-xs text-text-muted text-left">
<tr> <tr>
<th className="pb-2">{t("creditNoteNumberHeader")}</th> <th className="pb-2 pr-4">{t("creditNoteNumberHeader")}</th>
<th className="pb-2">{t("creditNoteKindHeader")}</th> <th className="pb-2 pr-4">{t("creditNoteKindHeader")}</th>
<th className="pb-2 text-right"> <th className="pb-2 pr-4 text-right">
{t("creditNoteAmountHeader")} {t("creditNoteAmountHeader")}
</th> </th>
<th className="pb-2">{t("creditNoteReasonHeader")}</th> <th className="pb-2 pr-4">{t("creditNoteReasonHeader")}</th>
<th className="pb-2">{t("creditNoteIssuedHeader")}</th> <th className="pb-2 pr-4">{t("creditNoteIssuedHeader")}</th>
<th className="pb-2 text-right">{t("creditNotePdfHeader")}</th> <th className="pb-2 text-right">{t("creditNotePdfHeader")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{creditNotes.map((cn) => ( {creditNotes.map((cn) => (
<tr key={cn.id} className="border-t border-border"> <tr key={cn.id} className="border-t border-border">
<td className="py-2 font-mono text-xs"> <td className="py-2 pr-4 font-mono text-xs">
{cn.creditNoteNumber} {cn.creditNoteNumber}
</td> </td>
<td className="py-2"> <td className="py-2 pr-4">
<span className="px-2 py-0.5 rounded text-xs text-error bg-error/10"> <span className="px-2 py-0.5 rounded text-xs text-error bg-error/10">
{t(`creditNoteKind_${cn.kind}` as any)} {t(`creditNoteKind_${cn.kind}` as any)}
</span> </span>
</td> </td>
<td className="py-2 text-right font-mono"> <td className="py-2 pr-4 text-right font-mono whitespace-nowrap">
CHF {cn.amountChf.toFixed(2)} CHF {cn.amountChf.toFixed(2)}
</td> </td>
<td className="py-2 text-text-secondary text-xs"> <td className="py-2 pr-4 text-text-secondary text-xs">
{cn.reason ?? "—"} {cn.reason ?? "—"}
</td> </td>
<td className="py-2 text-xs text-text-muted"> <td className="py-2 pr-4 text-xs text-text-muted whitespace-nowrap">
{cn.issuedAt.slice(0, 10)} {cn.issuedAt.slice(0, 10)}
</td> </td>
<td className="py-2 text-right"> <td className="py-2 text-right">

View File

@@ -39,11 +39,11 @@ export function CustomerCreditNoteList({ creditNotes }: Props) {
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead className="text-xs text-text-muted text-left"> <thead className="text-xs text-text-muted text-left">
<tr> <tr>
<th className="pb-2">{t("creditNoteNumberCol")}</th> <th className="pb-2 pr-4">{t("creditNoteNumberCol")}</th>
<th className="pb-2">{t("creditNoteInvoiceCol")}</th> <th className="pb-2 pr-4">{t("creditNoteInvoiceCol")}</th>
<th className="pb-2">{t("creditNoteIssuedCol")}</th> <th className="pb-2 pr-4">{t("creditNoteIssuedCol")}</th>
<th className="pb-2 text-right">{t("creditNoteAmountCol")}</th> <th className="pb-2 pr-4 text-right">{t("creditNoteAmountCol")}</th>
<th className="pb-2 text-right">{t("creditNoteKindCol")}</th> <th className="pb-2 pr-4 text-right">{t("creditNoteKindCol")}</th>
<th className="pb-2 text-right">{t("creditNotePdfCol")}</th> <th className="pb-2 text-right">{t("creditNotePdfCol")}</th>
</tr> </tr>
</thead> </thead>
@@ -53,19 +53,19 @@ export function CustomerCreditNoteList({ creditNotes }: Props) {
key={cn.id} key={cn.id}
className="border-t border-border align-middle" className="border-t border-border align-middle"
> >
<td className="py-2 font-mono text-xs"> <td className="py-2 pr-4 font-mono text-xs">
{cn.creditNoteNumber} {cn.creditNoteNumber}
</td> </td>
<td className="py-2 font-mono text-xs text-text-secondary"> <td className="py-2 pr-4 font-mono text-xs text-text-secondary">
{cn.invoiceNumber} {cn.invoiceNumber}
</td> </td>
<td className="py-2 text-text-secondary"> <td className="py-2 pr-4 text-text-secondary whitespace-nowrap">
{fmt.dateTime(new Date(cn.issuedAt), { dateStyle: "medium" })} {fmt.dateTime(new Date(cn.issuedAt), { dateStyle: "medium" })}
</td> </td>
<td className="py-2 text-right font-mono"> <td className="py-2 pr-4 text-right font-mono whitespace-nowrap">
CHF {cn.amountChf.toFixed(2)} CHF {cn.amountChf.toFixed(2)}
</td> </td>
<td className="py-2 text-right"> <td className="py-2 pr-4 text-right">
<span <span
className={`px-2 py-0.5 rounded text-xs ${ className={`px-2 py-0.5 rounded text-xs ${
kindColors[cn.kind] ?? "" kindColors[cn.kind] ?? ""

View File

@@ -24,9 +24,8 @@ import {
renderToBuffer, renderToBuffer,
} from "@react-pdf/renderer"; } from "@react-pdf/renderer";
import type { CreditNote, Invoice } from "@/types"; import type { CreditNote, Invoice } from "@/types";
import { BRAND, Logo, ACCENT_CREDIT_NOTE } from "./pdf-brand"; import { BRAND, Logo } from "./pdf-brand";
const ACCENT = ACCENT_CREDIT_NOTE;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Localized strings // Localized strings
@@ -207,15 +206,15 @@ const styles = StyleSheet.create({
logoBlock: { flexDirection: "row", alignItems: "center" }, logoBlock: { flexDirection: "row", alignItems: "center" },
brandName: { brandName: {
fontSize: 16, fontSize: 16,
color: ACCENT.primaryDark, color: BRAND.primaryDark,
marginLeft: 8, marginLeft: 8,
fontFamily: "Helvetica-Bold", fontFamily: "Helvetica-Bold",
}, },
issuerBlock: { textAlign: "right", fontSize: 8.5, color: BRAND.mutedColor }, issuerBlock: { textAlign: "right", fontSize: 8.5, color: BRAND.mutedColor },
issuerName: { fontSize: 11, color: ACCENT.primaryDark, marginBottom: 2 }, issuerName: { fontSize: 11, color: BRAND.primaryDark, marginBottom: 2 },
docTitle: { docTitle: {
fontSize: 22, fontSize: 22,
color: ACCENT.primaryDark, color: BRAND.primaryDark,
marginBottom: 8, marginBottom: 8,
fontFamily: "Helvetica-Bold", fontFamily: "Helvetica-Bold",
}, },
@@ -230,9 +229,9 @@ const styles = StyleSheet.create({
billTo: { billTo: {
marginBottom: 24, marginBottom: 24,
padding: 8, padding: 8,
backgroundColor: "#fdf2f2", backgroundColor: "#f7f7f5",
borderLeftWidth: 3, borderLeftWidth: 3,
borderLeftColor: ACCENT.primary, borderLeftColor: BRAND.primary,
}, },
billToLabel: { fontSize: 8, color: BRAND.mutedColor, marginBottom: 4 }, billToLabel: { fontSize: 8, color: BRAND.mutedColor, marginBottom: 4 },
billToName: { fontSize: 11, marginBottom: 2 }, billToName: { fontSize: 11, marginBottom: 2 },
@@ -245,7 +244,7 @@ const styles = StyleSheet.create({
}, },
amountHeader: { amountHeader: {
flexDirection: "row", flexDirection: "row",
backgroundColor: ACCENT.primaryDark, backgroundColor: BRAND.primaryDark,
color: "#ffffff", color: "#ffffff",
paddingVertical: 5, paddingVertical: 5,
paddingHorizontal: 6, paddingHorizontal: 6,
@@ -273,17 +272,17 @@ const styles = StyleSheet.create({
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between", justifyContent: "space-between",
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: ACCENT.primaryDark, borderTopColor: BRAND.primaryDark,
paddingTop: 6, paddingTop: 6,
marginTop: 4, marginTop: 4,
}, },
totalsGrandLabel: { totalsGrandLabel: {
color: ACCENT.primaryDark, color: BRAND.primaryDark,
fontSize: 11, fontSize: 11,
fontFamily: "Helvetica-Bold", fontFamily: "Helvetica-Bold",
}, },
totalsGrandValue: { totalsGrandValue: {
color: ACCENT.primaryDark, color: BRAND.primaryDark,
fontSize: 11, fontSize: 11,
textAlign: "right", textAlign: "right",
fontFamily: "Helvetica-Bold", fontFamily: "Helvetica-Bold",
@@ -353,7 +352,7 @@ function CreditNotePdfDocument({ creditNote, invoice }: CreditNotePdfProps) {
Issuer block from BRAND.issuer (shared with invoice). */} Issuer block from BRAND.issuer (shared with invoice). */}
<View style={styles.headerRow}> <View style={styles.headerRow}>
<View style={styles.logoBlock}> <View style={styles.logoBlock}>
<Logo size={42} color={ACCENT.primary} /> <Logo size={42} color={BRAND.primary} />
<Text style={styles.brandName}>{BRAND.name}</Text> <Text style={styles.brandName}>{BRAND.name}</Text>
</View> </View>
<View style={styles.issuerBlock}> <View style={styles.issuerBlock}>

View File

@@ -1261,10 +1261,12 @@ export async function sendCreditNoteEmail(params: {
const safeNumberINV = escapeHtml(params.invoiceNumber); const safeNumberINV = escapeHtml(params.invoiceNumber);
const safeReason = params.reason ? escapeHtml(params.reason) : null; const safeReason = params.reason ? escapeHtml(params.reason) : null;
// Red accent (#DC2626) for the credit-note emails, mirroring the // PieCed brand emerald — same accent the invoice email uses.
// PDF accent so the document family reads visually consistent. // A credit note is still a PieCed IT document; the company
// The invoice email uses emerald; the credit note uses red. // identity stays consistent across the document family. The
const ACCENT = "#DC2626"; // doc type is distinguished by the subject line and copy, not
// by colour.
const ACCENT = "#10B981";
try { try {
await getTransporter().sendMail({ await getTransporter().sendMail({

View File

@@ -57,24 +57,16 @@ export const BRAND = {
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Accent colours for document variants — credit notes are red so // Logo — PieCed's hexagon-pattern mark. Same shape used everywhere
// customers can tell them apart from invoices at a glance. // and same brand colour. The credit note is still a PieCed IT
// --------------------------------------------------------------------------- // document and reads with the same company identity as an invoice.
export const ACCENT_CREDIT_NOTE = {
primary: "#DC2626",
primaryDark: "#991B1B",
};
// ---------------------------------------------------------------------------
// Logo — PieCed's hexagon-pattern mark. Same shape used everywhere;
// only the colour changes per document type.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
interface LogoProps { interface LogoProps {
size?: number; size?: number;
/** Defaults to BRAND.primary (emerald). Pass ACCENT_CREDIT_NOTE.primary /** Defaults to BRAND.primary. Override only for special cases
* on credit notes for the red variant. */ * (e.g. an inverse variant on a dark background). Standard
* documents — invoices, credit notes — all use BRAND.primary. */
color?: string; color?: string;
} }