diff --git a/LOCAL_DEPLOY.md b/LOCAL_DEPLOY.md index ebb3b632..725ec1e8 100644 --- a/LOCAL_DEPLOY.md +++ b/LOCAL_DEPLOY.md @@ -145,8 +145,7 @@ docker compose -f docker-compose.local.yml start ## 修改后的生效方式 -- PHP、配置、模块、页面模板和路由目录已经挂载到容器;开发环境关闭 PHP OPcache 和 Blade 编译缓存,刷新浏览器即可加载最新源码,无需重启 PHP 服务; -- 开发环境启动时只执行 `optimize:clear`,不会生成 Laravel 的配置、路由或视图缓存;生产环境仍保留原有缓存构建机制; +- PHP、配置、模块、页面模板和路由目录已经挂载到容器,本地 PHP OPcache 会在每次请求检查源码变更,刷新浏览器即可生效; - 本地 Compose 会同时启动 `frontend` watcher。修改 `resources/assets` 下的 Vue、JavaScript 或 SCSS 后,Laravel Mix 会通过轮询自动重新编译,Apache 使用共享静态资源卷提供最新文件;浏览器刷新即可看到结果。 - 如果 watcher 构建失败,查看 `docker compose -f docker-compose.local.yml logs -f frontend`;修复源码后它会继续监听并重新编译。 - Composer 依赖、前端依赖、Dockerfile 或构建资源发生变化时,重新执行 `bash deploy/local-init.sh`,脚本会自动重建; diff --git a/app/Business/InventoryWarehouseNameMappingWorkbookImporter.php b/app/Business/InventoryWarehouseNameMappingWorkbookImporter.php deleted file mode 100644 index 48f00077..00000000 --- a/app/Business/InventoryWarehouseNameMappingWorkbookImporter.php +++ /dev/null @@ -1,213 +0,0 @@ -,sheet:string,headers:list,summary:array} */ - public function preview(UploadedFile $file): array - { - $spreadsheet = IOFactory::load($file->getRealPath()); - $worksheet = $this->findWorksheet($spreadsheet); - - if ($worksheet === null) { - throw ValidationException::withMessages([ - 'import' => '未找到包含“仓储方、仓库、仓库简称”三列的工作表。Sheet2 仅是辅助仓库清单,不能直接导入。', - ]); - } - - $rows = $worksheet->toArray(null, true, false, false); - $parsed = []; - $seen = []; - $created = 0; - $same = 0; - $conflicts = 0; - $invalid = 0; - - foreach (array_slice($rows, 1) as $offset => $row) { - $rowNumber = $offset + 2; - $provider = trim((string) ($row[0] ?? '')); - $warehouse = trim((string) ($row[1] ?? '')); - $shortName = trim((string) ($row[2] ?? '')); - if ($provider === '' && $warehouse === '' && $shortName === '') { - continue; - } - - $key = $this->key($provider, $warehouse); - $item = [ - 'row_number' => $rowNumber, - 'warehouse_provider' => $provider, - 'warehouse' => $warehouse, - 'warehouse_short_name' => $shortName, - 'status' => 'new', - 'message' => '待新增', - ]; - - if ($provider === '' || $warehouse === '' || $shortName === '') { - $item['status'] = 'invalid'; - $item['message'] = '仓储方、仓库、仓库简称均为必填字段'; - $invalid++; - } elseif (isset($seen[$key])) { - $item['status'] = $seen[$key]['warehouse_short_name'] === $shortName ? 'same' : 'conflict'; - $item['message'] = $item['status'] === 'same' - ? '与文件内前一行重复,将跳过' - : '与文件内前一行的仓库简称冲突'; - $item['status'] === 'same' ? $same++ : $conflicts++; - } else { - $existing = InventoryWarehouseNameMapping::query() - ->where('warehouse_provider', $provider) - ->where('warehouse', $warehouse) - ->first(); - - if ($existing) { - $item['status'] = $existing->warehouse_short_name === $shortName ? 'same' : 'conflict'; - $item['message'] = $item['status'] === 'same' - ? '数据库已有相同映射,将跳过' - : '数据库已有不同简称,禁止静默覆盖'; - $item['status'] === 'same' ? $same++ : $conflicts++; - } else { - $created++; - } - } - - $parsed[] = $item; - if ($provider !== '' && $warehouse !== '' && $shortName !== '' && ! isset($seen[$key])) { - $seen[$key] = $item; - } - } - - return [ - 'rows' => $parsed, - 'sheet' => $worksheet->getTitle(), - 'headers' => self::HEADERS, - 'summary' => compact('created', 'same', 'conflicts', 'invalid'), - ]; - } - - /** @param array{rows:list} $preview */ - public function commit(array $preview): array - { - $rows = $preview['rows'] ?? []; - $invalid = collect($rows)->first(fn (array $row): bool => in_array($row['status'] ?? '', ['invalid', 'conflict'], true)); - if ($invalid !== null) { - throw ValidationException::withMessages([ - 'import' => '导入预览存在必填字段错误或简称冲突,请先处理标记为失败的行。', - ]); - } - - $created = 0; - $skipped = 0; - DB::transaction(function () use ($rows, &$created, &$skipped): void { - foreach ($rows as $row) { - if (($row['status'] ?? '') === 'same') { - $skipped++; - continue; - } - - $query = InventoryWarehouseNameMapping::query() - ->where('warehouse_provider', $row['warehouse_provider']) - ->where('warehouse', $row['warehouse']); - if ($query->exists()) { - throw ValidationException::withMessages([ - 'import' => "第 {$row['row_number']} 行在确认时已被其他操作占用,请重新预览。", - ]); - } - - InventoryWarehouseNameMapping::create([ - 'company_id' => company_id() ?: 1, - 'warehouse_provider' => $row['warehouse_provider'], - 'warehouse' => $row['warehouse'], - 'warehouse_short_name' => $row['warehouse_short_name'], - 'created_by' => auth()->id(), - 'updated_by' => auth()->id(), - ]); - $created++; - } - }); - - return compact('created', 'skipped'); - } - - public static function suggestion(string $provider, string $warehouse): string - { - $provider = trim($provider); - $warehouse = trim($warehouse); - $haystack = $provider . ' ' . $warehouse; - - if (str_starts_with($provider, '奇门-')) { - return '不看'; - } - - foreach ([ - '配件单仓' => '配件单仓-不看', - 'POP仓' => '京东入仓-不看', - '自动发货仓' => '虚拟仓-不看', - '京东云仓' => '京东云仓-不看', - '京东京造' => '京东京造-不看', - '苏州福利品仓' => '苏州福利品仓-不看', - '信誉订单仓' => '信誉订单仓-不看', - '代发仓' => '代发仓', - '销退仓' => '销退仓-不看', - '客退' => '客退仓-不看', - '次品仓' => '退货报废-不看', - '数量校验仓' => '数量校验仓-不看', - ] as $needle => $result) { - if (str_contains($haystack, $needle)) { - return $result; - } - } - - foreach ([ - '运营C崔桥工厂仓' => '崔桥工厂仓', - '运营C横林二仓' => '横林二仓', - '运营C睢宁' => '睢宁仓', - '运营C新苏北' => '新苏北仓', - '江西仓送货' => '南昌樵舍仓', - ] as $needle => $result) { - if (str_contains($haystack, $needle)) { - return $result; - } - } - - $short = preg_replace('/(主仓|进货仓)$/u', '', $warehouse) ?: $warehouse; - $short = preg_replace('/^(简尚-|京仓-|三方-|奇门-)/u', '', $short) ?: $short; - $providerBase = preg_replace('/^(简尚-|京仓-|三方-|奇门-)/u', '', $provider) ?: $provider; - if ($short === $providerBase || str_starts_with($short, $providerBase)) { - $short = $providerBase; - } - - return $short !== '' ? $short : $warehouse; - } - - private function findWorksheet($spreadsheet): ?object - { - foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { - $row = $worksheet->toArray(null, true, false, false)[0] ?? []; - $headers = array_map(fn ($value): string => $this->normalize((string) $value), $row); - $expected = array_map(fn ($value): string => $this->normalize($value), self::HEADERS); - if (count(array_intersect($expected, $headers)) === count($expected)) { - return $worksheet; - } - } - - return null; - } - - private function normalize(string $value): string - { - return mb_strtolower(preg_replace('/[\s\x{FEFF}_\-()()]+/u', '', trim($value))); - } - - private function key(string $provider, string $warehouse): string - { - return $provider . "\0" . $warehouse; - } -} diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 8e6ed37a..7aa71b1d 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Business; use App\Business\BusinessTableImporter; -use App\Business\InventoryWarehouseNameMappingWorkbookImporter; use App\Business\CourierPayableSummaryFilter; use App\Business\CourierPayableSummaryStore; use App\Business\FundForecastTemplate; @@ -19,7 +18,6 @@ use App\Models\Business\CourierQuoteSignature; use App\Models\Business\CourierQuoteVersion; use App\Models\Business\CourierSigningSubject; use App\Models\Business\InstallationDetail; -use App\Models\Business\InventoryWarehouseNameMapping; use App\Business\CourierSurchargeAuditLogRepository; use App\Business\CourierSurchargeQuoteRepository; use App\Business\CourierSurchargeTypeRepository; @@ -34,11 +32,6 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Str; use Illuminate\Database\QueryException; use Modules\EcommerceFinance\Exceptions\UpstreamSnapshotPendingException; -use PhpOffice\PhpSpreadsheet\Cell\Coordinate; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Style\Alignment; -use PhpOffice\PhpSpreadsheet\Style\Fill; -use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use Modules\EcommerceFinance\Repositories\AlibabaAccountBalanceRepository; use Modules\EcommerceFinance\Repositories\AlibabaBillRepository; use Modules\EcommerceFinance\View\BillOffsetPageData; @@ -387,20 +380,11 @@ class Pages extends Controller $tabValue = null; $tabColumn = null; $upstreamError = null; - $inventoryWarehouseMappings = collect(); $agreementCourierNames = $this->agreementCourierNames($page['slug'] ?? null); $agreementCourierAccounts = $this->agreementCourierAccounts($page['slug'] ?? null, $agreementCourierNames); if (($section['slug'] ?? null) === 'product-inventory' && ($page['slug'] ?? null) === 'inventory-report') { - if (request()->boolean('mapping')) { - $rows = new LengthAwarePaginator([], 0, 1, 1, [ - 'path' => request()->url(), - 'query' => request()->query(), - ]); - } else { - [$rows, $upstreamError] = $this->inventoryReportRows(); - } - $inventoryWarehouseMappings = $this->inventoryWarehouseMappingRows(); + [$rows, $upstreamError] = $this->inventoryReportRows(); } if ($page && $content === null) { @@ -448,7 +432,6 @@ class Pages extends Controller 'agreementCourierNames' => $agreementCourierNames, 'agreementCourierAccounts' => $agreementCourierAccounts, 'agreementQuoteVersions' => $this->agreementQuoteVersions($page['slug']), - 'inventoryWarehouseMappings' => $inventoryWarehouseMappings, ]; } @@ -507,7 +490,6 @@ class Pages extends Controller 'agreementCourierNames' => $agreementCourierNames, 'agreementCourierAccounts' => $agreementCourierAccounts, 'agreementQuoteVersions' => $this->agreementQuoteVersions($page['slug'] ?? null), - 'inventoryWarehouseMappings' => $inventoryWarehouseMappings, ]; } @@ -553,163 +535,6 @@ class Pages extends Controller ]), $error]; } - private function inventoryWarehouseMappingRows(): LengthAwarePaginator - { - $query = InventoryWarehouseNameMapping::query()->orderBy('warehouse_provider')->orderBy('warehouse'); - $provider = mb_substr(trim((string) request('mapping_warehouse_provider', '')), 0, 191); - $warehouse = mb_substr(trim((string) request('mapping_warehouse', '')), 0, 191); - - if ($provider !== '') { - $query->where('warehouse_provider', 'like', '%' . addcslashes($provider, '%_\\') . '%'); - } - if ($warehouse !== '') { - $query->where('warehouse', 'like', '%' . addcslashes($warehouse, '%_\\') . '%'); - } - - return $query->paginate(50)->withQueryString(); - } - - public function storeInventoryWarehouseMapping(Request $request) - { - $data = $request->validate([ - 'warehouse_provider' => ['required', 'string', 'max:191'], - 'warehouse' => ['required', 'string', 'max:191'], - 'warehouse_short_name' => ['nullable', 'string', 'max:191'], - ]); - $data['warehouse_provider'] = trim($data['warehouse_provider']); - $data['warehouse'] = trim($data['warehouse']); - $data['warehouse_short_name'] = trim((string) ($data['warehouse_short_name'] ?? '')) - ?: InventoryWarehouseNameMappingWorkbookImporter::suggestion($data['warehouse_provider'], $data['warehouse']); - - if (InventoryWarehouseNameMapping::query() - ->where('warehouse_provider', $data['warehouse_provider']) - ->where('warehouse', $data['warehouse']) - ->exists()) { - return back()->withInput()->withErrors(['warehouse' => '相同“仓储方 + 仓库”已存在,请使用修改功能。']); - } - - InventoryWarehouseNameMapping::create($data + [ - 'company_id' => company_id() ?: 1, - 'created_by' => auth()->id(), - 'updated_by' => auth()->id(), - ]); - - return $this->inventoryWarehouseMappingRedirect()->with('success', '仓库名称映射已新增。'); - } - - public function updateInventoryWarehouseMapping(Request $request, int $id) - { - $mapping = InventoryWarehouseNameMapping::query()->findOrFail($id); - $data = $request->validate([ - 'warehouse_provider' => ['required', 'string', 'max:191'], - 'warehouse' => ['required', 'string', 'max:191'], - 'warehouse_short_name' => ['required', 'string', 'max:191'], - ]); - $data = array_map(static fn (string $value): string => trim($value), $data); - - if (InventoryWarehouseNameMapping::query() - ->where('id', '!=', $mapping->id) - ->where('warehouse_provider', $data['warehouse_provider']) - ->where('warehouse', $data['warehouse']) - ->exists()) { - return back()->withInput()->withErrors(['warehouse' => '相同“仓储方 + 仓库”已存在,不能产生重复台账。']); - } - - $mapping->update($data + ['updated_by' => auth()->id()]); - - return $this->inventoryWarehouseMappingRedirect()->with('success', '仓库名称映射已修改。'); - } - - public function previewInventoryWarehouseMappingImport( - Request $request, - InventoryWarehouseNameMappingWorkbookImporter $importer, - ) { - $request->validate([ - 'import' => ['required', 'file', 'mimes:xlsx,xls,csv', 'max:10240'], - ]); - $preview = $importer->preview($request->file('import')); - $token = Str::random(40); - session()->put('inventory_warehouse_mapping_previews.' . $token, $preview); - - return $this->inventoryWarehouseMappingRedirect() - ->with('warehouse_mapping_preview', $preview + ['token' => $token]); - } - - public function commitInventoryWarehouseMappingImport( - Request $request, - InventoryWarehouseNameMappingWorkbookImporter $importer, - ) { - $token = (string) $request->input('preview_token'); - $preview = session()->pull('inventory_warehouse_mapping_previews.' . $token); - abort_if($token === '' || ! is_array($preview), 422, '导入预览已过期,请重新上传文件。'); - $result = $importer->commit($preview); - - return $this->inventoryWarehouseMappingRedirect()->with( - 'success', - "导入完成:新增 {$result['created']} 条,跳过 {$result['skipped']} 条重复映射。", - ); - } - - public function exportInventoryWarehouseMappings(Request $request): StreamedResponse - { - $query = InventoryWarehouseNameMapping::query()->orderBy('warehouse_provider')->orderBy('warehouse'); - $provider = mb_substr(trim((string) $request->query('mapping_warehouse_provider', '')), 0, 191); - $warehouse = mb_substr(trim((string) $request->query('mapping_warehouse', '')), 0, 191); - if ($provider !== '') { - $query->where('warehouse_provider', 'like', '%' . addcslashes($provider, '%_\\') . '%'); - } - if ($warehouse !== '') { - $query->where('warehouse', 'like', '%' . addcslashes($warehouse, '%_\\') . '%'); - } - - return response()->streamDownload(function () use ($query): void { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); - $sheet->setTitle('仓库名称映射表'); - $headers = ['仓储方', '仓库', '仓库简称']; - $sheet->fromArray($headers, null, 'A1'); - $lastColumn = Coordinate::stringFromColumnIndex(count($headers)); - $headerRange = "A1:{$lastColumn}1"; - $sheet->getStyle($headerRange)->applyFromArray([ - 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']], - 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '856632']], - 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], - ]); - $sheet->getRowDimension(1)->setRowHeight(24); - $sheet->freezePane('A2'); - $sheet->setAutoFilter($headerRange); - foreach ([20, 32, 20] as $index => $width) { - $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($index + 1))->setWidth($width); - } - $rowNumber = 2; - foreach ($query->cursor() as $mapping) { - $sheet->fromArray([ - $mapping->warehouse_provider, - $mapping->warehouse, - $mapping->warehouse_short_name, - ], null, "A{$rowNumber}"); - $rowNumber++; - } - (new Xlsx($spreadsheet))->save('php://output'); - $spreadsheet->disconnectWorksheets(); - }, '仓库名称映射表_' . now()->format('Ymd_His') . '.xlsx', [ - 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'Cache-Control' => 'private, no-store, no-cache, must-revalidate', - ]); - } - - private function inventoryWarehouseMappingRedirect() - { - return redirect()->route('business.pages.show', [ - 'company_id' => company_id() ?: 1, - 'section' => 'product-inventory', - 'page' => 'inventory-report', - 'mapping' => 1, - 'mapping_warehouse_provider' => request('mapping_warehouse_provider', ''), - 'mapping_warehouse' => request('mapping_warehouse', ''), - ]); - } - /** @return array */ private function inventoryReportFilters(Request $request): array { diff --git a/app/Models/Business/InventoryWarehouseNameMapping.php b/app/Models/Business/InventoryWarehouseNameMapping.php deleted file mode 100644 index fda10996..00000000 --- a/app/Models/Business/InventoryWarehouseNameMapping.php +++ /dev/null @@ -1,34 +0,0 @@ - 'integer', - 'created_by' => 'integer', - 'updated_by' => 'integer', - ]; - - public static function columnLabels(): array - { - return [ - 'warehouse_provider' => '仓储方', - 'warehouse' => '仓库', - 'warehouse_short_name' => '仓库简称', - ]; - } -} diff --git a/config/business_navigation.php b/config/business_navigation.php index 355fca4d..2a20ea75 100644 --- a/config/business_navigation.php +++ b/config/business_navigation.php @@ -335,24 +335,6 @@ $navigation = [ 'pages' => [ ['slug' => 'normal-products', 'title' => '普通商品资料'], ['slug' => 'online-daifa-price', 'title' => '线上代发价格'], - [ - 'slug' => 'monthly-weighted-average-price', - 'title' => '商品月加权平均单价', - 'directory' => [ - 'icon' => 'calculate', - 'status' => '取数规则待确认', - 'description' => '商品月加权平均单价页面入口已建立;接入数据前需确认采购入库范围、金额字段和退货处理口径。', - 'empty' => '商品月加权平均单价尚未同步。确认取数规则后再接入经营分析副本库数据。', - 'fields' => [ - ['name' => 'month', 'label' => '月份', 'kind' => 'date'], - ['name' => 'product_code', 'label' => '商品编码'], - ['name' => 'product_name', 'label' => '商品名称'], - ['name' => 'purchase_quantity', 'label' => '采购数量', 'kind' => 'number'], - ['name' => 'purchase_amount', 'label' => '采购金额', 'kind' => 'number'], - ['name' => 'weighted_average_unit_price', 'label' => '月加权平均单价', 'kind' => 'number'], - ], - ], - ], [ 'slug' => 'inventory-report', 'title' => '进销存报表', diff --git a/config/view.php b/config/view.php index 76dca230..22b8a18d 100644 --- a/config/view.php +++ b/config/view.php @@ -33,20 +33,4 @@ return [ realpath(storage_path('framework/views')) ), - /* - |-------------------------------------------------------------------------- - | Compiled View Cache - |-------------------------------------------------------------------------- - | - | Blade still needs a writable path to generate PHP for each request, but - | local development must not trust an existing compiled template. Setting - | the compiler cache flag to false makes Laravel recompile every view and - | leaves production's normal compiled-view behavior unchanged. - |-------------------------------------------------------------------------- - */ - - 'cache' => env('APP_ENV', 'production') === 'local' - ? false - : env('VIEW_CACHE', true), - ]; diff --git a/database/migrations/2026_09_27_000000_create_inventory_warehouse_name_mappings.php b/database/migrations/2026_09_27_000000_create_inventory_warehouse_name_mappings.php deleted file mode 100644 index a5ba9a9d..00000000 --- a/database/migrations/2026_09_27_000000_create_inventory_warehouse_name_mappings.php +++ /dev/null @@ -1,41 +0,0 @@ -increments('id'); - $table->unsignedInteger('company_id')->default(1); - $table->string('warehouse_provider', 191)->comment('仓储方'); - $table->string('warehouse', 191)->comment('仓库'); - $table->string('warehouse_short_name', 191)->comment('仓库简称'); - $table->unsignedInteger('created_by')->nullable(); - $table->unsignedInteger('updated_by')->nullable(); - $table->timestamps(); - $table->softDeletes(); - - $table->unique( - ['company_id', 'warehouse_provider', 'warehouse'], - 'inventory_warehouse_mapping_identity', - ); - $table->index( - ['company_id', 'warehouse_short_name'], - 'inventory_warehouse_mapping_short_name_index', - ); - }); - } - - public function down(): void - { - Schema::dropIfExists('business_inventory_warehouse_name_mappings'); - } -}; diff --git a/deploy/entrypoint.sh b/deploy/entrypoint.sh index 422e0dd2..c4b2ace2 100644 --- a/deploy/entrypoint.sh +++ b/deploy/entrypoint.sh @@ -37,11 +37,6 @@ run_artisan_as_web() { runuser -u www-data -- php artisan "$@" } -app_environment="" -if [ -f .env ]; then - app_environment="$(sed -n 's/^APP_ENV=//p' .env | tail -n 1 | tr -d '\r' | sed 's/^"//; s/"$//; s/^'\''//; s/'\''$//')" -fi - if [ "${1:-}" = "apache2-foreground" ]; then sed -ri 's/^Listen 80$/Listen 18088/' /etc/apache2/ports.conf assert_web_writable @@ -58,17 +53,8 @@ if [ "${1:-}" = "apache2-foreground" ]; then run_artisan_as_web migrate --force fi - if [ "$app_environment" = "local" ]; then - # Keep development source live: optimize:clear above removes any - # stale bootstrap/view artifacts, while view.cache=false makes - # Blade recompile templates on every request without a restart. - run_artisan_as_web optimize:clear - else - # Production keeps the existing immutable config and compiled - # Blade caches for startup and request performance. - run_artisan_as_web config:cache - run_artisan_as_web view:cache - fi + run_artisan_as_web config:cache + run_artisan_as_web view:cache fi fi diff --git a/deploy/local-init.sh b/deploy/local-init.sh index 79f8b978..98d70ff9 100644 --- a/deploy/local-init.sh +++ b/deploy/local-init.sh @@ -81,7 +81,6 @@ calculate_local_image_fingerprint() { deploy/Dockerfile.local \ deploy/apache-vhost.conf \ deploy/php.ini \ - deploy/php-local.ini \ deploy/entrypoint.sh \ deploy/local-healthcheck.sh diff --git a/deploy/php-local.ini b/deploy/php-local.ini index 812a4875..8bbe1130 100644 --- a/deploy/php-local.ini +++ b/deploy/php-local.ini @@ -1,6 +1,3 @@ -; Local development serves bind-mounted PHP/Blade source directly. Disable -; opcode caching so a browser refresh always executes the current source. -opcache.enable=0 -opcache.enable_cli=0 +; Local bind-mounted PHP source must be revalidated on every request. opcache.validate_timestamps=1 opcache.revalidate_freq=0 diff --git a/resources/views/business/pages/partials/directory-table.blade.php b/resources/views/business/pages/partials/directory-table.blade.php index 658ed8eb..0f63b7fd 100644 --- a/resources/views/business/pages/partials/directory-table.blade.php +++ b/resources/views/business/pages/partials/directory-table.blade.php @@ -1,11 +1,6 @@ @php $directoryRows = $rows ?? collect(); $directoryFields = $directory['fields'] ?? []; - $isInventoryReportPage = ($page['slug'] ?? '') === 'inventory-report'; - $inventoryWarehouseMappings = $isInventoryReportPage - ? ($inventoryWarehouseMappings ?? collect()) - : collect(); - $warehouseMappingPreview = $isInventoryReportPage ? session('warehouse_mapping_preview') : null; // “最近三个月销量”必须是三个独立的月份列;月份按当前月向前取三个完整月份。 // 保留原字段名作为兼容契约,渲染时展开为 *_1、*_2、*_3 三个值。 $directoryFields = collect($directoryFields)->flatMap(function (array $field): array { @@ -570,256 +565,6 @@ text-decoration: underline; } - .inventory-report-tabs { - min-height: 5rem; - margin: 0; - padding: 0 2rem; - gap: 2rem; - border-color: var(--vip-border, #d8d2c7); - border-radius: 1rem; - box-shadow: none; - } - - .inventory-report-tabs [data-inventory-report-tab] { - position: relative; - display: inline-flex; - min-height: 5rem; - align-items: center; - padding: 0; - border: 0; - background: transparent; - color: var(--vip-content-muted, #595959); - font: inherit; - font-size: 1rem; - font-weight: 600; - white-space: nowrap; - cursor: pointer; - } - - .inventory-report-tabs [data-inventory-report-tab]:hover, - .inventory-report-tabs [data-inventory-report-tab][aria-selected="true"] { - color: var(--vip-content-accent-dark, #856632); - } - - .inventory-report-tabs [data-inventory-report-tab][aria-selected="true"]::after { - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 3px; - border-radius: 999px 999px 0 0; - background: var(--vip-content-accent, #b9954f); - content: ''; - } - - .inventory-report-tabs [data-inventory-report-tab]:focus-visible { - outline: 2px solid var(--vip-content-accent-dark, #856632); - outline-offset: 3px; - } - - .inventory-report-panel[hidden] { - display: none; - } - - .inventory-warehouse-mapping { - overflow: visible; - } - - .inventory-warehouse-mapping__table { - min-width: 52rem; - border-collapse: separate; - border-spacing: 0; - isolation: isolate; - } - - .inventory-warehouse-mapping__table thead th { - position: sticky !important; - top: 0 !important; - z-index: 20 !important; - background: var(--vip-content-accent-soft, #f7f0de); - box-shadow: 0 1px 0 var(--vip-border, #d8d2c7); - } - - .inventory-warehouse-mapping__table thead, - .inventory-warehouse-mapping__table thead tr { - position: sticky; - top: 0; - z-index: 5; - } - - .inventory-warehouse-mapping__table thead th[data-directory-fixed="start"], - .inventory-warehouse-mapping__table thead th[data-directory-fixed="end"] { - z-index: 21 !important; - background: var(--vip-content-accent-soft, #f7f0de); - } - - .inventory-warehouse-mapping__table tbody td { - position: relative !important; - z-index: 0 !important; - background: var(--vip-surface, #fff); - } - - .inventory-warehouse-mapping__table tbody tr:hover td { - background: #fbf8f0; - } - - .inventory-warehouse-mapping__empty { - padding: 3rem 1.25rem; - color: var(--vip-content-muted, #595959); - text-align: center; - } - - .inventory-warehouse-mapping__filters { - grid-template-columns: repeat(2, minmax(12rem, 1fr)) auto; - align-items: end; - border: 0; - border-bottom: 1px solid var(--vip-border, #d8d2c7); - } - - .inventory-warehouse-mapping__filters .directory-query-actions { - grid-column: auto; - justify-content: flex-start; - padding-top: 0; - } - - .inventory-warehouse-mapping__dialog input { - min-height: 2.25rem; - padding: 0.45rem 0.65rem; - border: 1px solid var(--vip-border, #d8d2c7); - border-radius: 0.5rem; - background: #fff; - color: var(--vip-content-ink, #171717); - } - - .inventory-warehouse-mapping__filters .biz-btn { - display: inline-flex; - height: 2.1rem; - align-items: center; - padding: 0 0.8rem; - text-decoration: none; - } - - .inventory-warehouse-mapping__import { - display: inline-flex; - flex-wrap: wrap; - align-items: center; - gap: 0.4rem; - } - - .inventory-warehouse-mapping__export, - .inventory-warehouse-mapping__file-label, - .inventory-warehouse-mapping__import button { - display: inline-flex; - height: 2.25rem; - align-items: center; - gap: 0.35rem; - padding: 0 0.875rem; - border: 1px solid var(--vip-content-accent, #b9954f); - border-radius: 0.25rem; - background: #fff; - color: var(--vip-content-accent-dark, #856632); - font: inherit; - text-decoration: none; - cursor: pointer; - } - - .inventory-warehouse-mapping__file-label input[type="file"] { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - clip: rect(0 0 0 0); - clip-path: inset(50%); - white-space: nowrap; - } - - .inventory-warehouse-mapping__actions { - display: inline-flex; - gap: 0.4rem; - } - - .inventory-warehouse-mapping__actions button { - padding: 0.25rem 0.45rem; - border: 1px solid var(--vip-border, #d8d2c7); - border-radius: 0.4rem; - background: #fff; - color: var(--vip-content-accent-dark, #856632); - cursor: pointer; - } - - .inventory-warehouse-mapping__preview { - margin: 1rem 1.25rem; - overflow: auto; - border: 1px solid var(--vip-border, #d8d2c7); - border-radius: 0.5rem; - } - - .inventory-warehouse-mapping__preview summary { - padding: 0.75rem 1rem; - cursor: pointer; - color: var(--vip-content-accent-dark, #856632); - } - - .inventory-warehouse-mapping__preview table { - width: 100%; - border-collapse: collapse; - font-size: 0.75rem; - } - - .inventory-warehouse-mapping__preview th, - .inventory-warehouse-mapping__preview td { - padding: 0.5rem 0.75rem; - border-top: 1px solid var(--vip-border, #d8d2c7); - text-align: left; - white-space: nowrap; - } - - .inventory-warehouse-mapping__preview .is-invalid, - .inventory-warehouse-mapping__preview .is-conflict { - color: #a33a32; - } - - .inventory-warehouse-mapping__preview .is-same { - color: var(--vip-content-muted, #595959); - } - - .inventory-warehouse-mapping__dialog { - width: min(32rem, calc(100vw - 2rem)); - padding: 0; - border: 1px solid var(--vip-border, #d8d2c7); - border-radius: 0.75rem; - box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.2); - } - - .inventory-warehouse-mapping__dialog::backdrop { - background: rgba(21, 18, 12, 0.38); - } - - .inventory-warehouse-mapping__dialog form { - display: grid; - gap: 1rem; - padding: 1.25rem; - } - - .inventory-warehouse-mapping__dialog h3 { - margin: 0; - color: var(--vip-content-ink, #171717); - font-size: 1rem; - } - - .inventory-warehouse-mapping__dialog label { - display: grid; - gap: 0.35rem; - color: var(--vip-content-muted, #595959); - font-size: 0.8rem; - } - - .inventory-warehouse-mapping__dialog__actions { - display: flex; - justify-content: flex-end; - gap: 0.5rem; - } - @media (max-width: 700px) { .directory-table-head { flex-direction: column; @@ -865,40 +610,18 @@ @endif @else - @if ($isInventoryReportPage) - -
- @else -
-
- -
- {{ $page['title'] ?? $section['title'] }} · 字段结构 - @if (!empty($directory['description'])) -

