L11 Kundenversand: Bericht-PDF an Kunden senden (Service, Action, API, Berichtsseite)
- sendReportToCustomer: report:approve, Scope, nur approved mit PDF, Empfänger
Ansprechpartner -> Kunde, Dedupe je Adresse+Version, Audit export
- Server Action + POST /api/v1/reports/{id}/send inkl. OpenAPI-Eintrag
- /reports/[id]: Abschnitt „An Kunden senden" mit bisherigen Versänden, Texte de/en
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,8 @@ import { ServiceError } from "@/server/services/context";
|
||||
import { tenantTimeZone } from "@/server/services/reports/build-content";
|
||||
import { getReportDetail } from "@/server/services/reports/queries";
|
||||
import { readCtx } from "@/server/services/reports/read-ctx";
|
||||
import { SendToCustomer } from "@/components/reports/send-to-customer";
|
||||
import { defaultReportRecipient, listCustomerMailings } from "@/server/services/reports/send-to-customer";
|
||||
|
||||
/** /reports/[id] — structured view, PDF, versions, approve/reject/new version (reject as popup ?reject=1). */
|
||||
export default async function ReportDetailPage({ params, searchParams }: { params: Promise<{ id: string }>; searchParams: Promise<{ reject?: string }> }) {
|
||||
@@ -29,6 +31,14 @@ export default async function ReportDetailPage({ params, searchParams }: { param
|
||||
const { report, content, versions, workOrder, permissions } = detail;
|
||||
const dt = (d: Date | null) => (d ? format.dateTime(d, { dateStyle: "medium", timeStyle: "short", timeZone }) : null);
|
||||
const base = `/reports/${id}`;
|
||||
// L11: customer mail section — approved report with PDF, backoffice right (the action re-checks against the DB)
|
||||
const customerMail =
|
||||
report.status === "approved" && report.pdfDocumentId && ctx.permissions.has("report:approve")
|
||||
? await Promise.all([defaultReportRecipient(ctx, report), listCustomerMailings(ctx, id)]).then(([defaultTo, rows]) => ({
|
||||
defaultTo,
|
||||
mailings: rows.map((m) => ({ id: m.id, to: m.to, status: m.status, version: m.version, when: dt(m.sentAt ?? m.createdAt) ?? "" })),
|
||||
}))
|
||||
: null;
|
||||
|
||||
const meta: Array<[string, string | null]> = [
|
||||
[t("field.workOrder"), `${workOrder.number}`],
|
||||
@@ -87,6 +97,11 @@ export default async function ReportDetailPage({ params, searchParams }: { param
|
||||
<div className="mb-4 max-w-3xl">
|
||||
<LotseReportPanel reportId={id} />
|
||||
</div>
|
||||
{customerMail && (
|
||||
<div className="mb-4 max-w-3xl">
|
||||
<SendToCustomer reportId={id} defaultTo={customerMail.defaultTo} mailings={customerMail.mailings} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_280px]">
|
||||
<ReportView content={content} reportId={id} timeZone={timeZone} />
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { requireApiContext } from "@/server/api/context";
|
||||
import { json, readJsonObject, withApi } from "@/server/api/respond";
|
||||
import { sendReportToCustomer } from "@/server/services/reports/send-to-customer";
|
||||
|
||||
/**
|
||||
* POST /api/v1/reports/:id/send — e-mail the approved report PDF to the customer (L11).
|
||||
* Body (optional): { to?: string, message?: string }. 202 queued, 200 sent/duplicate/failed.
|
||||
*/
|
||||
export const POST = withApi(async (req: Request, { params }: { params: Promise<{ id: string }> }) => {
|
||||
const ctx = await requireApiContext("reports", "report:approve");
|
||||
const { id } = await params;
|
||||
const body = await readJsonObject(req, { allowEmpty: true });
|
||||
const res = await sendReportToCustomer(ctx, { reportId: id, to: body.to, message: body.message });
|
||||
return json(res, { status: res.status === "queued" ? 202 : 200 });
|
||||
});
|
||||
Reference in New Issue
Block a user