Merge branch 'develop'

This commit is contained in:
Methapon2001 2025-01-23 10:23:20 +07:00
commit c7fccb6087
8 changed files with 184 additions and 40 deletions

View file

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "RequestDataStatus" ADD VALUE 'Ready';

View file

@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View file

@ -1406,6 +1406,7 @@ model Payment {
enum RequestDataStatus {
Pending
Ready
InProgress
Completed
Canceled

View file

@ -8,6 +8,7 @@ import { notFoundError } from "../utils/error";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { getFileBuffer, listFile } from "../utils/minio";
import { dateFormat } from "../utils/datetime";
const quotationData = (id: string) =>
prisma.quotation.findFirst({
@ -128,6 +129,26 @@ export class DocTemplateController extends Controller {
template,
data: record,
additionalJsContext: {
date: (date: string, locale?: string) => dateFormat({ date, locale }),
dateTime: (date: string, locale?: string) => dateFormat({ date, withTime: true, locale }),
dateLong: (date: string, locale?: string) =>
dateFormat({ date, locale, monthStyle: "long" }),
dateLongTime: (date: string, locale?: string) =>
dateFormat({ date, withTime: true, locale, monthStyle: "long" }),
dateTH: (date: string) => dateFormat({ date, locale: "th-TH" }),
dateTimeTH: (date: string) => dateFormat({ date, withTime: true, locale: "th-TH" }),
dateLongTH: (date: string) => dateFormat({ date, locale: "th-TH", monthStyle: "long" }),
dateTimeLongTH: (date: string) =>
dateFormat({ date, withTime: true, locale: "th-TH", monthStyle: "long" }),
dateEN: (date: string) => dateFormat({ date, locale: "en-US" }),
dateTimeEN: (date: string) =>
dateFormat({ date, withTime: true, locale: "en-US", monthStyle: "long" }),
dateLongEN: (date: string) => dateFormat({ date, locale: "en-US" }),
dateTimeLongEN: (date: string) =>
dateFormat({ date, withTime: true, locale: "en-US", monthStyle: "long" }),
address,
addressTH: (addr: FullAddress) => address(addr, "th"),
addressEN: (addr: FullAddress) => address(addr, "en"),
addressFull,
addressFullTH: (addr: FullAddress) => addressFull(addr, "th"),
addressFullEN: (addr: FullAddress) => addressFull(addr, "en"),
@ -175,6 +196,26 @@ type FullAddress = {
en?: boolean;
};
function address(addr: FullAddress, lang: "th" | "en" = "en") {
let fragments: string[];
switch (lang) {
case "th":
fragments = [`${addr.address},`];
if (addr.moo) fragments.push(`หมู่ ${addr.moo},`);
if (addr.soi) fragments.push(`ซอย ${addr.soi},`);
if (addr.street) fragments.push(`ถนน${addr.street},`);
break;
default:
fragments = [`${addr.addressEN},`];
if (addr.mooEN) fragments.push(`Moo ${addr.mooEN},`);
if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`);
if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`);
break;
}
return fragments.join(" ");
}
function addressFull(addr: FullAddress, lang: "th" | "en" = "en") {
let fragments: string[];
switch (lang) {

View file

@ -477,13 +477,15 @@ export class QuotationController extends Controller {
const list = body.productServiceList.map((v, i) => {
const p = product.find((p) => p.id === v.productId)!;
const price = body.agentPrice ? p.agentPrice : p.price;
const originalPrice = body.agentPrice ? p.agentPrice : p.price;
const finalPriceWithVat = precisionRound(
originalPrice + (p.vatIncluded ? 0 : originalPrice * 1.07),
);
const price = finalPriceWithVat;
const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
const vat = p.calcVat
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
VAT_DEFAULT *
(!v.discount ? v.amount : 1)
: 0;
const vat = p.calcVat ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0;
return {
order: i + 1,
@ -742,13 +744,16 @@ export class QuotationController extends Controller {
const list = body.productServiceList?.map((v, i) => {
const p = product.find((p) => p.id === v.productId)!;
const price = record.agentPrice ? p.agentPrice : p.price;
const originalPrice = record.agentPrice ? p.agentPrice : p.price;
const finalPriceWithVat = precisionRound(
originalPrice + (p.vatIncluded ? 0 : originalPrice * 1.07),
);
const price = finalPriceWithVat;
const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
const vat = p.calcVat
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
VAT_DEFAULT *
(!v.discount ? v.amount : 1)
: 0;
const vat = p.calcVat ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0;
return {
order: i + 1,
productId: v.productId,

View file

@ -63,6 +63,7 @@ export class RequestDataController extends Controller {
(a, c) => Object.assign(a, { [c.requestDataStatus]: c._count }),
{
[RequestDataStatus.Pending]: 0,
[RequestDataStatus.Ready]: 0,
[RequestDataStatus.InProgress]: 0,
[RequestDataStatus.Completed]: 0,
[RequestDataStatus.Canceled]: 0,
@ -188,6 +189,7 @@ export class RequestDataController extends Controller {
},
take: pageSize,
skip: (page - 1) * pageSize,
orderBy: { createdAt: "desc" },
}),
prisma.requestData.count({ where }),
]);
@ -520,7 +522,11 @@ export class RequestListController extends Controller {
return await prisma.$transaction(async (tx) => {
const record = await tx.requestWorkStepStatus.upsert({
include: {
requestWork: true,
requestWork: {
include: {
request: true,
},
},
},
where: {
step_requestWorkId: {
@ -537,6 +543,17 @@ export class RequestListController extends Controller {
});
switch (payload.workStatus) {
case "Ready":
if (record.requestWork.request.requestDataStatus === "Pending") {
await tx.requestData.updateMany({
where: {
id: record.requestWork.requestDataId,
requestDataStatus: "Pending",
},
data: { requestDataStatus: "Ready" },
});
}
break;
case "InProgress":
case "Waiting":
case "Validate":

View file

@ -381,43 +381,71 @@ export class TaskController extends Controller {
await permissionCheckCompany(req.user, record.registeredBranch);
await prisma.taskOrder.update({
where: { id: taskOrderId },
include: {
taskList: {
include: {
requestWorkStep: {
include: {
requestWork: true,
},
},
},
},
institution: true,
registeredBranch: true,
createdBy: true,
},
data: {
...body,
taskList: {
deleteMany: record?.taskList.filter(
if (record.taskList.some((v) => v.taskStatus !== "Pending")) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"One or more task is not pending",
"taskListNotPending",
);
}
return await prisma.$transaction(async (tx) => {
await Promise.all(
record.taskList
.filter(
(lhs) =>
!body.taskList.find(
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
),
)
.map((v) =>
tx.task.update({
where: { id: v.id },
data: {
requestWorkStep: { update: { workStatus: "Ready" } },
},
}),
),
createMany: {
data: body.taskList.filter(
);
return await tx.taskOrder.update({
where: { id: taskOrderId },
include: {
taskList: {
include: {
requestWorkStep: {
include: {
requestWork: true,
},
},
},
},
institution: true,
registeredBranch: true,
createdBy: true,
},
data: {
...body,
taskList: {
deleteMany: record?.taskList.filter(
(lhs) =>
!record?.taskList.find(
!body.taskList.find(
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
),
),
skipDuplicates: true,
createMany: {
data: body.taskList.filter(
(lhs) =>
!record?.taskList.find(
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
),
),
skipDuplicates: true,
},
},
taskProduct: { deleteMany: {}, create: body.taskProduct },
},
taskProduct: { deleteMany: {}, create: body.taskProduct },
},
});
});
}
@ -431,6 +459,7 @@ export class TaskController extends Controller {
registeredBranch: {
include: branchRelationPermInclude(req.user),
},
taskList: true,
},
});
@ -438,6 +467,14 @@ export class TaskController extends Controller {
await permissionCheck(req.user, record.registeredBranch);
if (record.taskList.some((v) => v.taskStatus !== "Pending")) {
throw new HttpError(
HttpStatus.BAD_REQUEST,
"One or more task is not pending",
"taskListNotPending",
);
}
await Promise.all([deleteFolder(fileLocation.task.attachment(taskOrderId))]);
await tx.taskOrder.delete({ where: { id: taskOrderId } });
});

41
src/utils/datetime.ts Normal file
View file

@ -0,0 +1,41 @@
export function dateFormat(opts: {
date: string | Date | null;
locale?: string;
dayStyle?: "numeric" | "2-digit";
monthStyle?: "numeric" | "2-digit" | "long" | "short";
timeStyle?: "full" | "long" | "medium" | "short";
timeOnly?: boolean;
noDay?: boolean;
noMonth?: boolean;
noYear?: boolean;
withTime?: boolean;
}): string {
const dt = opts.date ? new Date(opts.date) : new Date();
if (opts.timeOnly) {
opts.noDay = true;
opts.noMonth = true;
opts.noYear = true;
opts.timeStyle = opts.timeStyle || "short";
}
let timeText = opts.withTime
? " " +
dateFormat({
date: opts.date,
timeOnly: true,
timeStyle: opts.timeStyle,
})
: "";
if (timeText) opts.timeStyle = undefined;
let formatted = new Intl.DateTimeFormat(opts.locale, {
day: opts.noDay ? undefined : opts.dayStyle || "numeric",
month: opts.noMonth ? undefined : opts.monthStyle || "short",
timeStyle: opts.timeStyle,
year: opts.noYear ? undefined : "numeric",
}).format(dt);
return formatted + timeText;
}