เอกสารสำหรับใช้เป็น reference ในการย้ายระบบจาก Laravel 8 + Vue 2 ไปเป็น Next.js 16 + TypeScript + HeroUI
| หัวข้อ | ระบบปัจจุบัน | ระบบเป้าหมาย |
|---|---|---|
| Framework | Laravel 8.75 (PHP 7.3/8.0) | Next.js 16 (App Router) |
| Frontend | Vue 2.6 + Vuex + Blade SSR | React 19 + TypeScript 5.x |
| CSS | Bootstrap 5 + Tailwind + Custom SCSS | HeroUI v2 + Tailwind CSS v4 |
| State | Vuex 3.6 | Zustand / TanStack Query v5 |
| HTTP | Axios → /rest/* | fetch / TanStack Query v5 |
| Auth | JWT + Sanctum + Fortify 2FA | Auth.js v5 (next-auth v5) + JWT |
| Database | 5 MySQL DBs via Eloquent ORM | Prisma ORM 6 (multi-schema) |
| DomPDF, mPDF, TCPDF, FPDF | Puppeteer / @react-pdf | |
| Excel | Maatwebsite/Excel | ExcelJS / SheetJS |
| Charts | ApexCharts + Chart.js | Recharts / Tremor |
| Icons | Material Symbols (Google Fonts) | Iconify (@iconify/react) |
| i18n | vue-i18n (TH/EN) | next-intl v4 |
| Build | Laravel Mix (Webpack) | Turbopack (built-in Next.js 16) |
| Component | จำนวน |
|---|---|
| Controllers | 133 |
| Models (Eloquent) | 190 |
| Routes | 300+ |
| Blade Templates | 200+ |
| Vue Components | 170+ |
| Database Migrations | 153 |
| Database Tables | 150+ |
| Middleware | 14 |
| Queue Jobs | 8 |
| Route Files | 15 |
| Feature | คำอธิบาย | ใช้กับ |
|---|---|---|
| React 19 | useActionState, useOptimistic, Server Components เป็น default | ทุกหน้า |
| Turbopack (Stable) | Dev server เร็วกว่า Webpack 10x, ไม่ต้อง config เพิ่ม | Development |
| Server Actions (Stable) | เรียก server functions จาก client ได้โดยตรง ไม่ต้องสร้าง API route | Booking forms, Payment submit |
| Partial Prerendering (PPR) | Static shell + streaming dynamic parts — โหลดเร็วมาก | Dashboard, Booking list |
next/after | รัน code หลังส่ง response (logging, analytics) ไม่ block user | Activity logging, audit trail |
| Enhanced Caching | use cache directive, cacheLife, cacheTag — ควบคุม cache ละเอียดขึ้น | Series list, Country list, Settings |
forbidden() / unauthorized() | จัดการ 401/403 ด้วย built-in functions + forbidden.tsx, unauthorized.tsx | RBAC middleware |
| Metadata API | Dynamic metadata สำหรับ SEO ต่อหน้า | ทุกหน้า |
| Parallel Routes & Intercepting Routes | Modal routes, split layouts | Booking detail modal, Payment popup |
| Server Components + Streaming | SSR ที่ส่ง HTML ทีละส่วน ผู้ใช้เห็นข้อมูลเร็วขึ้น | Data tables, Reports |
| Edge Runtime | Middleware รันที่ Edge ใกล้ผู้ใช้ | Auth middleware, RBAC |
| Tailwind CSS v4 | Built-in support, ไม่ต้อง config PostCSS | Styling ทั้งโปรเจค |
// app/(dashboard)/booking/create/[busId]/page.tsx
import { createBooking } from './actions';
export default function CreateBookingPage() {
return (
<form action={createBooking}>
<input name="book_cus_name" placeholder="ชื่อลูกค้า" required />
<input name="book_pax" type="number" min={1} placeholder="จำนวนคน" />
<button type="submit">สร้าง Booking</button>
</form>
);
}// app/(dashboard)/dashboard/page.tsx
import { Suspense } from 'react';
import { SalesChart } from './sales-chart';
import { BookingSummary } from './booking-summary';
import { Skeleton } from '@heroui/react';
// Static shell renders immediately, dynamic parts stream in
export default function DashboardPage() {
return (
<div className="grid grid-cols-2 gap-4">
{/* Static: renders at build time */}
<h1>Dashboard</h1>
{/* Dynamic: streams in when ready */}
<Suspense fallback={<Skeleton className="h-64 w-full rounded-lg" />}>
<SalesChart />
</Suspense>
<Suspense fallback={<Skeleton className="h-48 w-full rounded-lg" />}>
<BookingSummary />
</Suspense>
</div>
);
}use cache สำหรับ Static Dataforbidden() / unauthorized() (Next.js 16)// app/(dashboard)/payment/page.tsx
import { auth } from '@/auth';
import { forbidden, unauthorized } from 'next/navigation';
export default async function PaymentPage() {
const session = await auth();
if (!session) unauthorized(); // → renders unauthorized.tsx (401)
if (!['payment', 'admin'].includes(session.user.scope)) {
forbidden(); // → renders forbidden.tsx (403)
}
return <PaymentList />;
}// app/forbidden.tsx — custom 403 page
export default function Forbidden() {
return (
<div className="flex h-screen items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold">403</h1>
<p className="mt-2 text-gray-500">คุณไม่มีสิทธิ์เข้าถึงหน้านี้</p>
</div>
</div>
);
}Booking.php):| Code | Status | คำอธิบาย |
|---|---|---|
| 00 | STATUS_WAITING | รอดำเนินการ |
| 05 | STATUS_WAITING | รอ |
| 10 | STATUS_INVOICE | ออก Invoice แล้ว |
| 20 | STATUS_DEPOSIT_PART | มัดจำบางส่วน |
| 25 | STATUS_DEPOSIT_FULL | มัดจำเต็ม |
| 30 | STATUS_FULL_PART | ชำระเต็มบางส่วน |
| 35 | STATUS_FULL_PAY | ชำระเต็มจำนวน |
| 40 | STATUS_CANCEL | ยกเลิก |
| 50 | STATUS_WAITING_WL | จอง Waitlist |
| 55 | STATUS_PAY | แจ้งชำระ |
| 60 | STATUS_CANCEL_PAY | ปฏิเสธชำระ |
status_cancel = 1 → ยกเลิกก่อน 30 วัน (คืนเงินได้)status_cancel = 2 → ยกเลิกก่อน 10 วัน (คืนบางส่วน)status_cancel = 3 → ไม่คืนเงินKernel.php):| Middleware Alias | Class | ใช้กับ Route Group |
|---|---|---|
auth | Authenticate | ทุก route ที่ต้อง login |
admin | AdminRoleAccess (scope: admin) | User management, Reports |
operation | AdminRoleAccess (scope: operation) | Tour/Period/Bus management |
payment | AdminRoleAccess (scope: payment) | Payment/Invoice/Receipt |
sales | AdminRoleAccess (scope: sales) | Sales dashboard |
ticket | AdminRoleAccess (scope: ticket) | Ticket management |
event | AdminRoleAccess (scope: event) | Event/Email |
settings | AdminRoleAccess (scope: settings) | Settings/Agency |
two-factor.confirmed | EnsureTwoFactorIsConfirmed | 2FA protected routes |
| Laravel Route File | Prefix | Next.js Directory | จำนวน Routes |
|---|---|---|---|
web.php | / | app/(dashboard)/ | ~25 |
booking.php | /booking | app/(dashboard)/booking/ | ~20 |
payment.php | /payment | app/(dashboard)/payment/ | ~10 |
invoice.php | /invoice | app/(dashboard)/invoice/ | ~10 |
ticket.php | /tickets | app/(dashboard)/tickets/ | ~25 |
incentive.php | /incentive | app/(dashboard)/incentive/ | ~15 |
reports.php | /reports | app/(dashboard)/reports/ | ~25 |
agency.php | /agency | app/(dashboard)/agency/ | ~30 |
setting.php | /setting | app/(dashboard)/settings/ | ~30 |
tour.php | /series | app/(dashboard)/tours/ | ~5 |
web-api.php | /rest/* | app/api/ | ~80+ |
api.php | /api/* | app/api/ | ~20 |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/ | GET | HomeController@index | app/(dashboard)/page.tsx |
/dashboard | GET | HomeController@dashboard | app/(dashboard)/dashboard/page.tsx |
/sales/dashboard | GET | HomeController@salesDashboard | app/(dashboard)/sales-dashboard/page.tsx |
/admin/dashboard | GET | HomeController@AdminDashboard | app/(dashboard)/admin-dashboard/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/booking | GET | BookingController@index | app/(dashboard)/booking/page.tsx |
/booking/create/bus/{bus} | GET | BookingController@create | app/(dashboard)/booking/create/[busId]/page.tsx |
/booking/{booking} | GET | BookingController@show | app/(dashboard)/booking/[id]/page.tsx |
/booking/{booking}/overview | GET | BookingController@overview | app/(dashboard)/booking/[id]/overview/page.tsx |
/booking/{id}/payment | GET | BookingController@payment | app/(dashboard)/booking/[id]/payment/page.tsx |
/booking/{id}/passport | GET | BookingController@passport | app/(dashboard)/booking/[id]/passport/page.tsx |
/booking/{id}/traveler | GET | BookingController@traveler | app/(dashboard)/booking/[id]/traveler/page.tsx |
/booking/all | GET | AllBookingController@index | app/(dashboard)/booking/all/page.tsx |
/my-booking | GET | MyBookingController@index | app/(dashboard)/my-booking/page.tsx |
/order/{order} | GET | BookingController@detail | app/(dashboard)/order/[id]/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/series-tour | GET | SeriesTourController@index | app/(dashboard)/series-tour/page.tsx |
/tours/create | GET | ManageTourController@create | app/(dashboard)/tours/create/page.tsx |
/tours/{tour}/edit | GET | ManageTourController@edit | app/(dashboard)/tours/[id]/edit/page.tsx |
/tours/{tour}/period/create | GET | TourPeriodController@create | app/(dashboard)/tours/[id]/period/create/page.tsx |
/tours/{tour}/period/{period}/edit | GET | TourPeriodController@edit | app/(dashboard)/tours/[id]/period/[periodId]/edit/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/period | GET | PeriodBookingController@index | app/(dashboard)/period/page.tsx |
/period/popup/{id} | GET | PeriodBookingController@showPopup | component (modal) |
/period/detail/bus/{busId}/busno/{busNo}/period/{periodId} | GET | PeriodBookingController@detail | app/(dashboard)/period/[periodId]/bus/[busId]/page.tsx |
/period/rushtoSell | GET | PeriodBookingController@rushToSell | app/(dashboard)/period/rush-to-sell/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/payment | GET | PaymentController@index | app/(dashboard)/payment/page.tsx |
/payment/create | GET | PaymentController@create | app/(dashboard)/payment/create/page.tsx |
/payment/{id}/edit | GET | PaymentController@edit | app/(dashboard)/payment/[id]/edit/page.tsx |
/payment-due | GET | PaymentDueController@index | app/(dashboard)/payment-due/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/invoices/all | GET | InvoicesController@index | app/(dashboard)/invoices/page.tsx |
/invoices/request | GET | InvoicesController@requestList | app/(dashboard)/invoices/request/page.tsx |
/invoice/list | GET | InvoiceController@list | app/(dashboard)/invoice/list/page.tsx |
/invoice/{InvoiceReport} | GET | InvoiceController@show | app/(dashboard)/invoice/[id]/page.tsx |
/invoicetax | GET | InvoiceTaxController@index | app/(dashboard)/invoice-tax/page.tsx |
/receipt | GET | ReceiptController@index | app/(dashboard)/receipt/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/ticket/series | GET | SeriesTicketController@index | app/(dashboard)/ticket/series/page.tsx |
/ticket/pre-sale | GET | PreSaleTicketController@index | app/(dashboard)/ticket/pre-sale/page.tsx |
/ticket/ticket | GET | ManageTicketController@index | app/(dashboard)/ticket/manage/page.tsx |
/tickets/series/{id}/period/{period}/edit | GET | PeriodTicketController@edit | app/(dashboard)/ticket/series/[id]/period/[periodId]/edit/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/incentive | GET | IncentiveController@index | app/(dashboard)/incentive/page.tsx |
/incentive/create | GET | IncentiveController@create | app/(dashboard)/incentive/create/page.tsx |
/incentive/{id}/edit | GET | IncentiveController@edit | app/(dashboard)/incentive/[id]/edit/page.tsx |
/incentive/{id}/program | GET | IncentiveProgramController@show | app/(dashboard)/incentive/[id]/program/page.tsx |
/incentive/{id}/payment | GET | IncentivePaymentController@index | app/(dashboard)/incentive/[id]/payment/page.tsx |
/incentive/{id}/traveler | GET | IncentiveTravelerController@index | app/(dashboard)/incentive/[id]/traveler/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/reports/invoice | GET | - | app/(dashboard)/reports/invoice/page.tsx |
/reports/payment | GET | - | app/(dashboard)/reports/payment/page.tsx |
/reports/period | GET | - | app/(dashboard)/reports/period/page.tsx |
/reports/costing | GET | - | app/(dashboard)/reports/costing/page.tsx |
/reports/income | GET | - | app/(dashboard)/reports/income/page.tsx |
/reports/estimates | GET | - | app/(dashboard)/reports/estimates/page.tsx |
/reports/commission | GET | - | app/(dashboard)/reports/commission/page.tsx |
/reports/agency | GET | - | app/(dashboard)/reports/agency/page.tsx |
/reports/invoice-full-payment | GET | - | app/(dashboard)/reports/invoice-full-payment/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/agency/sales | GET | AgencyController@index | app/(dashboard)/agency/sales/page.tsx |
/agency/sales/create | GET | AgencyController@create | app/(dashboard)/agency/sales/create/page.tsx |
/agency/sales/edit/{id} | GET | AgencyController@edit | app/(dashboard)/agency/sales/[id]/edit/page.tsx |
/agency | GET | CompanyController@index | app/(dashboard)/agency/company/page.tsx |
/agency/create | GET | CompanyController@create | app/(dashboard)/agency/company/create/page.tsx |
/agency/{id}/edit | GET | CompanyController@edit | app/(dashboard)/agency/company/[id]/edit/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/setting/airline | GET | AirlineController@index | app/(dashboard)/settings/airline/page.tsx |
/setting/route | GET | RouteController@index | app/(dashboard)/settings/route/page.tsx |
/setting/country | GET | CountryController@index | app/(dashboard)/settings/country/page.tsx |
/setting/city | GET | CityController@index | app/(dashboard)/settings/city/page.tsx |
/setting/bank | GET | BankController@index | app/(dashboard)/settings/bank/page.tsx |
/setting/banner | GET | BannerController@index | app/(dashboard)/settings/banner/page.tsx |
/setting/flashsale-banner | GET | FlashsaleBannerController@index | app/(dashboard)/settings/flashsale-banner/page.tsx |
/setting/land-operation | GET | LandOperationController@index | app/(dashboard)/settings/land-operation/page.tsx |
/setting/promotion | GET | PromotionController@index | app/(dashboard)/settings/promotion/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/event | GET | EventController@index | app/(dashboard)/event/page.tsx |
/event/email | GET | EventEmailController@index | app/(dashboard)/event/email/page.tsx |
| Laravel Route | Method | Controller | Next.js Path |
|---|---|---|---|
/auth/token/login | GET | TokenLoginController@login | app/(auth)/login/page.tsx |
/auth/jwt | GET | TokenLoginController@loginByJwt | app/api/auth/jwt/route.ts |
/two-factor-form | GET | HomeController@twoFactorLoginForm | app/(auth)/two-factor/page.tsx |
/account | GET | AccountController@index | app/(dashboard)/account/page.tsx |
pbc_center (~120 tables)| Table | คำอธิบาย | Key Columns |
|---|---|---|
users | ผู้ใช้ระบบ | id, name, email, password |
roles | บทบาท | id, name, scope |
user_role_permits | สิทธิ์ตาม role | user_id, role_id |
user_country_permits | สิทธิ์ตามประเทศ | user_id, country_id |
personal_access_tokens | Sanctum tokens | tokenable_id, token |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
agencies | ตัวแทนขาย | agen_id, name, company_id |
agency_companies | บริษัทตัวแทน | id, name, tax_id, address |
agency_access_tokens | API token | agency_id, token |
agency_credit_shells | เครดิต | agency_id, amount, type |
agency_email_masters | Email config | agency_id, email |
agency_company_verifications | การยืนยันบริษัท | company_id, status |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
series | แพ็คเกจทัวร์ | ser_id, name, country_id, airline_id, price |
series_programes | โปรแกรมรายวัน | id, ser_id, day, description |
series_location_cities | เมืองในทัวร์ | id, ser_id, city_id |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
periods | รอบเดินทาง | per_id, ser_id, start_date, end_date, seats, status |
tour_period_lockseats | ล็อคที่นั่ง | id, per_id, seats, agency_id |
period_leaders | หัวหน้าทัวร์ | id, per_id, user_id |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
bookings | การจอง | book_id, per_id, bus_id, agen_id, status, total |
booking_details | ผู้เดินทาง | id, book_id, name, passport, room_type |
booking_lists | รายการจอง | id, book_id, description, amount |
booking_logs | ประวัติ | id, book_id, action, user_id |
booking_activities | กิจกรรม | id, book_id, type, data |
booking_guarantees | หลักประกัน | id, book_id, file, status |
booking_refunds | คืนเงิน | id, book_id, amount, reason |
booking_deducts | หักเงิน | id, book_id, amount |
booking_extralists | รายการเสริม | id, book_id, name, price |
booking_send_mails | ส่ง email | id, book_id, type, sent_at |
booking_visa_eus | วีซ่า EU | id, book_id, status |
booking_visa_rejects | วีซ่าถูกปฏิเสธ | id, book_id, country |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
payments | การชำระ | pay_id, book_id, amount, type, status |
payment_methods | วิธีชำระ | id, name |
payment_transitions | การเปลี่ยนสถานะ | id, pay_id, from, to, user_id |
bankbooks | บัญชีธนาคาร | id, book_id, bank, account |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
invoices | ใบแจ้งหนี้ | id, book_id, invoice_no, amount |
invoices_details | รายการใน invoice | id, invoice_id, description, amount |
invoices_requests | คำขอ invoice | id, invoice_id, status |
receipts | ใบเสร็จ | id, book_id, receipt_no |
receipt_temporaries | ใบเสร็จชั่วคราว | id, book_id |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
bus_lists | รถ/กรุ๊ป | bus_id, per_id, bus_no, seats, price |
tour_buses | รถทัวร์ | id, tour_id, bus_no |
tour_lockseats | ล็อคที่นั่ง | id, bus_id, seats |
| Table | คำอธิบาย | Key Columns |
|---|---|---|
cost_seats | ต้นทุนต่อที่นั่ง | id, per_id, type, amount |
cost_seat_orders | คำสั่งต้นทุน | id, cost_seat_id |
cost_seat_order_details | รายละเอียด | id, order_id |
cost_seat_logs | ประวัติ | id, cost_seat_id |
cost_seat_averages | ค่าเฉลี่ย | id, per_id |
estimates | ประมาณการ | id, per_id, type |
estimate_details | รายละเอียดประมาณการ | id, estimate_id |
| Table | คำอธิบาย |
|---|---|
location_countries | ประเทศ |
location_cities | เมือง |
location_provinces | จังหวัด |
location_districts | อำเภอ |
countries | ประเทศ (master) |
provinces | จังหวัด (master) |
amphurs | อำเภอ (master) |
districts | ตำบล (master) |
| Table | คำอธิบาย |
|---|---|
airlines | สายการบิน |
passports | หนังสือเดินทาง |
room_details | ห้องพัก |
prefix_numbers | เลขลำดับ |
activities | log กิจกรรม |
notifications | การแจ้งเตือน |
approves | การอนุมัติ |
banners | แบนเนอร์เว็บ |
flashsale_banners | แบนเนอร์ Flash Sale |
land_operations | Land Operations |
country_land_operations | Land Ops ตามประเทศ |
promotions_bookings | โปรโมชัน |
pbc_incentive (~15 tables)| Table | คำอธิบาย |
|---|---|
incentive_quotations | ใบเสนอราคา Incentive |
incentive_quotation_details | รายละเอียด |
incentive_quotation_programs | โปรแกรม |
incentive_quotation_flights | เที่ยวบิน |
incentive_quotation_passports | หนังสือเดินทาง |
incentive_quotation_prices | ราคา |
incentive_quotation_payments | การชำระ |
incentive_quotation_payment_dues | กำหนดชำระ |
incentive_quotation_room_lists | ห ้องพัก |
incentive_payment_cencels | ยกเลิกชำระ |
incentive_payment_cancel_types | ประเภทยกเลิก |
invoice_reports | รายงาน Invoice |
invoice_report_details | รายละเอียด |
invoice_tax_reports | รายงานภาษี |
costing_reports | รายงานต้นทุน |
pbc_ticket (~30 tables)| Table | คำอธิบาย |
|---|---|
ticket | ตั๋วหลัก |
ticket_series | Series ตั๋ว |
ticket_period | Period ตั๋ว |
ticket_booking | จองตั๋ว |
ticket_flight | เที่ยวบิน |
ticket_payment | ชำระตั๋ว |
ticket_payment_real | ชำระจริง |
ticket_payment_other | ชำระอื่นๆ |
ticket_invoice_history | ประวัติ Invoice |
ticket_refund | คืนตั๋ว |
ticket_tax_refund | คืนภาษี |
guarantees | หลักประกัน |
ticket_quatations | ใบเสนอราคา |
ticket_namelist | รายชื่อ |
ticket_deduct_book | หักจองตั๋ว |
ticket_lockseat | ล็อคที่นั่ง |
pbc_report (~10 tables)| Table | คำอธิบาย |
|---|---|
invoice_full_payments | Invoice ชำระเต็ม |
invoice_full_payment_details | รายละเอียด |
report_booking_monthlies | รายงานรายเดือน |
reports_booking_yearly_activities | รายงานรายปี |
reports_periods_monthlies | Period รายเดือน |
reports_periods_yearly_activities | Period รายปี |
sales_booking_dailies | ยอดขายรายวัน |
pbc_costing (~5 tables)| Table | คำอธิบาย |
|---|---|
costing_series_reports | รายงานต้นทุน Series |
costing_series_types | ประเภทต้นทุน |
costing_series_type_lists | รายการต้นทุน |
costing_series_type_list_details | รายละเอียด |
costing_series_prices | ราคาต้นทุน |
pbc_incentive.invoice_reports.book_id → pbc_center.bookings.book_id
pbc_incentive.costing_reports.per_id → pbc_center.periods.per_id
pbc_report.invoice_full_payments.book_id → pbc_center.bookings.book_id
pbc_report.sales_booking_dailies.ser_id → pbc_center.series.ser_id
pbc_costing.costing_series_reports.ser_id → pbc_center.series.ser_id| ข้อดี | ข้อเสี ย |
|---|---|
| Codebase เดียว ไม่ต้อง hop network | ต้อง rewrite business logic 190 models ทั้งหมด |
| Server Components query ได้ตรง | Cross-DB JOIN ทำยากใน Prisma |
| Deploy ง่าย (1 service) | PDF/Excel generation ต้อง rewrite ทั้งหมด |
| Queue jobs ต้องใช้ BullMQ/Inngest แทน | |
| ไม่สามารถ migrate แบบค่อยเป็นค่อยไป |
| ข้อดี | ข้อเสีย |
|---|---|
มี API พร้อมแล้ว 80+ endpoints ใน web-api.php | ต้อง maintain 2 codebases |
| Cross-DB relationships ทำงานได้ทันที | Laravel ต้อง run ต่อไป |
| PDF/Excel/Queue ไม่ต้อง rewrite | Network hop เพิ่ม latency |
| ย้ายได้ทีละหน้า (gradual migration) | |
| Business logic ไม่ต้อง rewrite | |
| Team แยกทำ frontend/backend ได้ |
| ข้อดี | ข้อเสีย |
|---|---|
| Codebase เดียว (TypeScript ทั้งหมด) | ต้อง rewrite API ทั้งหมด |
| Type-safe end-to-end | Cross-DB ต้อง handle เอง |
| Modern tooling | ใช้เวลามาก |
| ข้อดี | ข้อเสีย |
|---|---|
| TypeScript ทั้ง stack — type-safe end-to-end | ต้อง rewrite business logic ทั้งหมด |
| แยก scale ได้ — frontend/backend scale อิสระ | 2 services ต้อง deploy + manage |
| Backend ไม่ผูกกับ framework — เปลี่ยน frontend ไม่กระทบ | เวลา develop นานกว่า Option B |
| ทีมใช้ภาษาเดียว — ไม่ต้องสลับ PHP/TS | ต้องเรียนรู้ ecosystem ใหม่ (Prisma, BullMQ) |
| tRPC ได้ — type-safe API โดยไม่ต้องเขียน REST | Cross-DB ต้อง handle เอง |
| Modern tooling — testing, linting, CI/CD เหมือนกัน | |
| Monorepo ได้ — ใช้ Turborepo shared types |
| Layer | Laravel (ปัจจุบัน) | Node.js TypeScript (ใหม่) |
|---|---|---|
| Runtime | PHP 7.3/8.0 | Node.js 20 LTS |
| Framework | Laravel 8.75 | Fastify 5 หรือ Express 5 + ts-rest |
| ORM | Eloquent (190 models) | Prisma (type-safe, multi-schema) |
| Validation | Laravel Validation | Zod (runtime + type inference) |
| Auth | JWT + Sanctum | jsonwebtoken + passport.js หรือ lucia-auth |
| Queue/Jobs | Laravel Queue (8 jobs) | BullMQ + Redis |
| DomPDF, mPDF, TCPDF, FPDF | Puppeteer หรือ @react-pdf | |
| Excel | Maatwebsite/Excel | ExcelJS |
| Laravel Mail | Nodemailer + React Email | |
| File Upload | Laravel Storage (local) | multer + S3 SDK |
| Cache | Laravel Cache | ioredis |
| Logging | Laravel Log | Pino (structured JSON logs) |
| Testing | PHPUnit | Vitest + Supertest |
| API Docs | — | Swagger/OpenAPI (auto-gen จาก Zod) |
pbc-monorepo/
├── apps/
│ ├── web/ # Next.js Frontend
│ │ ├── app/
│ │ │ ├── (auth)/
│ │ │ ├── (dashboard)/
│ │ │ └── api/auth/ # Auth.js v5 only
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── stores/
│ │ └── package.json
│ │
│ └── api/ # Node.js TypeScript Backend
│ ├── src/
│ │ ├── index.ts # Entry point
│ │ ├── app.ts # Fastify/Express setup
│ │ ├── config/
│ │ │ ├── database.ts # Prisma client instances
│ │ │ ├── redis.ts # Redis connection
│ │ │ ├── auth.ts # JWT config
│ │ │ └── env.ts # Zod-validated env vars
│ │ │
│ │ ├── modules/ # Feature modules
│ │ │ ├── auth/
│ │ │ │ ├── auth.controller.ts
│ │ │ │ ├── auth.service.ts
│ │ │ │ ├── auth.schema.ts # Zod schemas
│ │ │ │ └── auth.routes.ts
│ │ │ │
│ │ │ ├── booking/
│ │ │ │ ├── booking.controller.ts
│ │ │ │ ├── booking.service.ts
│ │ │ │ ├── booking.schema.ts
│ │ │ │ ├── booking.routes.ts
│ │ │ │ └── booking.constants.ts # Status codes
│ │ │ │
│ │ │ ├── payment/
│ │ │ │ ├── payment.controller.ts
│ │ │ │ ├── payment.service.ts
│ │ │ │ ├── payment.schema.ts
│ │ │ │ └── payment.routes.ts
│ │ │ │
│ │ │ ├── series/
│ │ │ ├── period/
│ │ │ ├── agency/
│ │ │ ├── ticket/
│ │ │ ├── incentive/
│ │ │ ├── invoice/
│ │ │ ├── report/
│ │ │ └── setting/
│ │ │
│ │ ├── jobs/ # Background jobs (BullMQ)
│ │ │ ├── export-booking.job.ts
│ │ │ ├── export-invoice.job.ts
│ │ │ ├── export-costing.job.ts
│ │ │ ├── send-email.job.ts
│ │ │ └── queue.ts # Queue setup
│ │ │
│ │ ├── services/ # Shared services
│ │ │ ├── pdf.service.ts # Puppeteer PDF
│ │ │ ├── excel.service.ts # ExcelJS
│ │ │ ├── email.service.ts # Nodemailer
│ │ │ ├── storage.service.ts # S3 file upload
│ │ │ └── prefix-number.service.ts
│ │ │
│ │ ├── middleware/
│ │ │ ├── auth.middleware.ts
│ │ │ ├── rbac.middleware.ts
│ │ │ └── error-handler.ts
│ │ │
│ │ └── utils/
│ │ ├── thai-date.ts
│ │ ├── currency.ts
│ │ └── pagination.ts
│ │
│ ├── prisma/
│ │ ├── schema-center.prisma
│ │ ├── schema-incentive.prisma
│ │ ├── schema-ticket.prisma
│ │ ├── schema-report.prisma
│ │ └── schema-costing.prisma
│ │
│ ├── tests/
│ │ ├── booking.test.ts
│ │ ├── payment.test.ts
│ │ └── setup.ts
│ │
│ ├── tsconfig.json
│ ├── Dockerfile
│ └── package.json
│
├── packages/
│ ├── shared-types/ # Shared TypeScript types
│ │ ├── src/
│ │ │ ├── booking.ts # Booking types + status enums
│ │ │ ├── payment.ts
│ │ │ ├── series.ts
│ │ │ ├── agency.ts
│ │ │ ├── ticket.ts
│ │ │ └── index.ts
│ │ └── package.json
│ │
│ ├── shared-utils/ # Shared utilities
│ │ ├── src/
│ │ │ ├── format-currency.ts
│ │ │ ├── format-date.ts
│ │ │ ├── thai-months.ts
│ │ │ └── status-labels.ts
│ │ └── package.json
│ │
│ └── api-client/ # Type-safe API client
│ ├── src/
│ │ ├── client.ts # Axios/fetch wrapper
│ │ └── endpoints.ts # All API endpoints
│ └── package.json
│
├── turbo.json
├── package.json
└── docker-compose.yml// Prisma: prisma/schema-center.prisma
datasource db {
provider = "mysql"
url = env("DATABASE_CENTER_URL")
}
model Booking {
book_id BigInt @id @default(autoincrement())
book_code String @unique
invoice_code String?
agen_id BigInt?
user_id BigInt?
per_id BigInt?
bus_id BigInt?
book_total Decimal? @db.Decimal(10, 2)
book_amountgrandtotal Decimal? @db.Decimal(18, 2)
book_pax Int? @db.SmallInt
status Int? @db.SmallInt
status_cancel Int? @db.SmallInt @default(0)
book_date DateTime?
book_cus_name String?
book_cus_tel String?
is_vat Int? @db.SmallInt
create_date DateTime?
update_date DateTime?
remark String? @db.Text
created_at DateTime?
updated_at DateTime?
// Relations (same database)
period Period? @relation(fields: [per_id], references: [per_id])
bus BusList? @relation(fields: [bus_id], references: [bus_id])
agency Agency? @relation(fields: [agen_id], references: [agen_id])
user User? @relation(fields: [user_id], references: [id])
details BookingDetails[]
additionals BookingAdditional[]
payments Payment[]
passports Passport[]
history BookingHistory[]
@@map("booking")
}| Phase | สิ่งที่ทำ | ผลลัพธ์ |
|---|---|---|
| 1 | Setup Turborepo, Prisma schemas ทั้ง 5 DBs, Auth (JWT), Middleware (RBAC) | โครงสร้างพร้อม |
| 2 | ย้าย Series/Period/Booking API (core business) | 60% ของ traffic |
| 3 | ย้าย Payment/Invoice/Agency API | ระบบการเงินทำงานบน Node.js |
| 4 | ย้าย Ticket/Incentive/Reports/Settings API | API ครบ |
| 5 | ย้าย Background jobs (BullMQ), PDF (Puppeteer), Excel (ExcelJS), Email (Nodemailer) | ปลด PHP dependencies |
| 6 | สร้าง/ย้าย Next.js frontend (ใช้ API ใหม่) | Frontend ใหม่ |
| 7 | Integration testing, performance testing, ปลด Laravel | Laravel-free |
| เกณฑ์ | Option B (Next.js + Laravel) | Option C (Next.js Full Stack) | Option D (Next.js + Node.js API) |
|---|---|---|---|
| ภาษา | TypeScript + PHP | TypeScript only | TypeScript only |
| Deploy | 2 services (Next.js + PHP) | 1 service | 2-3 services (web + api + worker) |
| Scale | แยก scale ได้ | scale เดียว | แยก scale ได้ดีที่สุด |
| Type Safety | ไม่ end-to-end | end-to-end | end-to-end (tRPC/shared-types) |
| Cross-DB | Eloquent (ง่าย) | Prisma (ยาก) | Prisma (ยาก แต่ handle ได้) |
| PDF/Excel | PHP (พร้อมใช้) | ต้อง rewrite | ต้อง rewrite |
| Queue | Laravel Queue (พร้อม) | BullMQ | BullMQ |
| เวลาย้าย | เร็วสุด ~20 สัปดาห์ | ~36 สัปดาห์ | ~36 สัปดาห์ |
| Maintenance | 2 ภาษา 2 ecosystem | 1 ภาษา 1 ecosystem | 1 ภาษา แต่ยืดหยุ่นกว่า |
| เหมาะกับ | ย้ายเร็ว ใช้ของเดิม | ทีมเล็ก deploy ง่าย | ทีมโต ระบบใหญ่ long-term |
web-api.php มี 80+ endpoints ที่ Vue ใช้อยู่ Next.js ใช้ได้เลย| ปัจจุบัน (Bootstrap/Vue) | HeroUI/React | หน้าที่ใช้ |
|---|---|---|
| Bootstrap Navbar | Navbar | Header (ทุกหน้า) |
| Custom Sidebar (Vue) | Custom Sidebar + Listbox | Sidebar navigation |
| Bootstrap Container | div with Tailwind | Layout wrapper |
| Bootstrap Card | Card, CardHeader, CardBody | Dashboard widgets, Detail views |
| Bootstrap Modal | Modal, ModalContent | Booking cancel, Agency select, etc. |
| Bootstrap Tabs | Tabs, Tab | Ticket tabs, Period tabs |
| ปัจจุบัน | HeroUI | หน้าที่ใช้ |
|---|---|---|
| Bootstrap Form Input | Input | ทุก form |
| Bootstrap Select | Select, SelectItem | Dropdown ทั่วไป |
| vue-multiselect | Autocomplete | Agency select, Country select |
| selectize | Autocomplete | Tag inputs, Search |
| Bootstrap Textarea | Textarea | หมายเหตุ, โปรแกรม |
| Bootstrap Checkbox | Checkbox, CheckboxGroup | Filter, Settings |
| Bootstrap Radio | RadioGroup, Radio | ประเภทห้อง, ช่องทางชำระ |
| Bootstrap Switch | Switch | Toggle status |
| vue2-datepicker | DatePicker (custom) | วันเดินทาง, วันชำระ |
| daterangepicker (jQuery) | react-date-range + Popover | Report filters |
| ปัจจุบัน | HeroUI | หน้าที่ใช้ |
|---|---|---|
| Bootstrap Table + DataTable | Table with useAsyncList | รายการ Booking, Payment, Period |
| Bootstrap Pagination | Pagination | ทุกหน้า list |
| Bootstrap Badge | Chip | Status badges |
| Bootstrap Alert | Alert หรือ toast library | แจ้งเตือน |
| SweetAlert2 | Modal (confirm) + Sonner (toast) | Confirm actions, Success/Error |
| Bootstrap Progress | Progress | Upload progress |
| Bootstrap Spinner | Spinner | Loading states |
| Bootstrap Tooltip | Tooltip | Help text |
| ApexCharts | Recharts / Tremor | Dashboard charts |
| ปัจจุบัน | HeroUI | หน้าที่ใช้ |
|---|---|---|
| Bootstrap Button | Button | ทุก action |
| Bootstrap Dropdown | Dropdown, DropdownMenu | Action menus |
| Bootstrap Breadcrumb | Breadcrumbs | Navigation path |
| Dragula (drag & drop) | @dnd-kit/core | Banner sorting |