childcafe/src/lib/validators/reserveLimitValidator.ts
2025-10-11 17:31:24 +09:00

119 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/lib/validators/reserveLimitValidator.ts
import { prisma } from "@/lib/prisma";
type LimitMethod = "block" | "all";
type LimitType = "set" | "person";
interface LimitCheckParams {
reserve_num: number;
// block のときのみ必要("HH:mm:ss"
reserve_time?: string;
/**
* この予約が占有するカウント
* limit_type=set のときは 1
* limit_type=person のときは (cust_parent + cust_child)
* フロントの事前確認なら未指定でもOK現在の満席判定だけ返す
*/
requestedCount?: number;
}
/**
* 開催設定の制限と現在の予約数を見て、満席か/受付可能かを判定する共通関数
* - ok=true: 受付可能
* - ok=false: 満席message="予約定員に達しています"
* - extra: 現在カウント/上限/残数 も返すのでUIにも利用可
*/
export async function checkReserveLimit(params: LimitCheckParams) {
const { reserve_num, reserve_time, requestedCount } = params;
const setting = await prisma.reserveSetting.findUnique({
where: { autonum: reserve_num },
select: {
limit_method: true, // "block" | "all"
limit_type: true, // "set" | "person"
reserve_limit: true, // number
},
});
if (!setting) {
return { ok: false, message: "開催設定が存在しません" as const };
}
const method = setting.limit_method as LimitMethod;
const type = setting.limit_type as LimitType;
const limit = setting.reserve_limit;
let currentCount = 0;
// ---- 無制限処理を先に ----
if (limit === 0) {
return {
ok: true,
message: "",
currentCount,
limit,
remain: Infinity,
method,
type,
};
}
if (method === "block") {
if (!reserve_time) {
return { ok: false, message: "来場時間が必要です" as const };
}
if (type === "set") {
currentCount = await prisma.reserveCustomer.count({
where: { reserve_num, reserve_time, cancel_flag: 0 },
});
} else {
const agg = await prisma.reserveCustomer.aggregate({
where: { reserve_num, reserve_time, cancel_flag: 0 },
_sum: { cust_parent: true, cust_child: true },
});
currentCount = (agg._sum.cust_parent ?? 0) + (agg._sum.cust_child ?? 0);
}
} else {
// method === "all"
if (type === "set") {
currentCount = await prisma.reserveCustomer.count({
where: { reserve_num, cancel_flag: 0 },
});
} else {
const agg = await prisma.reserveCustomer.aggregate({
where: { reserve_num, cancel_flag: 0 },
_sum: { cust_parent: true, cust_child: true },
});
currentCount = (agg._sum.cust_parent ?? 0) + (agg._sum.cust_child ?? 0);
}
}
// 事前確認requestedCount 未指定)のときは「現在満席かどうか」だけ返す
if (requestedCount == null) {
const ok = currentCount < limit;
return {
ok,
message: ok ? "" : ("予約定員に達しています" as const),
currentCount,
limit,
remain: Math.max(0, limit - currentCount),
method,
type,
};
}
// 予約実行時は今回の予約分も加味して判定
const willBe = currentCount + requestedCount;
const ok = willBe <= limit;
return {
ok,
message: ok ? "" : ("予約定員に達しています" as const),
currentCount,
limit,
remain: Math.max(0, limit - currentCount),
method,
type,
};
}