diff --git a/app/routes/api.orders.$orderId.invoice.tsx b/app/routes/api.orders.$orderId.invoice.tsx index 1abe9c1..cc3f53c 100644 --- a/app/routes/api.orders.$orderId.invoice.tsx +++ b/app/routes/api.orders.$orderId.invoice.tsx @@ -3,6 +3,7 @@ import { authenticate } from "../shopify.server"; import db from "../db.server"; import { generateInvoice } from "../services/invoice/generateInvoice.server"; import { cancelAndReissue } from "../services/invoice/cancelAndReissue.server"; +import { sendInvoiceEmail } from "../services/invoice/email.server"; /** * GET /api/orders/:orderId/invoice → returns latest invoice metadata + history @@ -60,6 +61,50 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { return cors(Response.json({ ok: true, op, ...result })); } + if (op === "send") { + const orderGid = orderId.startsWith("gid://") + ? orderId + : `gid://shopify/Order/${orderId}`; + let invoice = await db.invoice.findFirst({ + where: { + shopDomain: session.shop, + orderId: orderGid, + kind: "invoice", + cancelledAt: null, + }, + orderBy: [{ version: "desc" }, { createdAt: "desc" }], + }); + if (!invoice) { + const generated = await generateInvoice({ + shopDomain: session.shop, + admin, + orderId, + }); + invoice = await db.invoice.findUnique({ where: { id: generated.invoiceId } }); + } + if (!invoice) throw new Error("Failed to materialise an invoice for this order."); + const sendResult = await sendInvoiceEmail({ + shopDomain: session.shop, + invoiceId: invoice.id, + }); + if (!sendResult.ok) { + return cors( + Response.json( + { ok: false, op: "send", error: sendResult.errorMessage ?? "Email send failed." }, + { status: 422 }, + ), + ); + } + return cors( + Response.json({ + ok: true, + op: "send", + invoiceNumber: invoice.invoiceNumber, + toAddress: sendResult.toAddress, + }), + ); + } + const result = await generateInvoice({ shopDomain: session.shop, admin, diff --git a/data/regen-RE-1001.pdf b/data/regen-RE-1001.pdf new file mode 100644 index 0000000..7892605 Binary files /dev/null and b/data/regen-RE-1001.pdf differ diff --git a/data/regen-RE-1002.pdf b/data/regen-RE-1002.pdf new file mode 100644 index 0000000..d2a0219 Binary files /dev/null and b/data/regen-RE-1002.pdf differ diff --git a/data/sample-rechnung.pdf b/data/sample-rechnung.pdf index 31553f0..750ec80 100644 Binary files a/data/sample-rechnung.pdf and b/data/sample-rechnung.pdf differ diff --git a/data/sample-storno.pdf b/data/sample-storno.pdf index f0c1cfa..514d4e3 100644 Binary files a/data/sample-storno.pdf and b/data/sample-storno.pdf differ diff --git a/extensions/invoice-order-action/src/ActionExtension.tsx b/extensions/invoice-order-action/src/ActionExtension.tsx index f74e192..c62754a 100644 --- a/extensions/invoice-order-action/src/ActionExtension.tsx +++ b/extensions/invoice-order-action/src/ActionExtension.tsx @@ -13,7 +13,7 @@ function Extension() { const orderGid: string | undefined = data?.selected?.[0]?.id; const orderId = orderGid ? orderGid.split("/").pop() : undefined; - async function trigger(action: "generate" | "cancel_reissue") { + async function trigger(action: "generate" | "cancel_reissue" | "send") { if (!orderId) { setError("No order selected"); return; @@ -47,6 +47,9 @@ function Extension() { trigger("generate")} disabled={busy}> Generate / regenerate + trigger("send")} disabled={busy}> + Send invoice email + trigger("cancel_reissue")} disabled={busy}> Cancel & reissue diff --git a/shopify.app.toml b/shopify.app.toml index 0dbcaf0..12c18d0 100644 --- a/shopify.app.toml +++ b/shopify.app.toml @@ -39,5 +39,4 @@ redirect_urls = [ ] [build] -include_config_on_deploy = true automatically_update_urls_on_dev = true