fix(inventory): 修復倉庫低庫存警告計算與全站租戶名稱動態化
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 44s

This commit is contained in:
2026-02-02 11:03:09 +08:00
parent 1748eb007e
commit 75c634ffe4
8 changed files with 24 additions and 14 deletions

View File

@@ -37,6 +37,12 @@ class WarehouseController extends Controller
->orWhere('expiry_date', '>=', now()); ->orWhere('expiry_date', '>=', now());
}); });
}], 'quantity') }], 'quantity')
->addSelect(['low_stock_count' => function ($query) {
$query->selectRaw('count(*)')
->from('warehouse_product_safety_stocks as ss')
->whereColumn('ss.warehouse_id', 'warehouses.id')
->whereRaw('(SELECT COALESCE(SUM(quantity), 0) FROM inventories WHERE warehouse_id = ss.warehouse_id AND product_id = ss.product_id) < ss.safety_stock');
}])
->orderBy('created_at', 'desc') ->orderBy('created_at', 'desc')
->paginate(10) ->paginate(10)
->withQueryString(); ->withQueryString();

View File

@@ -21,7 +21,7 @@ export default function ApplicationLogo(props: ImgHTMLAttributes<HTMLImageElemen
<img <img
{...props} {...props}
src="/logo.png" src="/logo.png"
alt="小小冰室 Logo" alt={`${branding?.short_name || 'Star'} Logo`}
/> />
); );
} }

View File

