feat(invoices): added invoice + linking to forklift

This commit is contained in:
2025-11-04 22:12:08 -06:00
parent 577584ef4d
commit 2e05f6eeee
24 changed files with 13782 additions and 17 deletions

View File

@@ -0,0 +1,22 @@
meta {
name: Get Invoices
type: http
seq: 4
}
get {
url: {{url}}/lst/api/forklifts/invoices
body: none
auth: inherit
}
body:json {
{
"name":"Delage DLL"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,27 @@
meta {
name: Update lease
type: http
seq: 3
}
patch {
url: {{url}}/lst/api/forklifts/invoices/:id
body: json
auth: inherit
}
params:path {
id: de10c8ee-5756-4efb-9664-3c55338b2b60
}
body:json {
{
"companyId": "b34c6684-ec35-4364-acef-0c1570faf123"
}
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -17,7 +17,7 @@ params:path {
body:json { body:json {
{ {
"endDate": "3/25/2029" "companyId": "b34c6684-ec35-4364-acef-0c1570faf123"
} }
} }

View File

@@ -12,10 +12,10 @@ post {
body:json { body:json {
{ {
"leaseNumber":"Delage DLL", "leaseNumber":"500-50489192",
"startDate": "", "startDate": "11/08/2023",
"endDate": "", "endDate": "11/12/2025",
"companyId": "" "companyId": "b34c6684-ec35-4364-acef-0c1570faf123"
} }
} }

View File

@@ -0,0 +1,160 @@
import axios from "axios";
import { type DrizzleError, sql } from "drizzle-orm";
import type { Request, Response } from "express";
import { Router } from "express";
import https from "https";
import type z from "zod";
import { db } from "../../../../pkg/db/db.js";
import {
leaseInvoiceForklifts,
newForkliftInvoiceSchema,
} from "../../../../pkg/db/schema/forkliftLeasesInvoice.js";
import {
leaseInvoices,
newInvoiceSchema,
} from "../../../../pkg/db/schema/leaseInvoices.js";
import { createLogger } from "../../../../pkg/logger/logger.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
const router = Router();
router.post("/", async (req: Request, res: Response) => {
// when a new server is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there
//res.status(200).json({ message: "Server added", ip: req.hostname });
const log = createLogger({ module: "forklift", subModule: "add invoice" });
const parsedInvoice = newInvoiceSchema.safeParse({
leaseId: req.body.leaseId,
companyId: req.body.companyId,
invoiceNumber: req.body.invoiceNumber,
invoiceDate: req.body.invoiceDate,
uploadedBy: req.body.uploadedBy,
totalAmount: req.body.totalAmount,
});
if (!parsedInvoice.success)
return res.status(400).json({ error: parsedInvoice.error.flatten });
const invoiceData = parsedInvoice.data;
const forkliftItems = Array.isArray(req.body.forklifts)
? req.body.forklifts
: [];
const validatedForklifts = []; //z.infer<typeof newForkliftInvoiceSchema>[] = [];
for (const item of forkliftItems) {
// const parsedItem = newForkliftInvoiceSchema.safeParse(item);
// if (parsedItem.success) {
validatedForklifts.push(item);
//} else {
//return res.status(400).json({ error: parsedItem.error.flatten() });
//}
}
// this will be the total invoice amount minus each forklift this way we can keep the total amount in here plus forklifts seperated
const totalAmount = (
validatedForklifts.reduce((sum, f) => sum + Number(f.amount || 0), 0) -
req.body.totalInvoice
).toString();
const { data, error } = await tryCatch(
db
.insert(leaseInvoices)
.values({
...invoiceData,
uploadedBy: req.user!.username || "lst_user",
})
.onConflictDoUpdate({
target: leaseInvoices.invoiceNumber,
set: {
totalAmount,
invoiceDate: invoiceData.invoiceDate,
uploadedBy: req.user!.username || "lst_user",
},
})
.returning(),
);
if (error) {
const err: DrizzleError = error;
return res.status(400).json({
message: `Error adding lease`,
error: err.cause,
});
}
const invoiceId = data[0]?.id;
const forkliftInvoices = validatedForklifts.map((f) => ({
invoiceId,
forkliftId: f.forklift_Id,
amount: f.amount,
}));
console.log(forkliftInvoices);
if (validatedForklifts.length > 0) {
await db.insert(leaseInvoiceForklifts).values(forkliftInvoices);
// .onConflictDoUpdate({
// target: [
// leaseInvoiceForklifts.invoiceId,
// leaseInvoiceForklifts.forkliftId,
// ],
// set: { amount: (excluded) => excluded.amount },
// });
}
// if (req.hostname === "localhost" && process.env.MAIN_SERVER) {
// log.info({}, "Running in dev server about to add in a new server");
// const axiosInstance = axios.create({
// httpsAgent: new https.Agent({ rejectUnauthorized: false }),
// baseURL: process.env.MAIN_SERVER, // e.g. "https://example.com"
// withCredentials: true,
// });
// const loginRes = (await axiosInstance.post(
// `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`,
// {
// username: process.env.MAIN_SERVER_USERNAME,
// password: process.env.MAIN_SERVER_PASSWORD,
// },
// {
// headers: { "Content-Type": "application/json" },
// },
// )) as any;
// const setCookie = loginRes.headers["set-cookie"][0];
// if (!setCookie) {
// throw new Error("Did not receive a Set-Cookie header from login");
// }
// const { data, error } = await tryCatch(
// axios.post(
// `${process.env.MAIN_SERVER}/lst/api/forklifts/leases`,
// parsed.data,
// {
// headers: {
// "Content-Type": "application/json",
// Cookie: setCookie.split(";")[0],
// },
// withCredentials: true,
// },
// ),
// );
// if (error) {
// log.error(
// { stack: error },
// "There was an error adding the company to Main Server",
// );
// }
// log.info(
// { stack: data?.data },
// "A new Company was just added to the server.",
// );
// }
return res
.status(201)
.json({ message: `lease ${data[0]?.invoiceNumber} added`, data: data });
});
export default router;

View File

@@ -0,0 +1,52 @@
import { and, asc, eq, relations } from "drizzle-orm";
import type { Request, Response } from "express";
import { Router } from "express";
import { db } from "../../../../pkg/db/db.js";
import { forkliftCompanies } from "../../../../pkg/db/schema/forkliftLeaseCompanys.js";
import { leases } from "../../../../pkg/db/schema/forkliftLeases.js";
import { forklifts } from "../../../../pkg/db/schema/forklifts.js";
import { leaseInvoices } from "../../../../pkg/db/schema/leaseInvoices.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
const router = Router();
router.get("/", async (req: Request, res: Response) => {
const invoiceNumber = req.query.lease;
const conditions = [];
if (invoiceNumber !== undefined) {
conditions.push(eq(leaseInvoices.invoiceNumber, `${invoiceNumber}`));
}
//conditions.push(eq(forkliftCompanies.active, true));
const { data, error } = await tryCatch(
db
.select(
// {
// id: leases.id,
// leaseNumber: leases.leaseNumber,
// startDate: leases.startDate,
// endDate: leases.endDate,
// leaseLink: leases.leaseLink,
// companyName: forkliftCompanies.name,
// add_user: leases.add_user,
// add_date: leases.add_date,
// upd_user: leases.upd_user,
// upd_date: leases.upd_date,
// }
)
.from(leaseInvoices)
//.innerJoin(forkliftCompanies, eq(forkliftCompanies.id, leases.companyId))
.where(and(...conditions))
.orderBy(asc(leaseInvoices.invoiceNumber)),
);
if (error) {
return res.status(400).json({ error: error });
}
res.status(200).json({ message: "Current Leases", data: data });
});
export default router;

View File

@@ -0,0 +1,22 @@
import { Router } from "express";
import { requireAuth } from "../../../../pkg/middleware/authMiddleware.js";
import addInvoice from "./addInvoice.js";
import getInvoices from "./getInvoices.js";
import updateInvoice from "./updateInvoices.js";
const router = Router();
router.use(
"/",
requireAuth("forklifts", ["systemAdmin", "admin", "manager"]),
getInvoices,
);
router.use("/", requireAuth("forklifts", ["systemAdmin", "admin"]), addInvoice);
router.use(
"/",
requireAuth("forklifts", ["systemAdmin", "admin"]),
updateInvoice,
);
export default router;

View File

@@ -0,0 +1,114 @@
import axios from "axios";
import { eq, sql } from "drizzle-orm";
import type { Request, Response } from "express";
import { Router } from "express";
import https from "https";
import { db } from "../../../../pkg/db/db.js";
import { forkliftCompanies } from "../../../../pkg/db/schema/forkliftLeaseCompanys.js";
import { leases } from "../../../../pkg/db/schema/forkliftLeases.js";
import { serverData } from "../../../../pkg/db/schema/servers.js";
import { createLogger } from "../../../../pkg/logger/logger.js";
import { tryCatch } from "../../../../pkg/utils/tryCatch.js";
const router = Router();
router.patch("/:id", async (req: Request, res: Response) => {
const log = createLogger({
module: "forklifts",
subModule: "update invoice",
});
// when a server is updated and is posted from localhost or 127.0.0.1 we also want to post it to the test server so we can see it from there, we want to insert with update on conflict.
const id = req.params.id;
const updates: Record<string, any> = {};
console.log(req.body);
if (req.body?.leaseNumber !== undefined) {
updates.leaseNumber = req.body.leaseNumber;
}
if (req.body?.startDate !== undefined) {
updates.startDate = req.body.startDate;
}
if (req.body?.endDate !== undefined) {
updates.endDate = req.body.endDate;
}
if (req.body?.companyId !== undefined) {
updates.companyId = req.body.companyId;
}
if (req.body?.leaseLink !== undefined) {
updates.leaseLink = req.body.leaseLink;
}
updates.upd_user = req.user!.username || "lst_user";
updates.upd_date = sql`NOW()`;
console.log(updates);
try {
if (Object.keys(updates).length > 0) {
await db.update(leases).set(updates).where(eq(leases.id, id));
}
// if (req.hostname === "localhost" && process.env.MAIN_SERVER) {
// log.info({}, "Running in dev server about to add in a new server");
// const axiosInstance = axios.create({
// httpsAgent: new https.Agent({ rejectUnauthorized: false }),
// baseURL: process.env.MAIN_SERVER,
// withCredentials: true,
// });
// const loginRes = (await axiosInstance.post(
// `${process.env.MAIN_SERVER}/lst/api/auth/sign-in/username`,
// {
// username: process.env.MAIN_SERVER_USERNAME,
// password: process.env.MAIN_SERVER_PASSWORD,
// },
// {
// headers: { "Content-Type": "application/json" },
// },
// )) as any;
// const setCookie = loginRes?.headers["set-cookie"][0];
// //console.log(setCookie.split(";")[0].replace("__Secure-", ""));
// if (!setCookie) {
// throw new Error("Did not receive a Set-Cookie header from login");
// }
// const { data, error } = await tryCatch(
// axios.patch(
// `${process.env.MAIN_SERVER}/lst/api/forklifts/leases/${id}`,
// updates,
// {
// headers: {
// "Content-Type": "application/json",
// Cookie: setCookie.split(";")[0],
// },
// withCredentials: true,
// },
// ),
// );
// if (error) {
// //console.log(error);
// log.error(
// { stack: error },
// "There was an error updating the lease to Main Server",
// );
// }
// log.info(
// { stack: data?.data },
// "A new lease was just updated to the server.",
// );
// }
res.status(200).json({ message: `${id} was just updated` });
} catch (error) {
//console.log(error);
res.status(200).json({ message: "Error updating lease", error });
}
});
export default router;

View File

@@ -1,9 +1,10 @@
import { numeric, pgTable, serial, uuid } from "drizzle-orm/pg-core"; import { numeric, pgTable, serial, uuid } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { forklifts } from "./forklifts.js"; import { forklifts } from "./forklifts.js";
import { leaseInvoices } from "./leaseInvoices.js"; import { leaseInvoices } from "./leaseInvoices.js";
export const leaseInvoiceForklifts = pgTable("lease_invoice_forklifts", { export const leaseInvoiceForklifts = pgTable("lease_invoice_forklifts", {
id: serial("id").primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
invoiceId: uuid("invoice_id") invoiceId: uuid("invoice_id")
.notNull() .notNull()
.references(() => leaseInvoices.id, { onDelete: "cascade" }), .references(() => leaseInvoices.id, { onDelete: "cascade" }),
@@ -12,3 +13,7 @@ export const leaseInvoiceForklifts = pgTable("lease_invoice_forklifts", {
.references(() => forklifts.forklift_id, { onDelete: "cascade" }), .references(() => forklifts.forklift_id, { onDelete: "cascade" }),
amount: numeric("amount"), // optional: amount of invoice allocated to this lift amount: numeric("amount"), // optional: amount of invoice allocated to this lift
}); });
export const newForkliftInvoiceSchema = createInsertSchema(
leaseInvoiceForklifts,
);

View File

@@ -23,7 +23,7 @@ const status = pgEnum("forklift_status", [
export const forklifts = pgTable("forklifts", { export const forklifts = pgTable("forklifts", {
forklift_id: uuid("forklift_id").defaultRandom().primaryKey(), forklift_id: uuid("forklift_id").defaultRandom().primaryKey(),
forkliftNumber: serial("forklift_number").notNull(), forkliftNumber: serial("forklift_number").notNull(),
serialNumber: text("serial_number").notNull(), serialNumber: text("serial_number").unique().notNull(),
model: text("model").notNull(), model: text("model").notNull(),
plant: text("plant") plant: text("plant")
.notNull() .notNull()
@@ -41,8 +41,8 @@ export const forklifts = pgTable("forklifts", {
dataPlate: text("data_plate"), dataPlate: text("data_plate"),
add_date: timestamp("add_date").defaultNow(), add_date: timestamp("add_date").defaultNow(),
add_user: text("add_user").default("LST"), add_user: text("add_user").default("LST"),
upd_date: timestamp("add_date").defaultNow(), upd_date: timestamp("upd_date").defaultNow(),
upd_user: text("add_user").default("LST"), upd_user: text("upd_user").default("LST"),
}); });
export const forkliftsSchema = createSelectSchema(forklifts); export const forkliftsSchema = createSelectSchema(forklifts);

View File

@@ -1,19 +1,24 @@
import { date, numeric, pgTable, text, uuid } from "drizzle-orm/pg-core"; import {
import { forkliftCompanies } from "./forkliftLeaseCompanys.js"; date,
numeric,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { leases } from "./forkliftLeases.js"; import { leases } from "./forkliftLeases.js";
import { forklifts } from "./forklifts.js";
export const leaseInvoices = pgTable("lease_invoices", { export const leaseInvoices = pgTable("lease_invoices", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
leaseId: uuid("lease_id") leaseId: uuid("lease_id")
.notNull() .notNull()
.references(() => leases.id, { onDelete: "cascade" }), .references(() => leases.id, { onDelete: "cascade" }),
companyId: uuid("company_id").references(() => forkliftCompanies.id), invoiceNumber: text("invoice_number").unique().notNull(),
invoiceNumber: text("invoice_number").notNull(),
invoiceDate: date("invoice_date").notNull(), invoiceDate: date("invoice_date").notNull(),
forkliftId: uuid("forklift_id")
.notNull()
.references(() => forklifts.forklift_id, { onDelete: "cascade" }),
totalAmount: numeric("total_amount"), totalAmount: numeric("total_amount"),
add_date: timestamp("add_date"),
uploadedBy: text("uploaded_by"), uploadedBy: text("uploaded_by"),
}); });
export const newInvoiceSchema = createInsertSchema(leaseInvoices);

View File

@@ -0,0 +1,2 @@
ALTER TABLE "forklifts" ADD COLUMN "upd_date" timestamp DEFAULT now();--> statement-breakpoint
ALTER TABLE "forklifts" ADD COLUMN "upd_user" text DEFAULT 'LST';

View File

@@ -0,0 +1 @@
ALTER TABLE "forklifts" ADD CONSTRAINT "forklifts_serial_number_unique" UNIQUE("serial_number");

View File

@@ -0,0 +1,6 @@
ALTER TABLE "lease_invoices" DROP CONSTRAINT "lease_invoices_company_id_forklift_companies_id_fk";
--> statement-breakpoint
ALTER TABLE "lease_invoices" DROP CONSTRAINT "lease_invoices_forklift_id_forklifts_forklift_id_fk";
--> statement-breakpoint
ALTER TABLE "lease_invoices" DROP COLUMN "company_id";--> statement-breakpoint
ALTER TABLE "lease_invoices" DROP COLUMN "forklift_id";

View File

@@ -0,0 +1 @@
ALTER TABLE "lease_invoices" ADD COLUMN "add_date" timestamp;

View File

@@ -0,0 +1 @@
ALTER TABLE "lease_invoices" ADD CONSTRAINT "lease_invoices_invoice_number_unique" UNIQUE("invoice_number");

View File

@@ -0,0 +1,2 @@
ALTER TABLE "lease_invoice_forklifts" ALTER COLUMN "id" SET DATA TYPE uuid;--> statement-breakpoint
ALTER TABLE "lease_invoice_forklifts" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -274,6 +274,48 @@
"when": 1762298736944, "when": 1762298736944,
"tag": "0038_secret_luminals", "tag": "0038_secret_luminals",
"breakpoints": true "breakpoints": true
},
{
"idx": 39,
"version": "7",
"when": 1762310353351,
"tag": "0039_strange_iron_man",
"breakpoints": true
},
{
"idx": 40,
"version": "7",
"when": 1762310431830,
"tag": "0040_keen_abomination",
"breakpoints": true
},
{
"idx": 41,
"version": "7",
"when": 1762311550798,
"tag": "0041_aspiring_chamber",
"breakpoints": true
},
{
"idx": 42,
"version": "7",
"when": 1762312885208,
"tag": "0042_true_thor_girl",
"breakpoints": true
},
{
"idx": 43,
"version": "7",
"when": 1762313144802,
"tag": "0043_thick_sebastian_shaw",
"breakpoints": true
},
{
"idx": 44,
"version": "7",
"when": 1762314701057,
"tag": "0044_melted_mole_man",
"breakpoints": true
} }
] ]
} }