[RELEASE] 晉升 demo 到 main 正式上線
1. [FEAT] 重構商品與機台報表統計邏輯,對齊實際成功出貨件數 2. [FIX] 修正機台報表因 joinSub (INNER JOIN) 漏掉無付費營業額機台出貨數量的問題 3. [FIX] 修正商品與機台報表因 order_items 與 dispense_records 聯結產生笛卡爾積導致件數重複加總的問題 4. [PROMOTE] 同步部署工作流 .gitea/workflows/deploy-prod.yaml
This commit is contained in:
commit
954e2ceedd
@ -31,21 +31,9 @@ jobs:
|
||||
docker run --rm -v /home/twsystem1003/star-cloud:/target busybox mkdir -p /target/docker/emqx
|
||||
cat docker/emqx/acl.conf | docker run -i --rm -v /home/twsystem1003/star-cloud:/target busybox sh -c "cat > /target/docker/emqx/acl.conf"
|
||||
|
||||
- name: Step 1 - Ensure Existing Containers Are Running
|
||||
- name: Step 1 - Build & Deploy Containers
|
||||
run: |
|
||||
for container in \
|
||||
star-cloud-mysql \
|
||||
star-cloud-redis \
|
||||
star-cloud-emqx
|
||||
do
|
||||
if ! docker inspect "${container}" >/dev/null 2>&1; then
|
||||
echo ">>> Required container is missing: ${container}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">>> Ensuring ${container} is running..."
|
||||
docker start "${container}" >/dev/null
|
||||
done
|
||||
WWWGROUP=1001 WWWUSER=1001 docker-compose up -d --build
|
||||
|
||||
for container in star-cloud-mysql star-cloud-redis star-cloud-emqx; do
|
||||
echo ">>> Waiting for ${container} to become healthy..."
|
||||
@ -68,21 +56,6 @@ jobs:
|
||||
done
|
||||
done
|
||||
|
||||
for container in \
|
||||
star-cloud-laravel \
|
||||
star-cloud-queue \
|
||||
star-cloud-mqtt-worker \
|
||||
star-cloud-mqtt-gateway
|
||||
do
|
||||
if ! docker inspect "${container}" >/dev/null 2>&1; then
|
||||
echo ">>> Required container is missing: ${container}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ">>> Ensuring ${container} is running..."
|
||||
docker start "${container}" >/dev/null
|
||||
done
|
||||
|
||||
- name: Step 2 - Deploy Code
|
||||
run: |
|
||||
echo ">>> Syncing code to star-cloud-laravel..."
|
||||
|
||||
@ -99,56 +99,129 @@ 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 (按機台分組)
|
||||
$mainQuery = \Illuminate\Support\Facades\DB::table('order_items as oi')
|
||||
->join('orders as o', 'o.id', '=', 'oi.order_id')
|
||||
->leftJoin('machines as m', 'm.id', '=', 'o.machine_id')
|
||||
->leftJoin('products as p', 'p.id', '=', 'oi.product_id')
|
||||
// 子查詢 A:營業額 (計算機台實收)
|
||||
$salesQuery = \Illuminate\Support\Facades\DB::table('orders as o')
|
||||
->select([
|
||||
'o.machine_id',
|
||||
\Illuminate\Support\Facades\DB::raw('COALESCE(m.serial_no, "UNKNOWN") as serial_no'),
|
||||
\Illuminate\Support\Facades\DB::raw('COALESCE(m.name, "未知機台") as name'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.quantity) as total_quantity'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.subtotal) as total_sales'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.quantity * COALESCE(p.cost, 0)) as total_cost'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.subtotal) - SUM(oi.quantity * COALESCE(p.cost, 0)) as total_profit')
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(o.pay_amount) as total_sales')
|
||||
])
|
||||
->where('o.payment_status', \App\Models\Transaction\Order::PAYMENT_STATUS_SUCCESS)
|
||||
->whereNull('o.deleted_at')
|
||||
->whereBetween('o.created_at', [$startDate, $endDate]);
|
||||
|
||||
// 套用公司篩選
|
||||
// 套用篩選條件到營業額子查詢
|
||||
if ($effectiveCompanyId) {
|
||||
$mainQuery->where('o.company_id', $effectiveCompanyId);
|
||||
$salesQuery->where('o.company_id', $effectiveCompanyId);
|
||||
}
|
||||
if ($allowedMachineIds !== null) {
|
||||
if (empty($allowedMachineIds)) {
|
||||
$salesQuery->whereRaw('1 = 0');
|
||||
} else {
|
||||
$salesQuery->whereIn('o.machine_id', $allowedMachineIds);
|
||||
}
|
||||
}
|
||||
if ($selectedMachineId && $selectedMachineId !== 'ALL') {
|
||||
$salesQuery->where('o.machine_id', $selectedMachineId);
|
||||
}
|
||||
if (!empty($paymentTypes)) {
|
||||
$salesQuery->whereIn('o.payment_type', $paymentTypes);
|
||||
}
|
||||
|
||||
// 套用機台權限限制
|
||||
// 如果篩選特定商品,營業額只計算有成功出貨的該商品金額
|
||||
if ($selectedProductId && $selectedProductId !== 'ALL') {
|
||||
$salesQuery->join('dispense_records as dr', function($join) {
|
||||
$join->on('dr.order_id', '=', 'o.id')
|
||||
->where('dr.dispense_status', '=', 1);
|
||||
})
|
||||
->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(COALESCE(oi.unit_price, 0)) as total_sales')
|
||||
]);
|
||||
}
|
||||
$salesQuery->groupBy('o.machine_id');
|
||||
|
||||
// 子查詢 B:實際出貨件數與成本 (僅統計出貨成功且在範圍內)
|
||||
$dispenseQuery = \Illuminate\Support\Facades\DB::table('dispense_records as dr')
|
||||
->join('orders as o', 'o.id', '=', 'dr.order_id')
|
||||
->leftJoin('products as p', 'p.id', '=', 'dr.product_id')
|
||||
->select([
|
||||
'dr.machine_id',
|
||||
\Illuminate\Support\Facades\DB::raw('COUNT(dr.id) as total_quantity'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(COALESCE(p.cost, 0)) as total_cost')
|
||||
])
|
||||
->where('dr.dispense_status', 1)
|
||||
->whereNull('o.deleted_at')
|
||||
->whereBetween('o.created_at', [$startDate, $endDate]);
|
||||
|
||||
// 套用篩選條件到出貨子查詢
|
||||
if ($effectiveCompanyId) {
|
||||
$dispenseQuery->where('o.company_id', $effectiveCompanyId);
|
||||
}
|
||||
if ($allowedMachineIds !== null) {
|
||||
if (empty($allowedMachineIds)) {
|
||||
$dispenseQuery->whereRaw('1 = 0');
|
||||
} else {
|
||||
$dispenseQuery->whereIn('o.machine_id', $allowedMachineIds);
|
||||
}
|
||||
}
|
||||
if ($selectedMachineId && $selectedMachineId !== 'ALL') {
|
||||
$dispenseQuery->where('o.machine_id', $selectedMachineId);
|
||||
}
|
||||
if ($selectedProductId && $selectedProductId !== 'ALL') {
|
||||
$dispenseQuery->where('dr.product_id', $selectedProductId);
|
||||
}
|
||||
if (!empty($paymentTypes)) {
|
||||
$dispenseQuery->whereIn('o.payment_type', $paymentTypes);
|
||||
}
|
||||
$dispenseQuery->groupBy('dr.machine_id');
|
||||
|
||||
// 聯結機台與子查詢,取得最終統計資料
|
||||
$mainQuery = \Illuminate\Support\Facades\DB::table('machines as m')
|
||||
->leftJoinSub($salesQuery, 'sq', 'sq.machine_id', '=', 'm.id')
|
||||
->leftJoinSub($dispenseQuery, 'dq', 'dq.machine_id', '=', 'm.id')
|
||||
->select([
|
||||
'm.id as machine_id',
|
||||
\Illuminate\Support\Facades\DB::raw('COALESCE(m.serial_no, "UNKNOWN") as serial_no'),
|
||||
\Illuminate\Support\Facades\DB::raw('COALESCE(m.name, "未知機台") as name'),
|
||||
\Illuminate\Support\Facades\DB::raw('CAST(COALESCE(dq.total_quantity, 0) as SIGNED) as total_quantity'),
|
||||
\Illuminate\Support\Facades\DB::raw('CAST(COALESCE(sq.total_sales, 0) as DECIMAL(10,2)) as total_sales'),
|
||||
\Illuminate\Support\Facades\DB::raw('CAST(COALESCE(dq.total_cost, 0) as DECIMAL(10,2)) as total_cost'),
|
||||
\Illuminate\Support\Facades\DB::raw('CAST((COALESCE(sq.total_sales, 0) - COALESCE(dq.total_cost, 0)) as DECIMAL(10,2)) as total_profit')
|
||||
])
|
||||
->where(function($query) {
|
||||
$query->whereNotNull('sq.total_sales')
|
||||
->orWhereNotNull('dq.total_quantity');
|
||||
});
|
||||
|
||||
if ($effectiveCompanyId) {
|
||||
$mainQuery->where('m.company_id', $effectiveCompanyId);
|
||||
}
|
||||
if ($allowedMachineIds !== null) {
|
||||
if (empty($allowedMachineIds)) {
|
||||
$mainQuery->whereRaw('1 = 0');
|
||||
} else {
|
||||
$mainQuery->whereIn('o.machine_id', $allowedMachineIds);
|
||||
$mainQuery->whereIn('m.id', $allowedMachineIds);
|
||||
}
|
||||
}
|
||||
|
||||
// 套用特定機台篩選
|
||||
if ($selectedMachineId && $selectedMachineId !== 'ALL') {
|
||||
$mainQuery->where('o.machine_id', $selectedMachineId);
|
||||
$mainQuery->where('m.id', $selectedMachineId);
|
||||
}
|
||||
|
||||
// 套用特定商品篩選
|
||||
if ($selectedProductId && $selectedProductId !== 'ALL') {
|
||||
$mainQuery->where('oi.product_id', $selectedProductId);
|
||||
}
|
||||
|
||||
// 套用支付工具多選篩選
|
||||
if (!empty($paymentTypes)) {
|
||||
$mainQuery->whereIn('o.payment_type', $paymentTypes);
|
||||
}
|
||||
|
||||
$mainQuery->groupBy('o.machine_id')
|
||||
->groupBy(\Illuminate\Support\Facades\DB::raw('COALESCE(m.serial_no, "UNKNOWN")'))
|
||||
->groupBy(\Illuminate\Support\Facades\DB::raw('COALESCE(m.name, "未知機台")'));
|
||||
$tableData = $mainQuery->get();
|
||||
|
||||
// 7. 計算總計 Summary
|
||||
@ -176,46 +249,103 @@ class AnalysisController extends Controller
|
||||
'total_profit' => round($totalProfit, 2),
|
||||
];
|
||||
|
||||
// 8. 圖表日走勢統計 SQL
|
||||
$trendQuery = \Illuminate\Support\Facades\DB::table('order_items as oi')
|
||||
->join('orders as o', 'o.id', '=', 'oi.order_id')
|
||||
->leftJoin('products as p', 'p.id', '=', 'oi.product_id')
|
||||
// 8. 圖表日走勢統計
|
||||
// 每日營業額
|
||||
$trendSalesQuery = \Illuminate\Support\Facades\DB::table('orders as o')
|
||||
->select([
|
||||
\Illuminate\Support\Facades\DB::raw("DATE_FORMAT(o.created_at, '%Y-%m-%d') as date"),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.quantity) as total_quantity'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.subtotal) as total_sales'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.quantity * COALESCE(p.cost, 0)) as total_cost'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(o.pay_amount) as total_sales')
|
||||
])
|
||||
->where('o.payment_status', \App\Models\Transaction\Order::PAYMENT_STATUS_SUCCESS)
|
||||
->whereNull('o.deleted_at')
|
||||
->whereBetween('o.created_at', [$startDate, $endDate]);
|
||||
|
||||
// 套用篩選到走勢營業額
|
||||
if ($effectiveCompanyId) {
|
||||
$trendQuery->where('o.company_id', $effectiveCompanyId);
|
||||
$trendSalesQuery->where('o.company_id', $effectiveCompanyId);
|
||||
}
|
||||
|
||||
if ($allowedMachineIds !== null) {
|
||||
if (empty($allowedMachineIds)) {
|
||||
$trendQuery->whereRaw('1 = 0');
|
||||
$trendSalesQuery->whereRaw('1 = 0');
|
||||
} else {
|
||||
$trendQuery->whereIn('o.machine_id', $allowedMachineIds);
|
||||
$trendSalesQuery->whereIn('o.machine_id', $allowedMachineIds);
|
||||
}
|
||||
}
|
||||
|
||||
if ($selectedMachineId && $selectedMachineId !== 'ALL') {
|
||||
$trendQuery->where('o.machine_id', $selectedMachineId);
|
||||
$trendSalesQuery->where('o.machine_id', $selectedMachineId);
|
||||
}
|
||||
|
||||
if ($selectedProductId && $selectedProductId !== 'ALL') {
|
||||
$trendQuery->where('oi.product_id', $selectedProductId);
|
||||
}
|
||||
|
||||
if (!empty($paymentTypes)) {
|
||||
$trendQuery->whereIn('o.payment_type', $paymentTypes);
|
||||
$trendSalesQuery->whereIn('o.payment_type', $paymentTypes);
|
||||
}
|
||||
|
||||
$trendQuery->groupBy('date')->orderBy('date', 'asc');
|
||||
$trendData = $trendQuery->get();
|
||||
// 如果篩選特定商品,營業額只計算有成功出貨的商品價格
|
||||
if ($selectedProductId && $selectedProductId !== 'ALL') {
|
||||
$trendSalesQuery->join('dispense_records as dr', function($join) {
|
||||
$join->on('dr.order_id', '=', 'o.id')
|
||||
->where('dr.dispense_status', '=', 1);
|
||||
})
|
||||
->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(COALESCE(oi.unit_price, 0)) as total_sales')
|
||||
]);
|
||||
}
|
||||
$trendSalesQuery->groupBy('date');
|
||||
$trendSalesData = $trendSalesQuery->get();
|
||||
|
||||
// 每日出貨件數與成本
|
||||
$trendDispenseQuery = \Illuminate\Support\Facades\DB::table('dispense_records as dr')
|
||||
->join('orders as o', 'o.id', '=', 'dr.order_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('COUNT(dr.id) as total_quantity'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(COALESCE(p.cost, 0)) as total_cost')
|
||||
])
|
||||
->where('dr.dispense_status', 1)
|
||||
->whereNull('o.deleted_at')
|
||||
->whereBetween('o.created_at', [$startDate, $endDate]);
|
||||
|
||||
// 套用篩選到走勢出貨
|
||||
if ($effectiveCompanyId) {
|
||||
$trendDispenseQuery->where('o.company_id', $effectiveCompanyId);
|
||||
}
|
||||
if ($allowedMachineIds !== null) {
|
||||
if (empty($allowedMachineIds)) {
|
||||
$trendDispenseQuery->whereRaw('1 = 0');
|
||||
} else {
|
||||
$trendDispenseQuery->whereIn('o.machine_id', $allowedMachineIds);
|
||||
}
|
||||
}
|
||||
if ($selectedMachineId && $selectedMachineId !== 'ALL') {
|
||||
$trendDispenseQuery->where('o.machine_id', $selectedMachineId);
|
||||
}
|
||||
if ($selectedProductId && $selectedProductId !== 'ALL') {
|
||||
$trendDispenseQuery->where('dr.product_id', $selectedProductId);
|
||||
}
|
||||
if (!empty($paymentTypes)) {
|
||||
$trendDispenseQuery->whereIn('o.payment_type', $paymentTypes);
|
||||
}
|
||||
$trendDispenseQuery->groupBy('date');
|
||||
$trendDispenseData = $trendDispenseQuery->get();
|
||||
|
||||
// 將子查詢結果轉換為 PHP Map 對照
|
||||
$salesMap = [];
|
||||
foreach ($trendSalesData as $row) {
|
||||
$salesMap[$row->date] = $row->total_sales;
|
||||
}
|
||||
|
||||
$dispenseMap = [];
|
||||
foreach ($trendDispenseData as $row) {
|
||||
$dispenseMap[$row->date] = [
|
||||
'quantity' => $row->total_quantity,
|
||||
'cost' => $row->total_cost
|
||||
];
|
||||
}
|
||||
|
||||
// 補齊日期區間內所有空缺日走勢
|
||||
$period = new \DatePeriod(
|
||||
@ -227,21 +357,16 @@ class AnalysisController extends Controller
|
||||
$chartData = [];
|
||||
foreach ($period as $date) {
|
||||
$dateStr = $date->format('Y-m-d');
|
||||
$hasSales = isset($salesMap[$dateStr]);
|
||||
$hasDispense = isset($dispenseMap[$dateStr]);
|
||||
|
||||
$chartData[$dateStr] = [
|
||||
'date' => $dateStr,
|
||||
'quantity' => 0,
|
||||
'sales' => 0.0,
|
||||
'cost' => 0.0,
|
||||
'quantity' => $hasDispense ? (int)$dispenseMap[$dateStr]['quantity'] : 0,
|
||||
'sales' => $hasSales ? round((float)$salesMap[$dateStr], 2) : 0.0,
|
||||
'cost' => $hasDispense ? round((float)$dispenseMap[$dateStr]['cost'], 2) : 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($trendData as $row) {
|
||||
if (isset($chartData[$row->date])) {
|
||||
$chartData[$row->date]['quantity'] = (int)$row->total_quantity;
|
||||
$chartData[$row->date]['sales'] = round((float)$row->total_sales, 2);
|
||||
$chartData[$row->date]['cost'] = round((float)$row->total_cost, 2);
|
||||
}
|
||||
}
|
||||
$chartData = array_values($chartData);
|
||||
|
||||
// 9. 判斷是否為 AJAX 請求,回傳 JSON 格式
|
||||
@ -337,19 +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('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('SUM(oi.quantity) as total_quantity'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.subtotal) as total_sales'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.quantity * COALESCE(p.cost, 0)) as total_cost'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.subtotal) - SUM(oi.quantity * COALESCE(p.cost, 0)) 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]);
|
||||
@ -375,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();
|
||||
@ -409,15 +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('products as p', 'p.id', '=', 'oi.product_id')
|
||||
$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', '=', 'dr.product_id')
|
||||
->select([
|
||||
\Illuminate\Support\Facades\DB::raw("DATE_FORMAT(o.created_at, '%Y-%m-%d') as date"),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.quantity) as total_quantity'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.subtotal) as total_sales'),
|
||||
\Illuminate\Support\Facades\DB::raw('SUM(oi.quantity * COALESCE(p.cost, 0)) 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]);
|
||||
@ -439,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');
|
||||
|
||||
@ -8,6 +8,7 @@ use App\Models\Machine\Machine;
|
||||
use App\Models\Product\Product;
|
||||
use App\Models\Transaction\Order;
|
||||
use App\Models\Transaction\OrderItem;
|
||||
use App\Models\Transaction\DispenseRecord;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
@ -143,6 +144,27 @@ class AnalysisReportsTest extends TestCase
|
||||
'subtotal' => 30.00,
|
||||
]);
|
||||
|
||||
DispenseRecord::create([
|
||||
'company_id' => $this->company->id,
|
||||
'order_id' => $order1->id,
|
||||
'machine_id' => $this->machineA->id,
|
||||
'slot_no' => '1',
|
||||
'product_id' => $this->productX->id,
|
||||
'amount' => 15.00,
|
||||
'dispense_status' => 1,
|
||||
'machine_time' => now(),
|
||||
]);
|
||||
DispenseRecord::create([
|
||||
'company_id' => $this->company->id,
|
||||
'order_id' => $order1->id,
|
||||
'machine_id' => $this->machineA->id,
|
||||
'slot_no' => '1',
|
||||
'product_id' => $this->productX->id,
|
||||
'amount' => 15.00,
|
||||
'dispense_status' => 1,
|
||||
'machine_time' => now(),
|
||||
]);
|
||||
|
||||
// Order 2: Machine B, Product Y, payment type 2 (E-ticket), price 25, quantity 1. (Sales: 25, Cost: 20, Profit: 5)
|
||||
$order2 = Order::create([
|
||||
'company_id' => $this->company->id,
|
||||
@ -163,6 +185,17 @@ class AnalysisReportsTest extends TestCase
|
||||
'price' => 25.00,
|
||||
'subtotal' => 25.00,
|
||||
]);
|
||||
|
||||
DispenseRecord::create([
|
||||
'company_id' => $this->company->id,
|
||||
'order_id' => $order2->id,
|
||||
'machine_id' => $this->machineB->id,
|
||||
'slot_no' => '1',
|
||||
'product_id' => $this->productY->id,
|
||||
'amount' => 25.00,
|
||||
'dispense_status' => 1,
|
||||
'machine_time' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user