{{ $directory['description'] }}

- @endif -
+
+
+ +
+ {{ $page['title'] ?? $section['title'] }} · 字段结构 + @if (!empty($directory['description'])) +

{{ $directory['description'] }}

+ @endif
- {{ $directory['status'] ?? '尚未同步' }} -
- @endif +
+ {{ $directory['status'] ?? '尚未同步' }} +
@if (!empty($upstreamError)) @endif
- +
- + @foreach ($directoryFields as $field) @forelse ($directoryRows as $rowIndex => $row) - + @foreach ($directoryFields as $field) @php $source = array_key_exists('source', $field) ? $field['source'] : $field['name']; @@ -1400,164 +1118,6 @@ @endif - @if ($isInventoryReportPage) - -
## @if ($directoryUsesFieldQuery) @@ -1323,7 +1041,7 @@
{{ $rowIndex + 1 + (($directoryCurrentPage - 1) * $directoryPerPage) }}{{ $rowIndex + 1 + (($directoryCurrentPage - 1) * $directoryPerPage) }}
- - - @foreach (($warehouseMappingPreview['rows'] ?? []) as $previewRow) - - - - - - - - - @endforeach - -
行号仓储方仓库仓库简称结果原因
{{ $previewRow['row_number'] }}{{ $previewRow['warehouse_provider'] ?: '—' }}{{ $previewRow['warehouse'] ?: '—' }}{{ $previewRow['warehouse_short_name'] ?: '—' }}{{ match ($previewRow['status']) { 'new' => '待新增', 'same' => '跳过', 'conflict' => '冲突', default => '失败' } }}{{ $previewRow['message'] }}
- @if (($previewSummary['conflicts'] ?? 0) === 0 && ($previewSummary['invalid'] ?? 0) === 0) -
- @csrf - - -
- @else -

