store-manage/app/Admin/Controllers/CockpitController.php

381 lines
12 KiB
PHP

<?php
namespace App\Admin\Controllers;
use App\Admin\Filters\StoreFilter;
use App\Http\Controllers\Controller;
use App\Models\Employee;
use App\Models\Keyword;
use App\Models\LedgerItem;
use App\Models\Store;
use App\Models\TaskPerformance;
use App\Services\StatisticService;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
class CockpitController extends Controller
{
/**
* 基础数据统计
*/
public function basic(): array
{
// 门店总数
$storesCount = Store::onlyOpen()->count();
// 员工总数
$employeesCount = Employee::onlyOnline()->count();
return [
'stores_count' => $storesCount,
'employees_count' => $employeesCount,
];
}
/**
* 销售趋势
*/
public function salesTrend(Request $request, StatisticService $statisticService): array
{
$request->validate(
rules: [
'last' => ['bail', 'required', Rule::in(['7days', '30days', '180days', '365days'])],
],
);
$last = $request->input('last');
if (in_array($last, ['7days', '30days'])) {
// 按天
$days = match ($last) {
'7days' => 7,
'30days' => 30,
};
// 今天
$today = Carbon::today();
// 开始时间
$startAt = $today->copy()->subDays($days);
// 结束时间
$endAt = $today->copy()->subDay();
return $statisticService->dailyLedgerTrend($startAt, $endAt);
} elseif (in_array($last, ['180days', '365days'])) {
// 按月
$months = match ($last) {
'180days' => 6, // 6个月
'365days' => 12, // 12个月
};
// 今天
$today = Carbon::today();
// 开始时间
$startAt = $today->copy()->startOfMonth()->subMonths($months);
// 结束时间
$endAt = $today->copy()->startOfMonth()->subMonth()->endOfMonth();
return $statisticService->monthlyLedgerTrend($startAt, $endAt);
}
return [];
}
/**
* 彩种销售趋势
*/
public function lotterySalesTrend(Request $request): array
{
$request->validate(
rules: [
'last' => ['bail', 'required', Rule::in(['7days', '30days', '180days', '365days'])],
],
);
$last = $request->input('last');
$data = collect();
/** @var \Illuminate\Database\Eloquent\Collection */
$lotteryTypes = Keyword::where('parent_key', 'lottery_type')->get();
if (in_array($last, ['7days', '30days'])) {
// 按天
$days = match ($last) {
'7days' => 7,
'30days' => 30,
};
// 今天
$today = Carbon::today();
// 开始时间
$startAt = $today->copy()->subDays($days);
// 结束时间
$endAt = $today->copy()->subDay();
/** @var \Illuminate\Support\Collection */
$lotteryTypeLedgers = LedgerItem::select([
'date',
'ledger_item_type_id',
DB::raw('SUM(sales) as sales'),
])
->whereBetween('date', [$startAt->toDateString(), $endAt->toDateString()])
->groupBy(['date', 'ledger_item_type_id'])
->get()
->groupBy('date');
while ($startAt->lte($endAt)) {
$date = $startAt->toDateString();
$lotteryTypeLedgerItems = $lotteryTypeLedgers->get($date, collect())->keyBy('ledger_item_type_id');
$data->push([
'date' => $date,
'data' => $lotteryTypes->map(function ($lotteryType) use ($lotteryTypeLedgerItems) {
$lotteryTypeLedgerItem = $lotteryTypeLedgerItems->get($lotteryType->key);
return [
'id' => $lotteryType->key,
'name' => $lotteryType->name,
'sales' => trim_zeros($lotteryTypeLedgerItem->sales ?? 0),
];
}),
]);
$startAt->addDay();
}
} elseif (in_array($last, ['180days', '365days'])) {
// 按月
$months = match ($last) {
'180days' => 6, // 6个月
'365days' => 12, // 12个月
};
// 今天
$today = Carbon::today();
// 开始时间
$startAt = $today->copy()->startOfMonth()->subMonths($months);
// 结束时间
$endAt = $today->copy()->startOfMonth()->subMonth()->endOfMonth();
/** @var \Illuminate\Support\Collection */
$lotteryTypeLedgers = LedgerItem::select([
DB::raw("DATE_FORMAT(`date`, '%Y-%m') as month"),
'ledger_item_type_id',
DB::raw('SUM(sales) as sales'),
])
->whereBetween('date', [$startAt->toDateString(), $endAt->toDateString()])
->groupBy(['month', 'ledger_item_type_id'])
->get()
->groupBy('month');
for ($i = 0; $i < $months; $i++) {
$month = $startAt->format('Y-m');
$lotteryTypeLedgerItems = $lotteryTypeLedgers->get($month, collect())->keyBy('ledger_item_type_id');
$data->push([
'month' => $month,
'data' => $lotteryTypes->map(function ($lotteryType) use ($lotteryTypeLedgerItems) {
$lotteryTypeLedgerItem = $lotteryTypeLedgerItems->get($lotteryType->key);
return [
'id' => $lotteryType->key,
'name' => $lotteryType->name,
'sales' => trim_zeros($lotteryTypeLedgerItem->sales ?? 0),
];
}),
]);
$startAt->addMonth();
}
}
return [
'lottery_types' => $lotteryTypes->map(function ($lotteryType) {
return [
'id' => $lotteryType->key,
'name' => $lotteryType->name,
];
}),
'data' => $data->all(),
];
}
/**
* 门店销量排名
*/
public function storeSalesRanking(Request $request, StatisticService $statisticService): array
{
$request->validate(
rules: [
'last' => ['bail', 'required', Rule::in(['7days', '30days', '180days', '365days'])],
],
);
$today = Carbon::today();
$last = $request->input('last');
$input = ['sort' => '-sales'];
if (in_array($last, ['7days', '30days'])) {
$days = match ($last) {
'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();
}
return $statisticService->storeRanking($input, 30);
}
/**
* 年度目标
*/
public function yearlyGoals(Request $request): array
{
$request->validate(
rules: [
'year' => ['bail', 'required', 'int'],
],
);
$aggregates = TaskPerformance::select([
DB::raw('SUM(`expected_performance`) as expected_performance'),
DB::raw('SUM(`actual_performance`) as actual_performance'),
])->whereYear(DB::raw("STR_TO_DATE(CONCAT(`month`, '-01'), '%Y-%m-%d')"), $request->input('year'))->first();
return [
// 目标业绩
'expected_performance' => trim_zeros($aggregates['expected_performance'] ?? 0),
// 实际业绩
'actual_performance' => trim_zeros($aggregates['actual_performance'] ?? 0),
];
}
/**
* 门店数量分布
*/
public function storeNumberDistribution(Request $request): array
{
/** @var array */
$region = json_decode(Storage::disk('local')->get('region.json'), true);
/** @var \Illuminate\Support\Collection */
$numbers = Store::select(['region'])
->filter($request->input(), StoreFilter::class)
->onlyOpen()
->get()
->groupBy(fn ($store) => $store->region['provinceCode'] ?? 'other')
->map(fn ($stores) => $stores->count());
return collect($region['province'])->map(function ($province) use ($numbers) {
return array_merge($province, [
'stores_count' => $numbers->get($province['code'], 0),
]);
})->all();
}
/**
* 门店分布
*/
public function storeDistribution(Request $request): array
{
$request->validate(
rules: [
'province_code' => ['required'],
],
);
$provinceCode = $request->input('province_code');
/** @var array */
$region = json_decode(Storage::disk('local')->get('region.json'), true);
/** @var \Illuminate\Support\Collection */
$stores = Store::filter($request->input(), StoreFilter::class)
->onlyOpen()
->get()
->groupBy(fn ($store) => $store->region['cityCode'] ?? 'other');
return collect(data_get($region, "city.{$provinceCode}", []))->map(function ($city) use ($stores) {
return array_merge($city, [
'stores' => $stores->get($city['code'], collect())->map(function ($store) {
return [
'id' => $store->id,
'title' => $store->title,
'lon' => $store->lon,
'lat' => $store->lat,
];
}),
]);
})->all();
}
/**
* 门店分类
*/
public function storeCategory(Request $request): array
{
$request->validate(
rules: [
'category_id' => ['filled'],
],
);
$categoryId = $request->input('category_id', 'store_category');
$categories = collect();
if ($parent = Keyword::where('key', $categoryId)->first()) {
/** @var \Illuminate\Database\Eloquent\Collection */
$descendants = Keyword::where('path', 'like', "%-{$parent->id}-%")->get();
$categories = $descendants->where('parent_id', $parent->id)
->map(function ($category) use ($descendants) {
return [
'id' => $category->key,
'name' => $category->name,
'descendants' => $descendants->filter(function ($descendant) use ($category) {
return preg_match("/-{$category->id}-/", $descendant->path);
})->map(function ($descendant) {
return [
'id' => $descendant->key,
'name' => $descendant->name,
];
})->all(),
];
});
}
$aggregates = Store::select(['category_id', DB::raw('count(1) as stores_count')])
->onlyOpen()
->groupBy('category_id')
->get()
->keyBy('category_id');
return $categories->map(function ($category) use ($aggregates) {
$storesCount = 0;
$descendants = collect(
Arr::pull($category, 'descendants')
)->push($category);
foreach ($descendants as $item) {
if ($aggregate = $aggregates->get($item['id'])) {
$storesCount += $aggregate->stores_count;
}
}
return array_merge($category, [
'stores_count' => $storesCount,
]);
})->values()->all();
}
}