[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'),
];
// 子查詢:取得每個 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 (按機台分組)
// 子查詢 A營業額 (計算機台實收)
$salesQuery = \Illuminate\Support\Facades\DB::table('orders as o')
@ -130,16 +139,18 @@ class AnalysisController extends Controller
// 如果篩選特定商品,營業額只計算有成功出貨的該商品金額
if ($selectedProductId && $selectedProductId !== 'ALL') {
$salesQuery->join('order_items as oi', 'oi.order_id', '=', 'o.id')
->leftJoin('dispense_records as dr', function($join) {
$join->on('dr.order_id', '=', 'oi.order_id')
->on('dr.product_id', '=', 'oi.product_id')
$salesQuery->join('dispense_records as dr', function($join) {
$join->on('dr.order_id', '=', 'o.id')
->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([
'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');
@ -269,16 +280,18 @@ class AnalysisController extends Controller
// 如果篩選特定商品,營業額只計算有成功出貨的商品價格
if ($selectedProductId && $selectedProductId !== 'ALL') {
$trendSalesQuery->join('order_items as oi', 'oi.order_id', '=', 'o.id')
->leftJoin('dispense_records as dr', function($join) {
$join->on('dr.order_id', '=', 'oi.order_id')
->on('dr.product_id', '=', 'oi.product_id')
$trendSalesQuery->join('dispense_records as dr', function($join) {
$join->on('dr.order_id', '=', 'o.id')
->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([
\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');
@ -449,24 +462,35 @@ class AnalysisController extends Controller
$products = $productsQuery->active()->get(['id', 'name', 'barcode']);
// 5. 執行主報表統計 SQL
// 計算各商品的銷售數量、銷售額、總成本、利潤
$mainQuery = \Illuminate\Support\Facades\DB::table('order_items as oi')
->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')
// 子查詢:取得每個 order_id + product_id 的銷售單價與品名
$itemPriceQuery = \Illuminate\Support\Facades\DB::table('order_items')
->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.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(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(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(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(COUNT(dr.id) as SIGNED) as total_quantity'),
\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(COALESCE(p.cost, 0)) as DECIMAL(10,2)) as total_cost'),
\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)
->whereNull('o.deleted_at')
->whereBetween('o.created_at', [$startDate, $endDate]);
@ -492,10 +516,10 @@ class AnalysisController extends Controller
// 套用特定商品篩選
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.name, oi.product_name)'));
$tableData = $mainQuery->get();
@ -526,20 +550,20 @@ class AnalysisController extends Controller
];
// 7. 圖表日走勢統計 SQL
$trendQuery = \Illuminate\Support\Facades\DB::table('order_items as oi')
->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);
$trendQuery = \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', '=', 'oi.product_id')
->leftJoin('products as p', 'p.id', '=', 'dr.product_id')
->select([
\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(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(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(COUNT(dr.id) as SIGNED) as total_quantity'),
\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(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)
->whereNull('o.deleted_at')
->whereBetween('o.created_at', [$startDate, $endDate]);
@ -561,7 +585,7 @@ class AnalysisController extends Controller
}
if ($selectedProductId && $selectedProductId !== 'ALL') {
$trendQuery->where('oi.product_id', $selectedProductId);
$trendQuery->where('dr.product_id', $selectedProductId);
}
$trendQuery->groupBy('date')->orderBy('date', 'asc');