feat: 完成進貨單自動拋轉應付帳款流程與AP介面優化
All checks were successful
ERP-Deploy-Demo / deploy-demo (push) Successful in 1m8s

1. 新增 AccountPayable (應付帳款) 模組,包含 Migration、Model、Service 與 Controller
2. 修改 GoodsReceipt (進貨單) 流程,在確認進貨時自動產生對應的應付帳款單 (AP-YYYYMMDD-XX)
3. 實作應付帳款詳細頁面 (Show.tsx),包含發票登記與標記付款功能
4. 修正應付帳款 Show 頁面的排版,將發票資訊套用標準的綠色背景區塊,並調整按鈕位置
5. 更新相關的 Service Provider 與 Routes
This commit is contained in:
2026-02-24 16:46:55 +08:00
parent aaa93a921e
commit 455f945296
33 changed files with 1708 additions and 186 deletions

View File

@@ -1,5 +1,4 @@
import { useState, useCallback, useEffect } from "react";
import { useState, useCallback } from "react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { Head, Link, router } from "@inertiajs/react";
import { debounce } from "lodash";
@@ -56,12 +55,9 @@ export default function Index({ warehouses, orders, filters }: any) {
const [warehouseFilter, setWarehouseFilter] = useState(filters.warehouse_id || "all");
const [perPage, setPerPage] = useState(filters.per_page || "10");
// Sync state with props
useEffect(() => {
setSearchTerm(filters.search || "");
setWarehouseFilter(filters.warehouse_id || "all");
setPerPage(filters.per_page || "10");
}, [filters]);
// Sync state with props only on initial load or when necessary
// Removed overly aggressive useEffect that overwrites local state on every filters change
// This was causing the search input to reset when props returned before the next debounce cycle
// Create Dialog State
const [isCreateOpen, setIsCreateOpen] = useState(false);
@@ -70,47 +66,52 @@ export default function Index({ warehouses, orders, filters }: any) {
const [creating, setCreating] = useState(false);
const [deleteId, setDeleteId] = useState<string | null>(null);
// Debounced Search Handler
const debouncedSearch = useCallback(
debounce((term: string, warehouse: string) => {
router.get(
route('inventory.transfer.index'),
{ ...filters, search: term, warehouse_id: warehouse === "all" ? "" : warehouse },
{ preserveState: true, replace: true, preserveScroll: true }
);
const debouncedFilter = useCallback(
debounce((params: any) => {
router.get(route('inventory.transfer.index'), params, {
preserveState: true,
replace: true,
});
}, 500),
[filters]
[]
);
const handleSearchChange = (term: string) => {
setSearchTerm(term);
debouncedSearch(term, warehouseFilter);
debouncedFilter({
...filters,
search: term,
warehouse_id: warehouseFilter === "all" ? "" : warehouseFilter,
page: 1
});
};
const handleFilterChange = (value: string) => {
setWarehouseFilter(value);
router.get(
route('inventory.transfer.index'),
{ ...filters, warehouse_id: value === "all" ? "" : value },
{ preserveState: false, replace: true, preserveScroll: true }
);
debouncedFilter({
...filters,
search: searchTerm,
warehouse_id: value === "all" ? "" : value,
page: 1
});
};
const handleClearSearch = () => {
setSearchTerm("");
router.get(
route('inventory.transfer.index'),
{ ...filters, search: "", warehouse_id: warehouseFilter === "all" ? "" : warehouseFilter },
{ preserveState: true, replace: true, preserveScroll: true }
);
debouncedFilter({
...filters,
search: "",
warehouse_id: warehouseFilter === "all" ? "" : warehouseFilter,
page: 1
});
};
const handlePerPageChange = (value: string) => {
setPerPage(value);
router.get(
route('inventory.transfer.index'),
{ ...filters, per_page: value },
{ preserveState: false, replace: true, preserveScroll: true }
{ ...filters, search: searchTerm, warehouse_id: warehouseFilter === "all" ? "" : warehouseFilter, per_page: value, page: 1 },
{ preserveState: true, replace: true, preserveScroll: true }
);
};