302 lines
14 KiB
TypeScript
302 lines
14 KiB
TypeScript
|
|
import PocketBase from "pocketbase";
|
||
|
|
|
||
|
|
const PB_URL = "http://127.0.0.1:8090";
|
||
|
|
const ADMIN_EMAIL = "david.els1@outlook.com";
|
||
|
|
const ADMIN_PASSWORD = "1234567890";
|
||
|
|
|
||
|
|
async function setup() {
|
||
|
|
const pb = new PocketBase(PB_URL);
|
||
|
|
|
||
|
|
console.log("Authenticating as admin...");
|
||
|
|
const authData = await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
||
|
|
|
||
|
|
function withSystemFields(fields: Record<string, unknown>[]) {
|
||
|
|
const existing = fields.filter(f => f.name !== "created" && f.name !== "updated");
|
||
|
|
const idIdx = existing.findIndex(f => f.name === "id");
|
||
|
|
existing.splice(idIdx + 1, 0, ...SYSTEM_FIELDS);
|
||
|
|
return existing;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function createCollection(data: Record<string, unknown>) {
|
||
|
|
const body = {
|
||
|
|
...data,
|
||
|
|
fields: withSystemFields(data.fields as Record<string, unknown>[]),
|
||
|
|
};
|
||
|
|
const response = await fetch(`${PB_URL}/api/collections`, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
"Authorization": authData.token,
|
||
|
|
},
|
||
|
|
body: JSON.stringify(body),
|
||
|
|
});
|
||
|
|
if (!response.ok) {
|
||
|
|
const err = await response.json();
|
||
|
|
console.error("API Error:", JSON.stringify(err, null, 2));
|
||
|
|
throw new Error(err.message || "Failed to create collection");
|
||
|
|
}
|
||
|
|
return response.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
const authRule = "@request.auth.id != ''";
|
||
|
|
|
||
|
|
const SYSTEM_FIELDS = [
|
||
|
|
{ id: "autodate2990389176", name: "created", type: "autodate", system: true, hidden: false, onCreate: true, onUpdate: false, presentable: false },
|
||
|
|
{ id: "autodate3332085495", name: "updated", type: "autodate", system: true, hidden: false, onCreate: true, onUpdate: true, presentable: false },
|
||
|
|
];
|
||
|
|
|
||
|
|
// Delete all existing collections (including stale ones from other projects)
|
||
|
|
// Using raw API because SDK delete is unreliable for this
|
||
|
|
const allCollections = ["order_items", "quote_items", "purchase_orders", "orders", "quotes", "suppliers", "company_details", "companies", "customers", "stock_items", "book_out_records", "logs"];
|
||
|
|
for (const colName of allCollections) {
|
||
|
|
try {
|
||
|
|
const existing = await pb.collections.getList(1, 1, { filter: `name = "${colName}"` });
|
||
|
|
if (existing.items.length > 0) {
|
||
|
|
console.log(`Deleting "${colName}" (${existing.items[0].id})...`);
|
||
|
|
const resp = await fetch(`${PB_URL}/api/collections/${existing.items[0].id}`, {
|
||
|
|
method: "DELETE",
|
||
|
|
headers: { "Authorization": authData.token },
|
||
|
|
});
|
||
|
|
if (!resp.ok) console.error(` Failed to delete ${colName}: ${resp.status}`);
|
||
|
|
}
|
||
|
|
} catch {}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Phase 1: Base collections
|
||
|
|
console.log("\n--- Creating base collections ---");
|
||
|
|
|
||
|
|
await createCollection({
|
||
|
|
name: "customers",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "customer", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "email", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "alternative_email", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "buyer_email", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "number", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "company", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "dateadded", type: "date", required: false },
|
||
|
|
{ name: "lastvisiteddate", type: "date", required: false },
|
||
|
|
{ name: "rep", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "coordinates", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "description", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ customers");
|
||
|
|
|
||
|
|
await createCollection({
|
||
|
|
name: "companies",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "company", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "address", type: "text", required: false, options: { max: 500 } },
|
||
|
|
{ name: "vendor", type: "text", required: false, options: { max: 100 } },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ companies");
|
||
|
|
|
||
|
|
await createCollection({
|
||
|
|
name: "company_details",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "company_name", type: "text", required: true, options: { max: 100 } },
|
||
|
|
{ name: "address", type: "text", required: false, options: { max: 500 } },
|
||
|
|
{ name: "number", type: "text", required: false, options: { max: 20 } },
|
||
|
|
{ name: "email", type: "email", required: false },
|
||
|
|
{ name: "vat_number", type: "text", required: false, options: { max: 20 } },
|
||
|
|
{ name: "registration_number", type: "text", required: false, options: { max: 20 } },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ company_details");
|
||
|
|
|
||
|
|
await createCollection({
|
||
|
|
name: "suppliers",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "suppliername", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "email", type: "email", required: false },
|
||
|
|
{ name: "suppliernumber", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "supplieraddress", type: "text", required: false, options: { max: 500 } },
|
||
|
|
{ name: "supply_tags", type: "text", required: false, options: { max: 500 } },
|
||
|
|
{ name: "coordinates", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "closingtime", type: "text", required: false, options: { max: 255 } },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ suppliers");
|
||
|
|
|
||
|
|
// Get IDs
|
||
|
|
const customersId = (await pb.collections.getList(1, 1, { filter: 'name = "customers"' })).items[0].id;
|
||
|
|
const companiesId = (await pb.collections.getList(1, 1, { filter: 'name = "companies"' })).items[0].id;
|
||
|
|
const suppliersId = (await pb.collections.getList(1, 1, { filter: 'name = "suppliers"' })).items[0].id;
|
||
|
|
console.log(`\nIDs: customers=${customersId}, companies=${companiesId}, suppliers=${suppliersId}`);
|
||
|
|
|
||
|
|
// Phase 2: Quotes
|
||
|
|
console.log("\n--- Creating quotes ---");
|
||
|
|
await createCollection({
|
||
|
|
name: "quotes",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "quote_number", type: "text", required: true, options: { max: 50 } },
|
||
|
|
{ name: "quote_reference", type: "text", required: false, options: { max: 50 } },
|
||
|
|
{ name: "external_number", type: "text", required: false, options: { max: 50 } },
|
||
|
|
{ name: "rfq_number", type: "text", required: false, options: { max: 50 } },
|
||
|
|
{ name: "markup", type: "number", required: false },
|
||
|
|
{ name: "customer", type: "relation", required: true, collectionId: customersId, maxSelect: 1 },
|
||
|
|
{ name: "company", type: "relation", required: false, collectionId: companiesId, maxSelect: 1 },
|
||
|
|
{ name: "claimed_by", type: "text", required: false, options: { max: 100 } },
|
||
|
|
{ name: "description", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
{ name: "status", type: "select", required: false, values: ["new", "claimed", "processed", "emailed", "approval_pending", "approved", "rejected", "problem", "complete", "cancelled", "follow_up", "email_failed", "unmatched_bounce"] },
|
||
|
|
{ name: "approved_by", type: "text", required: false, options: { max: 100 } },
|
||
|
|
{ name: "notes", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
{ name: "email_sender", type: "email", required: false },
|
||
|
|
{ name: "email_subject", type: "text", required: false, options: { max: 255 } },
|
||
|
|
{ name: "email_body", type: "text", required: false, options: { max: 10000 } },
|
||
|
|
{ name: "has_attachments", type: "bool", required: false },
|
||
|
|
{ name: "company_letterhead", type: "select", required: false, values: ["CNL", "ISHERWOOD", "JEI"] },
|
||
|
|
{ name: "email_delivered", type: "bool", required: false },
|
||
|
|
{ name: "email_delivered_at", type: "date", required: false },
|
||
|
|
{ name: "email_opened", type: "bool", required: false },
|
||
|
|
{ name: "email_opened_at", type: "date", required: false },
|
||
|
|
{ name: "email_tracking_id", type: "text", required: false, options: { max: 64 } },
|
||
|
|
{ name: "has_new_reply", type: "bool", required: false },
|
||
|
|
{ name: "last_reply_at", type: "date", required: false },
|
||
|
|
{ name: "email_failed_at", type: "date", required: false },
|
||
|
|
{ name: "pdf_generated_at", type: "date", required: false },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ quotes");
|
||
|
|
|
||
|
|
const quotesId = (await pb.collections.getList(1, 1, { filter: 'name = "quotes"' })).items[0].id;
|
||
|
|
|
||
|
|
// Phase 3: Orders
|
||
|
|
console.log("\n--- Creating orders ---");
|
||
|
|
await createCollection({
|
||
|
|
name: "orders",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "order_number", type: "text", required: true, options: { max: 50 } },
|
||
|
|
{ name: "company", type: "relation", required: true, collectionId: companiesId, maxSelect: 1 },
|
||
|
|
{ name: "customer", type: "relation", required: false, collectionId: customersId, maxSelect: 1 },
|
||
|
|
{ name: "quote", type: "relation", required: false, collectionId: quotesId, maxSelect: 1 },
|
||
|
|
{ name: "status", type: "select", required: false, values: ["new", "processing", "order_ready", "po_generated", "completed", "cancelled"] },
|
||
|
|
{ name: "notes", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
{ name: "quote_matching_attempted", type: "bool", required: false },
|
||
|
|
{ name: "quote_match_confidence", type: "number", required: false },
|
||
|
|
{ name: "potential_quote", type: "relation", required: false, collectionId: quotesId, maxSelect: 1 },
|
||
|
|
{ name: "potential_quote_confidence", type: "number", required: false },
|
||
|
|
{ name: "is_repair_order", type: "bool", required: false },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ orders");
|
||
|
|
|
||
|
|
const ordersId = (await pb.collections.getList(1, 1, { filter: 'name = "orders"' })).items[0].id;
|
||
|
|
|
||
|
|
// Phase 4: PO, Quote Items, Order Items
|
||
|
|
console.log("\n--- Creating dependent collections ---");
|
||
|
|
|
||
|
|
await createCollection({
|
||
|
|
name: "purchase_orders",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "po_number", type: "text", required: true, options: { max: 50 } },
|
||
|
|
{ name: "order", type: "relation", required: true, collectionId: ordersId, maxSelect: 1 },
|
||
|
|
{ name: "supplier", type: "relation", required: true, collectionId: suppliersId, maxSelect: 1 },
|
||
|
|
{ name: "status", type: "select", required: false, values: ["draft", "sent", "received"] },
|
||
|
|
{ name: "total_amount", type: "number", required: false, options: { min: 0 } },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ purchase_orders");
|
||
|
|
|
||
|
|
await createCollection({
|
||
|
|
name: "quote_items",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "quote", type: "relation", required: true, collectionId: quotesId, maxSelect: 1 },
|
||
|
|
{ name: "description", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
{ name: "quote_number", type: "text", required: false, options: { max: 100 } },
|
||
|
|
{ name: "quote_reference", type: "text", required: false, options: { max: 100 } },
|
||
|
|
{ name: "quantity", type: "number", required: false, options: { min: 0 } },
|
||
|
|
{ name: "supplier", type: "relation", required: false, collectionId: suppliersId, maxSelect: 1 },
|
||
|
|
{ name: "cost_price", type: "number", required: false, options: { min: 0 } },
|
||
|
|
{ name: "selling_price", type: "number", required: false, options: { min: 0 } },
|
||
|
|
{ name: "notes", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
{ name: "is_processed", type: "bool", required: false },
|
||
|
|
{ name: "markup", type: "number", required: false },
|
||
|
|
{ name: "included_in_quote", type: "bool", required: false },
|
||
|
|
{ name: "quote_pdf_id", type: "text", required: false, options: { max: 50 } },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ quote_items");
|
||
|
|
|
||
|
|
const poId = (await pb.collections.getList(1, 1, { filter: 'name = "purchase_orders"' })).items[0].id;
|
||
|
|
|
||
|
|
await createCollection({
|
||
|
|
name: "order_items",
|
||
|
|
type: "base",
|
||
|
|
fields: [
|
||
|
|
{ name: "order", type: "relation", required: true, collectionId: ordersId, maxSelect: 1 },
|
||
|
|
{ name: "description", type: "text", required: true, options: { max: 255 } },
|
||
|
|
{ name: "po_description", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
{ name: "quantity", type: "number", required: false, options: { min: 0 } },
|
||
|
|
{ name: "order_qty", type: "number", required: false },
|
||
|
|
{ name: "selling_price", type: "number", required: true, options: { min: 0 } },
|
||
|
|
{ name: "supplier", type: "relation", required: false, collectionId: suppliersId, maxSelect: 1 },
|
||
|
|
{ name: "cost_price", type: "number", required: false, options: { min: 0 } },
|
||
|
|
{ name: "markup", type: "number", required: false },
|
||
|
|
{ name: "purchase_order", type: "relation", required: false, collectionId: poId, maxSelect: 1 },
|
||
|
|
{ name: "item_status", type: "select", required: false, values: ["pending", "processed", "po_generated", "driver_pool", "assigned", "collected", "stock_verified", "picking", "ready_delivery", "delivered", "cancelled"] },
|
||
|
|
{ name: "notes", type: "text", required: false, options: { max: 5000 } },
|
||
|
|
],
|
||
|
|
listRule: authRule,
|
||
|
|
viewRule: authRule,
|
||
|
|
createRule: authRule,
|
||
|
|
updateRule: authRule,
|
||
|
|
deleteRule: authRule,
|
||
|
|
});
|
||
|
|
console.log("✓ order_items");
|
||
|
|
|
||
|
|
console.log("\n✅ Setup complete!");
|
||
|
|
}
|
||
|
|
|
||
|
|
setup().catch(console.error);
|