refactor: invoice relation (#2)

* refactor: update database table

* refactor: update invoice to have relation with installments

* chore: add migration
This commit is contained in:
Methapon Metanipat 2024-10-30 17:32:46 +07:00 committed by GitHub
parent ba41bf45cb
commit 824727582d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 163 deletions

View file

@ -0,0 +1,77 @@
/*
Warnings:
- You are about to drop the column `invoice` on the `QuotationPaySplit` table. All the data in the column will be lost.
- You are about to drop the column `invoiceId` on the `QuotationProductServiceList` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "QuotationProductServiceList" DROP CONSTRAINT "QuotationProductServiceList_invoiceId_fkey";
-- AlterTable
ALTER TABLE "QuotationPaySplit" DROP COLUMN "invoice",
ADD COLUMN "invoiceId" TEXT;
-- AlterTable
ALTER TABLE "QuotationProductServiceList" DROP COLUMN "invoiceId";
-- CreateTable
CREATE TABLE "Notification" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"detail" TEXT NOT NULL,
"receiverId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "NotificationGroup" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "NotificationGroup_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_NotificationToNotificationGroup" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "_NotificationToUser" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "_NotificationToNotificationGroup_AB_unique" ON "_NotificationToNotificationGroup"("A", "B");
-- CreateIndex
CREATE INDEX "_NotificationToNotificationGroup_B_index" ON "_NotificationToNotificationGroup"("B");
-- CreateIndex
CREATE UNIQUE INDEX "_NotificationToUser_AB_unique" ON "_NotificationToUser"("A", "B");
-- CreateIndex
CREATE INDEX "_NotificationToUser_B_index" ON "_NotificationToUser"("B");
-- AddForeignKey
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_receiverId_fkey" FOREIGN KEY ("receiverId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "QuotationPaySplit" ADD CONSTRAINT "QuotationPaySplit_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_NotificationToNotificationGroup" ADD CONSTRAINT "_NotificationToNotificationGroup_A_fkey" FOREIGN KEY ("A") REFERENCES "Notification"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_NotificationToNotificationGroup" ADD CONSTRAINT "_NotificationToNotificationGroup_B_fkey" FOREIGN KEY ("B") REFERENCES "NotificationGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_NotificationToUser" ADD CONSTRAINT "_NotificationToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Notification"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_NotificationToUser" ADD CONSTRAINT "_NotificationToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -1194,12 +1194,14 @@ model Quotation {
model QuotationPaySplit {
id String @id @default(cuid())
no Int
amount Float
invoice Boolean @default(false)
no Int
amount Float
quotation Quotation? @relation(fields: [quotationId], references: [id], onDelete: Cascade)
quotationId String?
invoice Invoice? @relation(fields: [invoiceId], references: [id])
invoiceId String?
}
model QuotationWorker {
@ -1238,9 +1240,6 @@ model QuotationProductServiceList {
worker QuotationProductServiceWorker[]
requestWork RequestWork[]
invoice Invoice? @relation(fields: [invoiceId], references: [id])
invoiceId String?
}
model QuotationProductServiceWorker {
@ -1259,7 +1258,7 @@ model Invoice {
quotation Quotation @relation(fields: [quotationId], references: [id])
quotationId String
productServiceList QuotationProductServiceList[]
installments QuotationPaySplit[]
amount Float?

View file

@ -2,12 +2,10 @@ import { Prisma } from "@prisma/client";
import {
Body,
Controller,
Delete,
Get,
OperationId,
Path,
Post,
Put,
Query,
Request,
Route,
@ -26,10 +24,7 @@ import {
type InvoicePayload = {
quotationId: string;
amount: number;
// NOTE: For individual list that will be include in the quotation
productServiceListId?: string[];
// NOTE: Will be pulled from quotation
installmentNo?: number[];
installmentNo: number[];
};
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_account", "account"];
@ -67,14 +62,7 @@ export class InvoiceController extends Controller {
prisma.invoice.findMany({
where,
include: {
productServiceList: {
include: {
worker: true,
service: true,
work: true,
product: true,
},
},
installments: true,
quotation: true,
createdBy: true,
},
@ -93,14 +81,7 @@ export class InvoiceController extends Controller {
const record = await prisma.invoice.findFirst({
where: { id: invoiceId },
include: {
productServiceList: {
include: {
worker: true,
service: true,
work: true,
product: true,
},
},
installments: true,
quotation: true,
createdBy: true,
},
@ -116,44 +97,35 @@ export class InvoiceController extends Controller {
@OperationId("createInvoice")
@Security("keycloak", MANAGE_ROLES)
async createInvoice(@Request() req: RequestWithUser, @Body() body: InvoicePayload) {
const [quotation, productServiceList] = await prisma.$transaction([
const [quotation] = await prisma.$transaction([
prisma.quotation.findUnique({
where: { id: body.quotationId },
include: { registeredBranch: { include: branchRelationPermInclude(req.user) } },
}),
prisma.quotationProductServiceList.findMany({
where: {
OR: [
{ id: { in: body.productServiceListId }, invoiceId: null },
{ installmentNo: { in: body.installmentNo }, invoiceId: null },
],
},
}),
]);
if (!quotation) throw notFoundError("Quotation");
await permissionCheck(req.user, quotation.registeredBranch);
return await prisma.$transaction(async (tx) => {
await tx.quotation.update({
const record = await tx.quotation.update({
include: {
paySplit: {
where: { no: { in: body.installmentNo } },
},
},
where: { id: body.quotationId },
data: {
quotationStatus: "PaymentInProcess",
paySplit: body.installmentNo
? {
updateMany: {
where: { no: { in: body.installmentNo } },
data: { invoice: true },
},
}
: undefined,
},
});
return await tx.invoice.create({
data: {
productServiceList: { connect: productServiceList.map((v) => ({ id: v.id })) },
quotationId: body.quotationId,
amount: body.amount,
installments: {
connect: record.paySplit.map((v) => ({ id: v.id })),
},
payment: {
create: {
paymentStatus: "PaymentWait",
@ -165,85 +137,4 @@ export class InvoiceController extends Controller {
});
});
}
@Put("{invoiceId}")
@OperationId("updateInvoice")
@Security("keycloak", MANAGE_ROLES)
async updateInvoice(
@Request() req: RequestWithUser,
@Body() body: InvoicePayload,
@Path() invoiceId: string,
) {
const [record, quotation, productServiceList] = await prisma.$transaction([
prisma.invoice.findUnique({
where: { id: invoiceId },
include: {
productServiceList: {
where: {
id: { notIn: body.productServiceListId },
installmentNo: { notIn: body.installmentNo },
},
},
},
}),
prisma.quotation.findUnique({
where: { id: body.quotationId },
include: { registeredBranch: { include: branchRelationPermInclude(req.user) } },
}),
prisma.quotationProductServiceList.findMany({
where: {
OR: [
{ id: { in: body.productServiceListId }, invoiceId: null },
{ installmentNo: { in: body.installmentNo }, invoiceId: null },
],
},
}),
]);
if (!record) throw notFoundError("Invoice");
if (!quotation) throw notFoundError("Quotation");
await permissionCheck(req.user, quotation.registeredBranch);
return await prisma.$transaction(async (tx) => {
return await tx.invoice.update({
where: { id: invoiceId },
data: {
productServiceList: {
disconnect: record.productServiceList.map((v) => ({ id: v.id })),
connect: productServiceList.map((v) => ({ id: v.id })),
},
},
});
});
}
@Delete("{invoiceId}")
@OperationId("deleteInvoice")
@Security("keycloak", MANAGE_ROLES)
async deleteInvoice(@Request() req: RequestWithUser, @Path() invoiceId: string) {
return await prisma.$transaction(async (tx) => {
const record = await tx.invoice.delete({
where: { id: invoiceId },
include: {
productServiceList: {
include: {
worker: true,
service: true,
work: true,
product: true,
},
},
quotation: {
include: { registeredBranch: { include: branchRelationPermInclude(req.user) } },
},
createdBy: true,
},
});
if (!record) throw notFoundError("Invoice");
await permissionCheck(req.user, record.quotation.registeredBranch);
return record;
});
}
}

View file

@ -37,14 +37,7 @@ export class ReceiptController extends Controller {
include: {
invoice: {
include: {
productServiceList: {
include: {
worker: true,
service: true,
work: true,
product: true,
},
},
installments: true,
quotation: true,
createdBy: true,
},
@ -67,14 +60,7 @@ export class ReceiptController extends Controller {
include: {
invoice: {
include: {
productServiceList: {
include: {
worker: true,
service: true,
work: true,
product: true,
},
},
installments: true,
quotation: true,
createdBy: true,
},

View file

@ -61,14 +61,7 @@ export class QuotationPayment extends Controller {
include: {
invoice: {
include: {
productServiceList: {
include: {
worker: true,
service: true,
work: true,
product: true,
},
},
installments: true,
quotation: true,
createdBy: true,
},
@ -90,14 +83,7 @@ export class QuotationPayment extends Controller {
include: {
invoice: {
include: {
productServiceList: {
include: {
worker: true,
service: true,
work: true,
product: true,
},
},
installments: true,
quotation: true,
createdBy: true,
},

View file

@ -289,7 +289,9 @@ export class QuotationController extends Controller {
worker: true,
},
},
paySplit: true,
paySplit: {
include: { invoice: true },
},
createdBy: true,
updatedBy: true,
},
@ -482,7 +484,9 @@ export class QuotationController extends Controller {
worker: true,
},
},
paySplit: true,
paySplit: {
include: { invoice: true },
},
worker: true,
customerBranch: {
include: { customer: true },