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
|
||||
# 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"
|
||||
|
|
@ -1406,6 +1406,7 @@ model Payment {
|
|||
|
||||
enum RequestDataStatus {
|
||||
Pending
|
||||
Ready
|
||||
InProgress
|
||||
Completed
|
||||
Canceled
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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
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