登入驗證以及使用者按鈕
This commit is contained in:
60
app/Http/Controllers/Auth/LoginController.php
Normal file
60
app/Http/Controllers/Auth/LoginController.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class LoginController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the login view.
|
||||||
|
*/
|
||||||
|
public function show()
|
||||||
|
{
|
||||||
|
return Inertia::render('Auth/Login');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming authentication request.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'username' => ['required', 'string'],
|
||||||
|
'password' => ['required', 'string'],
|
||||||
|
], [
|
||||||
|
'username.required' => '請輸入帳號',
|
||||||
|
'password.required' => '請輸入密碼',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$credentials = $request->only('username', 'password');
|
||||||
|
|
||||||
|
if (Auth::attempt($credentials, $request->boolean('remember'))) {
|
||||||
|
$request->session()->regenerate();
|
||||||
|
|
||||||
|
return redirect()->intended(route('dashboard'));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'username' => '帳號或密碼錯誤。',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy an authenticated session.
|
||||||
|
*/
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
Auth::guard('web')->logout();
|
||||||
|
|
||||||
|
$request->session()->invalidate();
|
||||||
|
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,9 @@ class HandleInertiaRequests extends Middleware
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
...parent::share($request),
|
...parent::share($request),
|
||||||
//
|
'auth' => [
|
||||||
|
'user' => $request->user(),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class User extends Authenticatable
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
|
'username',
|
||||||
'password',
|
'password',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('username')->unique()->after('name');
|
||||||
|
$table->string('email')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('username');
|
||||||
|
$table->string('email')->nullable(false)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
11
resources/js/Components/ApplicationLogo.tsx
Normal file
11
resources/js/Components/ApplicationLogo.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ImgHTMLAttributes } from 'react';
|
||||||
|
|
||||||
|
export default function ApplicationLogo(props: ImgHTMLAttributes<HTMLImageElement>) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
{...props}
|
||||||
|
src="/logo.png"
|
||||||
|
alt="小小冰室 Logo"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
resources/js/Components/InputError.tsx
Normal file
10
resources/js/Components/InputError.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { HTMLAttributes } from 'react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export default function InputError({ message, className = '', ...props }: HTMLAttributes<HTMLParagraphElement> & { message?: string }) {
|
||||||
|
return message ? (
|
||||||
|
<p {...props} className={cn('text-sm text-red-600', className)}>
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
ChevronDown,
|
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Package,
|
Package,
|
||||||
ShoppingCart,
|
ShoppingCart,
|
||||||
@@ -11,13 +10,24 @@ import {
|
|||||||
Warehouse,
|
Warehouse,
|
||||||
Truck,
|
Truck,
|
||||||
Contact2,
|
Contact2,
|
||||||
FileText
|
FileText,
|
||||||
|
LogOut,
|
||||||
|
User,
|
||||||
|
ChevronDown
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Link, usePage } from "@inertiajs/react";
|
import { Link, usePage } from "@inertiajs/react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import BreadcrumbNav, { BreadcrumbItemType } from "@/Components/shared/BreadcrumbNav";
|
import BreadcrumbNav, { BreadcrumbItemType } from "@/Components/shared/BreadcrumbNav";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/Components/ui/dropdown-menu";
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -34,7 +44,9 @@ export default function AuthenticatedLayout({
|
|||||||
children: React.ReactNode,
|
children: React.ReactNode,
|
||||||
breadcrumbs?: BreadcrumbItemType[]
|
breadcrumbs?: BreadcrumbItemType[]
|
||||||
}) {
|
}) {
|
||||||
const { url } = usePage();
|
const { url, props } = usePage();
|
||||||
|
// @ts-ignore
|
||||||
|
const user = props.auth?.user || { name: 'Guest', username: 'guest' };
|
||||||
const [isCollapsed, setIsCollapsed] = useState(() => {
|
const [isCollapsed, setIsCollapsed] = useState(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
return localStorage.getItem("sidebar-collapsed") === "true";
|
return localStorage.getItem("sidebar-collapsed") === "true";
|
||||||
@@ -243,6 +255,38 @@ export default function AuthenticatedLayout({
|
|||||||
<span className="font-bold text-slate-900">小小冰室 ERP</span>
|
<span className="font-bold text-slate-900">小小冰室 ERP</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* User Menu */}
|
||||||
|
<DropdownMenu modal={false}>
|
||||||
|
<DropdownMenuTrigger className="flex items-center gap-2 outline-none group">
|
||||||
|
<div className="hidden md:flex flex-col items-end mr-1">
|
||||||
|
<span className="text-sm font-medium text-slate-700 group-hover:text-slate-900 transition-colors">
|
||||||
|
{user.name}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-slate-500">
|
||||||
|
{user.username || 'Administrator'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-9 w-9 bg-slate-100 rounded-full flex items-center justify-center text-slate-600 group-hover:bg-primary-lightest group-hover:text-primary-main transition-all">
|
||||||
|
<User className="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-56 z-[100]" sideOffset={8}>
|
||||||
|
<DropdownMenuLabel>我的帳號</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link
|
||||||
|
href={route('logout')}
|
||||||
|
method="post"
|
||||||
|
as="button"
|
||||||
|
className="w-full flex items-center cursor-pointer text-red-600 focus:text-red-600 focus:bg-red-50"
|
||||||
|
>
|
||||||
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
|
<span>登出系統</span>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Sidebar Desktop */}
|
{/* Sidebar Desktop */}
|
||||||
@@ -281,15 +325,17 @@ export default function AuthenticatedLayout({
|
|||||||
{isCollapsed ? <PanelLeftOpen className="h-5 w-5" /> : <PanelLeftClose className="h-5 w-5" />}
|
{isCollapsed ? <PanelLeftOpen className="h-5 w-5" /> : <PanelLeftClose className="h-5 w-5" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside >
|
||||||
|
|
||||||
{/* Mobile Sidebar Overlay */}
|
{/* Mobile Sidebar Overlay */}
|
||||||
{isMobileOpen && (
|
{
|
||||||
|
isMobileOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-[70] lg:hidden"
|
className="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-[70] lg:hidden"
|
||||||
onClick={() => setIsMobileOpen(false)}
|
onClick={() => setIsMobileOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{/* Mobile Sidebar Drawer */}
|
{/* Mobile Sidebar Drawer */}
|
||||||
<aside className={cn(
|
<aside className={cn(
|
||||||
@@ -329,6 +375,6 @@ export default function AuthenticatedLayout({
|
|||||||
</div>
|
</div>
|
||||||
<Toaster richColors closeButton position="top-center" />
|
<Toaster richColors closeButton position="top-center" />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
140
resources/js/Pages/Auth/Login.tsx
Normal file
140
resources/js/Pages/Auth/Login.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { Head, useForm } from "@inertiajs/react";
|
||||||
|
import { FormEventHandler, useEffect } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button } from "@/Components/ui/button";
|
||||||
|
import { Input } from "@/Components/ui/input";
|
||||||
|
import { Label } from "@/Components/ui/label";
|
||||||
|
import InputError from "../../Components/InputError";
|
||||||
|
import ApplicationLogo from "../../Components/ApplicationLogo";
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
const { data, setData, post, processing, errors, reset } = useForm({
|
||||||
|
username: localStorage.getItem("saved_username") || "",
|
||||||
|
password: "",
|
||||||
|
remember: false,
|
||||||
|
rememberUsername: localStorage.getItem("remember_username") === "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
reset("password");
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const submit: FormEventHandler = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 處理記住帳號邏輯
|
||||||
|
if (data.rememberUsername) {
|
||||||
|
localStorage.setItem("saved_username", data.username);
|
||||||
|
localStorage.setItem("remember_username", "true");
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem("saved_username");
|
||||||
|
localStorage.setItem("remember_username", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
post(route("login"), {
|
||||||
|
onFinish: () => reset("password"),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-50 relative overflow-hidden">
|
||||||
|
<Head title="登入" />
|
||||||
|
|
||||||
|
{/* 動態背景裝飾 */}
|
||||||
|
<div className="absolute top-0 -left-4 w-72 h-72 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob"></div>
|
||||||
|
<div className="absolute top-0 -right-4 w-72 h-72 bg-yellow-300 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-2000"></div>
|
||||||
|
<div className="absolute -bottom-8 left-20 w-72 h-72 bg-pink-300 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-4000"></div>
|
||||||
|
|
||||||
|
<div className="w-full max-w-md p-8 relative z-10">
|
||||||
|
<div className="flex flex-col items-center mb-6">
|
||||||
|
<ApplicationLogo className="w-40 h-40 object-contain" />
|
||||||
|
</div>
|
||||||
|
<div className="glass-panel p-8 rounded-2xl shadow-xl bg-white/80 backdrop-blur-md border border-white/50">
|
||||||
|
<form onSubmit={submit} className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="username">帳號</Label>
|
||||||
|
<Input
|
||||||
|
id="username"
|
||||||
|
type="text"
|
||||||
|
className="mt-1 block w-full bg-white/50"
|
||||||
|
value={data.username}
|
||||||
|
onChange={(e) => setData("username", e.target.value)}
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<InputError message={errors.username} className="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="password">密碼</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
className="mt-1 block w-full bg-white/50"
|
||||||
|
value={data.password}
|
||||||
|
onChange={(e) => setData("password", e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<InputError message={errors.password} className="mt-2" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="flex items-center cursor-pointer group">
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="sr-only"
|
||||||
|
checked={data.rememberUsername}
|
||||||
|
onChange={(e) => setData("rememberUsername", e.target.checked)}
|
||||||
|
/>
|
||||||
|
<div className={cn(
|
||||||
|
"w-9 h-5 rounded-full shadow-inner transition-colors duration-300 ease-in-out",
|
||||||
|
data.rememberUsername ? "bg-[#01ab83]" : "bg-gray-300"
|
||||||
|
)}></div>
|
||||||
|
<div className={cn(
|
||||||
|
"absolute left-0.5 top-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform duration-300 ease-in-out",
|
||||||
|
data.rememberUsername ? "translate-x-4" : "translate-x-0"
|
||||||
|
)}></div>
|
||||||
|
</div>
|
||||||
|
<span className="ml-2 text-sm text-gray-600 group-hover:text-gray-900 transition-colors">記住帳號</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="flex items-center cursor-pointer group">
|
||||||
|
<span className="mr-2 text-sm text-gray-600 group-hover:text-gray-900 transition-colors">保持登入</span>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="sr-only"
|
||||||
|
checked={data.remember}
|
||||||
|
onChange={(e) => setData("remember", e.target.checked)}
|
||||||
|
/>
|
||||||
|
<div className={cn(
|
||||||
|
"w-9 h-5 rounded-full shadow-inner transition-colors duration-300 ease-in-out",
|
||||||
|
data.remember ? "bg-[#01ab83]" : "bg-gray-300"
|
||||||
|
)}></div>
|
||||||
|
<div className={cn(
|
||||||
|
"absolute left-0.5 top-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform duration-300 ease-in-out",
|
||||||
|
data.remember ? "translate-x-4" : "translate-x-0"
|
||||||
|
)}></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full h-11 text-base bg-[#01ab83] hover:bg-[#018a6a] transition-all shadow-lg hover:shadow-xl"
|
||||||
|
disabled={processing}
|
||||||
|
>
|
||||||
|
{processing ? "登入中..." : "登入系統"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-center text-gray-400 text-sm mt-8">
|
||||||
|
© 2026 小小冰室. All rights reserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
125
routes/web.php
125
routes/web.php
@@ -5,66 +5,77 @@ use Inertia\Inertia;
|
|||||||
use App\Http\Controllers\CategoryController;
|
use App\Http\Controllers\CategoryController;
|
||||||
use App\Http\Controllers\VendorController;
|
use App\Http\Controllers\VendorController;
|
||||||
use App\Http\Controllers\VendorProductController;
|
use App\Http\Controllers\VendorProductController;
|
||||||
|
|
||||||
use App\Http\Controllers\DashboardController;
|
use App\Http\Controllers\DashboardController;
|
||||||
|
|
||||||
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
|
|
||||||
|
|
||||||
use App\Http\Controllers\ProductController;
|
use App\Http\Controllers\ProductController;
|
||||||
|
use App\Http\Controllers\Auth\LoginController;
|
||||||
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
|
|
||||||
Route::post('/products', [ProductController::class, 'store'])->name('products.store');
|
|
||||||
Route::put('/products/{product}', [ProductController::class, 'update'])->name('products.update');
|
|
||||||
Route::delete('/products/{product}', [ProductController::class, 'destroy'])->name('products.destroy');
|
|
||||||
|
|
||||||
Route::post('/categories', [CategoryController::class, 'store'])->name('categories.store');
|
|
||||||
Route::put('/categories/{category}', [CategoryController::class, 'update'])->name('categories.update');
|
|
||||||
Route::delete('/categories/{category}', [CategoryController::class, 'destroy'])->name('categories.destroy');
|
|
||||||
|
|
||||||
|
|
||||||
// 倉庫管理
|
|
||||||
Route::resource('warehouses', \App\Http\Controllers\WarehouseController::class);
|
|
||||||
|
|
||||||
// 庫存管理
|
|
||||||
Route::get('warehouses/{warehouse}/inventory', [\App\Http\Controllers\InventoryController::class, 'index'])->name('warehouses.inventory.index');
|
|
||||||
|
|
||||||
// 安全庫存管理
|
|
||||||
Route::prefix('warehouses/{warehouse}/safety-stock-settings')->name('warehouses.safety-stock.')->group(function () {
|
|
||||||
Route::get('/', [\App\Http\Controllers\SafetyStockController::class, 'index'])->name('index');
|
|
||||||
Route::post('/', [\App\Http\Controllers\SafetyStockController::class, 'store'])->name('store');
|
|
||||||
Route::put('/{inventory}', [\App\Http\Controllers\SafetyStockController::class, 'update'])->name('update');
|
|
||||||
Route::delete('/{inventory}', [\App\Http\Controllers\SafetyStockController::class, 'destroy'])->name('destroy');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::get('/warehouses/{warehouse}/add-inventory', [\App\Http\Controllers\InventoryController::class, 'create'])->name('warehouses.add-inventory');
|
|
||||||
Route::post('/warehouses/{warehouse}/inventory', [\App\Http\Controllers\InventoryController::class, 'store'])->name('warehouses.inventory.store');
|
|
||||||
Route::get('/warehouses/{warehouse}/inventory/{inventory}/edit', [\App\Http\Controllers\InventoryController::class, 'edit'])->name('warehouses.inventory.edit');
|
|
||||||
Route::put('/warehouses/{warehouse}/inventory/{inventory}', [\App\Http\Controllers\InventoryController::class, 'update'])->name('warehouses.inventory.update');
|
|
||||||
Route::delete('/warehouses/{warehouse}/inventory/{inventory}', [\App\Http\Controllers\InventoryController::class, 'destroy'])->name('warehouses.inventory.destroy');
|
|
||||||
Route::get('/warehouses/{warehouse}/inventory/{inventory}/history', [\App\Http\Controllers\InventoryController::class, 'history'])->name('warehouses.inventory.history');
|
|
||||||
|
|
||||||
// 撥補單 (Transfer Order)
|
|
||||||
Route::post('/transfer-orders', [\App\Http\Controllers\TransferOrderController::class, 'store'])->name('transfer-orders.store');
|
|
||||||
Route::get('/api/warehouses/{warehouse}/inventories', [\App\Http\Controllers\TransferOrderController::class, 'getWarehouseInventories'])->name('api.warehouses.inventories');
|
|
||||||
|
|
||||||
use App\Http\Controllers\PurchaseOrderController;
|
use App\Http\Controllers\PurchaseOrderController;
|
||||||
|
use App\Http\Controllers\WarehouseController;
|
||||||
|
use App\Http\Controllers\InventoryController;
|
||||||
|
use App\Http\Controllers\SafetyStockController;
|
||||||
|
use App\Http\Controllers\TransferOrderController;
|
||||||
|
|
||||||
Route::get('/purchase-orders', [PurchaseOrderController::class, 'index'])->name('purchase-orders.index');
|
Route::get('/login', [LoginController::class, 'show'])->name('login');
|
||||||
Route::get('/purchase-orders/create', [PurchaseOrderController::class, 'create'])->name('purchase-orders.create');
|
Route::post('/login', [LoginController::class, 'store']);
|
||||||
Route::post('/purchase-orders', [PurchaseOrderController::class, 'store'])->name('purchase-orders.store');
|
Route::post('/logout', [LoginController::class, 'destroy'])->name('logout');
|
||||||
Route::get('/purchase-orders/{id}', [PurchaseOrderController::class, 'show'])->name('purchase-orders.show');
|
|
||||||
Route::get('/purchase-orders/{id}/edit', [PurchaseOrderController::class, 'edit'])->name('purchase-orders.edit');
|
|
||||||
Route::put('/purchase-orders/{id}', [PurchaseOrderController::class, 'update'])->name('purchase-orders.update');
|
|
||||||
Route::delete('/purchase-orders/{id}', [PurchaseOrderController::class, 'destroy'])->name('purchase-orders.destroy');
|
|
||||||
|
|
||||||
// 廠商管理
|
Route::middleware('auth')->group(function () {
|
||||||
Route::get('/vendors', [VendorController::class, 'index'])->name('vendors.index');
|
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
|
||||||
Route::get('/vendors/{vendor}', [VendorController::class, 'show'])->name('vendors.show');
|
|
||||||
Route::post('/vendors', [VendorController::class, 'store'])->name('vendors.store');
|
|
||||||
Route::put('/vendors/{vendor}', [VendorController::class, 'update'])->name('vendors.update');
|
|
||||||
Route::delete('/vendors/{vendor}', [VendorController::class, 'destroy'])->name('vendors.destroy');
|
|
||||||
|
|
||||||
// 供貨商品相關路由
|
// 類別管理 (用於商品對話框)
|
||||||
Route::post('/vendors/{vendor}/products', [VendorProductController::class, 'store'])->name('vendors.products.store');
|
Route::get('/categories', [CategoryController::class, 'index'])->name('categories.index');
|
||||||
Route::put('/vendors/{vendor}/products/{product}', [VendorProductController::class, 'update'])->name('vendors.products.update');
|
Route::post('/categories', [CategoryController::class, 'store'])->name('categories.store');
|
||||||
Route::delete('/vendors/{vendor}/products/{product}', [VendorProductController::class, 'destroy'])->name('vendors.products.destroy');
|
Route::put('/categories/{category}', [CategoryController::class, 'update'])->name('categories.update');
|
||||||
|
Route::delete('/categories/{category}', [CategoryController::class, 'destroy'])->name('categories.destroy');
|
||||||
|
|
||||||
|
// 商品管理
|
||||||
|
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
|
||||||
|
Route::post('/products', [ProductController::class, 'store'])->name('products.store');
|
||||||
|
Route::put('/products/{product}', [ProductController::class, 'update'])->name('products.update');
|
||||||
|
|
||||||
|
// 廠商管理
|
||||||
|
Route::get('/vendors', [VendorController::class, 'index'])->name('vendors.index');
|
||||||
|
Route::get('/vendors/{vendor}', [VendorController::class, 'show'])->name('vendors.show');
|
||||||
|
Route::post('/vendors', [VendorController::class, 'store'])->name('vendors.store');
|
||||||
|
Route::put('/vendors/{vendor}', [VendorController::class, 'update'])->name('vendors.update');
|
||||||
|
Route::delete('/vendors/{vendor}', [VendorController::class, 'destroy'])->name('vendors.destroy');
|
||||||
|
|
||||||
|
// 供貨商品相關路由
|
||||||
|
Route::post('/vendors/{vendor}/products', [VendorProductController::class, 'store'])->name('vendors.products.store');
|
||||||
|
Route::put('/vendors/{vendor}/products/{product}', [VendorProductController::class, 'update'])->name('vendors.products.update');
|
||||||
|
Route::delete('/vendors/{vendor}/products/{product}', [VendorProductController::class, 'destroy'])->name('vendors.products.destroy');
|
||||||
|
|
||||||
|
// 倉庫管理
|
||||||
|
Route::get('/warehouses', [WarehouseController::class, 'index'])->name('warehouses.index');
|
||||||
|
Route::post('/warehouses', [WarehouseController::class, 'store'])->name('warehouses.store');
|
||||||
|
Route::put('/warehouses/{warehouse}', [WarehouseController::class, 'update'])->name('warehouses.update');
|
||||||
|
Route::delete('/warehouses/{warehouse}', [WarehouseController::class, 'destroy'])->name('warehouses.destroy');
|
||||||
|
|
||||||
|
// 倉庫庫存管理
|
||||||
|
Route::get('/warehouses/{warehouse}/inventory', [InventoryController::class, 'index'])->name('warehouses.inventory.index');
|
||||||
|
Route::get('/warehouses/{warehouse}/inventory/create', [InventoryController::class, 'create'])->name('warehouses.inventory.create');
|
||||||
|
Route::post('/warehouses/{warehouse}/inventory', [InventoryController::class, 'store'])->name('warehouses.inventory.store');
|
||||||
|
Route::get('/warehouses/{warehouse}/inventory/{inventoryId}/edit', [InventoryController::class, 'edit'])->name('warehouses.inventory.edit');
|
||||||
|
Route::put('/warehouses/{warehouse}/inventory/{inventoryId}', [InventoryController::class, 'update'])->name('warehouses.inventory.update');
|
||||||
|
Route::delete('/warehouses/{warehouse}/inventory/{inventoryId}', [InventoryController::class, 'destroy'])->name('warehouses.inventory.destroy');
|
||||||
|
Route::get('/warehouses/{warehouse}/inventory/{inventoryId}/history', [InventoryController::class, 'history'])->name('warehouses.inventory.history');
|
||||||
|
|
||||||
|
// 安全庫存設定
|
||||||
|
Route::get('/warehouses/{warehouse}/safety-stock', [SafetyStockController::class, 'index'])->name('warehouses.safety-stock.index');
|
||||||
|
Route::post('/warehouses/{warehouse}/safety-stock', [SafetyStockController::class, 'store'])->name('warehouses.safety-stock.store');
|
||||||
|
Route::put('/warehouses/{warehouse}/safety-stock/{inventory}', [SafetyStockController::class, 'update'])->name('warehouses.safety-stock.update');
|
||||||
|
Route::delete('/warehouses/{warehouse}/safety-stock/{inventory}', [SafetyStockController::class, 'destroy'])->name('warehouses.safety-stock.destroy');
|
||||||
|
|
||||||
|
// 採購單管理
|
||||||
|
Route::get('/purchase-orders', [PurchaseOrderController::class, 'index'])->name('purchase-orders.index');
|
||||||
|
Route::get('/purchase-orders/create', [PurchaseOrderController::class, 'create'])->name('purchase-orders.create');
|
||||||
|
Route::post('/purchase-orders', [PurchaseOrderController::class, 'store'])->name('purchase-orders.store');
|
||||||
|
Route::get('/purchase-orders/{id}', [PurchaseOrderController::class, 'show'])->name('purchase-orders.show');
|
||||||
|
Route::get('/purchase-orders/{id}/edit', [PurchaseOrderController::class, 'edit'])->name('purchase-orders.edit');
|
||||||
|
Route::put('/purchase-orders/{id}', [PurchaseOrderController::class, 'update'])->name('purchase-orders.update');
|
||||||
|
Route::delete('/purchase-orders/{id}', [PurchaseOrderController::class, 'destroy'])->name('purchase-orders.destroy');
|
||||||
|
|
||||||
|
// 撥補單 (在庫存調撥時使用)
|
||||||
|
Route::post('/transfer-orders', [TransferOrderController::class, 'store'])->name('transfer-orders.store');
|
||||||
|
Route::get('/api/warehouses/{warehouse}/inventories', [TransferOrderController::class, 'getWarehouseInventories'])->name('api.warehouses.inventories');
|
||||||
|
|
||||||
|
}); // End of auth middleware group
|
||||||
|
|||||||
Reference in New Issue
Block a user