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