统计优化

main
Jing Li 2024-04-24 13:09:38 +08:00
parent 302b6dd702
commit 61f229cb70
4 changed files with 161 additions and 168 deletions

View File

@ -6,10 +6,10 @@ use App\Admin\Filters\StoreFilter;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Employee; use App\Models\Employee;
use App\Models\Keyword; use App\Models\Keyword;
use App\Models\Ledger;
use App\Models\LedgerItem; use App\Models\LedgerItem;
use App\Models\Store; use App\Models\Store;
use App\Models\TaskPerformance; use App\Models\TaskPerformance;
use App\Services\StatisticService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -38,7 +38,7 @@ class CockpitController extends Controller
/** /**
* 销售趋势 * 销售趋势
*/ */
public function salesTrend(Request $request): array public function salesTrend(Request $request, StatisticService $statisticService): array
{ {
$request->validate( $request->validate(
rules: [ rules: [
@ -48,15 +48,12 @@ class CockpitController extends Controller
$last = $request->input('last'); $last = $request->input('last');
$data = collect();
if (in_array($last, ['7days', '30days'])) { if (in_array($last, ['7days', '30days'])) {
// 按天 // 按天
$days = match ($last) { $days = match ($last) {
'7days' => 7, '7days' => 7,
'30days' => 30, '30days' => 30,
}; };
// 今天 // 今天
$today = Carbon::today(); $today = Carbon::today();
// 开始时间 // 开始时间
@ -64,24 +61,7 @@ class CockpitController extends Controller
// 结束时间 // 结束时间
$endAt = $today->copy()->subDay(); $endAt = $today->copy()->subDay();
$ledgers = Ledger::select(['date', DB::raw('SUM(sales) as sales')]) return $statisticService->dailyLedgerTrend($startAt, $endAt);
->whereBetween('date', [$startAt->toDateString(), $endAt->toDateString()])
->groupBy('date')
->get()
->keyBy('date');
while ($startAt->lte($endAt)) {
$date = $startAt->toDateString();
$ledger = $ledgers->get($date);
$data->push([
'date' => $date,
'sales' => trim_zeros($ledger->sales ?? 0),
]);
$startAt->addDay();
}
} }
elseif (in_array($last, ['180days', '365days'])) { elseif (in_array($last, ['180days', '365days'])) {
// 按月 // 按月
@ -89,7 +69,6 @@ class CockpitController extends Controller
'180days' => 6, // 6个月 '180days' => 6, // 6个月
'365days' => 12, // 12个月 '365days' => 12, // 12个月
}; };
// 今天 // 今天
$today = Carbon::today(); $today = Carbon::today();
// 开始时间 // 开始时间
@ -97,27 +76,10 @@ class CockpitController extends Controller
// 结束时间 // 结束时间
$endAt = $today->copy()->startOfMonth()->subMonth()->endOfMonth(); $endAt = $today->copy()->startOfMonth()->subMonth()->endOfMonth();
$ledgers = Ledger::select([DB::raw("DATE_FORMAT(`date`, '%Y-%m') as month"), DB::raw('SUM(sales) as sales')]) return $statisticService->monthlyLedgerTrend($startAt, $endAt);
->whereBetween('date', [$startAt->toDateString(), $endAt->toDateString()])
->groupBy('month')
->get()
->keyBy('month');
for ($i=0; $i < $months; $i++) {
$month = $startAt->format('Y-m');
$ledger = $ledgers->get($month);
$data->push([
'month' => $month,
'sales' => trim_zeros($ledger->sales ?? 0),
]);
$startAt->addMonth();
}
} }
return $data->all(); return [];
} }
/** /**
@ -243,7 +205,7 @@ class CockpitController extends Controller
/** /**
* 门店销量排名 * 门店销量排名
*/ */
public function storeSalesRanking(Request $request): array public function storeSalesRanking(Request $request, StatisticService $statisticService): array
{ {
$request->validate( $request->validate(
rules: [ rules: [
@ -251,50 +213,25 @@ class CockpitController extends Controller
], ],
); );
$today = Carbon::today();
$last = $request->input('last'); $last = $request->input('last');
$input = ['sort' => '-sales'];
$storeSales = Ledger::select(['store_id', DB::raw('SUM(sales) as sales')]) if (in_array($last, ['7days', '30days'])) {
->when($last, function ($query, $last) { $days = match ($last) {
$today = Carbon::today(); '7days' => 7,
'30days' => 30,
};
$input['date_range'] = $today->copy()->subDays($days)->toDateString().','.$today->copy()->subDay()->toDateString();
} elseif (in_array($last, ['180days', '365days'])) {
$months = match ($last) {
'180days' => 6, // 6个月
'365days' => 12, // 12个月
};
$input['date_range'] = $today->copy()->startOfMonth()->subMonths($months)->toDateString().','.$today->copy()->startOfMonth()->subMonth()->endOfMonth()->toDateString();
}
if (in_array($last, ['7days', '30days'])) { return $statisticService->storeRanking($input, 30);
$days = match ($last) {
'7days' => 7,
'30days' => 30,
};
$query->whereBetween('date', [
$today->copy()->subDays($days)->toDateString(),
$today->copy()->subDay()->toDateString(),
]);
} elseif (in_array($last, ['180days', '365days'])) {
$months = match ($last) {
'180days' => 6, // 6个月
'365days' => 12, // 12个月
};
$query->whereBetween('date', [
$today->copy()->startOfMonth()->subMonths($months)->toDateString(),
$today->copy()->startOfMonth()->subMonth()->endOfMonth()->toDateString(),
]);
}
})
->groupBy('store_id');
$stores = Store::leftJoinSub($storeSales, 'store_sales', fn ($join) => $join->on('stores.id', '=', 'store_sales.store_id'))
->orderBy('store_sales.sales', 'DESC')
->limit(30)
->get();
return $stores->map(function (Store $store) {
return [
'store' => [
'id' => $store->id,
'title' => $store->title,
],
'sales' => trim_zeros($store->sales ?: 0),
];
})->all();
} }
/** /**
@ -380,6 +317,9 @@ class CockpitController extends Controller
})->all(); })->all();
} }
/**
* 门店分类
*/
public function storeCategory(Request $request): array public function storeCategory(Request $request): array
{ {
$request->validate( $request->validate(

View File

@ -4,21 +4,22 @@ namespace App\Admin\Controllers\Finance;
use App\Admin\Controllers\AdminController; use App\Admin\Controllers\AdminController;
use App\Services\StatisticService; use App\Services\StatisticService;
use Illuminate\Support\Arr;
class StoreStatisticController extends AdminController class StoreStatisticController extends AdminController
{ {
public function index() public function index()
{ {
if ($this->actionOfGetData()) { if ($this->actionOfGetData()) {
$input = request()->input(); $input = Arr::except(request()->input(), ['orderBy', 'orderDir']);
$sorts = [ $orderBy = request()->input('orderBy') ?: 'sales';
[request()->input('orderBy') ?: 'sales', request()->input('orderDir') ?: 'desc'], $orderDir = request()->input('orderDir') ?: 'desc';
['id', 'asc'],
]; $input['sort'] = ($orderDir === 'desc' ? '-' : '').$orderBy;
return $this->response()->success([ return $this->response()->success([
'items' => (new StatisticService())->stores($input, $sorts), 'items' => (new StatisticService())->storeRanking($input),
]); ]);
} }

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Services\StatisticService; use App\Services\StatisticService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
class StatisticsController extends Controller class StatisticsController extends Controller
@ -18,7 +19,7 @@ class StatisticsController extends Controller
attributes: ['date' => '日期'], attributes: ['date' => '日期'],
); );
$input = $this->defaultFilterInput($request); $input = Arr::except($this->filterInput($request), 'date');
$date = Carbon::yesterday(); $date = Carbon::yesterday();
if ($request->filled('date')) { if ($request->filled('date')) {
@ -29,9 +30,10 @@ class StatisticsController extends Controller
$monthLedger = array_merge( $monthLedger = array_merge(
['deadline' => $date->format('Y-m-d')], ['deadline' => $date->format('Y-m-d')],
$statisticService->ledger( $statisticService->ledger(
$date->copy()->startOfMonth(), array_merge($input, [
$date->copy(), 'start_at' => $date->copy()->startOfMonth()->toDateString(),
$input, 'end_at' => $date->copy()->toDateString(),
])
), ),
); );
@ -39,14 +41,15 @@ class StatisticsController extends Controller
$yesdayLedger = array_merge( $yesdayLedger = array_merge(
['date' => $date->format('Y-m-d')], ['date' => $date->format('Y-m-d')],
$statisticService->ledger( $statisticService->ledger(
$date->copy(), array_merge($input, [
$date->copy(), 'start_at' => $date->copy()->toDateString(),
$input, 'end_at' => $date->copy()->toDateString(),
])
), ),
); );
// 近30天趋势数据 // 近30天趋势数据
$trendsOf30days = $statisticService->ledgerTrends( $trendsOf30days = $statisticService->dailyLedgerTrend(
$date->copy()->subDays(29), $date->copy()->subDays(29),
$date->copy(), $date->copy(),
$input, $input,
@ -82,18 +85,20 @@ class StatisticsController extends Controller
], ],
); );
$input = $this->defaultFilterInput($request); $input = $this->filterInput($request);
$ledger = $statisticService->ledger( $ledger = $statisticService->ledger(
Carbon::parse($request->input('start_at')), array_merge($input, [
Carbon::parse($request->input('end_at')), 'start_at' => $request->input('start_at'),
$input, 'end_at' => $request->input('end_at'),
])
); );
$beforeLedger = $statisticService->ledger( $beforeLedger = $statisticService->ledger(
Carbon::parse($request->input('before_start_at')), array_merge($input, [
Carbon::parse($request->input('before_end_at')), 'start_at' => $request->input('before_start_at'),
$input, 'end_at' => $request->input('before_end_at'),
])
); );
// 销售涨幅 // 销售涨幅
@ -128,13 +133,11 @@ class StatisticsController extends Controller
); );
$input = array_merge( $input = array_merge(
$this->defaultFilterInput($request), $this->filterInput($request),
$request->only(['start_at', 'end_at']), $request->only(['start_at', 'end_at']),
); );
$sorts = [['sales', 'desc'], ['id', 'asc']]; return $statisticService->storeRanking($input);
return $statisticService->stores($input, $sorts);
} }
/** /**
@ -156,28 +159,30 @@ class StatisticsController extends Controller
return $statisticService->sales( return $statisticService->sales(
Carbon::parse($request->input('start_at')), Carbon::parse($request->input('start_at')),
Carbon::parse($request->input('end_at')), Carbon::parse($request->input('end_at')),
$this->defaultFilterInput($request), $this->filterInput($request),
); );
} }
/** /**
* 处理区域和门店过滤条件 * 处理区域和门店过滤条件
*/ */
protected function defaultFilterInput(Request $request): array protected function filterInput(Request $request): array
{ {
$input = []; $input = Arr::except($request->input(), ['store_id', 'province_code', 'city_code']);
if ($request->filled('store_id')) { if ($request->filled('store_id')) {
$input['store_id'] = $request->input('store_id'); $input['store_id'] = $request->input('store_id');
} else { } elseif ($request->anyFilled(['province_code', 'city_code'])) {
$region = []; $region = [];
if ($request->filled('province_code')) { $provinceCode = (string) $request->input('province_code');
$region['provinceCode'] = $request->input('province_code'); if ($provinceCode !== '') {
$region['provinceCode'] = $provinceCode;
} }
if ($request->filled('city_code')) { $cityCode = (string) $request->input('city_code');
$region['cityCode'] = $request->input('city_code'); if ($cityCode !== '') {
$region['cityCode'] = $provinceCode;
} }
$input['region'] = $region; $input['region'] = $region;

View File

@ -18,77 +18,99 @@ class StatisticService
/** /**
* 总账统计 * 总账统计
*/ */
public function ledger(Carbon $start, Carbon $end, array $input = []): array public function ledger(array $input = []): array
{ {
$ledger = Ledger::filter($input, LedgerFilter::class) $aggregate = Ledger::filter($input, LedgerFilter::class)
->select([DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure')]) ->select([DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure')])
->whereBetween('date', [$start->format('Y-m-d'), $end->format('Y-m-d')])
->first(); ->first();
return [ return [
'sales' => trim_zeros($ledger->sales ?? 0), 'sales' => trim_zeros($aggregate->sales ?? 0),
'expenditure' => trim_zeros($ledger->expenditure ?? 0), 'expenditure' => trim_zeros($aggregate->expenditure ?? 0),
]; ];
} }
/** /**
* 总账数据趋势 * 总账数据趋势()
*/ */
public function ledgerTrends(Carbon $start, Carbon $end, array $input = []): array public function dailyLedgerTrend(Carbon $start, Carbon $end, array $input = []): array
{ {
$ledgers = Ledger::filter($input, LedgerFilter::class) $input = array_merge($input, [
->whereBetween('date', [$start->format('Y-m-d'), $end->format('Y-m-d')]) 'date_range' => $start->toDateString().','.$end->toDateString(),
->get(['date', 'sales', 'expenditure']) ]);
$aggregates = Ledger::select(['date', DB::raw('SUM(`sales`) as `sales`'), DB::raw('SUM(`expenditure`) as `expenditure`')])
->filter($input, LedgerFilter::class)
->groupBy('date')
->get()
->keyBy('date'); ->keyBy('date');
$data = collect(); $trend = collect();
while ($start->lte($end)) { while ($start->lte($end)) {
$ledger = $ledgers->get( $date = $start->toDateString();
$date = $start->format('Y-m-d')
);
$data->push([ $aggregate = $aggregates->get($date);
$trend->push([
'date' => $date, 'date' => $date,
'sales' => trim_zeros($ledger->sales ?? 0), 'sales' => trim_zeros($aggregate->sales ?? 0),
'expenditure' => trim_zeros($ledger->expenditure ?? 0), 'expenditure' => trim_zeros($aggregate->expenditure ?? 0),
]); ]);
$start->addDay(); $start->addDay();
} }
return $data->all(); return $trend->all();
} }
/** /**
* 门店统计 * 总账数据趋势()
*/ */
public function stores(array $input = [], array $sorts = []): array public function monthlyLedgerTrend(Carbon $start, Carbon $end, array $input = []): array
{ {
$storeLedgerStats = Ledger::select(['store_id', DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure')]) $input = array_merge($input, [
->filter(Arr::only($input, ['date_range', 'start_at', 'end_at']), LedgerFilter::class) 'date_range' => $start->toDateString().','.$end->toDateString(),
->groupBy('store_id'); ]);
$stores = Store::filter(Arr::only($input, ['store_id', 'region']), StoreFilter::class) $aggregates = Ledger::select([DB::raw("DATE_FORMAT(`date`, '%Y-%m') as `month`"), DB::raw('SUM(`sales`) as `sales`'), DB::raw('SUM(`expenditure`) as `expenditure`')])
->leftJoinSub($storeLedgerStats, 'store_ledger_stats', fn ($join) => $join->on('stores.id', '=', 'store_ledger_stats.store_id')) ->filter($input, LedgerFilter::class)
->when($sorts, function ($query, $sorts) { ->groupBy('month')
foreach ($sorts as $sort) { ->get()
$query->orderBy($sort[0], $sort[1]); ->keyBy('month');
$diffMonths = 0;
if ($start->lte($end)) {
$datetime = $start->copy();
while (true) {
$diffMonths += 1;
if ($datetime->isSameMonth($end)) {
break;
} }
})
->get();
return $stores->map(function (Store $store, $key) { $datetime->addMonth();
return [ }
'ranking' => $key + 1, }
'store' => [
'id' => $store->id, $trend = collect();
'title' => $store->title,
], for ($i=0; $i < $diffMonths; $i++) {
'sales' => trim_zeros($store->sales ?: 0), $month = $start->format('Y-m');
'expenditure' => trim_zeros($store->expenditure ?: 0),
]; $aggregate = $aggregates->get($month);
})->all();
$trend->push([
'month' => $month,
'sales' => trim_zeros($aggregate->sales ?? 0),
'expenditure' => trim_zeros($aggregate->expenditure ?? 0),
]);
$start->addMonth();
}
return $trend->all();
} }
/** /**
@ -135,15 +157,6 @@ class StatisticService
/** @var \Illuminate\Support\Collection */ /** @var \Illuminate\Support\Collection */
$lotteryTypeStatistics = $ledgerItemStatistics->get($date, collect())->keyBy('ledger_item_type_id'); $lotteryTypeStatistics = $ledgerItemStatistics->get($date, collect())->keyBy('ledger_item_type_id');
$lotteryTypes->map(function ($lotteryType) use ($lotteryTypeStatistics) {
$lotteryTypeStatistic = $lotteryTypeStatistics->get($lotteryType->key);
return [
'name' => $lotteryType->name,
'sales' => trim_zeros($lotteryTypeStatistic->sales ?? 0),
'expenditure' => trim_zeros($lotteryTypeStatistic->expenditure ?? 0),
];
});
$data->push([ $data->push([
'date' => $date, 'date' => $date,
'ledger' => [ 'ledger' => [
@ -166,4 +179,38 @@ class StatisticService
return $data->all(); return $data->all();
} }
/**
* 门店排名
*/
public function storeRanking(array $input = [], int $top = 0): array
{
$storeLedgers = Ledger::select(['store_id', DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure')])
->filter(Arr::except($input, ['region']), LedgerFilter::class)
->groupBy('store_id');
$stores = Store::filter($input, StoreFilter::class)
->leftJoinSub($storeLedgers, 'store_ledgers', fn ($join) => $join->on('stores.id', '=', 'store_ledgers.store_id'))
->when($input['sort'] ?? '', function ($query, $sort) {
foreach (explode(',', $sort) as $sort) {
$column = ltrim($sort, '-');
$direction = str_starts_with($sort, '-') ? 'desc' : 'asc';
$query->orderBy($column, $direction);
}
}, fn ($query) => $query->orderBy('sales', 'desc'))
->when($top > 0, fn ($query) => $query->limit($top))
->get();
return $stores->map(function (Store $store, $key) {
return [
'ranking' => $key + 1,
'store' => [
'id' => $store->id,
'title' => $store->title,
],
'sales' => trim_zeros($store->sales ?: 0),
'expenditure' => trim_zeros($store->expenditure ?: 0),
];
})->all();
}
} }