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(); } }