Merge branch 'develop'
This commit is contained in:
commit
c7fccb6087
8 changed files with 184 additions and 40 deletions
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "RequestDataStatus" ADD VALUE 'Ready';
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
# Please do not edit this file manually
|
# 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"
|
provider = "postgresql"
|
||||||
|
|
@ -1406,6 +1406,7 @@ model Payment {
|
||||||
|
|
||||||
enum RequestDataStatus {
|
enum RequestDataStatus {
|
||||||
Pending
|
Pending
|
||||||
|
Ready
|
||||||
InProgress
|
InProgress
|
||||||
Completed
|
Completed
|
||||||
Canceled
|
Canceled
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { notFoundError } from "../utils/error";
|
||||||
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 { getFileBuffer, listFile } from "../utils/minio";
|
import { getFileBuffer, listFile } from "../utils/minio";
|
||||||
|
import { dateFormat } from "../utils/datetime";
|
||||||
|
|
||||||
const quotationData = (id: string) =>
|
const quotationData = (id: string) =>
|
||||||
prisma.quotation.findFirst({
|
prisma.quotation.findFirst({
|
||||||
|
|
@ -128,6 +129,26 @@ export class DocTemplateController extends Controller {
|
||||||
template,
|
template,
|
||||||
data: record,
|
data: record,
|
||||||
additionalJsContext: {
|
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,
|
addressFull,
|
||||||
addressFullTH: (addr: FullAddress) => addressFull(addr, "th"),
|
addressFullTH: (addr: FullAddress) => addressFull(addr, "th"),
|
||||||
addressFullEN: (addr: FullAddress) => addressFull(addr, "en"),
|
addressFullEN: (addr: FullAddress) => addressFull(addr, "en"),
|
||||||
|
|
@ -175,6 +196,26 @@ type FullAddress = {
|
||||||
en?: boolean;
|
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") {
|
function addressFull(addr: FullAddress, lang: "th" | "en" = "en") {
|
||||||
let fragments: string[];
|
let fragments: string[];
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
|
|
|
||||||
|
|
@ -477,13 +477,15 @@ 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 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 pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
|
||||||
const vat = p.calcVat
|
const vat = p.calcVat ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0;
|
||||||
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
|
|
||||||
VAT_DEFAULT *
|
|
||||||
(!v.discount ? v.amount : 1)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
order: i + 1,
|
order: i + 1,
|
||||||
|
|
@ -742,13 +744,16 @@ 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 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 pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price;
|
||||||
const vat = p.calcVat
|
const vat = p.calcVat ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0;
|
||||||
? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) *
|
|
||||||
VAT_DEFAULT *
|
|
||||||
(!v.discount ? v.amount : 1)
|
|
||||||
: 0;
|
|
||||||
return {
|
return {
|
||||||
order: i + 1,
|
order: i + 1,
|
||||||
productId: v.productId,
|
productId: v.productId,
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ export class RequestDataController extends Controller {
|
||||||
(a, c) => Object.assign(a, { [c.requestDataStatus]: c._count }),
|
(a, c) => Object.assign(a, { [c.requestDataStatus]: c._count }),
|
||||||
{
|
{
|
||||||
[RequestDataStatus.Pending]: 0,
|
[RequestDataStatus.Pending]: 0,
|
||||||
|
[RequestDataStatus.Ready]: 0,
|
||||||
[RequestDataStatus.InProgress]: 0,
|
[RequestDataStatus.InProgress]: 0,
|
||||||
[RequestDataStatus.Completed]: 0,
|
[RequestDataStatus.Completed]: 0,
|
||||||
[RequestDataStatus.Canceled]: 0,
|
[RequestDataStatus.Canceled]: 0,
|
||||||
|
|
@ -188,6 +189,7 @@ export class RequestDataController extends Controller {
|
||||||
},
|
},
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
skip: (page - 1) * pageSize,
|
skip: (page - 1) * pageSize,
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
}),
|
}),
|
||||||
prisma.requestData.count({ where }),
|
prisma.requestData.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
@ -520,7 +522,11 @@ export class RequestListController extends Controller {
|
||||||
return await prisma.$transaction(async (tx) => {
|
return await prisma.$transaction(async (tx) => {
|
||||||
const record = await tx.requestWorkStepStatus.upsert({
|
const record = await tx.requestWorkStepStatus.upsert({
|
||||||
include: {
|
include: {
|
||||||
requestWork: true,
|
requestWork: {
|
||||||
|
include: {
|
||||||
|
request: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
step_requestWorkId: {
|
step_requestWorkId: {
|
||||||
|
|
@ -537,6 +543,17 @@ export class RequestListController extends Controller {
|
||||||
});
|
});
|
||||||
|
|
||||||
switch (payload.workStatus) {
|
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 "InProgress":
|
||||||
case "Waiting":
|
case "Waiting":
|
||||||
case "Validate":
|
case "Validate":
|
||||||
|
|
|
||||||
|
|
@ -381,43 +381,71 @@ export class TaskController extends Controller {
|
||||||
|
|
||||||
await permissionCheckCompany(req.user, record.registeredBranch);
|
await permissionCheckCompany(req.user, record.registeredBranch);
|
||||||
|
|
||||||
await prisma.taskOrder.update({
|
if (record.taskList.some((v) => v.taskStatus !== "Pending")) {
|
||||||
where: { id: taskOrderId },
|
throw new HttpError(
|
||||||
include: {
|
HttpStatus.BAD_REQUEST,
|
||||||
taskList: {
|
"One or more task is not pending",
|
||||||
include: {
|
"taskListNotPending",
|
||||||
requestWorkStep: {
|
);
|
||||||
include: {
|
}
|
||||||
requestWork: true,
|
|
||||||
},
|
return await prisma.$transaction(async (tx) => {
|
||||||
},
|
await Promise.all(
|
||||||
},
|
record.taskList
|
||||||
},
|
.filter(
|
||||||
institution: true,
|
|
||||||
registeredBranch: true,
|
|
||||||
createdBy: true,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
...body,
|
|
||||||
taskList: {
|
|
||||||
deleteMany: record?.taskList.filter(
|
|
||||||
(lhs) =>
|
(lhs) =>
|
||||||
!body.taskList.find(
|
!body.taskList.find(
|
||||||
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
|
(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) =>
|
(lhs) =>
|
||||||
!record?.taskList.find(
|
!body.taskList.find(
|
||||||
(rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step,
|
(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: {
|
registeredBranch: {
|
||||||
include: branchRelationPermInclude(req.user),
|
include: branchRelationPermInclude(req.user),
|
||||||
},
|
},
|
||||||
|
taskList: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -438,6 +467,14 @@ export class TaskController extends Controller {
|
||||||
|
|
||||||
await permissionCheck(req.user, record.registeredBranch);
|
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 Promise.all([deleteFolder(fileLocation.task.attachment(taskOrderId))]);
|
||||||
await tx.taskOrder.delete({ where: { id: taskOrderId } });
|
await tx.taskOrder.delete({ where: { id: taskOrderId } });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
41
src/utils/datetime.ts
Normal file
41
src/utils/datetime.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue