import { Body, Controller, Delete, Get, Path, Post, Put, Query, Request, Route, Security, Tags, } from "tsoa"; // import { Prisma } from "@prisma/client"; import prisma from "../db"; import { RequestWithUser } from "../interfaces/user"; import { branchRelationPermInclude, createPermCheck, createPermCondition, } from "../services/permission"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { notFoundError } from "../utils/error"; import { Prisma } from "@prisma/client"; import { queryOrNot } from "../utils/relation"; import { RequestWorkStatus } from "../generated/kysely/types"; const MANAGE_ROLES = [ "system", "head_of_admin", "admin", "head_of_accountant", "accountant", "head_of_sale", "sale", ]; function globalAllow(user: RequestWithUser["user"]) { const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; return allowList.some((v) => user.roles?.includes(v)); } // NOTE: permission condition/check in requestWork -> requestData -> quotation -> registeredBranch const permissionCond = createPermCondition(globalAllow); const permissionCondCompany = createPermCondition((_) => true); const permissionCheck = createPermCheck(globalAllow); const permissionCheckCompany = createPermCheck((_) => true); type CreditNoteCreate = { requestWorkId: string[]; quotationId: string; }; type CreditNoteUpdate = { requestWorkId: string[]; quotationId: string; }; @Route("api/v1/credit-note") @Tags("Credit Note") export class CreditNoteController extends Controller { @Get("stats") @Security("keycloak") async getCreditNoteStats(@Request() req: RequestWithUser, @Query() quotationId: string) { const where = { requestWork: { some: { request: { quotationId, quotation: { registeredBranch: { OR: permissionCondCompany(req.user) }, }, }, }, }, } satisfies Prisma.CreditNoteWhereInput; return await prisma.creditNote.count({ where }); } @Get() @Security("keycloak") async getCreditNoteList( @Request() req: RequestWithUser, @Query() page: number = 1, @Query() pageSize: number = 30, @Query() query: string = "", @Query() quotationId?: string, ) { return await this.getCreditNoteListByCriteria(req, page, pageSize, query, quotationId); } // NOTE: only when needed or else remove this and implement in getCreditNoteList @Post("list") @Security("keycloak") async getCreditNoteListByCriteria( @Request() req: RequestWithUser, @Query() page: number = 1, @Query() pageSize: number = 30, @Query() query: string = "", @Query() quotationId?: string, @Body() body?: {}, ) { const where = { OR: queryOrNot(query, [ { requestWork: { some: { request: { OR: queryOrNot(query, [ { quotation: { code: { contains: query, mode: "insensitive" } } }, { quotation: { workName: { contains: query } } }, { quotation: { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, { customerName: { contains: query } }, { firstName: { contains: query } }, { firstNameEN: { contains: query } }, { lastName: { contains: query } }, { lastNameEN: { contains: query } }, ], }, }, employee: { OR: [ { employeePassport: { some: { number: { contains: query } }, }, }, { code: { contains: query, mode: "insensitive" } }, { firstName: { contains: query } }, { firstNameEN: { contains: query } }, { lastName: { contains: query } }, { lastNameEN: { contains: query } }, ], }, }, ]), }, }, }, }, ]), requestWork: { some: { request: { quotationId, quotation: { registeredBranch: { OR: permissionCondCompany(req.user) }, }, }, }, }, } satisfies Prisma.CreditNoteWhereInput; const [result, total] = await prisma.$transaction([ prisma.creditNote.findMany({ where, include: { quotation: true, requestWork: { include: { request: true }, }, }, }), prisma.creditNote.count({ where }), ]); return { result, page, pageSize, total }; } @Get("{creditNoteId}") @Security("keycloak") async getCreditNote(@Request() req: RequestWithUser, @Path() creditNoteId: string) { const where = { id: creditNoteId, requestWork: { some: { request: { quotation: { registeredBranch: { OR: permissionCondCompany(req.user) }, }, }, }, }, } satisfies Prisma.CreditNoteWhereInput; return prisma.creditNote.findFirst({ where, include: { requestWork: { include: { request: true }, }, }, }); } @Post() @Security("keycloak", MANAGE_ROLES) async createCreditNote(@Request() req: RequestWithUser, @Body() body: CreditNoteCreate) { const requestWork = await prisma.requestWork.findMany({ where: { request: { quotation: { id: body.quotationId, }, }, stepStatus: { some: { workStatus: "Canceled", }, }, id: { in: body.requestWorkId }, }, include: { request: { include: { quotation: { include: { registeredBranch: { include: branchRelationPermInclude(req.user) }, }, }, }, }, }, }); if (requestWork.length !== body.requestWorkId.length) { throw new HttpError(HttpStatus.BAD_REQUEST, "Not Match", "reqNotMet"); } await Promise.all( requestWork.map((item) => permissionCheck(req.user, item.request.quotation.registeredBranch)), ); const record = await prisma.creditNote.create({ include: { requestWork: { include: { request: true, }, }, quotation: true, }, data: { requestWork: { connect: body.requestWorkId.map((v) => ({ id: v, })), }, quotationId: body.quotationId, }, }); this.setStatus(HttpStatus.CREATED); return record; } @Put("{creditNoteId}") @Security("keycloak", MANAGE_ROLES) async updateCreditNote( @Request() req: RequestWithUser, @Path() creditNoteId: string, @Body() body: CreditNoteUpdate, ) { const creditNoteData = await prisma.creditNote.findFirst({ where: { id: creditNoteId }, include: { requestWork: true, quotation: { include: { registeredBranch: { include: branchRelationPermInclude(req.user) }, }, }, }, }); if (!creditNoteData) throw notFoundError("Credit Note"); await permissionCheck(req.user, creditNoteData.quotation.registeredBranch); const requestWork = await prisma.requestWork.findMany({ where: { request: { quotation: { id: body.quotationId, }, }, stepStatus: { some: { workStatus: RequestWorkStatus.Canceled, }, }, id: { in: body.requestWorkId }, }, }); if (requestWork.length !== body.requestWorkId.length) { throw new HttpError(HttpStatus.BAD_REQUEST, "Not Match", "reqNotMet"); } const record = await prisma.creditNote.update({ where: { id: creditNoteId, }, include: { requestWork: { include: { request: true, }, }, quotation: true, }, data: { requestWork: { disconnect: creditNoteData.requestWork .map((item) => ({ id: item.id, })) .filter((data) => !body.requestWorkId.find((item) => (item = data.id))), connect: body.requestWorkId.map((v) => ({ id: v, })), }, quotationId: body.quotationId, }, }); return record; } @Delete("{creditNoteId}") @Security("keycloak", MANAGE_ROLES) async deleteCreditNote(@Request() req: RequestWithUser, @Path() creditNoteId: string) { const record = await prisma.creditNote.findFirst({ where: { id: creditNoteId, }, include: { quotation: { include: { registeredBranch: { include: branchRelationPermInclude(req.user) }, }, }, }, }); if (!record) throw notFoundError("Credit Note"); await permissionCheck(req.user, record.quotation.registeredBranch); return await prisma.creditNote.delete({ where: { id: creditNoteId } }); } }