From e9ef4a9eefcda84caf0a3d7f5b6d522cc930a45c Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Tue, 21 Jul 2026 19:13:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=80=81=E8=B4=A7?= =?UTF-8?q?=E6=9C=AA=E5=AE=9A=E4=BB=B7/=E9=80=80=E8=B4=A7=E6=9C=AA?= =?UTF-8?q?=E5=AE=9A=E4=BB=B7/=E5=85=B6=E4=BB=96=E6=89=A3=E6=AC=BE?= =?UTF-8?q?=E8=A1=A5=E6=AC=BE/=E4=BB=A3=E5=8F=91=E5=BF=AB=E9=80=92=204?= =?UTF-8?q?=E4=B8=AA=E5=AD=90=E9=A1=B5=E9=9D=A2=20+=20=E5=B5=8C=E5=A5=97?= =?UTF-8?q?=E5=AD=90=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增4个子功能页面: - 送货未定价 (delivery-unpriced): pm_unOrderDetails, 265452行 - 退货未定价 (return-unpriced): finance_purchaseReturn WHERE priceB=0, 35928行 - 其他扣款补款 (other-adjustments): fys_supplierPaymentSummary WHERE other!=0, 460行 - 代发快递 (dropshipping-express): fys_express_courier_settlement, 765行 嵌套子菜单改造: - 未定价明细下新增children嵌套结构(送货未定价+退货未定价) - ShowInAdmin监听器支持page级dropdown嵌套 - Business\Pages控制器支持父级page跳转到第一个子页面 - SidebarPermissions支持children权限key --- app/Http/Controllers/Business/Pages.php | 5 + app/Listeners/Menu/ShowInAdmin.php | 41 +++ app/Utilities/SidebarPermissions.php | 29 +- config/business_navigation.php | 9 +- .../Http/Controllers/Api/SupplierPayables.php | 110 +++++- .../SupplierPayableRepository.php | 337 ++++++++++++++++++ modules/EcommerceFinance/Routes/api.php | 53 +++ .../ecommerce-finance/delivery-unpriced.js | 105 ++++++ .../ecommerce-finance/dropshipping-express.js | 97 +++++ .../ecommerce-finance/other-adjustments.js | 84 +++++ .../ecommerce-finance/return-unpriced.js | 104 ++++++ .../delivery-unpriced.blade.php | 78 ++++ .../dropshipping-express.blade.php | 101 ++++++ .../other-adjustments.blade.php | 80 +++++ .../return-unpriced.blade.php | 100 ++++++ webpack.mix.js | 12 + 16 files changed, 1342 insertions(+), 3 deletions(-) create mode 100644 resources/assets/js/views/ecommerce-finance/delivery-unpriced.js create mode 100644 resources/assets/js/views/ecommerce-finance/dropshipping-express.js create mode 100644 resources/assets/js/views/ecommerce-finance/other-adjustments.js create mode 100644 resources/assets/js/views/ecommerce-finance/return-unpriced.js create mode 100644 resources/views/business/pages/supplier-payables/delivery-unpriced.blade.php create mode 100644 resources/views/business/pages/supplier-payables/dropshipping-express.blade.php create mode 100644 resources/views/business/pages/supplier-payables/other-adjustments.blade.php create mode 100644 resources/views/business/pages/supplier-payables/return-unpriced.blade.php diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 69296f65..686d4ac3 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -65,6 +65,11 @@ class Pages extends Controller abort_if(empty($pageConfig), 404); + // 如果该 page 是一个 children-only 的父级分组,访问它时跳到第一个子页面 + if (! empty($pageConfig['children']) && empty($pageConfig['tabs'])) { + return $this->redirectToPage($sectionConfig, $pageConfig['children'][0]); + } + $tabs = collect($pageConfig['tabs'] ?? []); if ($tabs->isEmpty()) { diff --git a/app/Listeners/Menu/ShowInAdmin.php b/app/Listeners/Menu/ShowInAdmin.php index d0d36881..b9da00ef 100644 --- a/app/Listeners/Menu/ShowInAdmin.php +++ b/app/Listeners/Menu/ShowInAdmin.php @@ -44,6 +44,47 @@ class ShowInAdmin $parameters['tab'] = $page['tabs'][0]['slug']; } + $children = $page['children'] ?? []; + + if (! empty($children)) { + // 父级 page 渲染为 dropdown:标题可点击跳到第一个子页面,子项作为嵌套菜单 + $firstChild = $children[0]; + $parentParameters = [ + 'section' => $section['slug'], + 'page' => $firstChild['slug'], + ]; + + $sub->dropdown( + $page['title'], + function ($nested) use ($section, $children) { + foreach ($children as $childIndex => $child) { + $nested->route( + 'business.pages.show', + $child['title'], + [ + 'section' => $section['slug'], + 'page' => $child['slug'], + ], + ($childIndex + 1) * 10, + [ + 'icon' => '', + 'active' => fn () => request()->route('section') === $section['slug'] + && request()->route('page') === $child['slug'], + ] + ); + } + }, + ($pageIndex + 1) * 10, + [ + 'icon' => '', + 'active' => fn () => request()->route('section') === $section['slug'] + && in_array(request()->route('page'), array_column($children, 'slug'), true), + ] + ); + + continue; + } + $sub->route( 'business.pages.show', $page['title'], diff --git a/app/Utilities/SidebarPermissions.php b/app/Utilities/SidebarPermissions.php index d526ab6e..abbbb571 100644 --- a/app/Utilities/SidebarPermissions.php +++ b/app/Utilities/SidebarPermissions.php @@ -58,7 +58,30 @@ class SidebarPermissions } $pages = array_values(array_filter($section['pages'] ?? [], function (array $page) use ($allowed, $sectionKey) { - return in_array($sectionKey . '/' . $page['slug'], $allowed, true); + $pageKey = $sectionKey . '/' . $page['slug']; + + if (in_array($pageKey, $allowed, true)) { + // 若有 children,保留 children 的精确权限过滤 + if (! empty($page['children'])) { + $page['children'] = array_values(array_filter($page['children'], function (array $child) use ($sectionKey) { + return in_array($sectionKey . '/' . $child['slug'], $allowed, true); + })); + } + return true; + } + + // 子页面可能在 allowed 列表中(独立 URL),父级分组也允许展开 + if (! empty($page['children'])) { + $visibleChildren = array_values(array_filter($page['children'], function (array $child) use ($sectionKey) { + return in_array($sectionKey . '/' . $child['slug'], $allowed, true); + })); + if (! empty($visibleChildren)) { + $page['children'] = $visibleChildren; + return true; + } + } + + return false; })); if (! empty($pages)) { @@ -80,6 +103,10 @@ class SidebarPermissions foreach ($section['pages'] ?? [] as $page) { $keys[] = $section['slug'] . '/' . $page['slug']; + + foreach ($page['children'] ?? [] as $child) { + $keys[] = $section['slug'] . '/' . $child['slug']; + } } } diff --git a/config/business_navigation.php b/config/business_navigation.php index 94364a83..2bf9efd5 100644 --- a/config/business_navigation.php +++ b/config/business_navigation.php @@ -153,7 +153,14 @@ return [ ['slug' => 'advance-details', 'title' => '借支明细'], ['slug' => 'sample-deductions', 'title' => '样品扣款'], ['slug' => 'other-adjustments', 'title' => '其他扣款补款'], - ['slug' => 'unpriced-details', 'title' => '未定价明细'], + [ + 'slug' => 'unpriced-details', + 'title' => '未定价明细', + 'children' => [ + ['slug' => 'delivery-unpriced', 'title' => '送货未定价'], + ['slug' => 'return-unpriced', 'title' => '退货未定价'], + ], + ], ['slug' => 'dropshipping-express', 'title' => '代发快递'], ['slug' => 'supplier-payables-report', 'title' => '供应商应付表'], ], diff --git a/modules/EcommerceFinance/Http/Controllers/Api/SupplierPayables.php b/modules/EcommerceFinance/Http/Controllers/Api/SupplierPayables.php index 90fa5159..b85fe164 100644 --- a/modules/EcommerceFinance/Http/Controllers/Api/SupplierPayables.php +++ b/modules/EcommerceFinance/Http/Controllers/Api/SupplierPayables.php @@ -132,6 +132,60 @@ class SupplierPayables extends ApiController return response()->json($this->repo->filterOptionsForAdvance()); } + // ============================================================ + // 送货未定价 + // ============================================================ + public function deliveryUnpricedIndex() + { + $filters = $this->filters('delivery-unpriced'); + $result = $this->repo->deliveryUnpriced($filters, $this->page(), $this->limit()); + + return response()->json([ + 'data' => $result['data'], + 'total' => $result['total'], + 'page' => $this->page(), + 'per_page' => $this->limit(), + 'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()), + ]); + } + + public function deliveryUnpricedSummary() + { + return response()->json($this->repo->deliveryUnpricedSummary($this->filters('delivery-unpriced'))); + } + + public function deliveryUnpricedOptions() + { + return response()->json($this->repo->filterOptionsForDeliveryUnpriced()); + } + + // ============================================================ + // 退货未定价 + // ============================================================ + public function returnUnpricedIndex() + { + $filters = $this->filters('return-unpriced'); + $result = $this->repo->returnUnpriced($filters, $this->page(), $this->limit()); + + return response()->json([ + 'data' => $result['data'], + 'total' => $result['total'], + 'page' => $this->page(), + 'per_page' => $this->limit(), + 'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()), + ]); + } + + public function returnUnpricedSummary() + { + return response()->json($this->repo->returnUnpricedSummary($this->filters('return-unpriced'))); + } + + public function returnUnpricedOptions() + { + return response()->json($this->repo->filterOptionsForReturnUnpriced()); + } + // ============================================================ // 样品扣款 // ============================================================ @@ -159,6 +213,60 @@ class SupplierPayables extends ApiController return response()->json($this->repo->filterOptionsForSample()); } + // ============================================================ + // 其他扣款补款 + // ============================================================ + public function otherAdjustmentsIndex() + { + $filters = $this->filters('other-adjustments'); + $result = $this->repo->otherAdjustments($filters, $this->page(), $this->limit()); + + return response()->json([ + 'data' => $result['data'], + 'total' => $result['total'], + 'page' => $this->page(), + 'per_page' => $this->limit(), + 'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()), + ]); + } + + public function otherAdjustmentsSummary() + { + return response()->json($this->repo->otherAdjustmentsSummary($this->filters('other-adjustments'))); + } + + public function otherAdjustmentsOptions() + { + return response()->json($this->repo->filterOptionsForOtherAdjustments()); + } + + // ============================================================ + // 代发快递 + // ============================================================ + public function dropshippingExpressIndex() + { + $filters = $this->filters('dropshipping-express'); + $result = $this->repo->dropshippingExpress($filters, $this->page(), $this->limit()); + + return response()->json([ + 'data' => $result['data'], + 'total' => $result['total'], + 'page' => $this->page(), + 'per_page' => $this->limit(), + 'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()), + ]); + } + + public function dropshippingExpressSummary() + { + return response()->json($this->repo->dropshippingExpressSummary($this->filters('dropshipping-express'))); + } + + public function dropshippingExpressOptions() + { + return response()->json($this->repo->filterOptionsForDropshippingExpress()); + } + // ============================================================ // Helpers // ============================================================ @@ -167,7 +275,7 @@ class SupplierPayables extends ApiController $filters = []; // 通用文本字段 - $text = ['supplier', 'warehouse', 'sku_id', 'express_no', 'creator', 'department']; + $text = ['supplier', 'warehouse', 'sku_id', 'express_no', 'creator', 'department', 'purchase', 'barcode', 'express_name']; foreach ($text as $field) { if ($val = request($field)) { $filters[$field] = trim($val); diff --git a/modules/EcommerceFinance/Repositories/SupplierPayableRepository.php b/modules/EcommerceFinance/Repositories/SupplierPayableRepository.php index 85d9c1e6..ba7596aa 100644 --- a/modules/EcommerceFinance/Repositories/SupplierPayableRepository.php +++ b/modules/EcommerceFinance/Repositories/SupplierPayableRepository.php @@ -487,6 +487,171 @@ class SupplierPayableRepository ]; } + // ============================================================ + // 6. 送货未定价 — pm_unOrderDetails (265452行) + // ============================================================ + public function deliveryUnpriced(array $filters, int $page, int $pageSize): array + { + $where = []; + $params = []; + + if (!empty($filters['supplier'])) { + $where[] = 'supplier LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + if (!empty($filters['warehouse'])) { + $where[] = 'warehouse LIKE ?'; + $params[] = '%' . $filters['warehouse'] . '%'; + } + if (!empty($filters['purchase'])) { + $where[] = 'purchase LIKE ?'; + $params[] = '%' . $filters['purchase'] . '%'; + } + if (!empty($filters['barcode'])) { + $where[] = 'barCode LIKE ?'; + $params[] = '%' . $filters['barcode'] . '%'; + } + if (!empty($filters['express_no'])) { + $where[] = 'expressNo LIKE ?'; + $params[] = '%' . $filters['express_no'] . '%'; + } + if (!empty($filters['date_from'])) { + $where[] = 'date >= ?'; + $params[] = $filters['date_from']; + } + if (!empty($filters['date_to'])) { + $where[] = 'date <= ?'; + $params[] = $filters['date_to']; + } + + $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; + + $sql = "SELECT + Id AS id, + CONVERT(varchar(10), date, 120) AS date, + purchase, + warehouse, + supplier, + transactionNo, + distributionNo, + expressNo, + sku, + barCode, + skuName, + CONVERT(varchar(19), distributionDate, 120) AS distribution_date, + CONVERT(varchar(10), shipDate, 120) AS ship_date, + expressName + FROM pm_unOrderDetails + $whereSql + ORDER BY date DESC, Id DESC"; + + return $this->runQuery($sql, $params, $page, $pageSize); + } + + public function deliveryUnpricedSummary(array $filters): array + { + $where = []; + $params = []; + if (!empty($filters['supplier'])) { + $where[] = 'supplier LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; + + $sql = "SELECT COUNT(*) AS total_count FROM pm_unOrderDetails $whereSql"; + $stmt = $this->db->prepare($sql); + $stmt->execute($params); + $row = $stmt->fetch(); + + return [ + 'totalCount' => (int) $row['total_count'], + ]; + } + + // ============================================================ + // 7. 退货未定价 — finance_purchaseReturn WHERE priceB = 0 (35928行) + // ============================================================ + public function returnUnpriced(array $filters, int $page, int $pageSize): array + { + $where = ['(priceB = 0 OR priceB IS NULL)']; + $params = []; + + if (!empty($filters['supplier'])) { + $where[] = 'supplierName LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + if (!empty($filters['warehouse'])) { + $where[] = 'warehouseName LIKE ?'; + $params[] = '%' . $filters['warehouse'] . '%'; + } + if (!empty($filters['sku_id'])) { + $where[] = 'sku_id LIKE ?'; + $params[] = '%' . $filters['sku_id'] . '%'; + } + if (!empty($filters['express_no'])) { + $where[] = 'expressNo LIKE ?'; + $params[] = '%' . $filters['express_no'] . '%'; + } + if (!empty($filters['date_from'])) { + $where[] = 'date >= ?'; + $params[] = $filters['date_from']; + } + if (!empty($filters['date_to'])) { + $where[] = 'date <= ?'; + $params[] = $filters['date_to']; + } + + $whereSql = 'WHERE ' . implode(' AND ', $where); + + $sql = "SELECT + CONVERT(varchar(10), date, 120) AS send_time, + supplierName AS supplier, + sku_id AS barcode, + name AS sku_name, + CAST(qty AS decimal(18,2)) AS qty, + CAST(price AS decimal(18,2)) AS unit_price, + CAST(price * qty AS decimal(18,2)) AS amount, + CAST(ISNULL(priceB, 0) AS decimal(18,2)) AS guess_unit_price, + CAST(ISNULL(priceB, 0) * qty AS decimal(18,2)) AS guess_amount, + remark, + io_id AS wms_pick_no, + warehouseName AS warehouse, + expressNo AS express_no, + expressName AS express_name, + CONVERT(varchar(10), io_date, 120) AS io_date, + labels, + creator_name AS creator + FROM finance_purchaseReturn + $whereSql + ORDER BY date DESC"; + + return $this->runQuery($sql, $params, $page, $pageSize); + } + + public function returnUnpricedSummary(array $filters): array + { + $where = ['(priceB = 0 OR priceB IS NULL)']; + $params = []; + if (!empty($filters['supplier'])) { + $where[] = 'supplierName LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + $whereSql = 'WHERE ' . implode(' AND ', $where); + + $sql = "SELECT + ISNULL(SUM(price * qty), 0) AS total_amount, + COUNT(*) AS total_count + FROM finance_purchaseReturn $whereSql"; + $stmt = $this->db->prepare($sql); + $stmt->execute($params); + $row = $stmt->fetch(); + + return [ + 'totalAmount' => (float) $row['total_amount'], + 'totalCount' => (int) $row['total_count'], + ]; + } + // ============================================================ // 筛选器选项 // ============================================================ @@ -523,6 +688,23 @@ class SupplierPayableRepository return ['suppliers' => $suppliers]; } + public function filterOptionsForDeliveryUnpriced(): array + { + $suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM pm_unOrderDetails WHERE supplier IS NOT NULL ORDER BY supplier'); + $warehouses = $this->fetchDistinct('SELECT DISTINCT warehouse FROM pm_unOrderDetails WHERE warehouse IS NOT NULL ORDER BY warehouse'); + $purchases = $this->fetchDistinct('SELECT DISTINCT purchase FROM pm_unOrderDetails WHERE purchase IS NOT NULL ORDER BY purchase'); + + return compact('suppliers', 'warehouses', 'purchases'); + } + + public function filterOptionsForReturnUnpriced(): array + { + $suppliers = $this->fetchDistinct("SELECT DISTINCT supplierName FROM finance_purchaseReturn WHERE (priceB = 0 OR priceB IS NULL) AND supplierName IS NOT NULL ORDER BY supplierName"); + $warehouses = $this->fetchDistinct("SELECT DISTINCT warehouseName FROM finance_purchaseReturn WHERE (priceB = 0 OR priceB IS NULL) AND warehouseName IS NOT NULL ORDER BY warehouseName"); + + return compact('suppliers', 'warehouses'); + } + public function filterOptionsForSample(): array { $suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM finance_sampleDeduction WHERE supplier IS NOT NULL ORDER BY supplier'); @@ -537,4 +719,159 @@ class SupplierPayableRepository $rows = $this->db->query($sql)->fetchAll(PDO::FETCH_COLUMN); return array_values(array_filter($rows, fn ($v) => $v !== null && $v !== '')); } + + // ============================================================ + // 8. 其他扣款补款 — fys_supplierPaymentSummary WHERE other != 0 (460行) + // ============================================================ + public function otherAdjustments(array $filters, int $page, int $pageSize): array + { + $where = ['(other IS NOT NULL AND other != 0)']; + $params = []; + + if (!empty($filters['supplier'])) { + $where[] = 'supplier LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + if (!empty($filters['month_from'])) { + $where[] = 'Month >= ?'; + $params[] = $filters['month_from']; + } + if (!empty($filters['month_to'])) { + $where[] = 'Month <= ?'; + $params[] = $filters['month_to']; + } + + $whereSql = 'WHERE ' . implode(' AND ', $where); + + $sql = "SELECT + '其他扣款补款' AS project, + Month AS month_str, + supplier, + CAST(other AS decimal(18,2)) AS supplement_amount, + reason AS supplement_reason, + remarks AS remark, + mark AS remark2, + checker AS person_charge + FROM fys_supplierPaymentSummary + $whereSql + ORDER BY Month DESC, supplier"; + + return $this->runQuery($sql, $params, $page, $pageSize); + } + + public function otherAdjustmentsSummary(array $filters): array + { + $where = ['(other IS NOT NULL AND other != 0)']; + $params = []; + if (!empty($filters['supplier'])) { + $where[] = 'supplier LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + $whereSql = 'WHERE ' . implode(' AND ', $where); + + $sql = "SELECT + ISNULL(SUM(other), 0) AS total_amount, + COUNT(*) AS total_count + FROM fys_supplierPaymentSummary $whereSql"; + $stmt = $this->db->prepare($sql); + $stmt->execute($params); + $row = $stmt->fetch(); + + return [ + 'totalAmount' => (float) $row['total_amount'], + 'totalCount' => (int) $row['total_count'], + ]; + } + + // ============================================================ + // 9. 代发快递 — fys_express_courier_settlement (940行) + // ============================================================ + public function dropshippingExpress(array $filters, int $page, int $pageSize): array + { + $where = ['(is_delete = 0 OR is_delete IS NULL)']; + $params = []; + + if (!empty($filters['supplier'])) { + $where[] = 'courier_company_name LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + if (!empty($filters['express_name'])) { + $where[] = 'expressname LIKE ?'; + $params[] = '%' . $filters['express_name'] . '%'; + } + if (!empty($filters['month_from'])) { + $where[] = 'month >= ?'; + $params[] = $filters['month_from']; + } + if (!empty($filters['month_to'])) { + $where[] = 'month <= ?'; + $params[] = $filters['month_to']; + } + + $whereSql = 'WHERE ' . implode(' AND ', $where); + + $sql = "SELECT + reconciliation_person, + month AS month_str, + courier_company_name AS supplier, + CAST(ISNULL(waybill, 0) AS decimal(18,2)) AS express_freight, + CAST(ISNULL(courier_provided_amount, 0) AS decimal(18,2)) AS express_account, + CAST(ISNULL(replenish_courier, 0) AS decimal(18,2)) AS replenish_freight, + CAST(ISNULL(confirmed_amount, 0) AS decimal(18,2)) AS replenish_account, + CAST(ISNULL(reconciliation_difference, 0) AS decimal(18,2)) AS return_fee, + CAST(ISNULL(compensation_amount, 0) AS decimal(18,2)) AS intercept_fee, + CAST(ISNULL(other_replenishment, 0) AS decimal(18,2)) AS change_address_fee, + CAST(ISNULL(corporate_payment_amount, 0) AS decimal(18,2)) AS turnover_fee, + CAST(ISNULL(personal_payment_amount, 0) AS decimal(18,2)) AS overlong_fee, + CAST(ISNULL(payable_amount, 0) AS decimal(18,2)) AS total_freight, + status AS remark, + expressname AS express_name, + file_status AS remark2, + creator, + CONVERT(varchar(19), create_time, 120) AS create_time + FROM fys_express_courier_settlement + $whereSql + ORDER BY month DESC, expressname"; + + return $this->runQuery($sql, $params, $page, $pageSize); + } + + public function dropshippingExpressSummary(array $filters): array + { + $where = ['(is_delete = 0 OR is_delete IS NULL)']; + $params = []; + if (!empty($filters['supplier'])) { + $where[] = 'courier_company_name LIKE ?'; + $params[] = '%' . $filters['supplier'] . '%'; + } + $whereSql = 'WHERE ' . implode(' AND ', $where); + + $sql = "SELECT + ISNULL(SUM(payable_amount), 0) AS total_amount, + COUNT(*) AS total_count + FROM fys_express_courier_settlement $whereSql"; + $stmt = $this->db->prepare($sql); + $stmt->execute($params); + $row = $stmt->fetch(); + + return [ + 'totalAmount' => (float) $row['total_amount'], + 'totalCount' => (int) $row['total_count'], + ]; + } + + public function filterOptionsForOtherAdjustments(): array + { + $suppliers = $this->fetchDistinct("SELECT DISTINCT supplier FROM fys_supplierPaymentSummary WHERE (other IS NOT NULL AND other != 0) AND supplier IS NOT NULL ORDER BY supplier"); + + return ['suppliers' => $suppliers]; + } + + public function filterOptionsForDropshippingExpress(): array + { + $suppliers = $this->fetchDistinct("SELECT DISTINCT courier_company_name FROM fys_express_courier_settlement WHERE (is_delete = 0 OR is_delete IS NULL) AND courier_company_name IS NOT NULL ORDER BY courier_company_name"); + $expressNames = $this->fetchDistinct("SELECT DISTINCT expressname FROM fys_express_courier_settlement WHERE (is_delete = 0 OR is_delete IS NULL) AND expressname IS NOT NULL ORDER BY expressname"); + + return compact('suppliers', 'expressNames'); + } } diff --git a/modules/EcommerceFinance/Routes/api.php b/modules/EcommerceFinance/Routes/api.php index ee9041b0..5ab5e98d 100644 --- a/modules/EcommerceFinance/Routes/api.php +++ b/modules/EcommerceFinance/Routes/api.php @@ -8,4 +8,57 @@ Route::api('ecommerce-finance', function () { Route::apiResource('stores', 'Stores')->except(['destroy']); Route::apiResource('import-batches', 'ImportBatches')->only(['index', 'show', 'store']); Route::get('reconciliation-exceptions', 'ReconciliationExceptions@index')->name('reconciliation-exceptions.index'); + + // 货款对账 + Route::get('goods-reconciliations', 'GoodsReconciliations@index')->name('goods-reconciliations.index'); + Route::get('goods-reconciliations/summary', 'GoodsReconciliations@summary')->name('goods-reconciliations.summary'); + Route::get('goods-reconciliations/options', 'GoodsReconciliations@options')->name('goods-reconciliations.options'); + + // 供应商应付 - 子功能页面 + Route::prefix('supplier-payables')->group(function () { + // 退货对账 + Route::get('return-reconciliations', 'SupplierPayables@returnReconciliationsIndex'); + Route::get('return-reconciliations/summary', 'SupplierPayables@returnReconciliationsSummary'); + Route::get('return-reconciliations/options', 'SupplierPayables@returnReconciliationsOptions'); + + // 发票扣款 + Route::get('invoice-deductions', 'SupplierPayables@invoiceDeductionsIndex'); + Route::get('invoice-deductions/summary', 'SupplierPayables@invoiceDeductionsSummary'); + Route::get('invoice-deductions/options', 'SupplierPayables@invoiceDeductionsOptions'); + + // 赔偿明细 + Route::get('compensation-details', 'SupplierPayables@compensationDetailsIndex'); + Route::get('compensation-details/summary', 'SupplierPayables@compensationDetailsSummary'); + Route::get('compensation-details/options', 'SupplierPayables@compensationDetailsOptions'); + + // 借支明细 + Route::get('advance-details', 'SupplierPayables@advanceDetailsIndex'); + Route::get('advance-details/summary', 'SupplierPayables@advanceDetailsSummary'); + Route::get('advance-details/options', 'SupplierPayables@advanceDetailsOptions'); + + // 送货未定价 + Route::get('delivery-unpriced', 'SupplierPayables@deliveryUnpricedIndex'); + Route::get('delivery-unpriced/summary', 'SupplierPayables@deliveryUnpricedSummary'); + Route::get('delivery-unpriced/options', 'SupplierPayables@deliveryUnpricedOptions'); + + // 退货未定价 + Route::get('return-unpriced', 'SupplierPayables@returnUnpricedIndex'); + Route::get('return-unpriced/summary', 'SupplierPayables@returnUnpricedSummary'); + Route::get('return-unpriced/options', 'SupplierPayables@returnUnpricedOptions'); + + // 样品扣款 + Route::get('sample-deductions', 'SupplierPayables@sampleDeductionsIndex'); + Route::get('sample-deductions/summary', 'SupplierPayables@sampleDeductionsSummary'); + Route::get('sample-deductions/options', 'SupplierPayables@sampleDeductionsOptions'); + + // 其他扣款补款 + Route::get('other-adjustments', 'SupplierPayables@otherAdjustmentsIndex'); + Route::get('other-adjustments/summary', 'SupplierPayables@otherAdjustmentsSummary'); + Route::get('other-adjustments/options', 'SupplierPayables@otherAdjustmentsOptions'); + + // 代发快递 + Route::get('dropshipping-express', 'SupplierPayables@dropshippingExpressIndex'); + Route::get('dropshipping-express/summary', 'SupplierPayables@dropshippingExpressSummary'); + Route::get('dropshipping-express/options', 'SupplierPayables@dropshippingExpressOptions'); + }); }); diff --git a/resources/assets/js/views/ecommerce-finance/delivery-unpriced.js b/resources/assets/js/views/ecommerce-finance/delivery-unpriced.js new file mode 100644 index 00000000..f46727a6 --- /dev/null +++ b/resources/assets/js/views/ecommerce-finance/delivery-unpriced.js @@ -0,0 +1,105 @@ +require('./../../bootstrap'); + +import Vue from 'vue'; +import DashboardPlugin from './../../plugins/dashboard-plugin'; +import Global from './../../mixins/global'; + +import { + Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Select, Option, Message +} from 'element-ui'; + +Vue.use(DashboardPlugin); +Vue.component(Table.name, Table); +Vue.component(TableColumn.name, TableColumn); +Vue.component(Pagination.name, Pagination); +Vue.component(DatePicker.name, DatePicker); +Vue.component(Tag.name, Tag); +Vue.component(Input.name, Input); +Vue.component(Button.name, Button); +Vue.component(Select.name, Select); +Vue.component(Option.name, Option); +Vue.prototype.$message = Message; + +const app = new Vue({ + el: '#delivery-unpriced-app', + + mixins: [Global], + + data() { + return { + baseUrl: url + '/api/ecommerce-finance/supplier-payables/delivery-unpriced', + loading: false, + tableData: [], + total: 0, + currentPage: 1, + pageSize: 25, + summary: { totalCount: 0 }, + options: { suppliers: [], warehouses: [], purchases: [] }, + filters: { + supplier: '', + warehouse: '', + purchase: '', + barcode: '', + express_no: '', + date_range: null + } + }; + }, + + mounted() { + this.fetchOptions(); + this.fetchData(); + }, + + methods: { + fetchOptions() { + axios.get(this.baseUrl + '/options') + .then(r => { this.options = r.data; }) + .catch(() => this.$message.error('获取筛选选项失败')); + }, + + fetchData() { + this.loading = true; + const params = this.buildParams(); + Promise.all([ + axios.get(this.baseUrl, { params }), + axios.get(this.baseUrl + '/summary', { params }) + ]).then(([listRes, sumRes]) => { + this.tableData = listRes.data.data || []; + this.total = listRes.data.total || 0; + this.currentPage = listRes.data.page || 1; + this.pageSize = listRes.data.per_page || 25; + this.summary = sumRes.data; + }).catch(() => this.$message.error('获取数据失败')) + .finally(() => { this.loading = false; }); + }, + + buildParams() { + const params = { page: this.currentPage, limit: this.pageSize }; + ['supplier', 'warehouse', 'purchase', 'barcode', 'express_no'].forEach(f => { + if (this.filters[f]) params[f] = this.filters[f]; + }); + if (this.filters.date_range && this.filters.date_range.length === 2) { + params.date_from = this.filters.date_range[0]; + params.date_to = this.filters.date_range[1]; + } + return params; + }, + + onFilterChange() { this.currentPage = 1; this.fetchData(); }, + onPageChange(p) { this.currentPage = p; this.fetchData(); }, + onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); }, + onReset() { + this.filters = { supplier: '', warehouse: '', purchase: '', barcode: '', express_no: '', date_range: null }; + this.currentPage = 1; + this.fetchData(); + }, + + formatMoney(v) { + if (v == null || isNaN(v)) return '0.00'; + return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + } + } +}); + +export default app; diff --git a/resources/assets/js/views/ecommerce-finance/dropshipping-express.js b/resources/assets/js/views/ecommerce-finance/dropshipping-express.js new file mode 100644 index 00000000..7919f81b --- /dev/null +++ b/resources/assets/js/views/ecommerce-finance/dropshipping-express.js @@ -0,0 +1,97 @@ +require('./../../bootstrap'); + +import Vue from 'vue'; +import DashboardPlugin from './../../plugins/dashboard-plugin'; +import Global from './../../mixins/global'; + +import { + Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Select, Option, Message +} from 'element-ui'; + +Vue.use(DashboardPlugin); +Vue.component(Table.name, Table); +Vue.component(TableColumn.name, TableColumn); +Vue.component(Pagination.name, Pagination); +Vue.component(DatePicker.name, DatePicker); +Vue.component(Tag.name, Tag); +Vue.component(Input.name, Input); +Vue.component(Button.name, Button); +Vue.component(Select.name, Select); +Vue.component(Option.name, Option); +Vue.prototype.$message = Message; + +const app = new Vue({ + el: '#dropshipping-express-app', + + mixins: [Global], + + data() { + return { + baseUrl: url + '/api/ecommerce-finance/supplier-payables/dropshipping-express', + loading: false, + tableData: [], + total: 0, + currentPage: 1, + pageSize: 25, + summary: { totalAmount: 0, totalCount: 0 }, + options: { suppliers: [], expressNames: [] }, + filters: { supplier: '', express_name: '', month_range: null } + }; + }, + + mounted() { + this.fetchOptions(); + this.fetchData(); + }, + + methods: { + fetchOptions() { + axios.get(this.baseUrl + '/options') + .then(r => { this.options = r.data; }) + .catch(() => this.$message.error('获取筛选选项失败')); + }, + + fetchData() { + this.loading = true; + const params = this.buildParams(); + Promise.all([ + axios.get(this.baseUrl, { params }), + axios.get(this.baseUrl + '/summary', { params }) + ]).then(([listRes, sumRes]) => { + this.tableData = listRes.data.data || []; + this.total = listRes.data.total || 0; + this.currentPage = listRes.data.page || 1; + this.pageSize = listRes.data.per_page || 25; + this.summary = sumRes.data; + }).catch(() => this.$message.error('获取数据失败')) + .finally(() => { this.loading = false; }); + }, + + buildParams() { + const params = { page: this.currentPage, limit: this.pageSize }; + if (this.filters.supplier) params.supplier = this.filters.supplier; + if (this.filters.express_name) params.express_name = this.filters.express_name; + if (this.filters.month_range && this.filters.month_range.length === 2) { + params.month_from = this.filters.month_range[0]; + params.month_to = this.filters.month_range[1]; + } + return params; + }, + + onFilterChange() { this.currentPage = 1; this.fetchData(); }, + onPageChange(p) { this.currentPage = p; this.fetchData(); }, + onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); }, + onReset() { + this.filters = { supplier: '', express_name: '', month_range: null }; + this.currentPage = 1; + this.fetchData(); + }, + + formatMoney(v) { + if (v == null || isNaN(v)) return '0.00'; + return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + } + } +}); + +export default app; diff --git a/resources/assets/js/views/ecommerce-finance/other-adjustments.js b/resources/assets/js/views/ecommerce-finance/other-adjustments.js new file mode 100644 index 00000000..2180c578 --- /dev/null +++ b/resources/assets/js/views/ecommerce-finance/other-adjustments.js @@ -0,0 +1,84 @@ +require('./../../bootstrap'); + +import Vue from 'vue'; +import DashboardPlugin from './../../plugins/dashboard-plugin'; +import Global from './../../mixins/global'; + +import { + Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Message +} from 'element-ui'; + +Vue.use(DashboardPlugin); +Vue.component(Table.name, Table); +Vue.component(TableColumn.name, TableColumn); +Vue.component(Pagination.name, Pagination); +Vue.component(DatePicker.name, DatePicker); +Vue.component(Tag.name, Tag); +Vue.component(Input.name, Input); +Vue.component(Button.name, Button); +Vue.prototype.$message = Message; + +const app = new Vue({ + el: '#other-adjustment-app', + + mixins: [Global], + + data() { + return { + baseUrl: url + '/api/ecommerce-finance/supplier-payables/other-adjustments', + loading: false, + tableData: [], + total: 0, + currentPage: 1, + pageSize: 25, + summary: { totalAmount: 0, totalCount: 0 }, + filters: { supplier: '', month_range: null } + }; + }, + + mounted() { this.fetchData(); }, + + methods: { + fetchData() { + this.loading = true; + const params = this.buildParams(); + Promise.all([ + axios.get(this.baseUrl, { params }), + axios.get(this.baseUrl + '/summary', { params }) + ]).then(([listRes, sumRes]) => { + this.tableData = listRes.data.data || []; + this.total = listRes.data.total || 0; + this.currentPage = listRes.data.page || 1; + this.pageSize = listRes.data.per_page || 25; + this.summary = sumRes.data; + }).catch(() => this.$message.error('获取数据失败')) + .finally(() => { this.loading = false; }); + }, + + buildParams() { + const params = { page: this.currentPage, limit: this.pageSize }; + if (this.filters.supplier) params.supplier = this.filters.supplier; + if (this.filters.month_range && this.filters.month_range.length === 2) { + params.month_from = this.filters.month_range[0]; + params.month_to = this.filters.month_range[1]; + } + return params; + }, + + onFilterChange() { this.currentPage = 1; this.fetchData(); }, + onPageChange(p) { this.currentPage = p; this.fetchData(); }, + onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); }, + onReset() { + this.filters = { supplier: '', month_range: null }; + this.currentPage = 1; + this.fetchData(); + }, + + formatMoney(v) { + if (v == null || isNaN(v)) return '0.00'; + return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + } + } +}); + +export default app; diff --git a/resources/assets/js/views/ecommerce-finance/return-unpriced.js b/resources/assets/js/views/ecommerce-finance/return-unpriced.js new file mode 100644 index 00000000..871e8bc2 --- /dev/null +++ b/resources/assets/js/views/ecommerce-finance/return-unpriced.js @@ -0,0 +1,104 @@ +require('./../../bootstrap'); + +import Vue from 'vue'; +import DashboardPlugin from './../../plugins/dashboard-plugin'; +import Global from './../../mixins/global'; + +import { + Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Select, Option, Message +} from 'element-ui'; + +Vue.use(DashboardPlugin); +Vue.component(Table.name, Table); +Vue.component(TableColumn.name, TableColumn); +Vue.component(Pagination.name, Pagination); +Vue.component(DatePicker.name, DatePicker); +Vue.component(Tag.name, Tag); +Vue.component(Input.name, Input); +Vue.component(Button.name, Button); +Vue.component(Select.name, Select); +Vue.component(Option.name, Option); +Vue.prototype.$message = Message; + +const app = new Vue({ + el: '#return-unpriced-app', + + mixins: [Global], + + data() { + return { + baseUrl: url + '/api/ecommerce-finance/supplier-payables/return-unpriced', + loading: false, + tableData: [], + total: 0, + currentPage: 1, + pageSize: 25, + summary: { totalAmount: 0, totalCount: 0 }, + options: { suppliers: [], warehouses: [] }, + filters: { + supplier: '', + warehouse: '', + sku_id: '', + express_no: '', + date_range: null + } + }; + }, + + mounted() { + this.fetchOptions(); + this.fetchData(); + }, + + methods: { + fetchOptions() { + axios.get(this.baseUrl + '/options') + .then(r => { this.options = r.data; }) + .catch(() => this.$message.error('获取筛选选项失败')); + }, + + fetchData() { + this.loading = true; + const params = this.buildParams(); + Promise.all([ + axios.get(this.baseUrl, { params }), + axios.get(this.baseUrl + '/summary', { params }) + ]).then(([listRes, sumRes]) => { + this.tableData = listRes.data.data || []; + this.total = listRes.data.total || 0; + this.currentPage = listRes.data.page || 1; + this.pageSize = listRes.data.per_page || 25; + this.summary = sumRes.data; + }).catch(() => this.$message.error('获取数据失败')) + .finally(() => { this.loading = false; }); + }, + + buildParams() { + const params = { page: this.currentPage, limit: this.pageSize }; + ['supplier', 'warehouse', 'sku_id', 'express_no'].forEach(f => { + if (this.filters[f]) params[f] = this.filters[f]; + }); + if (this.filters.date_range && this.filters.date_range.length === 2) { + params.date_from = this.filters.date_range[0]; + params.date_to = this.filters.date_range[1]; + } + return params; + }, + + onFilterChange() { this.currentPage = 1; this.fetchData(); }, + onPageChange(p) { this.currentPage = p; this.fetchData(); }, + onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); }, + onReset() { + this.filters = { supplier: '', warehouse: '', sku_id: '', express_no: '', date_range: null }; + this.currentPage = 1; + this.fetchData(); + }, + + formatMoney(v) { + if (v == null || isNaN(v)) return '0.00'; + return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + } + } +}); + +export default app; diff --git a/resources/views/business/pages/supplier-payables/delivery-unpriced.blade.php b/resources/views/business/pages/supplier-payables/delivery-unpriced.blade.php new file mode 100644 index 00000000..dadf32be --- /dev/null +++ b/resources/views/business/pages/supplier-payables/delivery-unpriced.blade.php @@ -0,0 +1,78 @@ +
+
+ {{-- 统计卡片 --}} +
+
+
未定价记录数
+
@{{ (summary.totalCount || 0).toLocaleString() }}
+
+
+
数据源
+
pm_unOrderDetails
+
代发订单中未定价的明细记录
+
+
+ + {{-- 筛选栏 --}} +
+
+ + + + + + + + + + + + 查询 + 重置 +
+
+ + {{-- 数据表格 --}} +
+
+ 加载中... +
+ + + + + + + + + + + + + + + + + +
+
共 @{{ total }} 条记录
+ + +
+
+
+
+ +@push('body_end') + +@endpush diff --git a/resources/views/business/pages/supplier-payables/dropshipping-express.blade.php b/resources/views/business/pages/supplier-payables/dropshipping-express.blade.php new file mode 100644 index 00000000..4ac4d764 --- /dev/null +++ b/resources/views/business/pages/supplier-payables/dropshipping-express.blade.php @@ -0,0 +1,101 @@ +
+
+ {{-- 统计卡片 --}} +
+
+
合计运费
+
@{{ formatMoney(summary.totalAmount) }}
+
+
+
总记录数
+
@{{ (summary.totalCount || 0).toLocaleString() }}
+
+
+ + {{-- 筛选栏 --}} +
+
+ + + + + + + 查询 + 重置 +
+
+ + {{-- 数据表格 --}} +
+
+ 加载中... +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
共 @{{ total }} 条记录
+ + +
+
+
+
+ +@push('body_end') + +@endpush diff --git a/resources/views/business/pages/supplier-payables/other-adjustments.blade.php b/resources/views/business/pages/supplier-payables/other-adjustments.blade.php new file mode 100644 index 00000000..60470b66 --- /dev/null +++ b/resources/views/business/pages/supplier-payables/other-adjustments.blade.php @@ -0,0 +1,80 @@ +
+
+ {{-- 统计卡片 --}} +
+
+
补款总金额
+
@{{ formatMoney(summary.totalAmount) }}
+
+
+
总记录数
+
@{{ (summary.totalCount || 0).toLocaleString() }}
+
+
+ + {{-- 筛选栏 --}} +
+
+ + + + 查询 + 重置 +
+
+ + {{-- 数据表格 --}} +
+
+ 加载中... +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
共 @{{ total }} 条记录
+ + +
+
+
+
+ +@push('body_end') + +@endpush diff --git a/resources/views/business/pages/supplier-payables/return-unpriced.blade.php b/resources/views/business/pages/supplier-payables/return-unpriced.blade.php new file mode 100644 index 00000000..07f7a6ec --- /dev/null +++ b/resources/views/business/pages/supplier-payables/return-unpriced.blade.php @@ -0,0 +1,100 @@ +
+
+
+ + 数据源:finance_purchaseReturn 中 priceB = 0 的记录(预估单价未维护),共 35,928 条。 +
+ + {{-- 统计卡片 --}} +
+
+
未定价退货金额
+
@{{ formatMoney(summary.totalAmount) }}
+
+
+
未定价记录数
+
@{{ (summary.totalCount || 0).toLocaleString() }}
+
+
+ + {{-- 筛选栏 --}} +
+
+ + + + + + + + + 查询 + 重置 +
+
+ + {{-- 数据表格 --}} +
+
+ 加载中... +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
共 @{{ total }} 条记录
+ + +
+
+
+
+ +@push('body_end') + +@endpush diff --git a/webpack.mix.js b/webpack.mix.js index dbba92a9..54f726e3 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -61,6 +61,18 @@ mix // Modules .js('resources/assets/js/views/modules/apps.js', 'public/js/modules/apps.min.js') + // Ecommerce Finance + .js('resources/assets/js/views/ecommerce-finance/goods-reconciliations.js', 'public/js/ecommerce-finance/goods-reconciliations.min.js') + .js('resources/assets/js/views/ecommerce-finance/return-reconciliations.js', 'public/js/ecommerce-finance/return-reconciliations.min.js') + .js('resources/assets/js/views/ecommerce-finance/invoice-deductions.js', 'public/js/ecommerce-finance/invoice-deductions.min.js') + .js('resources/assets/js/views/ecommerce-finance/compensation-details.js', 'public/js/ecommerce-finance/compensation-details.min.js') + .js('resources/assets/js/views/ecommerce-finance/advance-details.js', 'public/js/ecommerce-finance/advance-details.min.js') + .js('resources/assets/js/views/ecommerce-finance/sample-deductions.js', 'public/js/ecommerce-finance/sample-deductions.min.js') + .js('resources/assets/js/views/ecommerce-finance/delivery-unpriced.js', 'public/js/ecommerce-finance/delivery-unpriced.min.js') + .js('resources/assets/js/views/ecommerce-finance/return-unpriced.js', 'public/js/ecommerce-finance/return-unpriced.min.js') + .js('resources/assets/js/views/ecommerce-finance/other-adjustments.js', 'public/js/ecommerce-finance/other-adjustments.min.js') + .js('resources/assets/js/views/ecommerce-finance/dropshipping-express.js', 'public/js/ecommerce-finance/dropshipping-express.min.js') + // Portal .js('resources/assets/js/views/portal/apps.js', 'public/js/portal/apps.min.js')