@voyantjs/products 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +109 -0
- package/README.md +43 -0
- package/dist/booking-extension.d.ts +278 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +163 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/routes.d.ts +3674 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +819 -0
- package/dist/schema.d.ts +4156 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +655 -0
- package/dist/service.d.ts +2395 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +1501 -0
- package/dist/tasks/generate-pdf.d.ts +8 -0
- package/dist/tasks/generate-pdf.d.ts.map +1 -0
- package/dist/tasks/generate-pdf.js +102 -0
- package/dist/tasks/index.d.ts +2 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +1 -0
- package/dist/validation.d.ts +855 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +433 -0
- package/package.json +60 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
export type GenerateProductPdfResult = {
|
|
3
|
+
pdfBytes: Uint8Array;
|
|
4
|
+
filename: string;
|
|
5
|
+
sizeBytes: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function generateProductPdf(db: PostgresJsDatabase, productId: string): Promise<GenerateProductPdfResult>;
|
|
8
|
+
//# sourceMappingURL=generate-pdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-pdf.d.ts","sourceRoot":"","sources":["../../src/tasks/generate-pdf.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAKjE,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,UAAU,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,wBAAwB,CAAC,CAuHnC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { asc, eq } from "drizzle-orm";
|
|
2
|
+
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
|
|
3
|
+
import { productDayServices, productDays, products } from "../schema.js";
|
|
4
|
+
export async function generateProductPdf(db, productId) {
|
|
5
|
+
// 1. Fetch product
|
|
6
|
+
const [product] = await db
|
|
7
|
+
.select()
|
|
8
|
+
.from(products)
|
|
9
|
+
.where(eq(products.id, productId))
|
|
10
|
+
.limit(1);
|
|
11
|
+
if (!product) {
|
|
12
|
+
throw new Error(`Product not found: ${productId}`);
|
|
13
|
+
}
|
|
14
|
+
// 2. Fetch days with services
|
|
15
|
+
const days = await db
|
|
16
|
+
.select()
|
|
17
|
+
.from(productDays)
|
|
18
|
+
.where(eq(productDays.productId, productId))
|
|
19
|
+
.orderBy(asc(productDays.dayNumber));
|
|
20
|
+
const daysWithServices = await Promise.all(days.map(async (day) => {
|
|
21
|
+
const services = await db
|
|
22
|
+
.select()
|
|
23
|
+
.from(productDayServices)
|
|
24
|
+
.where(eq(productDayServices.dayId, day.id))
|
|
25
|
+
.orderBy(asc(productDayServices.sortOrder));
|
|
26
|
+
return { ...day, services };
|
|
27
|
+
}));
|
|
28
|
+
// 3. Generate PDF
|
|
29
|
+
const pdfDoc = await PDFDocument.create();
|
|
30
|
+
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
|
31
|
+
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
32
|
+
const PAGE_WIDTH = 595.28;
|
|
33
|
+
const PAGE_HEIGHT = 841.89;
|
|
34
|
+
const MARGIN = 50;
|
|
35
|
+
const LINE_HEIGHT = 16;
|
|
36
|
+
let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
37
|
+
let y = PAGE_HEIGHT - MARGIN;
|
|
38
|
+
function ensureSpace(needed) {
|
|
39
|
+
if (y - needed < MARGIN) {
|
|
40
|
+
page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
41
|
+
y = PAGE_HEIGHT - MARGIN;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function drawText(text, options) {
|
|
45
|
+
const f = options.font ?? font;
|
|
46
|
+
const size = options.size ?? 10;
|
|
47
|
+
const x = options.x ?? MARGIN;
|
|
48
|
+
ensureSpace(LINE_HEIGHT);
|
|
49
|
+
page.drawText(text, { x, y, size, font: f, color: rgb(0.1, 0.1, 0.1) });
|
|
50
|
+
y -= LINE_HEIGHT;
|
|
51
|
+
}
|
|
52
|
+
// Title
|
|
53
|
+
drawText(product.name, { font: boldFont, size: 20 });
|
|
54
|
+
y -= 8;
|
|
55
|
+
// Status & dates
|
|
56
|
+
if (product.startDate || product.endDate) {
|
|
57
|
+
drawText(`Dates: ${product.startDate ?? "TBD"} — ${product.endDate ?? "TBD"}`, { size: 10 });
|
|
58
|
+
}
|
|
59
|
+
if (product.pax) {
|
|
60
|
+
drawText(`Travelers: ${product.pax}`, { size: 10 });
|
|
61
|
+
}
|
|
62
|
+
if (product.sellAmountCents != null) {
|
|
63
|
+
const amount = (product.sellAmountCents / 100).toFixed(2);
|
|
64
|
+
drawText(`Total: ${amount} ${product.sellCurrency}`, { font: boldFont, size: 12 });
|
|
65
|
+
}
|
|
66
|
+
if (product.description) {
|
|
67
|
+
y -= 8;
|
|
68
|
+
drawText(product.description, { size: 10 });
|
|
69
|
+
}
|
|
70
|
+
y -= 16;
|
|
71
|
+
// Days
|
|
72
|
+
for (const day of daysWithServices) {
|
|
73
|
+
ensureSpace(LINE_HEIGHT * 3);
|
|
74
|
+
// Day header
|
|
75
|
+
const dayTitle = day.title ? `Day ${day.dayNumber}: ${day.title}` : `Day ${day.dayNumber}`;
|
|
76
|
+
drawText(dayTitle, { font: boldFont, size: 13 });
|
|
77
|
+
if (day.location) {
|
|
78
|
+
drawText(`Location: ${day.location}`, { size: 9 });
|
|
79
|
+
}
|
|
80
|
+
if (day.description) {
|
|
81
|
+
drawText(day.description, { size: 9 });
|
|
82
|
+
}
|
|
83
|
+
// Services
|
|
84
|
+
for (const svc of day.services) {
|
|
85
|
+
ensureSpace(LINE_HEIGHT * 2);
|
|
86
|
+
const costStr = `${(svc.costAmountCents / 100).toFixed(2)} ${svc.costCurrency}`;
|
|
87
|
+
const qty = svc.quantity > 1 ? ` x${svc.quantity}` : "";
|
|
88
|
+
drawText(` • ${svc.name} (${svc.serviceType})${qty} — ${costStr}`, { size: 9 });
|
|
89
|
+
if (svc.notes) {
|
|
90
|
+
drawText(` ${svc.notes}`, { size: 8 });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
y -= 8;
|
|
94
|
+
}
|
|
95
|
+
// 4. Serialize to bytes
|
|
96
|
+
const pdfBytes = await pdfDoc.save();
|
|
97
|
+
return {
|
|
98
|
+
pdfBytes,
|
|
99
|
+
filename: `${product.name.replace(/[^a-zA-Z0-9]/g, "_")}.pdf`,
|
|
100
|
+
sizeBytes: pdfBytes.length,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generateProductPdf } from "./generate-pdf.js";
|