存在失败或冲突行,系统不会写入任何记录。请修正 Excel 后重新预览。

- @endif - - @endif - @if ($inventoryWarehouseMappings->isEmpty()) -
当前查询暂无仓库名称映射。可点击“新增仓库”或上传 Sheet1 导入。
- @else -
- - - - - - - - - - - - @foreach ($inventoryWarehouseMappings as $mapping) - - - - - - - - @endforeach - -
#仓储方仓库仓库简称操作
{{ ($inventoryWarehouseMappings->currentPage() - 1) * $inventoryWarehouseMappings->perPage() + $loop->iteration }}{{ $mapping->warehouse_provider }}{{ $mapping->warehouse }}{{ $mapping->warehouse_short_name }} - - - -
-
-
- 共 {{ $inventoryWarehouseMappings->total() }} 条 · 第 {{ $inventoryWarehouseMappings->currentPage() }} / {{ $inventoryWarehouseMappings->lastPage() }} 页 - - @if ($inventoryWarehouseMappings->onFirstPage()) 上一页 @else 上一页 @endif - · - @if ($inventoryWarehouseMappings->hasMorePages()) 下一页 @else 下一页 @endif - -
- @endif -
- -
- @csrf - -

新增仓库名称映射

- - - -
- - -
-
-
-
- @endif - @if (($page['slug'] ?? '') === 'subject-operating-report')