import path from "path"; import fs from "fs"; import { factories } from "@strapi/strapi"; import { sanitize } from "@strapi/utils"; import { ID } from "@strapi/database/dist/types"; import { vatDecimal, vatIncludedDecimal, baseUrl, adminEmail, shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl } from "../../../../config/constants"; import { calculateTotalProductPrice, productDefaultParams } from "../../product/services/product"; import { CartProduct, Order, PdfBody } from "../../../../types"; import { pdfApi } from "../../../services/PdfApi"; import { invoiceEmailTemplate, deliveryNoteEmailTemplate } from "../../../templates/emailTemplates"; import _, { template } from "lodash"; import { PdfMessageBody, MessageBody, mailApi } from "../../../services/MailApi"; /** * This service is responsible for handling the order logic. */ export default factories.createCoreService("api::order.order", ({ strapi }) => ({ findOne: async (id: ID) => { return await strapi.entityService.findOne("api::order.order", id, orderDefaultParams); }, findOneByUuid: async (uuid: string) => { const params = { filters: { uuid: { $eq: uuid } }, ...orderDefaultParams }; const orderUnsafe = await strapi.db.query("api::order.order").findOne({ where: { uuid }, ...orderDefaultParams }); orderUnsafe.cart = orderUnsafe?.cart.map((item: CartProduct) => ({ ...item, product: { ...item.product, totalProductPrice: calculateTotalProductPrice(item.product) } })); return (await sanitize.contentAPI.output(orderUnsafe, strapi.getModel("api::order.order"))) as Order; }, uploadPdf: async (orderUnsafe: Order, field: "invoice" | "deliveryNote", name = "Upload") => { strapi.log.verbose(`app:v:order-service: – Generating ${field} pdf`); const pdfBody = field === "invoice" ? invoicePdfBody(orderUnsafe) : deliveryNotePdfBody(orderUnsafe); strapi.log.debug(`app:d:order-service: - PDF body`, pdfBody); const blob = field === "invoice" ? await pdfApi.generateInvoice(pdfBody) : await pdfApi.generateDeliveryNote(pdfBody); strapi.log.verbose(`app:v:order-service: ✔ Generating ${field} pdf`); // Save blob to a temp folder const tempFolder = path.join(__dirname, "temp"); if (!fs.existsSync(tempFolder)) { fs.mkdirSync(tempFolder); } const deliveryNotePath = path.join(tempFolder, `${name}.pdf`); fs.writeFileSync(deliveryNotePath, Buffer.from(blob)); const uploadData = { data: { ref: "api::order.order", refId: orderUnsafe.id, field }, files: { path: deliveryNotePath, name: `${name}_${pdfBody.date.replace(/[^a-z0-9]/gi, "_").toLowerCase()}`, type: "application/pdf", size: fs.statSync(deliveryNotePath).size } }; strapi.log.verbose(`app:v:order-service: - Uploading ${field} pdf`); await strapi.plugins.upload.services.upload.upload(uploadData); strapi.log.verbose(`app:v:order-service: ✔ Uploading ${field} pdf`); return await strapi.service("api::order.order").findOne(orderUnsafe.id); }, sendInvoice: async (uuid: string, blob?: any) => { strapi.log.info(`app:i:order-service: - Sending invoice for order ${uuid}`); const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial; const shopContext = { shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl }; try { const emailConfig: MessageBody = { to_email: oderUnsafe.email, subject: template(invoiceEmailTemplate.subject)({ ...shopContext, ...oderUnsafe }), message: template(invoiceEmailTemplate.text)({ baseUrl, ...shopContext, ...oderUnsafe }), html: template(invoiceEmailTemplate.html)({ baseUrl, ...shopContext, ...oderUnsafe }) }; strapi.log.debug(`app:d:order-service: - Email config ${emailConfig.html}`); // Send email to customer await mailApi.sendTextMessage(emailConfig); // Send email to administator await mailApi.sendTextMessage({ ...emailConfig, to_email: adminEmail }); strapi.log.info(`app:i:order-service: ✔ Sending invoice for order ${uuid}`); return await strapi.db.query("api::order.order").update({ where: { id: oderUnsafe.id }, data: { invoiceSent: true } }); } catch (error) { strapi.log.error(`app:e:order-service: χ Sending invoice for order ${uuid}`); strapi.log.debug(`app:d:order-service: Error: ${JSON.stringify({ error })}`); return new Error("Could not send invoice"); } }, sendDeliveryNote: async (uuid: string, blob?: ArrayBuffer) => { strapi.log.info(`app:i:order-service: - Sending delivery note for order ${uuid}`); const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial; const shopContext = { shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl }; try { const emailConfig: MessageBody = { to_email: oderUnsafe.email, subject: template(deliveryNoteEmailTemplate.subject)({ ...shopContext, ...oderUnsafe }), message: template(deliveryNoteEmailTemplate.text)({ ...shopContext, ...oderUnsafe }), html: template(deliveryNoteEmailTemplate.html)({ baseUrl, ...shopContext, ...oderUnsafe }) }; await mailApi.sendTextMessage(emailConfig); strapi.log.info(`app:i:order-service: ✔ Sending delivery note for order ${uuid}`); return await strapi.db.query("api::order.order").update({ where: { id: oderUnsafe.id }, data: { deliveryNoteSent: true } }); } catch (error) { strapi.log.error(`app:e:order-service: χ Sending delivery note for order ${uuid}`); strapi.log.debug(`app:d:order-service: Error: ${JSON.stringify({ error })}`); return new Error("Could not send delivery note"); } }, update: async (id: ID, params: Record): Promise => { const dataUnsafe = (await strapi.entityService.update("api::order.order", id, params)) as Order; strapi.log.verbose(`app:v:order-service: Updated order ${id} with initial params`); let data: { subtotal: number; total: number; VAT: number }; if (dataUnsafe?.cart) { const productsTotal = dataUnsafe.cart.reduce((v, p) => v + (calculateTotalProductPrice(p.product) ?? 0) * p.count, 0); const productsVAT = dataUnsafe.cart.reduce((v, p) => { const totalProductPrice = calculateTotalProductPrice(p.product) ?? 0; const amount = totalProductPrice / vatIncludedDecimal; const tax = totalProductPrice - amount; return v + tax * p.count; }, 0); const deliveryPrice = dataUnsafe.delivery?.price ?? 0; const paymentPrice = dataUnsafe.payment?.price ?? 0; strapi.log.debug(`app:d:order-service: Calculating totals`, { productsTotal, deliveryPrice, paymentPrice }); const subtotal = productsTotal; const total = Math.round((subtotal + deliveryPrice + paymentPrice) * 100) / 100; const VAT = Math.round((subtotal / vatIncludedDecimal) * vatDecimal * 100) / 100; strapi.log.verbose(`app:v:order-service: Calculated totals`, { productsTotal, total, VAT, subtotal }); data = { subtotal, total, VAT }; } const orderUnsafe = await strapi.entityService.update("api::order.order", id, { data, ...orderDefaultParams }); strapi.log.verbose(`app:v:order-service: Updated order ${id} with calculated totals`); orderUnsafe.cart = orderUnsafe?.cart.map((item: CartProduct) => ({ ...item, product: { ...item.product, totalProductPrice: calculateTotalProductPrice(item.product) } })); return (await sanitize.contentAPI.output(orderUnsafe, strapi.getModel("api::order.order"))) as Order; } })); export const orderDefaultParams = { populate: { invoice: true, deliveryNote: true, delivery: true, payment: true, customer: { populate: { addressStructured: true } }, invoiceAddressStructured: true, deliveryAddressStructured: true, cart: { populate: { product: productDefaultParams } } } }; const invoicePdfBody = (order: Order): PdfBody => { const pdfBody = defaultPdfBody(order); return { ...pdfBody, subject: "RECHNUNG", to: { name: `${order.customer.addressStructured?.givenName} ${order.customer.addressStructured?.familyName}` || "", address: [ order.customer.addressStructured?.streetAddress || "", `${order.customer.addressStructured?.postalCode} ${order.customer.addressStructured?.addressLevel2}` || "" ] }, nr: { ...pdfBody.nr, invoice: order.invoiceNumber } } as PdfBody; }; const deliveryNotePdfBody = (order: Order): PdfBody => { const pdfBody = defaultPdfBody(order); return { ...pdfBody, subject: "LIEFERSCHEIN", to: { name: "", address: order.deliveryAddress.split("\n") }, nr: { ...pdfBody.nr, shipping: order.deliveryNoteNumber } } as PdfBody; }; const defaultPdfBody = (order: Order): Partial => ({ date: new Date(order.acceptedTermsAndConditionsAt).toLocaleDateString("de-DE"), nr: { customer: `${order.customer.id}`, order: `${order.id}` }, service: order.cart?.map((cartProduct) => ({ description: cartProduct.product.name, price: { per_unit: calculateTotalProductPrice(cartProduct.product) || 0, total: (calculateTotalProductPrice(cartProduct.product) || 0) * cartProduct.count }, count: cartProduct.count, nr: `P${cartProduct.product.id}` })) || [], currency: "\\euro", body: "Vielen Dank für Ihren Einkauf und Ihr Vertrauen.", total: order.total, subtotal: order.subtotal, VAT: { amount: order.VAT, rate: vatDecimal * 100 }, shipping: order.delivery?.price ?? 0 });