797 lines
23 KiB
TypeScript
797 lines
23 KiB
TypeScript
import { PayCondition, Prisma, Status } from "@prisma/client";
|
|
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
Path,
|
|
Post,
|
|
Put,
|
|
Query,
|
|
Request,
|
|
Route,
|
|
Security,
|
|
Tags,
|
|
} from "tsoa";
|
|
import { RequestWithUser } from "../interfaces/user";
|
|
import prisma from "../db";
|
|
import HttpError from "../interfaces/http-error";
|
|
import HttpStatus from "../interfaces/http-status";
|
|
|
|
type QuotationCreate = {
|
|
status?: Status;
|
|
|
|
payCondition: PayCondition;
|
|
|
|
paySplitCount?: number;
|
|
paySplit?: Date[];
|
|
|
|
payBillDate?: Date;
|
|
|
|
workerCount: number;
|
|
// EmployeeId or Create new employee
|
|
worker: (
|
|
| string
|
|
| {
|
|
dateOfBirth: Date;
|
|
gender: string;
|
|
nationality: string;
|
|
|
|
firstName: string;
|
|
firstNameEN: string;
|
|
lastName: string;
|
|
lastNameEN: string;
|
|
|
|
addressEN: string;
|
|
address: string;
|
|
zipCode: string;
|
|
|
|
passportType: string;
|
|
passportNumber: string;
|
|
passportIssueDate: Date;
|
|
passportExpiryDate: Date;
|
|
passportIssuingCountry: string;
|
|
passportIssuingPlace: string;
|
|
previousPassportReference?: string;
|
|
}
|
|
)[];
|
|
|
|
customerBranchId: string;
|
|
customerId: string;
|
|
|
|
urgent?: boolean;
|
|
|
|
service: {
|
|
id: string;
|
|
// Other fields will come from original data
|
|
work: {
|
|
id: string;
|
|
// Name field will come from original data
|
|
excluded?: boolean;
|
|
product: {
|
|
id: string;
|
|
amount: number;
|
|
/**
|
|
* @maximum 1
|
|
* @minimum 0
|
|
*/
|
|
discount: number;
|
|
/**
|
|
* @maximum 1
|
|
* @minimum 0
|
|
*/
|
|
vat?: number;
|
|
}[];
|
|
}[];
|
|
}[];
|
|
};
|
|
|
|
type QuotationUpdate = {
|
|
status?: "ACTIVE" | "INACTIVE";
|
|
|
|
payCondition?: PayCondition;
|
|
|
|
paySplitCount?: number;
|
|
paySplit?: Date[];
|
|
|
|
payBillDate?: Date;
|
|
|
|
workerCount?: number;
|
|
// EmployeeId or Create new employee
|
|
worker?: (
|
|
| string
|
|
| {
|
|
dateOfBirth: Date;
|
|
gender: string;
|
|
nationality: string;
|
|
|
|
firstName: string;
|
|
firstNameEN: string;
|
|
lastName: string;
|
|
lastNameEN: string;
|
|
|
|
addressEN: string;
|
|
address: string;
|
|
zipCode: string;
|
|
|
|
passportType: string;
|
|
passportNumber: string;
|
|
passportIssueDate: Date;
|
|
passportExpiryDate: Date;
|
|
passportIssuingCountry: string;
|
|
passportIssuingPlace: string;
|
|
previousPassportReference?: string;
|
|
}
|
|
)[];
|
|
|
|
customerBranchId?: string;
|
|
customerId?: string;
|
|
|
|
urgent?: boolean;
|
|
|
|
service?: {
|
|
id: string;
|
|
// Other fields will come from original data
|
|
work: {
|
|
id: string;
|
|
excluded?: boolean;
|
|
// Name field will come from original data
|
|
product: {
|
|
id: string;
|
|
/**
|
|
* @isInt
|
|
*/
|
|
amount: number;
|
|
/**
|
|
* @maximum 1
|
|
* @minimum 0
|
|
*/
|
|
discount: number;
|
|
/**
|
|
* @maximum 1
|
|
* @minimum 0
|
|
*/
|
|
vat: number;
|
|
}[];
|
|
}[];
|
|
}[];
|
|
};
|
|
|
|
const MANAGE_ROLES = [
|
|
"system",
|
|
"head_of_admin",
|
|
"admin",
|
|
"branch_manager",
|
|
"head_of_account",
|
|
"account",
|
|
];
|
|
|
|
function globalAllow(roles?: string[]) {
|
|
return ["system", "head_of_admin", "admin", "branch_manager", "head_of_account"].some((v) =>
|
|
roles?.includes(v),
|
|
);
|
|
}
|
|
|
|
@Route("/api/v1/quotation")
|
|
@Tags("Quotation")
|
|
export class QuotationController extends Controller {
|
|
@Get()
|
|
@Security("keycloak")
|
|
async getQuotationList(@Query() page: number = 1, @Query() pageSize: number = 30) {
|
|
const [result, total] = await prisma.$transaction([
|
|
prisma.quotation.findMany({
|
|
include: {
|
|
worker: true,
|
|
service: {
|
|
include: {
|
|
_count: { select: { work: true } },
|
|
work: {
|
|
include: {
|
|
_count: { select: { productOnWork: true } },
|
|
productOnWork: {
|
|
include: { product: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
prisma.quotation.count(),
|
|
]);
|
|
|
|
return { result: result, page, pageSize, total };
|
|
}
|
|
|
|
@Get("{quotationId}")
|
|
@Security("keycloak")
|
|
async getQuotationById(@Path() quotationId: string) {
|
|
const record = await prisma.quotation.findUnique({
|
|
include: {
|
|
worker: true,
|
|
service: {
|
|
include: {
|
|
_count: { select: { work: true } },
|
|
work: {
|
|
include: {
|
|
_count: { select: { productOnWork: true } },
|
|
productOnWork: {
|
|
include: { product: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
where: { id: quotationId },
|
|
});
|
|
|
|
if (!record) {
|
|
throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound");
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
@Post()
|
|
@Security("keycloak", MANAGE_ROLES)
|
|
async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) {
|
|
const existingEmployee = body.worker.filter((v) => typeof v === "string");
|
|
const serviceIdList = body.service.map((v) => v.id);
|
|
const productIdList = body.service.flatMap((a) =>
|
|
a.work.flatMap((b) => b.product.map((c) => c.id)),
|
|
);
|
|
|
|
const [customer, customerBranch, employee, service, product] = await prisma.$transaction([
|
|
prisma.customer.findUnique({
|
|
where: { id: body.customerId },
|
|
}),
|
|
prisma.customerBranch.findUnique({
|
|
include: { customer: true },
|
|
where: { id: body.customerBranchId },
|
|
}),
|
|
prisma.employee.findMany({
|
|
where: { id: { in: existingEmployee } },
|
|
}),
|
|
prisma.service.findMany({
|
|
include: { work: true },
|
|
where: { id: { in: serviceIdList } },
|
|
}),
|
|
prisma.product.findMany({
|
|
where: { id: { in: productIdList } },
|
|
}),
|
|
]);
|
|
|
|
if (serviceIdList.length !== service.length) {
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Some service cannot be found.",
|
|
"relationServiceNotFound",
|
|
);
|
|
}
|
|
if (productIdList.length !== product.length) {
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Some product cannot be found.",
|
|
"relationProductNotFound",
|
|
);
|
|
}
|
|
if (existingEmployee.length !== employee.length) {
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Some worker(employee) cannot be found.",
|
|
"relationWorkerNotFound",
|
|
);
|
|
}
|
|
if (!customer)
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Customer cannot be found.",
|
|
"relationCustomerNotFound",
|
|
);
|
|
if (!customerBranch)
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Customer Branch cannot be found.",
|
|
"relationCustomerBranchNotFound",
|
|
);
|
|
if (customerBranch.customerId !== customer.id)
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Customer conflict with customer branch.",
|
|
"customerConflictCustomerBranch",
|
|
);
|
|
|
|
const { service: _service, worker: _worker, ...rest } = body;
|
|
|
|
return await prisma.$transaction(async (tx) => {
|
|
const nonExistEmployee = body.worker.filter((v) => typeof v !== "string");
|
|
const lastEmployee = await tx.runningNo.upsert({
|
|
where: {
|
|
key: `EMPLOYEE_${customerBranch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`,
|
|
},
|
|
create: {
|
|
key: `EMPLOYEE_${customerBranch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`,
|
|
value: 1,
|
|
},
|
|
update: { value: { increment: nonExistEmployee.length } },
|
|
});
|
|
const newEmployee = await Promise.all(
|
|
nonExistEmployee.map(async (v, i) =>
|
|
tx.employee.create({
|
|
data: {
|
|
...v,
|
|
code: `${customerBranch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}${`${lastEmployee.value + i}`.padStart(7, "0")}`,
|
|
customerBranchId: customerBranch.id,
|
|
},
|
|
}),
|
|
),
|
|
);
|
|
const sortedEmployeeId: string[] = [];
|
|
|
|
while (body.worker.length > 0) {
|
|
const popExist = body.worker.shift();
|
|
if (typeof popExist === "string") sortedEmployeeId.push(popExist);
|
|
else {
|
|
const popNew = newEmployee.shift();
|
|
popNew && sortedEmployeeId.push(popNew.id);
|
|
}
|
|
}
|
|
|
|
const price = { totalPrice: 0, totalDiscount: 0, totalVat: 0 };
|
|
|
|
const restructureService = body.service.flatMap((a) => {
|
|
const currentService = service.find((b) => b.id === a.id);
|
|
|
|
if (!currentService) return []; // should not possible
|
|
|
|
return {
|
|
id: currentService.id,
|
|
name: currentService.name,
|
|
code: currentService.code,
|
|
detail: currentService.detail,
|
|
attributes: currentService.attributes as Prisma.JsonObject,
|
|
work: a.work.flatMap((c) => {
|
|
if (c.excluded) return [];
|
|
|
|
const currentWork = currentService.work.find((d) => d.id === c.id);
|
|
|
|
if (!currentWork) return []; // additional will get stripped
|
|
|
|
return {
|
|
id: currentWork.id,
|
|
order: currentWork.order,
|
|
name: currentWork.name,
|
|
attributes: currentWork.attributes as Prisma.JsonObject,
|
|
product: c.product.flatMap((e) => {
|
|
const currentProduct = product.find((f) => f.id === e.id);
|
|
|
|
if (!currentProduct) return []; // should not possible
|
|
|
|
price.totalPrice += currentProduct.price * e.amount;
|
|
price.totalDiscount +=
|
|
Math.round(currentProduct.price * e.amount * e.discount * 100) / 100;
|
|
price.totalVat +=
|
|
Math.round(
|
|
(currentProduct.price * e.amount -
|
|
currentProduct.price * e.amount * e.discount) *
|
|
(e.vat === undefined ? 0.07 : e.vat) *
|
|
100,
|
|
) / 100;
|
|
|
|
return {
|
|
...e,
|
|
vat: e.vat === undefined ? 0.07 : e.vat,
|
|
pricePerUnit: currentProduct.price,
|
|
};
|
|
}),
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
|
|
const currentYear = new Date().getFullYear();
|
|
const currentMonth = new Date().getMonth() + 1;
|
|
const currentDate = new Date().getDate();
|
|
|
|
const lastQuotation = await tx.runningNo.upsert({
|
|
where: {
|
|
key: `QUOTATION_${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${currentDate.toString().padStart(2, "0")}`,
|
|
},
|
|
create: {
|
|
key: `QUOTATION_${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${currentDate.toString().padStart(2, "0")}`,
|
|
value: 1,
|
|
},
|
|
update: { value: { increment: 1 } },
|
|
});
|
|
|
|
return await tx.quotation.create({
|
|
include: {
|
|
service: {
|
|
include: {
|
|
work: {
|
|
include: {
|
|
productOnWork: {
|
|
include: { product: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
paySplit: true,
|
|
worker: true,
|
|
customerBranch: {
|
|
include: { customer: true },
|
|
},
|
|
_count: {
|
|
select: { service: true },
|
|
},
|
|
},
|
|
|
|
data: {
|
|
...rest,
|
|
statusOrder: +(rest.status === "INACTIVE"),
|
|
code: `${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${currentDate.toString().padStart(2, "0")}${lastQuotation.value.toString().padStart(4, "0")}`,
|
|
worker: {
|
|
createMany: {
|
|
data: sortedEmployeeId.map((v, i) => ({
|
|
no: i,
|
|
code: "",
|
|
employeeId: v,
|
|
})),
|
|
},
|
|
},
|
|
totalPrice: price.totalPrice,
|
|
totalDiscount: price.totalDiscount,
|
|
vat: price.totalVat,
|
|
vatExcluded: 0,
|
|
finalPrice: price.totalPrice - price.totalDiscount,
|
|
paySplit: {
|
|
createMany: {
|
|
data: (rest.paySplit || []).map((v, i) => ({
|
|
no: i + 1,
|
|
date: v,
|
|
})),
|
|
},
|
|
},
|
|
service: {
|
|
create: restructureService.map((a) => ({
|
|
code: a.code,
|
|
name: a.name,
|
|
detail: a.detail,
|
|
attributes: a.attributes,
|
|
refServiceId: a.id,
|
|
work: {
|
|
create: a.work.map((b) => ({
|
|
order: b.order,
|
|
name: b.name,
|
|
attributes: b.attributes,
|
|
productOnWork: {
|
|
createMany: {
|
|
data: b.product.map((v, i) => ({
|
|
productId: v.id,
|
|
order: i + 1,
|
|
vat: v.vat,
|
|
amount: v.amount,
|
|
discount: v.discount,
|
|
pricePerUnit: v.pricePerUnit,
|
|
})),
|
|
},
|
|
},
|
|
})),
|
|
},
|
|
})),
|
|
},
|
|
createdByUserId: req.user.sub,
|
|
updatedByUserId: req.user.sub,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
@Put("{quotationId}")
|
|
@Security("keycloak", MANAGE_ROLES)
|
|
async editQuotation(
|
|
@Request() req: RequestWithUser,
|
|
@Path() quotationId: string,
|
|
@Body() body: QuotationUpdate,
|
|
) {
|
|
const record = await prisma.quotation.findUnique({
|
|
include: { customer: true },
|
|
where: { id: quotationId },
|
|
});
|
|
|
|
if (!record) {
|
|
throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound");
|
|
}
|
|
|
|
const existingEmployee = body.worker?.filter((v) => typeof v === "string");
|
|
const serviceIdList = body.service?.map((v) => v.id);
|
|
const productIdList = body.service?.flatMap((a) =>
|
|
a.work.flatMap((b) => b.product.map((c) => c.id)),
|
|
);
|
|
|
|
const [customer, customerBranch, employee, service, product] = await prisma.$transaction(
|
|
async (tx) =>
|
|
await Promise.all([
|
|
tx.customer.findFirst({
|
|
where: { id: body.customerId },
|
|
}),
|
|
tx.customerBranch.findFirst({
|
|
include: { customer: true },
|
|
where: { id: body.customerBranchId },
|
|
}),
|
|
body.worker
|
|
? tx.employee.findMany({
|
|
where: { id: { in: existingEmployee } },
|
|
})
|
|
: null,
|
|
body.service
|
|
? tx.service.findMany({
|
|
include: { work: true },
|
|
where: { id: { in: serviceIdList } },
|
|
})
|
|
: null,
|
|
body.service
|
|
? tx.product.findMany({
|
|
where: { id: { in: productIdList } },
|
|
})
|
|
: null,
|
|
]),
|
|
);
|
|
|
|
if (serviceIdList?.length !== service?.length) {
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Some service cannot be found.",
|
|
"relationServiceNotFound",
|
|
);
|
|
}
|
|
if (productIdList?.length !== product?.length) {
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Some product cannot be found.",
|
|
"relationProductNotFound",
|
|
);
|
|
}
|
|
if (existingEmployee?.length !== employee?.length) {
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Some worker(employee) cannot be found.",
|
|
"relationWorkerNotFound",
|
|
);
|
|
}
|
|
if (!customer)
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Customer cannot be found.",
|
|
"relationCustomerNotFound",
|
|
);
|
|
if (!customerBranch)
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Customer Branch cannot be found.",
|
|
"relationCustomerBranchNotFound",
|
|
);
|
|
if (customerBranch.customerId !== customer.id)
|
|
throw new HttpError(
|
|
HttpStatus.BAD_REQUEST,
|
|
"Customer conflict with customer branch.",
|
|
"customerConflictCustomerBranch",
|
|
);
|
|
|
|
const { service: _service, worker: _worker, ...rest } = body;
|
|
|
|
return await prisma.$transaction(async (tx) => {
|
|
const sortedEmployeeId: string[] = [];
|
|
|
|
if (body.worker) {
|
|
const nonExistEmployee = body.worker.filter((v) => typeof v !== "string");
|
|
const lastEmployee = await tx.runningNo.upsert({
|
|
where: {
|
|
key: `EMPLOYEE_${customerBranch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`,
|
|
},
|
|
create: {
|
|
key: `EMPLOYEE_${customerBranch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`,
|
|
value: 1,
|
|
},
|
|
update: { value: { increment: nonExistEmployee.length } },
|
|
});
|
|
const newEmployee = await Promise.all(
|
|
nonExistEmployee.map(async (v, i) =>
|
|
tx.employee.create({
|
|
data: {
|
|
...v,
|
|
code: `${customerBranch.code}-${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}${`${lastEmployee.value - nonExistEmployee.length + i + 1}`.padStart(7, "0")}`,
|
|
customerBranchId: customerBranch.id,
|
|
},
|
|
}),
|
|
),
|
|
);
|
|
|
|
while (body.worker.length > 0) {
|
|
const popExist = body.worker.shift();
|
|
if (typeof popExist === "string") sortedEmployeeId.push(popExist);
|
|
else {
|
|
const popNew = newEmployee.shift();
|
|
popNew && sortedEmployeeId.push(popNew.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
const price = { totalPrice: 0, totalDiscount: 0, totalVat: 0 };
|
|
|
|
const restructureService = body.service?.flatMap((a) => {
|
|
const currentService = service?.find((b) => b.id === a.id);
|
|
|
|
if (!currentService) return []; // should not possible
|
|
|
|
return {
|
|
id: currentService.id,
|
|
name: currentService.name,
|
|
code: currentService.code,
|
|
detail: currentService.detail,
|
|
attributes: currentService.attributes as Prisma.JsonObject,
|
|
work: a.work.flatMap((c) => {
|
|
if (c.excluded) return [];
|
|
|
|
const currentWork = currentService.work.find((d) => d.id === c.id);
|
|
|
|
if (!currentWork) return []; // additional will get stripped
|
|
|
|
return {
|
|
id: currentWork.id,
|
|
order: currentWork.order,
|
|
name: currentWork.name,
|
|
attributes: currentWork.attributes as Prisma.JsonObject,
|
|
product: c.product.flatMap((e) => {
|
|
const currentProduct = product?.find((f) => f.id === e.id);
|
|
|
|
if (!currentProduct) return []; // should not possible
|
|
|
|
price.totalPrice += currentProduct.price * e.amount;
|
|
price.totalDiscount +=
|
|
Math.round(currentProduct.price * e.amount * e.discount * 100) / 100;
|
|
price.totalVat +=
|
|
Math.round(
|
|
(currentProduct.price * e.amount -
|
|
currentProduct.price * e.amount * e.discount) *
|
|
(e.vat === undefined ? 0.07 : e.vat) *
|
|
100,
|
|
) / 100;
|
|
|
|
return {
|
|
...e,
|
|
vat: e.vat === undefined ? 0.07 : e.vat,
|
|
pricePerUnit: currentProduct.price,
|
|
};
|
|
}),
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
|
|
return await tx.quotation.update({
|
|
include: {
|
|
service: {
|
|
include: {
|
|
work: {
|
|
include: {
|
|
productOnWork: {
|
|
include: { product: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
paySplit: true,
|
|
worker: true,
|
|
customerBranch: {
|
|
include: { customer: true },
|
|
},
|
|
_count: {
|
|
select: { service: true },
|
|
},
|
|
},
|
|
where: { id: quotationId },
|
|
data: {
|
|
...rest,
|
|
statusOrder: +(rest.status === "INACTIVE"),
|
|
code: "",
|
|
worker:
|
|
sortedEmployeeId.length > 0
|
|
? {
|
|
deleteMany: { id: { notIn: sortedEmployeeId } },
|
|
createMany: {
|
|
skipDuplicates: true,
|
|
data: sortedEmployeeId.map((v, i) => ({
|
|
no: i,
|
|
code: "",
|
|
employeeId: v,
|
|
})),
|
|
},
|
|
}
|
|
: undefined,
|
|
totalPrice: body.service ? price.totalPrice : undefined,
|
|
totalDiscount: body.service ? price.totalDiscount : undefined,
|
|
|
|
vat: body.service ? price.totalVat : undefined,
|
|
vatExcluded: body.service ? 0 : undefined,
|
|
|
|
finalPrice: body.service ? price.totalPrice - price.totalDiscount : undefined,
|
|
|
|
paySplit: rest.paySplit
|
|
? {
|
|
deleteMany: {},
|
|
createMany: {
|
|
data: (rest.paySplit || []).map((v, i) => ({
|
|
no: i + 1,
|
|
date: v,
|
|
})),
|
|
},
|
|
}
|
|
: undefined,
|
|
|
|
service:
|
|
body.service && restructureService
|
|
? {
|
|
deleteMany: {},
|
|
create: restructureService.map((a) => ({
|
|
code: a.code,
|
|
name: a.name,
|
|
detail: a.detail,
|
|
attributes: a.attributes,
|
|
refServiceId: a.id,
|
|
work: {
|
|
create: a.work.map((b) => ({
|
|
order: b.order,
|
|
name: b.name,
|
|
attributes: b.attributes,
|
|
productOnWork: {
|
|
createMany: {
|
|
data: b.product.map((v, i) => ({
|
|
productId: v.id,
|
|
order: i + 1,
|
|
vat: v.vat,
|
|
amount: v.amount,
|
|
discount: v.discount,
|
|
pricePerUnit: v.pricePerUnit,
|
|
})),
|
|
},
|
|
},
|
|
})),
|
|
},
|
|
})),
|
|
}
|
|
: undefined,
|
|
|
|
updatedByUserId: req.user.sub,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
@Delete("{quotationId}")
|
|
@Security("keycloak", MANAGE_ROLES)
|
|
async deleteQuotationById(@Path() quotationId: string) {
|
|
const record = await prisma.quotation.findUnique({
|
|
where: { id: quotationId },
|
|
});
|
|
|
|
if (!record) {
|
|
throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound");
|
|
}
|
|
|
|
if (record.status !== Status.CREATED) {
|
|
throw new HttpError(HttpStatus.FORBIDDEN, "Quotation is in used.", "quotationInUsed");
|
|
}
|
|
|
|
return await prisma.quotation.delete({
|
|
include: {
|
|
createdBy: true,
|
|
updatedBy: true,
|
|
},
|
|
where: { id: quotationId },
|
|
});
|
|
}
|
|
}
|