generated from liutk/owl-admin-base
381 lines
12 KiB
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();
|
|
}
|
|
}
|