[PROMOTE] 晉升 dev 到 demo

1. [FIX] 修正商品與機台報表因 order_items 與 dispense_records 聯結產生笛卡爾積導致件數重複加總的問題
This commit is contained in:
sky121113 2026-06-05 21:57:02 +08:00
commit b8fea39e2b

View File

@ -99,6 +99,15 @@ class AnalysisController extends Controller
42 => __('Pickup Voucher'), 42 => __('Pickup Voucher'),
]; ];
// 子查詢:取得每個 order_id + product_id 的銷售單價
$itemPriceQuery = \Illuminate\Support\Facades\DB::table('order_items')
->select([
'order_id',
'product_id',
\Illuminate\Support\Facades\DB::raw('MIN(price) as unit_price')
])
->groupBy('order_id', 'product_id');
// 6. 執行主報表統計 SQL (按機台分組) // 6. 執行主報表統計 SQL (按機台分組)
// 子查詢 A營業額 (計算機台實收) // 子查詢 A營業額 (計算機台實收)
$salesQuery = \Illuminate\Support\Facades\DB::table('orders as o') $salesQuery = \Illuminate\Support\Facades\DB::table('orders as o')
@ -130,16 +139,18 @@ class AnalysisController extends Controller
// 如果篩選特定商品,營業額只計算有成功出貨的該商品金額 // 如果篩選特定商品,營業額只計算有成功出貨的該商品金額
if ($selectedProductId && $selectedProductId !== 'ALL') { if ($selectedProductId && $selectedProductId !== 'ALL') {
$salesQuery->join('order_items as oi', 'oi.order_id', '=', 'o.id') $salesQuery->join('dispense_records as dr', function($join) {
->leftJoin('dispense_records as dr', function($join) { $join->on('dr.order_id', '=', 'o.id')
$join->on('dr.order_id', '=', 'oi.order_id')
->on('dr.product_id', '=', 'oi.product_id')
->where('dr.dispense_status', '=', 1); ->where('dr.dispense_status', '=', 1);
}) })
->where('oi.product_id', $selectedProductId) ->leftJoinSub($itemPriceQuery, 'oi', function($join) {
$join->on('oi.order_id', '=', 'dr.order_id')
->on('oi.product_id', '=', 'dr.product_id');
})
->where('dr.product_id', $selectedProductId)
->select([ ->select([
'o.machine_id', 'o.machine_id',
\Illuminate\Support\Facades\DB::raw('SUM(CASE WHEN dr.id IS NOT NULL THEN oi.subtotal ELSE 0 END) as total_sales') \Illuminate\Support\Facades\DB::raw('SUM(COALESCE(oi.unit_price, 0)) as total_sales')
]); ]);
} }
$salesQuery->groupBy('o.machine_id'); $salesQuery->groupBy('o.machine_id');
@ -269,16 +280,18 @@ class AnalysisController extends Controller
// 如果篩選特定商品,營業額只計算有成功出貨的商品價格 // 如果篩選特定商品,營業額只計算有成功出貨的商品價格
if ($selectedProductId && $selectedProductId !== 'ALL') { if ($selectedProductId && $selectedProductId !== 'ALL') {
$trendSalesQuery->join('order_items as oi', 'oi.order_id', '=', 'o.id') $trendSalesQuery->join('dispense_records as dr', function($join) {
->leftJoin('dispense_records as dr', function($join) { $join->on('dr.order_id', '=', 'o.id')
$join->on('dr.order_id', '=', 'oi.order_id')
->on('dr.product_id', '=', 'oi.product_id')
->where('dr.dispense_status', '=', 1); ->where('dr.dispense_status', '=', 1);
}) })
->where('oi.product_id', $selectedProductId) ->leftJoinSub($itemPriceQuery, 'oi', function($join) {
$join->on('oi.order_id', '=', 'dr.order_id')
->on('oi.product_id', '=', 'dr.product_id');
})
->where('dr.product_id', $selectedProductId)
->select([ ->select([
\Illuminate\Support\Facades\DB::raw("DATE_FORMAT(o.created_at, '%Y-%m-%d') as date"), \Illuminate\Support\Facades\DB::raw("DATE_FORMAT(o.created_at, '%Y-%m-%d') as date"),
\Illuminate\Support\Facades\DB::raw('SUM(CASE WHEN dr.id IS NOT NULL THEN oi.subtotal ELSE 0 END) as total_sales') \Illuminate\Support\Facades\DB::raw('SUM(COALESCE(oi.unit_price, 0)) as total_sales')
]); ]);
} }
$trendSalesQuery->groupBy('date'); $trendSalesQuery->groupBy('date');
@ -449,24 +462,35 @@ class AnalysisController extends Controller
$products = $productsQuery->active()->get(['id', 'name', 'barcode']); $products = $productsQuery->active()->get(['id', 'name', 'barcode']);
// 5. 執行主報表統計 SQL // 5. 執行主報表統計 SQL
// 計算各商品的銷售數量、銷售額、總成本、利潤 // 子查詢:取得每個 order_id + product_id 的銷售單價與品名
$mainQuery = \Illuminate\Support\Facades\DB::table('order_items as oi') $itemPriceQuery = \Illuminate\Support\Facades\DB::table('order_items')
->join('orders as o', 'o.id', '=', 'oi.order_id')
->leftJoin('dispense_records as dr', function($join) {
$join->on('dr.order_id', '=', 'oi.order_id')
->on('dr.product_id', '=', 'oi.product_id')
->where('dr.dispense_status', '=', 1);
})
->leftJoin('products as p', 'p.id', '=', 'oi.product_id')
->select([ ->select([
'oi.product_id', 'order_id',
'product_id',
\Illuminate\Support\Facades\DB::raw('MIN(price) as unit_price'),
\Illuminate\Support\Facades\DB::raw('MIN(barcode) as barcode'),
\Illuminate\Support\Facades\DB::raw('MIN(product_name) as product_name')
])
->groupBy('order_id', 'product_id');
// 計算各商品的銷售數量、銷售額、總成本、利潤 (以出貨成功為主體)
$mainQuery = \Illuminate\Support\Facades\DB::table('dispense_records as dr')
->join('orders as o', 'o.id', '=', 'dr.order_id')
->leftJoinSub($itemPriceQuery, 'oi', function($join) {
$join->on('oi.order_id', '=', 'dr.order_id')
->on('oi.product_id', '=', 'dr.product_id');
})
->leftJoin('products as p', 'p.id', '=', 'dr.product_id')
->select([
'dr.product_id',
\Illuminate\Support\Facades\DB::raw('COALESCE(p.barcode, oi.barcode) as barcode'), \Illuminate\Support\Facades\DB::raw('COALESCE(p.barcode, oi.barcode) as barcode'),
\Illuminate\Support\Facades\DB::raw('COALESCE(p.name, oi.product_name) as name'), \Illuminate\Support\Facades\DB::raw('COALESCE(p.name, oi.product_name) as name'),
\Illuminate\Support\Facades\DB::raw('CAST(SUM(CASE WHEN dr.id IS NOT NULL THEN 1 ELSE 0 END) as SIGNED) as total_quantity'), \Illuminate\Support\Facades\DB::raw('CAST(COUNT(dr.id) as SIGNED) as total_quantity'),
\Illuminate\Support\Facades\DB::raw('CAST(SUM(CASE WHEN dr.id IS NOT NULL THEN oi.price ELSE 0 END) as DECIMAL(10,2)) as total_sales'), \Illuminate\Support\Facades\DB::raw('CAST(SUM(COALESCE(oi.unit_price, 0)) as DECIMAL(10,2)) as total_sales'),
\Illuminate\Support\Facades\DB::raw('CAST(SUM(CASE WHEN dr.id IS NOT NULL THEN COALESCE(p.cost, 0) ELSE 0 END) as DECIMAL(10,2)) as total_cost'), \Illuminate\Support\Facades\DB::raw('CAST(SUM(COALESCE(p.cost, 0)) as DECIMAL(10,2)) as total_cost'),
\Illuminate\Support\Facades\DB::raw('CAST((SUM(CASE WHEN dr.id IS NOT NULL THEN oi.price ELSE 0 END) - SUM(CASE WHEN dr.id IS NOT NULL THEN COALESCE(p.cost, 0) ELSE 0 END)) as DECIMAL(10,2)) as total_profit') \Illuminate\Support\Facades\DB::raw('CAST((SUM(COALESCE(oi.unit_price, 0)) - SUM(COALESCE(p.cost, 0))) as DECIMAL(10,2)) as total_profit')
]) ])
->where('dr.dispense_status', 1)
->where('o.payment_status', \App\Models\Transaction\Order::PAYMENT_STATUS_SUCCESS) ->where('o.payment_status', \App\Models\Transaction\Order::PAYMENT_STATUS_SUCCESS)
->whereNull('o.deleted_at') ->whereNull('o.deleted_at')
->whereBetween('o.created_at', [$startDate, $endDate]); ->whereBetween('o.created_at', [$startDate, $endDate]);
@ -492,10 +516,10 @@ class AnalysisController extends Controller
// 套用特定商品篩選 // 套用特定商品篩選
if ($selectedProductId && $selectedProductId !== 'ALL') { if ($selectedProductId && $selectedProductId !== 'ALL') {
$mainQuery->where('oi.product_id', $selectedProductId); $mainQuery->where('dr.product_id', $selectedProductId);
} }
$mainQuery->groupBy('oi.product_id') $mainQuery->groupBy('dr.product_id')
->groupBy(\Illuminate\Support\Facades\DB::raw('COALESCE(p.barcode, oi.barcode)')) ->groupBy(\Illuminate\Support\Facades\DB::raw('COALESCE(p.barcode, oi.barcode)'))
->groupBy(\Illuminate\Support\Facades\DB::raw('COALESCE(p.name, oi.product_name)')); ->groupBy(\Illuminate\Support\Facades\DB::raw('COALESCE(p.name, oi.product_name)'));
$tableData = $mainQuery->get(); $tableData = $mainQuery->get();
@ -526,20 +550,20 @@ class AnalysisController extends Controller
]; ];
// 7. 圖表日走勢統計 SQL // 7. 圖表日走勢統計 SQL
$trendQuery = \Illuminate\Support\Facades\DB::table('order_items as oi') $trendQuery = \Illuminate\Support\Facades\DB::table('dispense_records as dr')
->join('orders as o', 'o.id', '=', 'oi.order_id') ->join('orders as o', 'o.id', '=', 'dr.order_id')
->leftJoin('dispense_records as dr', function($join) { ->leftJoinSub($itemPriceQuery, 'oi', function($join) {
$join->on('dr.order_id', '=', 'oi.order_id') $join->on('oi.order_id', '=', 'dr.order_id')
->on('dr.product_id', '=', 'oi.product_id') ->on('oi.product_id', '=', 'dr.product_id');
->where('dr.dispense_status', '=', 1);
}) })
->leftJoin('products as p', 'p.id', '=', 'oi.product_id') ->leftJoin('products as p', 'p.id', '=', 'dr.product_id')
->select([ ->select([
\Illuminate\Support\Facades\DB::raw("DATE_FORMAT(o.created_at, '%Y-%m-%d') as date"), \Illuminate\Support\Facades\DB::raw("DATE_FORMAT(o.created_at, '%Y-%m-%d') as date"),
\Illuminate\Support\Facades\DB::raw('CAST(SUM(CASE WHEN dr.id IS NOT NULL THEN 1 ELSE 0 END) as SIGNED) as total_quantity'), \Illuminate\Support\Facades\DB::raw('CAST(COUNT(dr.id) as SIGNED) as total_quantity'),
\Illuminate\Support\Facades\DB::raw('CAST(SUM(CASE WHEN dr.id IS NOT NULL THEN oi.price ELSE 0 END) as DECIMAL(10,2)) as total_sales'), \Illuminate\Support\Facades\DB::raw('CAST(SUM(COALESCE(oi.unit_price, 0)) as DECIMAL(10,2)) as total_sales'),
\Illuminate\Support\Facades\DB::raw('CAST(SUM(CASE WHEN dr.id IS NOT NULL THEN COALESCE(p.cost, 0) ELSE 0 END) as DECIMAL(10,2)) as total_cost') \Illuminate\Support\Facades\DB::raw('CAST(SUM(COALESCE(p.cost, 0)) as DECIMAL(10,2)) as total_cost')
]) ])
->where('dr.dispense_status', 1)
->where('o.payment_status', \App\Models\Transaction\Order::PAYMENT_STATUS_SUCCESS) ->where('o.payment_status', \App\Models\Transaction\Order::PAYMENT_STATUS_SUCCESS)
->whereNull('o.deleted_at') ->whereNull('o.deleted_at')
->whereBetween('o.created_at', [$startDate, $endDate]); ->whereBetween('o.created_at', [$startDate, $endDate]);
@ -561,7 +585,7 @@ class AnalysisController extends Controller
} }
if ($selectedProductId && $selectedProductId !== 'ALL') { if ($selectedProductId && $selectedProductId !== 'ALL') {
$trendQuery->where('oi.product_id', $selectedProductId); $trendQuery->where('dr.product_id', $selectedProductId);
} }
$trendQuery->groupBy('date')->orderBy('date', 'asc'); $trendQuery->groupBy('date')->orderBy('date', 'asc');