@@ -454,7 +454,7 @@ export default function AuthenticatedLayout({
</button> </button>
<Link href="/" className="flex items-center gap-2"> <Link href="/" className="flex items-center gap-2">
<ApplicationLogo className="w-8 h-8 rounded-lg object-contain" /> <ApplicationLogo className="w-8 h-8 rounded-lg object-contain" />
<span className="font-bold text-slate-900">{branding?.short_name || '小小冰室'} ERP</span> <span className="font-bold text-slate-900">{branding?.short_name || 'Star'} ERP</span>
</Link> </Link>
</div> </div>
@@ -510,7 +510,7 @@ export default function AuthenticatedLayout({
{!isCollapsed && ( {!isCollapsed && (
<Link href="/" className="flex items-center gap-2 group"> <Link href="/" className="flex items-center gap-2 group">
<ApplicationLogo className="w-8 h-8 rounded-lg object-contain group-hover:scale-110 transition-transform" /> <ApplicationLogo className="w-8 h-8 rounded-lg object-contain group-hover:scale-110 transition-transform" />
<span className="font-extrabold text-primary-main text-lg tracking-tight">{branding?.short_name || '小小冰室'} ERP</span> <span className="font-extrabold text-primary-main text-lg tracking-tight">{branding?.short_name || 'Star'} ERP</span>
</Link> </Link>
)} )}
{isCollapsed && ( {isCollapsed && (
@@ -559,7 +559,7 @@ export default function AuthenticatedLayout({
<div className="h-16 flex items-center justify-between px-6 border-b border-slate-100"> <div className="h-16 flex items-center justify-between px-6 border-b border-slate-100">
<Link href="/" className="flex items-center gap-2"> <Link href="/" className="flex items-center gap-2">
<ApplicationLogo className="w-8 h-8 rounded-lg object-contain" /> <ApplicationLogo className="w-8 h-8 rounded-lg object-contain" />
<span className="font-extrabold text-primary-main text-lg">{branding?.short_name || '小小冰室'} ERP</span> <span className="font-extrabold text-primary-main text-lg">{branding?.short_name || 'Star'} ERP</span>
</Link> </Link>
<button onClick={() => setIsMobileOpen(false)} className="p-2 text-slate-400"> <button onClick={() => setIsMobileOpen(false)} className="p-2 text-slate-400">
<X className="h-5 w-5" /> <X className="h-5 w-5" />
@@ -588,7 +588,7 @@ export default function AuthenticatedLayout({
{children} {children}
</div> </div>
<footer className="mt-auto py-6 text-center text-sm text-slate-400"> <footer className="mt-auto py-6 text-center text-sm text-slate-400">
Copyright &copy; {new Date().getFullYear()} {branding?.name || '小小冰室'}. All rights reserved. Design by Copyright &copy; {new Date().getFullYear()} {branding?.name || branding?.short_name || 'Star ERP'}. All rights reserved. Design by
</footer> </footer>
<Toaster richColors closeButton position="top-center" /> <Toaster richColors closeButton position="top-center" />
</main> </main>

View File

@@ -42,7 +42,7 @@ export default function Login() {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 relative overflow-hidden"> <div className="min-h-screen flex items-center justify-center bg-gray-50 relative overflow-hidden">
<Head title="登入"> <Head title={`登入 - ${props.branding?.short_name || 'Star ERP'}`}>
<link rel="icon" type="image/png" href="/favicon.png" /> <link rel="icon" type="image/png" href="/favicon.png" />
</Head> </Head>
@@ -136,7 +136,7 @@ export default function Login() {
</div> </div>
<p className="text-center text-gray-400 text-sm mt-8"> <p className="text-center text-gray-400 text-sm mt-8">
&copy; {new Date().getFullYear()} {props.branding?.name || '小小冰室'}. All rights reserved. &copy; {new Date().getFullYear()} {props.branding?.name || props.branding?.short_name || 'Star ERP'}. All rights reserved.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Link, Head } from '@inertiajs/react'; import { Link, Head, usePage } from '@inertiajs/react';
import { PageProps } from '@/types/global';
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
Package, Package,
@@ -27,6 +28,7 @@ interface Props {
} }
export default function Dashboard({ stats }: Props) { export default function Dashboard({ stats }: Props) {
const { branding } = usePage<PageProps>().props;
const cardData = [ const cardData = [
{ {
label: '商品總數', label: '商品總數',
@@ -75,7 +77,7 @@ export default function Dashboard({ stats }: Props) {
return ( return (
<AuthenticatedLayout> <AuthenticatedLayout>
<Head title="控制台 - 小小冰室 ERP" /> <Head title={`控制台 - ${branding?.short_name || 'Star'} ERP`} />
<div className="p-8 max-w-7xl mx-auto"> <div className="p-8 max-w-7xl mx-auto">
<div className="mb-8"> <div className="mb-8">
@@ -83,7 +85,7 @@ export default function Dashboard({ stats }: Props) {
<TrendingUp className="h-6 w-6 text-primary-main" /> <TrendingUp className="h-6 w-6 text-primary-main" />
</h1> </h1>
<p className="text-gray-500 mt-1"> ERP </p> <p className="text-gray-500 mt-1"> {branding?.short_name || 'Star'} ERP </p>
</div> </div>
{/* 主要數據卡片 */} {/* 主要數據卡片 */}

View File

@@ -8,7 +8,8 @@ import ProductDialog from "@/Components/Product/ProductDialog";
import CategoryManagerDialog from "@/Components/Category/CategoryManagerDialog"; import CategoryManagerDialog from "@/Components/Category/CategoryManagerDialog";
import UnitManagerDialog, { Unit } from "@/Components/Unit/UnitManagerDialog"; import UnitManagerDialog, { Unit } from "@/Components/Unit/UnitManagerDialog";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, router } from "@inertiajs/react"; import { Head, router, usePage } from "@inertiajs/react";
import { PageProps as GlobalPageProps } from "@/types/global";
import { debounce } from "lodash"; import { debounce } from "lodash";
import Pagination from "@/Components/shared/Pagination"; import Pagination from "@/Components/shared/Pagination";
import { getBreadcrumbs } from "@/utils/breadcrumb"; import { getBreadcrumbs } from "@/utils/breadcrumb";
@@ -57,6 +58,7 @@ interface PageProps {
} }
export default function ProductManagement({ products, categories, units, filters }: PageProps) { export default function ProductManagement({ products, categories, units, filters }: PageProps) {
const { branding } = usePage<GlobalPageProps>().props;
const [searchTerm, setSearchTerm] = useState(filters.search || ""); const [searchTerm, setSearchTerm] = useState(filters.search || "");
const [typeFilter, setTypeFilter] = useState<string>(filters.category_id || "all"); const [typeFilter, setTypeFilter] = useState<string>(filters.category_id || "all");
const [perPage, setPerPage] = useState<string>(filters.per_page || "10"); const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
@@ -182,7 +184,7 @@ export default function ProductManagement({ products, categories, units, filters
<Package className="h-6 w-6 text-primary-main" /> <Package className="h-6 w-6 text-primary-main" />
</h1> </h1>
<p className="text-gray-500 mt-1"></p> <p className="text-gray-500 mt-1"> {branding?.short_name || 'Star'} </p>
</div> </div>
{/* Toolbar */} {/* Toolbar */}

View File

@@ -225,7 +225,7 @@ export default function WarehouseIndex({ warehouses, totals, filters }: PageProp
key={warehouse.id} key={warehouse.id}
warehouse={warehouse} warehouse={warehouse}
stats={{ stats={{
totalQuantity: warehouse.total_quantity || 0, totalQuantity: warehouse.book_stock || 0,
lowStockCount: warehouse.low_stock_count || 0, lowStockCount: warehouse.low_stock_count || 0,
replenishmentNeeded: warehouse.low_stock_count || 0 replenishmentNeeded: warehouse.low_stock_count || 0
}} }}

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title inertia>{{ config('app.name', 'Laravel') }}</title> <title inertia>{{ $appName ?? config('app.name', 'Star ERP') }}</title>
<!-- Fonts --> <!-- Fonts -->
<link rel="icon" type="image/png" href="{{ $branding['logo_url'] ?? '/favicon.png' }}"> <link rel="icon" type="image/png" href="{{ $branding['logo_url'] ?? '/favicon.png' }}">