Merge branch 'develop'
This commit is contained in:
commit
f1d4584d02
17 changed files with 569 additions and 76 deletions
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "CreditNoteStatus" AS ENUM ('Pending', 'Success');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CreditNote" ADD COLUMN "creditNoteStatus" "CreditNoteStatus";
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Made the column `creditNoteStatus` on table `CreditNote` required. This step will fail if there are existing NULL values in that column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CreditNote" ALTER COLUMN "creditNoteStatus" SET NOT NULL,
|
||||||
|
ALTER COLUMN "creditNoteStatus" SET DEFAULT 'Pending';
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CreditNote" ADD COLUMN "value" DOUBLE PRECISION NOT NULL DEFAULT 0;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `code` to the `CreditNote` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CreditNote" ADD COLUMN "code" TEXT NOT NULL;
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CreditNote" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "createdByUserId" TEXT;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "CreditNote" ADD CONSTRAINT "CreditNote_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "TaskStatus" ADD VALUE 'Restart';
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "UserTaskStatus" ADD VALUE 'Restart';
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserTask" ADD COLUMN "acceptedAt" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "submittedAt" TIMESTAMP(3);
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "CreditNotePaybackType" AS ENUM ('Cash', 'BankTransfer');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CreditNote" ADD COLUMN "detail" TEXT,
|
||||||
|
ADD COLUMN "paybackAccount" TEXT,
|
||||||
|
ADD COLUMN "paybackAccountName" TEXT,
|
||||||
|
ADD COLUMN "paybackBank" TEXT,
|
||||||
|
ADD COLUMN "paybackType" "CreditNotePaybackType",
|
||||||
|
ADD COLUMN "reason" TEXT;
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "PaybackStatus" AS ENUM ('Pending', 'Verify', 'Done');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CreditNote" ADD COLUMN "paybackStatus" "PaybackStatus" NOT NULL DEFAULT 'Pending';
|
||||||
|
|
@ -482,6 +482,7 @@ model User {
|
||||||
notificationReceive Notification[] @relation("NotificationReceiver")
|
notificationReceive Notification[] @relation("NotificationReceiver")
|
||||||
notificationRead Notification[]
|
notificationRead Notification[]
|
||||||
taskOrderCreated TaskOrder[] @relation("TaskOrderCreatedByUser")
|
taskOrderCreated TaskOrder[] @relation("TaskOrderCreatedByUser")
|
||||||
|
creditNoteCreated CreditNote[] @relation("CreditNoteCreatedByUser")
|
||||||
|
|
||||||
requestWorkStepStatus RequestWorkStepStatus[]
|
requestWorkStepStatus RequestWorkStepStatus[]
|
||||||
userTask UserTask[]
|
userTask UserTask[]
|
||||||
|
|
@ -1497,6 +1498,7 @@ enum TaskStatus {
|
||||||
|
|
||||||
Success
|
Success
|
||||||
Failed
|
Failed
|
||||||
|
Restart
|
||||||
Redo
|
Redo
|
||||||
|
|
||||||
Validate
|
Validate
|
||||||
|
|
@ -1559,6 +1561,7 @@ model TaskOrder {
|
||||||
|
|
||||||
enum UserTaskStatus {
|
enum UserTaskStatus {
|
||||||
Pending // Should not be use but define here for type
|
Pending // Should not be use but define here for type
|
||||||
|
Restart
|
||||||
Accept
|
Accept
|
||||||
Submit
|
Submit
|
||||||
}
|
}
|
||||||
|
|
@ -1572,16 +1575,53 @@ model UserTask {
|
||||||
taskOrderId String
|
taskOrderId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
userId String
|
userId String
|
||||||
|
|
||||||
|
acceptedAt DateTime?
|
||||||
|
submittedAt DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CreditNoteStatus {
|
||||||
|
Pending
|
||||||
|
Success
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CreditNotePaybackType {
|
||||||
|
Cash
|
||||||
|
BankTransfer
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PaybackStatus {
|
||||||
|
Pending
|
||||||
|
Verify
|
||||||
|
Done
|
||||||
}
|
}
|
||||||
|
|
||||||
model CreditNote {
|
model CreditNote {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
code String
|
||||||
|
|
||||||
|
creditNoteStatus CreditNoteStatus @default(Pending)
|
||||||
|
|
||||||
|
value Float @default(0)
|
||||||
|
reason String?
|
||||||
|
detail String?
|
||||||
|
|
||||||
|
paybackType CreditNotePaybackType?
|
||||||
|
paybackBank String?
|
||||||
|
paybackAccount String?
|
||||||
|
paybackAccountName String?
|
||||||
|
paybackStatus PaybackStatus @default(Pending)
|
||||||
|
|
||||||
quotation Quotation @relation(fields: [quotationId], references: [id], onDelete: Cascade)
|
quotation Quotation @relation(fields: [quotationId], references: [id], onDelete: Cascade)
|
||||||
quotationId String
|
quotationId String
|
||||||
|
|
||||||
// NOTE: only status cancel
|
// NOTE: only status cancel
|
||||||
requestWork RequestWork[]
|
requestWork RequestWork[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
createdBy User? @relation(name: "CreditNoteCreatedByUser", fields: [createdByUserId], references: [id])
|
||||||
|
createdByUserId String?
|
||||||
}
|
}
|
||||||
|
|
||||||
model DebitNote {
|
model DebitNote {
|
||||||
|
|
|
||||||
|
|
@ -642,11 +642,13 @@ export class UserController extends Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.username) {
|
if (body.username || body.email || body.firstName || body.lastName) {
|
||||||
await editUser(userId, {
|
await editUser(userId, {
|
||||||
|
firstName: body.firstName,
|
||||||
|
lastName: body.lastName,
|
||||||
username: body.username,
|
username: body.username,
|
||||||
email: body.email,
|
email: body.email,
|
||||||
enabled: body.status !== "INACTIVE",
|
enabled: body.status ? body.status !== "INACTIVE" : undefined,
|
||||||
});
|
});
|
||||||
} else if (body.status) {
|
} else if (body.status) {
|
||||||
await editUser(userId, { enabled: body.status !== "INACTIVE" });
|
await editUser(userId, { enabled: body.status !== "INACTIVE" });
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import { queryOrNot } from "../utils/relation";
|
||||||
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
import { RequestWorkStatus } from "../generated/kysely/types";
|
||||||
|
|
||||||
type QuotationCreate = {
|
type QuotationCreate = {
|
||||||
registeredBranchId: string;
|
registeredBranchId: string;
|
||||||
|
|
@ -190,6 +191,8 @@ export class QuotationController extends Controller {
|
||||||
@Query() payCondition?: PayCondition,
|
@Query() payCondition?: PayCondition,
|
||||||
@Query() status?: QuotationStatus,
|
@Query() status?: QuotationStatus,
|
||||||
@Query() urgentFirst?: boolean,
|
@Query() urgentFirst?: boolean,
|
||||||
|
@Query() includeRegisteredBranch?: boolean,
|
||||||
|
@Query() hasCancel?: boolean,
|
||||||
@Query() code?: string,
|
@Query() code?: string,
|
||||||
@Query() query = "",
|
@Query() query = "",
|
||||||
) {
|
) {
|
||||||
|
|
@ -214,6 +217,18 @@ export class QuotationController extends Controller {
|
||||||
payCondition,
|
payCondition,
|
||||||
registeredBranch: isSystem(req.user) ? undefined : { OR: permissionCond(req.user) },
|
registeredBranch: isSystem(req.user) ? undefined : { OR: permissionCond(req.user) },
|
||||||
quotationStatus: status,
|
quotationStatus: status,
|
||||||
|
requestData: hasCancel
|
||||||
|
? {
|
||||||
|
some: {
|
||||||
|
requestWork: {
|
||||||
|
some: {
|
||||||
|
creditNoteId: null,
|
||||||
|
stepStatus: { some: { workStatus: RequestWorkStatus.Canceled } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
} satisfies Prisma.QuotationWhereInput;
|
} satisfies Prisma.QuotationWhereInput;
|
||||||
|
|
||||||
const [result, total] = await prisma.$transaction([
|
const [result, total] = await prisma.$transaction([
|
||||||
|
|
@ -223,6 +238,7 @@ export class QuotationController extends Controller {
|
||||||
_count: {
|
_count: {
|
||||||
select: { worker: true },
|
select: { worker: true },
|
||||||
},
|
},
|
||||||
|
registeredBranch: includeRegisteredBranch,
|
||||||
customerBranch: {
|
customerBranch: {
|
||||||
include: {
|
include: {
|
||||||
customer: {
|
customer: {
|
||||||
|
|
@ -246,6 +262,46 @@ export class QuotationController extends Controller {
|
||||||
prisma.quotation.count({ where }),
|
prisma.quotation.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (hasCancel) {
|
||||||
|
const canceled = await prisma.requestData.findMany({
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
requestWork: {
|
||||||
|
where: {
|
||||||
|
creditNoteId: null,
|
||||||
|
stepStatus: { some: { workStatus: RequestWorkStatus.Canceled } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
requestWork: {
|
||||||
|
some: {
|
||||||
|
creditNoteId: null,
|
||||||
|
stepStatus: { some: { workStatus: RequestWorkStatus.Canceled } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
quotationId: { in: result.map((v) => v.id) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: result.map((v) => {
|
||||||
|
const canceledCount = canceled
|
||||||
|
.filter((item) => item.quotationId === v.id)
|
||||||
|
.reduce((a, c) => a + c._count.requestWork, 0);
|
||||||
|
return Object.assign(v, {
|
||||||
|
_count: { ...v._count, canceledWork: canceledCount },
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return { result: result, page, pageSize, total };
|
return { result: result, page, pageSize, total };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,11 +475,11 @@ export class QuotationController extends Controller {
|
||||||
const list = body.productServiceList.map((v, i) => {
|
const list = body.productServiceList.map((v, i) => {
|
||||||
const p = product.find((p) => p.id === v.productId)!;
|
const p = product.find((p) => p.id === v.productId)!;
|
||||||
const price = body.agentPrice ? p.agentPrice : p.price;
|
const price = body.agentPrice ? p.agentPrice : p.price;
|
||||||
const pricePerUnit = p.vatIncluded ? precisionRound(price / (1 + VAT_DEFAULT)) : price;
|
const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
|
||||||
const vat = p.calcVat
|
const vat = p.calcVat
|
||||||
? precisionRound(
|
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
|
||||||
(pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) * VAT_DEFAULT,
|
VAT_DEFAULT *
|
||||||
) * (!v.discount ? v.amount : 1)
|
(!v.discount ? v.amount : 1)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -684,11 +740,11 @@ export class QuotationController extends Controller {
|
||||||
const list = body.productServiceList?.map((v, i) => {
|
const list = body.productServiceList?.map((v, i) => {
|
||||||
const p = product.find((p) => p.id === v.productId)!;
|
const p = product.find((p) => p.id === v.productId)!;
|
||||||
const price = record.agentPrice ? p.agentPrice : p.price;
|
const price = record.agentPrice ? p.agentPrice : p.price;
|
||||||
const pricePerUnit = p.vatIncluded ? precisionRound(price / (1 + VAT_DEFAULT)) : price;
|
const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
|
||||||
const vat = p.calcVat
|
const vat = p.calcVat
|
||||||
? precisionRound(
|
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
|
||||||
(pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) * VAT_DEFAULT,
|
VAT_DEFAULT *
|
||||||
) * (!v.discount ? v.amount : 1)
|
(!v.discount ? v.amount : 1)
|
||||||
: 0;
|
: 0;
|
||||||
return {
|
return {
|
||||||
order: i + 1,
|
order: i + 1,
|
||||||
|
|
@ -980,7 +1036,7 @@ export class QuotationActionController extends Controller {
|
||||||
@Route("api/v1/quotation/{quotationId}/attachment")
|
@Route("api/v1/quotation/{quotationId}/attachment")
|
||||||
@Tags("Quotation")
|
@Tags("Quotation")
|
||||||
export class QuotationFileController extends Controller {
|
export class QuotationFileController extends Controller {
|
||||||
private async checkPermission(user: RequestWithUser["user"], id: string) {
|
async #checkPermission(user: RequestWithUser["user"], id: string) {
|
||||||
const data = await prisma.quotation.findUnique({
|
const data = await prisma.quotation.findUnique({
|
||||||
include: {
|
include: {
|
||||||
registeredBranch: {
|
registeredBranch: {
|
||||||
|
|
@ -989,14 +1045,14 @@ export class QuotationFileController extends Controller {
|
||||||
},
|
},
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
if (!data) throw notFoundError("Payment");
|
if (!data) throw notFoundError("Quotation");
|
||||||
await permissionCheck(user, data.registeredBranch);
|
await permissionCheck(user, data.registeredBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async listAttachment(@Request() req: RequestWithUser, @Path() quotationId: string) {
|
async listAttachment(@Request() req: RequestWithUser, @Path() quotationId: string) {
|
||||||
await this.checkPermission(req.user, quotationId);
|
await this.#checkPermission(req.user, quotationId);
|
||||||
return await listFile(fileLocation.quotation.attachment(quotationId));
|
return await listFile(fileLocation.quotation.attachment(quotationId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1018,7 +1074,7 @@ export class QuotationFileController extends Controller {
|
||||||
@Path() quotationId: string,
|
@Path() quotationId: string,
|
||||||
@Path() name: string,
|
@Path() name: string,
|
||||||
) {
|
) {
|
||||||
await this.checkPermission(req.user, quotationId);
|
await this.#checkPermission(req.user, quotationId);
|
||||||
return await getFile(fileLocation.quotation.attachment(quotationId, name));
|
return await getFile(fileLocation.quotation.attachment(quotationId, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1029,7 +1085,7 @@ export class QuotationFileController extends Controller {
|
||||||
@Path() quotationId: string,
|
@Path() quotationId: string,
|
||||||
@Path() name: string,
|
@Path() name: string,
|
||||||
) {
|
) {
|
||||||
await this.checkPermission(req.user, quotationId);
|
await this.#checkPermission(req.user, quotationId);
|
||||||
return await setFile(fileLocation.quotation.attachment(quotationId, name));
|
return await setFile(fileLocation.quotation.attachment(quotationId, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1040,7 +1096,7 @@ export class QuotationFileController extends Controller {
|
||||||
@Path() quotationId: string,
|
@Path() quotationId: string,
|
||||||
@Path() name: string,
|
@Path() name: string,
|
||||||
) {
|
) {
|
||||||
await this.checkPermission(req.user, quotationId);
|
await this.#checkPermission(req.user, quotationId);
|
||||||
return await deleteFile(fileLocation.quotation.attachment(quotationId, name));
|
return await deleteFile(fileLocation.quotation.attachment(quotationId, name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,8 @@ export class RequestListController extends Controller {
|
||||||
@Query() requestDataId?: string,
|
@Query() requestDataId?: string,
|
||||||
@Query() workStatus?: RequestWorkStatus,
|
@Query() workStatus?: RequestWorkStatus,
|
||||||
@Query() readyToTask?: boolean,
|
@Query() readyToTask?: boolean,
|
||||||
|
@Query() cancelOnly?: boolean,
|
||||||
|
@Query() quotationId?: string,
|
||||||
) {
|
) {
|
||||||
let statusCondition: Prisma.RequestWorkWhereInput["stepStatus"] = {};
|
let statusCondition: Prisma.RequestWorkWhereInput["stepStatus"] = {};
|
||||||
|
|
||||||
|
|
@ -313,16 +315,29 @@ export class RequestListController extends Controller {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (cancelOnly) {
|
||||||
|
statusCondition = {
|
||||||
|
some: { workStatus: RequestWorkStatus.Canceled },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workStatus && !readyToTask && !cancelOnly) {
|
||||||
statusCondition = {
|
statusCondition = {
|
||||||
some: { workStatus },
|
some: { workStatus },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
stepStatus: readyToTask || workStatus ? statusCondition : undefined,
|
stepStatus: readyToTask || cancelOnly || workStatus ? statusCondition : undefined,
|
||||||
|
creditNote: cancelOnly ? null : undefined,
|
||||||
request: {
|
request: {
|
||||||
id: requestDataId,
|
id: requestDataId,
|
||||||
|
requestDataStatus: readyToTask
|
||||||
|
? { notIn: [RequestDataStatus.Canceled, RequestDataStatus.Completed] }
|
||||||
|
: undefined,
|
||||||
|
quotationId,
|
||||||
quotation: {
|
quotation: {
|
||||||
registeredBranch: { OR: permissionCond(req.user) },
|
registeredBranch: { OR: permissionCond(req.user) },
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ export class TaskController extends Controller {
|
||||||
userTask: true,
|
userTask: true,
|
||||||
taskList: true,
|
taskList: true,
|
||||||
institution: true,
|
institution: true,
|
||||||
|
registeredBranch: true,
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
@ -150,6 +151,7 @@ export class TaskController extends Controller {
|
||||||
where: {
|
where: {
|
||||||
requestWorkStep: { responsibleUserId: taskAssignedUserId },
|
requestWorkStep: { responsibleUserId: taskAssignedUserId },
|
||||||
},
|
},
|
||||||
|
orderBy: { id: "asc" },
|
||||||
include: {
|
include: {
|
||||||
requestWorkStep: {
|
requestWorkStep: {
|
||||||
include: {
|
include: {
|
||||||
|
|
@ -451,6 +453,7 @@ export class TaskActionController extends Controller {
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const promises = body.map(async (v) => {
|
const promises = body.map(async (v) => {
|
||||||
const record = await tx.task.findFirst({
|
const record = await tx.task.findFirst({
|
||||||
|
include: { requestWorkStep: true },
|
||||||
where: {
|
where: {
|
||||||
step: v.step,
|
step: v.step,
|
||||||
requestWorkId: v.requestWorkId,
|
requestWorkId: v.requestWorkId,
|
||||||
|
|
@ -458,6 +461,16 @@ export class TaskActionController extends Controller {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!record) throw notFoundError("Task List");
|
if (!record) throw notFoundError("Task List");
|
||||||
|
|
||||||
|
if (v.taskStatus === TaskStatus.Restart && record.requestWorkStep.responsibleUserId) {
|
||||||
|
await tx.userTask.updateMany({
|
||||||
|
where: {
|
||||||
|
taskOrderId: record.taskOrderId,
|
||||||
|
userId: record.requestWorkStep.responsibleUserId,
|
||||||
|
},
|
||||||
|
data: { userTaskStatus: UserTaskStatus.Restart },
|
||||||
|
});
|
||||||
|
}
|
||||||
return await tx.task.update({
|
return await tx.task.update({
|
||||||
where: { id: record.id },
|
where: { id: record.id },
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -512,20 +525,14 @@ export class TaskActionController extends Controller {
|
||||||
taskOrderId: taskOrderId,
|
taskOrderId: taskOrderId,
|
||||||
userId: submitUserId,
|
userId: submitUserId,
|
||||||
},
|
},
|
||||||
data: { userTaskStatus: UserTaskStatus.Submit },
|
data: { userTaskStatus: UserTaskStatus.Submit, submittedAt: new Date() },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("complete")
|
@Post("complete")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async completeTaskOrder(
|
async completeTaskOrder(@Request() req: RequestWithUser, @Path() taskOrderId: string) {
|
||||||
@Request() req: RequestWithUser,
|
|
||||||
@Path() taskOrderId: string,
|
|
||||||
@Query() submitUserId?: string,
|
|
||||||
) {
|
|
||||||
submitUserId = submitUserId ?? req.user.sub;
|
|
||||||
|
|
||||||
const record = await prisma.taskOrder.findFirst({ where: { id: taskOrderId } });
|
const record = await prisma.taskOrder.findFirst({ where: { id: taskOrderId } });
|
||||||
|
|
||||||
if (!record) throw notFoundError("Task Order");
|
if (!record) throw notFoundError("Task Order");
|
||||||
|
|
@ -534,21 +541,54 @@ export class TaskActionController extends Controller {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
tx.taskOrder.update({
|
tx.taskOrder.update({
|
||||||
where: { id: taskOrderId },
|
where: { id: taskOrderId },
|
||||||
data: { taskOrderStatus: TaskOrderStatus.Complete },
|
data: {
|
||||||
|
taskOrderStatus: TaskOrderStatus.Complete,
|
||||||
|
userTask: {
|
||||||
|
updateMany: {
|
||||||
|
where: { taskOrderId },
|
||||||
|
data: {
|
||||||
|
userTaskStatus: UserTaskStatus.Submit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
tx.requestWorkStepStatus.updateMany({
|
tx.requestWorkStepStatus.updateMany({
|
||||||
where: {
|
where: {
|
||||||
task: {
|
task: {
|
||||||
some: { taskOrderId, taskStatus: TaskStatus.Redo },
|
some: {
|
||||||
|
taskOrderId,
|
||||||
|
taskStatus: {
|
||||||
|
notIn: [
|
||||||
|
TaskStatus.Canceled,
|
||||||
|
TaskStatus.Success,
|
||||||
|
TaskStatus.Validate,
|
||||||
|
TaskStatus.Complete,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: { workStatus: RequestWorkStatus.Ready },
|
data: { workStatus: RequestWorkStatus.Ready },
|
||||||
}),
|
}),
|
||||||
|
tx.task.updateMany({
|
||||||
|
where: {
|
||||||
|
taskOrderId: taskOrderId,
|
||||||
|
taskStatus: {
|
||||||
|
notIn: [
|
||||||
|
TaskStatus.Canceled,
|
||||||
|
TaskStatus.Success,
|
||||||
|
TaskStatus.Validate,
|
||||||
|
TaskStatus.Complete,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: { taskStatus: TaskStatus.Redo },
|
||||||
|
}),
|
||||||
tx.task.updateMany({
|
tx.task.updateMany({
|
||||||
where: {
|
where: {
|
||||||
taskOrderId: taskOrderId,
|
taskOrderId: taskOrderId,
|
||||||
taskStatus: TaskStatus.Validate,
|
taskStatus: TaskStatus.Validate,
|
||||||
requestWorkStep: { responsibleUserId: submitUserId },
|
|
||||||
},
|
},
|
||||||
data: { taskStatus: TaskStatus.Complete },
|
data: { taskStatus: TaskStatus.Complete },
|
||||||
}),
|
}),
|
||||||
|
|
@ -708,17 +748,38 @@ export class UserTaskController extends Controller {
|
||||||
requestWorkStep: { responsibleUserId: req.user.sub },
|
requestWorkStep: { responsibleUserId: req.user.sub },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
userTask: userTaskStatus
|
AND: userTaskStatus
|
||||||
? {
|
? [
|
||||||
some:
|
{
|
||||||
|
OR:
|
||||||
|
userTaskStatus === UserTaskStatus.Pending
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
userTask: {
|
||||||
|
some: {
|
||||||
|
userTaskStatus: {
|
||||||
|
in: [UserTaskStatus.Pending, UserTaskStatus.Restart],
|
||||||
|
},
|
||||||
|
userId: req.user.sub,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userTask: { none: { userId: req.user.sub } },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
|
userTask:
|
||||||
userTaskStatus !== UserTaskStatus.Pending
|
userTaskStatus !== UserTaskStatus.Pending
|
||||||
? {
|
? {
|
||||||
|
some: {
|
||||||
userTaskStatus,
|
userTaskStatus,
|
||||||
userId: req.user.sub,
|
userId: req.user.sub,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
none: userTaskStatus === UserTaskStatus.Pending ? { userId: req.user.sub } : undefined,
|
},
|
||||||
}
|
]
|
||||||
: undefined,
|
: undefined,
|
||||||
OR: queryOrNot(query, [
|
OR: queryOrNot(query, [
|
||||||
{ code: { contains: query, mode: "insensitive" } },
|
{ code: { contains: query, mode: "insensitive" } },
|
||||||
|
|
@ -735,6 +796,7 @@ export class UserTaskController extends Controller {
|
||||||
userTask: {
|
userTask: {
|
||||||
where: { userId: req.user.sub },
|
where: { userId: req.user.sub },
|
||||||
},
|
},
|
||||||
|
registeredBranch: true,
|
||||||
institution: true,
|
institution: true,
|
||||||
createdBy: true,
|
createdBy: true,
|
||||||
},
|
},
|
||||||
|
|
@ -783,9 +845,11 @@ export class UserTaskController extends Controller {
|
||||||
data: {
|
data: {
|
||||||
taskOrderStatus: TaskOrderStatus.InProgress,
|
taskOrderStatus: TaskOrderStatus.InProgress,
|
||||||
userTask: {
|
userTask: {
|
||||||
|
deleteMany: { userId: req.user.sub },
|
||||||
create: {
|
create: {
|
||||||
userId: req.user.sub,
|
userId: req.user.sub,
|
||||||
userTaskStatus: UserTaskStatus.Accept,
|
userTaskStatus: UserTaskStatus.Accept,
|
||||||
|
acceptedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -793,7 +857,7 @@ export class UserTaskController extends Controller {
|
||||||
tx.task.updateMany({
|
tx.task.updateMany({
|
||||||
where: {
|
where: {
|
||||||
taskOrderId: taskOrderId,
|
taskOrderId: taskOrderId,
|
||||||
taskStatus: TaskStatus.Pending,
|
taskStatus: { in: [TaskStatus.Pending, TaskStatus.Restart] },
|
||||||
requestWorkStep: { responsibleUserId: req.user.sub },
|
requestWorkStep: { responsibleUserId: req.user.sub },
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -812,13 +876,6 @@ export class UserTaskController extends Controller {
|
||||||
},
|
},
|
||||||
data: { requestDataStatus: RequestDataStatus.InProgress },
|
data: { requestDataStatus: RequestDataStatus.InProgress },
|
||||||
}),
|
}),
|
||||||
tx.userTask.create({
|
|
||||||
data: {
|
|
||||||
userId: req.user.sub,
|
|
||||||
taskOrderId: taskOrderId,
|
|
||||||
userTaskStatus: UserTaskStatus.Accept,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
|
Head,
|
||||||
Path,
|
Path,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
|
|
@ -13,7 +14,6 @@ import {
|
||||||
Tags,
|
Tags,
|
||||||
} from "tsoa";
|
} from "tsoa";
|
||||||
|
|
||||||
// import { Prisma } from "@prisma/client";
|
|
||||||
import prisma from "../db";
|
import prisma from "../db";
|
||||||
|
|
||||||
import { RequestWithUser } from "../interfaces/user";
|
import { RequestWithUser } from "../interfaces/user";
|
||||||
|
|
@ -24,10 +24,11 @@ import {
|
||||||
} from "../services/permission";
|
} from "../services/permission";
|
||||||
import HttpError from "../interfaces/http-error";
|
import HttpError from "../interfaces/http-error";
|
||||||
import HttpStatus from "../interfaces/http-status";
|
import HttpStatus from "../interfaces/http-status";
|
||||||
|
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
|
||||||
import { notFoundError } from "../utils/error";
|
import { notFoundError } from "../utils/error";
|
||||||
import { Prisma } from "@prisma/client";
|
import { CreditNotePaybackType, CreditNoteStatus, Prisma } from "@prisma/client";
|
||||||
import { queryOrNot } from "../utils/relation";
|
import { queryOrNot } from "../utils/relation";
|
||||||
import { RequestWorkStatus } from "../generated/kysely/types";
|
import { PaybackStatus, RequestWorkStatus } from "../generated/kysely/types";
|
||||||
|
|
||||||
const MANAGE_ROLES = [
|
const MANAGE_ROLES = [
|
||||||
"system",
|
"system",
|
||||||
|
|
@ -53,10 +54,22 @@ const permissionCheckCompany = createPermCheck((_) => true);
|
||||||
type CreditNoteCreate = {
|
type CreditNoteCreate = {
|
||||||
requestWorkId: string[];
|
requestWorkId: string[];
|
||||||
quotationId: string;
|
quotationId: string;
|
||||||
|
reason?: string;
|
||||||
|
detail?: string;
|
||||||
|
paybackType?: CreditNotePaybackType;
|
||||||
|
paybackBank?: string;
|
||||||
|
paybackAccount?: string;
|
||||||
|
paybackAccountName?: string;
|
||||||
};
|
};
|
||||||
type CreditNoteUpdate = {
|
type CreditNoteUpdate = {
|
||||||
requestWorkId: string[];
|
requestWorkId: string[];
|
||||||
quotationId: string;
|
quotationId: string;
|
||||||
|
reason?: string;
|
||||||
|
detail?: string;
|
||||||
|
paybackType?: CreditNotePaybackType;
|
||||||
|
paybackBank?: string;
|
||||||
|
paybackAccount?: string;
|
||||||
|
paybackAccountName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Route("api/v1/credit-note")
|
@Route("api/v1/credit-note")
|
||||||
|
|
@ -64,7 +77,7 @@ type CreditNoteUpdate = {
|
||||||
export class CreditNoteController extends Controller {
|
export class CreditNoteController extends Controller {
|
||||||
@Get("stats")
|
@Get("stats")
|
||||||
@Security("keycloak")
|
@Security("keycloak")
|
||||||
async getCreditNoteStats(@Request() req: RequestWithUser, @Query() quotationId: string) {
|
async getCreditNoteStats(@Request() req: RequestWithUser, @Query() quotationId?: string) {
|
||||||
const where = {
|
const where = {
|
||||||
requestWork: {
|
requestWork: {
|
||||||
some: {
|
some: {
|
||||||
|
|
@ -88,8 +101,16 @@ export class CreditNoteController extends Controller {
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
|
@Query() creditNoteStatus?: CreditNoteStatus,
|
||||||
) {
|
) {
|
||||||
return await this.getCreditNoteListByCriteria(req, page, pageSize, query, quotationId);
|
return await this.getCreditNoteListByCriteria(
|
||||||
|
req,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
query,
|
||||||
|
quotationId,
|
||||||
|
creditNoteStatus,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: only when needed or else remove this and implement in getCreditNoteList
|
// NOTE: only when needed or else remove this and implement in getCreditNoteList
|
||||||
|
|
@ -101,6 +122,7 @@ export class CreditNoteController extends Controller {
|
||||||
@Query() pageSize: number = 30,
|
@Query() pageSize: number = 30,
|
||||||
@Query() query: string = "",
|
@Query() query: string = "",
|
||||||
@Query() quotationId?: string,
|
@Query() quotationId?: string,
|
||||||
|
@Query() creditNoteStatus?: CreditNoteStatus,
|
||||||
@Body() body?: {},
|
@Body() body?: {},
|
||||||
) {
|
) {
|
||||||
const where = {
|
const where = {
|
||||||
|
|
@ -146,6 +168,7 @@ export class CreditNoteController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
creditNoteStatus,
|
||||||
requestWork: {
|
requestWork: {
|
||||||
some: {
|
some: {
|
||||||
request: {
|
request: {
|
||||||
|
|
@ -162,6 +185,16 @@ export class CreditNoteController extends Controller {
|
||||||
prisma.creditNote.findMany({
|
prisma.creditNote.findMany({
|
||||||
where,
|
where,
|
||||||
include: {
|
include: {
|
||||||
|
quotation: {
|
||||||
|
include: {
|
||||||
|
registeredBranch: true,
|
||||||
|
customerBranch: {
|
||||||
|
include: {
|
||||||
|
customer: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
requestWork: {
|
requestWork: {
|
||||||
include: { request: true },
|
include: { request: true },
|
||||||
},
|
},
|
||||||
|
|
@ -191,8 +224,33 @@ export class CreditNoteController extends Controller {
|
||||||
return prisma.creditNote.findFirst({
|
return prisma.creditNote.findFirst({
|
||||||
where,
|
where,
|
||||||
include: {
|
include: {
|
||||||
|
quotation: {
|
||||||
|
include: {
|
||||||
|
registeredBranch: true,
|
||||||
|
customerBranch: {
|
||||||
|
include: {
|
||||||
|
customer: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
requestWork: {
|
requestWork: {
|
||||||
include: { request: true },
|
include: {
|
||||||
|
request: {
|
||||||
|
include: { employee: true },
|
||||||
|
},
|
||||||
|
productService: {
|
||||||
|
include: {
|
||||||
|
service: true,
|
||||||
|
work: {
|
||||||
|
include: { productOnWork: true },
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
include: { document: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -203,6 +261,7 @@ export class CreditNoteController extends Controller {
|
||||||
async createCreditNote(@Request() req: RequestWithUser, @Body() body: CreditNoteCreate) {
|
async createCreditNote(@Request() req: RequestWithUser, @Body() body: CreditNoteCreate) {
|
||||||
const requestWork = await prisma.requestWork.findMany({
|
const requestWork = await prisma.requestWork.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
creditNote: null,
|
||||||
request: {
|
request: {
|
||||||
quotation: {
|
quotation: {
|
||||||
id: body.quotationId,
|
id: body.quotationId,
|
||||||
|
|
@ -216,6 +275,15 @@ export class CreditNoteController extends Controller {
|
||||||
id: { in: body.requestWorkId },
|
id: { in: body.requestWorkId },
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
|
stepStatus: true,
|
||||||
|
productService: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
work: {
|
||||||
|
include: { productOnWork: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
request: {
|
request: {
|
||||||
include: {
|
include: {
|
||||||
quotation: {
|
quotation: {
|
||||||
|
|
@ -236,7 +304,46 @@ export class CreditNoteController extends Controller {
|
||||||
requestWork.map((item) => permissionCheck(req.user, item.request.quotation.registeredBranch)),
|
requestWork.map((item) => permissionCheck(req.user, item.request.quotation.registeredBranch)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const record = await prisma.creditNote.create({
|
const value = requestWork.reduce((a, c) => {
|
||||||
|
const serviceChargeStepCount = c.productService.work?.productOnWork.find(
|
||||||
|
(v) => v.productId === c.productService.productId,
|
||||||
|
)?.stepCount;
|
||||||
|
|
||||||
|
const successCount = c.stepStatus.filter(
|
||||||
|
(v) => v.workStatus === RequestWorkStatus.Completed,
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const price = c.request.quotation.agentPrice ? "price" : "agentPrice";
|
||||||
|
|
||||||
|
if (serviceChargeStepCount && successCount) {
|
||||||
|
return (
|
||||||
|
a +
|
||||||
|
c.productService.product[price] -
|
||||||
|
c.productService.product.serviceCharge * successCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return a + c.productService.product.price;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
this.setStatus(HttpStatus.CREATED);
|
||||||
|
|
||||||
|
return await prisma.$transaction(
|
||||||
|
async (tx) => {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const currentMonth = new Date().getMonth() + 1;
|
||||||
|
|
||||||
|
const last = await tx.runningNo.upsert({
|
||||||
|
where: {
|
||||||
|
key: `CREDIT_NOTE_${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}`,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: `CREDIT_NOTE_${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}`,
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
update: { value: { increment: 1 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return await prisma.creditNote.create({
|
||||||
include: {
|
include: {
|
||||||
requestWork: {
|
requestWork: {
|
||||||
include: {
|
include: {
|
||||||
|
|
@ -246,6 +353,14 @@ export class CreditNoteController extends Controller {
|
||||||
quotation: true,
|
quotation: true,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
reason: body.reason,
|
||||||
|
detail: body.detail,
|
||||||
|
paybackType: body.paybackType,
|
||||||
|
paybackBank: body.paybackBank,
|
||||||
|
paybackAccount: body.paybackAccount,
|
||||||
|
paybackAccountName: body.paybackAccountName,
|
||||||
|
code: `CN${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${last.value.toString().padStart(6, "0")}`,
|
||||||
|
value,
|
||||||
requestWork: {
|
requestWork: {
|
||||||
connect: body.requestWorkId.map((v) => ({
|
connect: body.requestWorkId.map((v) => ({
|
||||||
id: v,
|
id: v,
|
||||||
|
|
@ -254,10 +369,9 @@ export class CreditNoteController extends Controller {
|
||||||
quotationId: body.quotationId,
|
quotationId: body.quotationId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
},
|
||||||
this.setStatus(HttpStatus.CREATED);
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
|
||||||
|
);
|
||||||
return record;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("{creditNoteId}")
|
@Put("{creditNoteId}")
|
||||||
|
|
@ -284,6 +398,7 @@ export class CreditNoteController extends Controller {
|
||||||
|
|
||||||
const requestWork = await prisma.requestWork.findMany({
|
const requestWork = await prisma.requestWork.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
OR: [{ creditNote: null }, { creditNoteId }],
|
||||||
request: {
|
request: {
|
||||||
quotation: {
|
quotation: {
|
||||||
id: body.quotationId,
|
id: body.quotationId,
|
||||||
|
|
@ -296,16 +411,55 @@ export class CreditNoteController extends Controller {
|
||||||
},
|
},
|
||||||
id: { in: body.requestWorkId },
|
id: { in: body.requestWorkId },
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
stepStatus: true,
|
||||||
|
productService: {
|
||||||
|
include: {
|
||||||
|
product: true,
|
||||||
|
work: {
|
||||||
|
include: { productOnWork: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
include: {
|
||||||
|
quotation: {
|
||||||
|
include: {
|
||||||
|
registeredBranch: { include: branchRelationPermInclude(req.user) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (requestWork.length !== body.requestWorkId.length) {
|
if (requestWork.length !== body.requestWorkId.length) {
|
||||||
throw new HttpError(HttpStatus.BAD_REQUEST, "Not Match", "reqNotMet");
|
throw new HttpError(HttpStatus.BAD_REQUEST, "Not Match", "reqNotMet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const value = requestWork.reduce((a, c) => {
|
||||||
|
const serviceChargeStepCount = c.productService.work?.productOnWork.find(
|
||||||
|
(v) => v.productId === c.productService.productId,
|
||||||
|
)?.stepCount;
|
||||||
|
|
||||||
|
const successCount = c.stepStatus.filter(
|
||||||
|
(v) => v.workStatus === RequestWorkStatus.Completed,
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const price = c.request.quotation.agentPrice ? "price" : "agentPrice";
|
||||||
|
|
||||||
|
if (serviceChargeStepCount && successCount) {
|
||||||
|
return (
|
||||||
|
a +
|
||||||
|
c.productService.product[price] -
|
||||||
|
c.productService.product.serviceCharge * successCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return a + c.productService.product.price;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
const record = await prisma.creditNote.update({
|
const record = await prisma.creditNote.update({
|
||||||
where: {
|
where: { id: creditNoteId },
|
||||||
id: creditNoteId,
|
|
||||||
},
|
|
||||||
include: {
|
include: {
|
||||||
requestWork: {
|
requestWork: {
|
||||||
include: {
|
include: {
|
||||||
|
|
@ -315,6 +469,13 @@ export class CreditNoteController extends Controller {
|
||||||
quotation: true,
|
quotation: true,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
reason: body.reason,
|
||||||
|
detail: body.detail,
|
||||||
|
paybackType: body.paybackType,
|
||||||
|
paybackBank: body.paybackBank,
|
||||||
|
paybackAccount: body.paybackAccount,
|
||||||
|
paybackAccountName: body.paybackAccountName,
|
||||||
|
value,
|
||||||
requestWork: {
|
requestWork: {
|
||||||
disconnect: creditNoteData.requestWork
|
disconnect: creditNoteData.requestWork
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
|
|
@ -353,3 +514,110 @@ export class CreditNoteController extends Controller {
|
||||||
return await prisma.creditNote.delete({ where: { id: creditNoteId } });
|
return await prisma.creditNote.delete({ where: { id: creditNoteId } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Route("api/v1/credit-note/{creditNoteId}")
|
||||||
|
@Tags("Credit Note")
|
||||||
|
export class CreditNoteActionController extends Controller {
|
||||||
|
async #checkPermission(user: RequestWithUser["user"], id: string) {
|
||||||
|
const creditNoteData = await prisma.creditNote.findFirst({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
requestWork: true,
|
||||||
|
quotation: {
|
||||||
|
include: {
|
||||||
|
registeredBranch: { include: branchRelationPermInclude(user) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!creditNoteData) throw notFoundError("Credit Note");
|
||||||
|
await permissionCheck(user, creditNoteData.quotation.registeredBranch);
|
||||||
|
return creditNoteData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("payback-status")
|
||||||
|
@Security("keycloak", MANAGE_ROLES)
|
||||||
|
async updateStatus(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Path() creditNoteId: string,
|
||||||
|
@Body() body: PaybackStatus,
|
||||||
|
) {
|
||||||
|
await this.#checkPermission(req.user, creditNoteId);
|
||||||
|
return await prisma.creditNote.update({
|
||||||
|
where: { id: creditNoteId },
|
||||||
|
include: {
|
||||||
|
requestWork: {
|
||||||
|
include: {
|
||||||
|
request: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
quotation: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
creditNoteStatus: body === PaybackStatus.Done ? CreditNoteStatus.Success : undefined,
|
||||||
|
paybackStatus: body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Route("api/v1/credit-note/{creditNoteId}")
|
||||||
|
@Tags("Credit Note")
|
||||||
|
export class CreditNoteAttachmentController extends Controller {
|
||||||
|
async #checkPermission(user: RequestWithUser["user"], id: string) {
|
||||||
|
const creditNoteData = await prisma.creditNote.findFirst({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
requestWork: true,
|
||||||
|
quotation: {
|
||||||
|
include: {
|
||||||
|
registeredBranch: { include: branchRelationPermInclude(user) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!creditNoteData) throw notFoundError("Credit Note");
|
||||||
|
await permissionCheck(user, creditNoteData.quotation.registeredBranch);
|
||||||
|
return creditNoteData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("attachment")
|
||||||
|
@Security("keycloak")
|
||||||
|
async listAttachment(@Request() req: RequestWithUser, @Path() creditNoteId: string) {
|
||||||
|
await this.#checkPermission(req.user, creditNoteId);
|
||||||
|
return await listFile(fileLocation.creditNote.attachment(creditNoteId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("attachment/{name}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async getAttachment(@Path() creditNoteId: string, @Path() name: string) {
|
||||||
|
return await getFile(fileLocation.creditNote.attachment(creditNoteId, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Head("attachment/{name}")
|
||||||
|
async headAttachment(@Path() creditNoteId: string, @Path() name: string) {
|
||||||
|
return await getPresigned("head", fileLocation.creditNote.attachment(creditNoteId, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put("attachment/{name}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async putAttachment(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Path() creditNoteId: string,
|
||||||
|
@Path() name: string,
|
||||||
|
) {
|
||||||
|
await this.#checkPermission(req.user, creditNoteId);
|
||||||
|
return await setFile(fileLocation.creditNote.attachment(creditNoteId, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete("attachment/{name}")
|
||||||
|
@Security("keycloak")
|
||||||
|
async delAttachment(
|
||||||
|
@Request() req: RequestWithUser,
|
||||||
|
@Path() creditNoteId: string,
|
||||||
|
@Path() name: string,
|
||||||
|
) {
|
||||||
|
await this.#checkPermission(req.user, creditNoteId);
|
||||||
|
return await deleteFile(fileLocation.creditNote.attachment(creditNoteId, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,4 +112,7 @@ export const fileLocation = {
|
||||||
task: {
|
task: {
|
||||||
attachment: (taskId: string, name?: string) => `task/attachment-${taskId}/${name || ""}`,
|
attachment: (taskId: string, name?: string) => `task/attachment-${taskId}/${name || ""}`,
|
||||||
},
|
},
|
||||||
|
creditNote: {
|
||||||
|
attachment: (taskId: string, name?: string) => `credit-note/attachment-${taskId}/${name || ""}`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue