同日で時刻が被らない開催を登録できるように変更
This commit is contained in:
parent
2cd12da2f6
commit
60bb6935c9
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3004",
|
||||
"build": "next build",
|
||||
"build": "dotenv -e .env.production -- next build",
|
||||
"start": "dotenv -e .env.production -- next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,186 +1,184 @@
|
|||
// src/app/api/reserve-setting/[id]/route.ts
|
||||
|
||||
import { NextResponse, type NextRequest } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import dayjs from "dayjs";
|
||||
import { validateReserveSetting } from "@/lib/validators/reserveSettingValidator";
|
||||
|
||||
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const settingId = parseInt(id, 10);
|
||||
type RouteContext = { params: Promise<{ id: string }> };
|
||||
|
||||
const setting = await prisma.reserveSetting.findUnique({
|
||||
where: { autonum: settingId },
|
||||
select: {
|
||||
autonum: true,
|
||||
diner_date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
time_range: true,
|
||||
limit_method: true,
|
||||
limit_type: true,
|
||||
reserve_limit: true,
|
||||
},
|
||||
});
|
||||
export async function GET(req: NextRequest, context: RouteContext) {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const settingId = parseInt(id, 10);
|
||||
|
||||
if (!setting) {
|
||||
return NextResponse.json({ error: "開催が見つかりません" }, { status: 404 });
|
||||
}
|
||||
const setting = await prisma.reserveSetting.findUnique({
|
||||
where: { autonum: settingId },
|
||||
select: {
|
||||
autonum: true,
|
||||
diner_date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
time_range: true,
|
||||
limit_method: true,
|
||||
limit_type: true,
|
||||
reserve_limit: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 予約数集計
|
||||
let allCount = 0;
|
||||
const blockCounts: Record<string, number> = {};
|
||||
if (!setting) {
|
||||
return NextResponse.json({ error: "開催が見つかりません" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (setting.limit_method === "all") {
|
||||
if (setting.limit_type === "set") {
|
||||
allCount = await prisma.reserveCustomer.count({
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
});
|
||||
} else {
|
||||
const agg = await prisma.reserveCustomer.aggregate({
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
_sum: { cust_parent: true, cust_child: true },
|
||||
});
|
||||
allCount = (agg._sum.cust_parent ?? 0) + (agg._sum.cust_child ?? 0);
|
||||
}
|
||||
} else {
|
||||
if (setting.limit_type === "set") {
|
||||
const rows = await prisma.reserveCustomer.groupBy({
|
||||
by: ["reserve_time"],
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
_count: { _all: true },
|
||||
});
|
||||
rows.forEach((r) => (blockCounts[r.reserve_time] = r._count._all));
|
||||
} else {
|
||||
const rows = await prisma.reserveCustomer.groupBy({
|
||||
by: ["reserve_time"],
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
_sum: { cust_parent: true, cust_child: true },
|
||||
});
|
||||
rows.forEach((r) => {
|
||||
blockCounts[r.reserve_time] =
|
||||
(r._sum.cust_parent ?? 0) + (r._sum.cust_child ?? 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
let allCount = 0;
|
||||
const blockCounts: Record<string, number> = {};
|
||||
|
||||
return NextResponse.json({
|
||||
...setting,
|
||||
current: { all: allCount, blocks: blockCounts },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("GET Error:", error);
|
||||
return NextResponse.json({ error: "取得に失敗しました" }, { status: 500 });
|
||||
}
|
||||
if (setting.limit_method === "all") {
|
||||
if (setting.limit_type === "set") {
|
||||
allCount = await prisma.reserveCustomer.count({
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
});
|
||||
} else {
|
||||
const agg = await prisma.reserveCustomer.aggregate({
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
_sum: { cust_parent: true, cust_child: true },
|
||||
});
|
||||
allCount = (agg._sum.cust_parent ?? 0) + (agg._sum.cust_child ?? 0);
|
||||
}
|
||||
} else {
|
||||
if (setting.limit_type === "set") {
|
||||
const rows = await prisma.reserveCustomer.groupBy({
|
||||
by: ["reserve_time"],
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
_count: { _all: true },
|
||||
});
|
||||
rows.forEach((r) => (blockCounts[r.reserve_time] = r._count._all));
|
||||
} else {
|
||||
const rows = await prisma.reserveCustomer.groupBy({
|
||||
by: ["reserve_time"],
|
||||
where: { reserve_num: settingId, cancel_flag: 0 },
|
||||
_sum: { cust_parent: true, cust_child: true },
|
||||
});
|
||||
rows.forEach((r) => {
|
||||
blockCounts[r.reserve_time] =
|
||||
(r._sum.cust_parent ?? 0) + (r._sum.cust_child ?? 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
...setting,
|
||||
current: { all: allCount, blocks: blockCounts },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("GET Error:", error);
|
||||
return NextResponse.json({ error: "取得に失敗しました" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const settingId = Number(id);
|
||||
const data = await req.json();
|
||||
export async function PUT(req: NextRequest, context: RouteContext) {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const settingId = parseInt(id, 10);
|
||||
const data = await req.json();
|
||||
|
||||
// 必須チェック
|
||||
if (!data.dinerDate || !data.reserveStartDate || !data.reserveLimitDate) {
|
||||
return NextResponse.json({ error: "必須項目が入力されていません" }, { status: 400 });
|
||||
}
|
||||
if (!data.dinerDate || !data.reserveStartDate || !data.reserveLimitDate) {
|
||||
return NextResponse.json({ error: "必須項目が入力されていません" }, { status: 400 });
|
||||
}
|
||||
|
||||
const dinerDate = dayjs(data.dinerDate);
|
||||
if (dinerDate.isBefore(dayjs())) {
|
||||
return NextResponse.json({ error: "過去の日付は登録できません" }, { status: 400 });
|
||||
}
|
||||
const now = dayjs();
|
||||
const dinerDate = dayjs(data.dinerDate);
|
||||
if (dinerDate.isBefore(now)) {
|
||||
return NextResponse.json({ error: "過去の日付は登録できません" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 開催日重複チェック(自分以外)
|
||||
const checkDate = dinerDate.format("YYYY-MM-DD");
|
||||
// 同日開催OK(時間重複はNG)
|
||||
const dinerDateStr = dinerDate.format("YYYY-MM-DD");
|
||||
const newStartTime = dayjs(data.dinerDate).format("HH:mm:ss");
|
||||
const newEndTime = dayjs(`${data.dinerDate.split("T")[0]}T${data.endTime}`).format("HH:mm:ss");
|
||||
|
||||
const exists = await prisma.$queryRawUnsafe<{ dummy: number }[]>(
|
||||
`SELECT 1 as dummy FROM reserve_setting WHERE diner_date = ? AND delete_flag = 0 AND autonum <> ? LIMIT 1`,
|
||||
checkDate,
|
||||
data.id
|
||||
);
|
||||
console.log([checkDate, data.id, exists]);
|
||||
if (exists.length > 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "同じ開催日が既に登録されています" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const overlaps = await prisma.$queryRawUnsafe<{ dummy: number }[]>(
|
||||
`
|
||||
SELECT 1 as dummy
|
||||
FROM reserve_setting
|
||||
WHERE diner_date = ?
|
||||
AND delete_flag = 0
|
||||
AND autonum <> ?
|
||||
AND start_time < ?
|
||||
AND end_time > ?
|
||||
LIMIT 1
|
||||
`,
|
||||
dinerDateStr,
|
||||
settingId,
|
||||
newEndTime,
|
||||
newStartTime
|
||||
);
|
||||
|
||||
// 共通バリデーション
|
||||
const validationError = validateReserveSetting(data);
|
||||
if (validationError) {
|
||||
return NextResponse.json({ error: validationError }, { status: 400 });
|
||||
}
|
||||
if (overlaps.length > 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "同じ開催日で時間が重複する開催が既に登録されています" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// JST文字列に変換
|
||||
const dinerDateStr = dayjs(data.dinerDate).format("YYYY-MM-DD");
|
||||
const startTimeStr = dayjs(data.dinerDate).format("HH:mm:ss");
|
||||
const endTimeStr = dayjs(`${data.dinerDate.split("T")[0]}T${data.endTime}`).format(
|
||||
"HH:mm:ss"
|
||||
);
|
||||
const reserveStartStr = dayjs(data.reserveStartDate).format("YYYY-MM-DD HH:mm:ss");
|
||||
const reserveLimitStr = dayjs(data.reserveLimitDate).format("YYYY-MM-DD HH:mm:ss");
|
||||
const validationError = validateReserveSetting(data);
|
||||
if (validationError) {
|
||||
return NextResponse.json({ error: validationError }, { status: 400 });
|
||||
}
|
||||
|
||||
// Raw SQL UPDATE
|
||||
await prisma.$executeRawUnsafe(
|
||||
`UPDATE reserve_setting
|
||||
const reserveStartStr = dayjs(data.reserveStartDate).format("YYYY-MM-DD HH:mm:ss");
|
||||
const reserveLimitStr = dayjs(data.reserveLimitDate).format("YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
await prisma.$executeRawUnsafe(
|
||||
`UPDATE reserve_setting
|
||||
SET diner_date = ?, start_time = ?, end_time = ?, time_range = ?,
|
||||
reserve_startdate = ?, reserve_limitdate = ?,
|
||||
parent_money = ?, child_money = ?, limit_method = ?, limit_type = ?,
|
||||
reserve_limit = ?, note = ?, lastupdate = NOW()
|
||||
WHERE autonum = ?`,
|
||||
dinerDateStr,
|
||||
startTimeStr,
|
||||
endTimeStr,
|
||||
Number(data.timeRange),
|
||||
reserveStartStr,
|
||||
reserveLimitStr,
|
||||
Number(data.priceAdult),
|
||||
Number(data.priceChild),
|
||||
data.limitMethod,
|
||||
data.limitUnit,
|
||||
Number(data.limitValue),
|
||||
data.note || null,
|
||||
settingId
|
||||
);
|
||||
dinerDateStr,
|
||||
newStartTime,
|
||||
newEndTime,
|
||||
Number(data.timeRange),
|
||||
reserveStartStr,
|
||||
reserveLimitStr,
|
||||
Number(data.priceAdult),
|
||||
Number(data.priceChild),
|
||||
data.limitMethod,
|
||||
data.limitUnit,
|
||||
Number(data.limitValue),
|
||||
data.note || null,
|
||||
settingId
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("PUT Error:", error);
|
||||
return NextResponse.json({ error: "更新に失敗しました" }, { status: 500 });
|
||||
}
|
||||
return NextResponse.json({ success: true }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("PUT Error:", error);
|
||||
return NextResponse.json({ error: "更新に失敗しました" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteExists {
|
||||
autonum: number;
|
||||
}
|
||||
// DELETE: 論理削除(Raw SQL版)
|
||||
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const settingId = parseInt(id, 10);
|
||||
|
||||
// 存在確認
|
||||
const rows = (await prisma.$queryRawUnsafe(
|
||||
`SELECT autonum FROM reserve_setting WHERE autonum = ? AND delete_flag = 0 LIMIT 1`,
|
||||
settingId
|
||||
)) as DeleteExists[];
|
||||
if (rows.length === 0) {
|
||||
return NextResponse.json({ error: "対象データが存在しません" }, { status: 404 });
|
||||
}
|
||||
|
||||
// 論理削除
|
||||
await prisma.$executeRawUnsafe(
|
||||
`UPDATE reserve_setting
|
||||
SET delete_flag = 1, lastupdate = NOW()
|
||||
WHERE autonum = ?`,
|
||||
settingId
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("DELETE Error:", error);
|
||||
return NextResponse.json({ error: "削除に失敗しました" }, { status: 500 });
|
||||
}
|
||||
export async function DELETE(req: NextRequest, context: RouteContext) {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const settingId = parseInt(id, 10);
|
||||
|
||||
const rows = (await prisma.$queryRawUnsafe(
|
||||
`SELECT autonum FROM reserve_setting WHERE autonum = ? AND delete_flag = 0 LIMIT 1`,
|
||||
settingId
|
||||
)) as { autonum: number }[];
|
||||
|
||||
if (rows.length === 0) {
|
||||
return NextResponse.json({ error: "対象データが存在しません" }, { status: 404 });
|
||||
}
|
||||
|
||||
await prisma.$executeRawUnsafe(
|
||||
`UPDATE reserve_setting SET delete_flag = 1, lastupdate = NOW() WHERE autonum = ?`,
|
||||
settingId
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true }, { status: 200 });
|
||||
} catch (error) {
|
||||
console.error("DELETE Error:", error);
|
||||
return NextResponse.json({ error: "削除に失敗しました" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,6 @@ export async function GET() {
|
|||
diner_range: `${dayjs(row.diner_date).format("M/DD")}\n${startHM}~${endHM}`,
|
||||
end_hm: endHM,
|
||||
|
||||
// ここは SQL 側で '%Y-%m-%d %H:%i:%s' の文字列にして返しているので
|
||||
// その文字列を dayjs のフォーマットで ISO 風に成形
|
||||
reserve_start: row.reserve_start_str
|
||||
? dayjs(row.reserve_start_str, "YYYY-MM-DD HH:mm:ss").format("YYYY-MM-DDTHH:mm")
|
||||
: "",
|
||||
|
|
@ -106,38 +104,30 @@ export async function GET() {
|
|||
}
|
||||
}
|
||||
|
||||
// POST: 新規作成
|
||||
// POST: 新規作成(同日でも時間が重ならなければOK)
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const data = await req.json();
|
||||
console.log("受信:", data.timeRange);
|
||||
|
||||
// 必須チェック
|
||||
if (!data.dinerDate || !data.reserveStartDate || !data.reserveLimitDate) {
|
||||
return NextResponse.json({ error: "必須項目が入力されていません" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 過去日禁止
|
||||
const now = dayjs();
|
||||
const dinerDate = dayjs(data.dinerDate);
|
||||
if (dinerDate.isBefore(now)) {
|
||||
return NextResponse.json({ error: "過去の日付は登録できません" }, { status: 400 });
|
||||
}
|
||||
// --- 同日開催禁止(Raw SQL)---
|
||||
const checkDate = dinerDate.format("YYYY-MM-DD");
|
||||
const exists = await prisma.$queryRawUnsafe<{ dummy: number }[]>(
|
||||
`SELECT 1 as dummy FROM reserve_setting WHERE diner_date = ? AND delete_flag = 0 LIMIT 1`,
|
||||
checkDate
|
||||
);
|
||||
if (exists.length > 0) {
|
||||
return NextResponse.json({ error: "同じ開催日で既に登録があります" }, { status: 400 });
|
||||
}
|
||||
// 追加バリデーション
|
||||
|
||||
// 追加バリデーション(形式・範囲など)
|
||||
const validationError = validateReserveSetting(data);
|
||||
if (validationError) {
|
||||
return NextResponse.json({ error: validationError }, { status: 400 });
|
||||
}
|
||||
|
||||
// JST文字列に変換
|
||||
// JST文字列に変換(※時間重複チェックでも使う)
|
||||
const dinerDateStr = dayjs(data.dinerDate).format("YYYY-MM-DD");
|
||||
const startTimeStr = dayjs(data.dinerDate).format("HH:mm:ss");
|
||||
const endTimeStr = dayjs(`${data.dinerDate.split("T")[0]}T${data.endTime}`).format(
|
||||
|
|
@ -146,6 +136,30 @@ export async function POST(req: NextRequest) {
|
|||
const reserveStartStr = dayjs(data.reserveStartDate).format("YYYY-MM-DD HH:mm:ss");
|
||||
const reserveLimitStr = dayjs(data.reserveLimitDate).format("YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
// --- 同日・時間重複チェック ---
|
||||
// 重なり判定: existingStart < newEnd AND existingEnd > newStart
|
||||
const overlap = await prisma.$queryRawUnsafe<{ dummy: number }[]>(
|
||||
`
|
||||
SELECT 1 as dummy
|
||||
FROM reserve_setting
|
||||
WHERE diner_date = ?
|
||||
AND delete_flag = 0
|
||||
AND start_time < ?
|
||||
AND end_time > ?
|
||||
LIMIT 1
|
||||
`,
|
||||
dinerDateStr,
|
||||
endTimeStr,
|
||||
startTimeStr
|
||||
);
|
||||
|
||||
if (overlap.length > 0) {
|
||||
return NextResponse.json(
|
||||
{ error: "同じ開催日で時間帯が重複しています" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Raw SQL で保存
|
||||
await prisma.$executeRawUnsafe(
|
||||
`INSERT INTO reserve_setting
|
||||
|
|
@ -163,7 +177,7 @@ export async function POST(req: NextRequest) {
|
|||
parseInt(data.priceAdult, 10),
|
||||
parseInt(data.priceChild, 10),
|
||||
data.limitMethod,
|
||||
data.limitUnit,
|
||||
data.limitUnit, // NOTE: フロントのキー名に合わせて現状維持(DB列は limit_type)
|
||||
parseInt(data.limitValue, 10)
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user