generated from liutk/owl-admin-base
merge
commit
06b694f6bf
|
|
@ -3,20 +3,22 @@
|
|||
namespace App\Admin\Controllers\Finance;
|
||||
|
||||
use App\Admin\Controllers\AdminController;
|
||||
use App\Admin\Filters\LedgerFilter;
|
||||
use App\Admin\Filters\StoreFilter;
|
||||
use App\Models\Ledger;
|
||||
use App\Models\Store;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\StatisticService;
|
||||
|
||||
class StoreStatisticController extends AdminController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
if ($this->actionOfGetData()) {
|
||||
$input = request()->input();
|
||||
|
||||
$sorts = [
|
||||
[request()->input('orderBy') ?: 'sales', request()->input('orderDir') ?: 'desc'],
|
||||
['id', 'asc'],
|
||||
];
|
||||
|
||||
return $this->response()->success([
|
||||
'items' => $this->getStoreStatistics(request()),
|
||||
'items' => (new StatisticService())->stores($input, $sorts),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -40,36 +42,11 @@ class StoreStatisticController extends AdminController
|
|||
]))
|
||||
->columns([
|
||||
amis()->TableColumn('ranking', '排序'),
|
||||
amis()->TableColumn('title', '门店'),
|
||||
amis()->TableColumn('store.title', '门店'),
|
||||
amis()->TableColumn('sales', '收入')->sortable(),
|
||||
amis()->TableColumn('expenditure', '支出')->sortable(),
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function getStoreStatistics(Request $request): array
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$stats = Ledger::with(['store'])
|
||||
->select(['store_id', DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure')])
|
||||
->filter($request->input(), LedgerFilter::class)
|
||||
->groupBy('store_id')
|
||||
->get();
|
||||
|
||||
// 排序规则
|
||||
$sortBy = [
|
||||
[$request->input('orderBy') ?: 'sales', $request->input('orderDir') ?: 'desc'],
|
||||
];
|
||||
|
||||
return $stats->map(fn ($item) => [
|
||||
'title' => $item->store->title,
|
||||
'sales' => trim_zeros($item->sales ?? '0'),
|
||||
'expenditure' => trim_zeros($item->expenditure ?? '0'),
|
||||
])
|
||||
->sortBy($sortBy)
|
||||
->values()
|
||||
->map(fn ($item, $key) => array_merge($item, ['ranking' => $key + 1]))
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,4 +44,14 @@ class LedgerFilter extends ModelFilter
|
|||
$query->whereIn('check_status', explode(',', $checkStatus));
|
||||
});
|
||||
}
|
||||
|
||||
public function startAt($startAt)
|
||||
{
|
||||
$this->where('date', '>=', $startAt);
|
||||
}
|
||||
|
||||
public function endAt($endAt)
|
||||
{
|
||||
$this->where('date', '<=', $endAt);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ class LedgerItemFilter extends ModelFilter
|
|||
return;
|
||||
}
|
||||
|
||||
$provinceCode = Arr::get($region, 'provinceCode');
|
||||
$cityCode = Arr::get($region, 'cityCode');
|
||||
if (empty($provinceCode) && empty($cityCode)) {
|
||||
// 区划代码 - 省份
|
||||
$provinceCode = (string) Arr::get($region, 'provinceCode');
|
||||
// 区划代码 - 城市
|
||||
$cityCode = (string) Arr::get($region, 'cityCode');
|
||||
|
||||
if ($provinceCode === '' && $cityCode === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,78 +2,119 @@
|
|||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Admin\Filters\LedgerFilter;
|
||||
use App\Admin\Filters\StoreFilter;
|
||||
use App\Http\Resources\StoreResource;
|
||||
use App\Models\Ledger;
|
||||
use App\Models\Store;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use App\Services\StatisticService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StatisticsController extends Controller
|
||||
{
|
||||
/**
|
||||
* 首页统计
|
||||
*/
|
||||
public function dashboard(Request $request): array
|
||||
public function dashboard(Request $request, StatisticService $statisticService): array
|
||||
{
|
||||
$query = Ledger::filter(
|
||||
$this->defaultFilterInput($request), LedgerFilter::class
|
||||
$request->validate(
|
||||
rules: ['date' => ['filled', 'date_format:Y-m-d']],
|
||||
attributes: ['date' => '日期'],
|
||||
);
|
||||
|
||||
// 昨天
|
||||
$yesterday = Carbon::yesterday();
|
||||
$input = $this->defaultFilterInput($request);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 本月总账录入
|
||||
//--------------------------------------------------------------------------
|
||||
$currentMonthLedger = (clone $query)
|
||||
->select([DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure')])
|
||||
->whereBetween('date', [$yesterday->copy()->startOfMonth()->format('Y-m-d'), $yesterday->format('Y-m-d')])
|
||||
->first();
|
||||
$date = Carbon::yesterday();
|
||||
if ($request->filled('date')) {
|
||||
$date = Carbon::parse($request->input('date'));
|
||||
}
|
||||
|
||||
// 本月上报数据统计
|
||||
$monthLedger = array_merge(
|
||||
['deadline' => $date->format('Y-m-d')],
|
||||
$statisticService->ledger(
|
||||
$date->copy()->startOfMonth(),
|
||||
$date->copy(),
|
||||
$input,
|
||||
),
|
||||
);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 昨日总账录入
|
||||
//--------------------------------------------------------------------------
|
||||
$yesterdayLedger = (clone $query)->where('date', $yesterday->format('Y-m-d'))->first();
|
||||
$yesdayLedger = array_merge(
|
||||
['date' => $date->format('Y-m-d')],
|
||||
$statisticService->ledger(
|
||||
$date->copy(),
|
||||
$date->copy(),
|
||||
$input,
|
||||
),
|
||||
);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 近 30 天趋势数据
|
||||
//--------------------------------------------------------------------------
|
||||
$start = $yesterday->copy()->subDays(29);
|
||||
$end = $yesterday->copy();
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$ledgers30days = (clone $query)
|
||||
->whereBetween('date', [$start, $end])
|
||||
->get(['date', 'sales', 'expenditure'])
|
||||
->keyBy('date');
|
||||
// 近30天趋势数据
|
||||
$trendsOf30days = $statisticService->ledgerTrends(
|
||||
$date->copy()->subDays(29),
|
||||
$date->copy(),
|
||||
$input,
|
||||
);
|
||||
|
||||
return [
|
||||
// 本月总账录入
|
||||
'current_month_ledger' => [
|
||||
// 截止日期
|
||||
'deadline' => $yesterday->format('Y-m-d'),
|
||||
'sales' => trim_zeros($currentMonthLedger->sales ?? 0),
|
||||
'expenditure' => trim_zeros($currentMonthLedger->expenditure ?? 0),
|
||||
],
|
||||
'current_month_ledger' => $monthLedger,
|
||||
// 昨日累计金额
|
||||
'yesterday_ledger' => [
|
||||
'date' => $yesterday->format('Y-m-d'),
|
||||
'sales' => trim_zeros($yesterdayLedger->sales ?? 0),
|
||||
'expenditure' => trim_zeros($yesterdayLedger->expenditure ?? 0),
|
||||
],
|
||||
'yesday_ledger' => $yesdayLedger,
|
||||
// 近30天趋势数据
|
||||
'trend_data_of_30days' => $this->prepareTrendData($start->copy(), $end->copy(), $ledgers30days),
|
||||
'trends_of_30days' => $trendsOf30days,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 总账统计
|
||||
*/
|
||||
public function ledger(Request $request, StatisticService $statisticService)
|
||||
{
|
||||
$request->validate(
|
||||
rules: [
|
||||
'start_at' => ['bail', 'required', 'date_format:Y-m-d'],
|
||||
'end_at' => ['bail', 'required', 'date_format:Y-m-d'],
|
||||
'before_start_at' => ['bail', 'required', 'date_format:Y-m-d'],
|
||||
'before_end_at' => ['bail', 'required', 'date_format:Y-m-d'],
|
||||
],
|
||||
attributes: [
|
||||
'start_at' => '开始日期',
|
||||
'end_at' => '结束日期',
|
||||
'before_start_at' => '对比开始日期',
|
||||
'before_end_at' => '对比结束日期',
|
||||
],
|
||||
);
|
||||
|
||||
$input = $this->defaultFilterInput($request);
|
||||
|
||||
$ledger = $statisticService->ledger(
|
||||
Carbon::parse($request->input('start_at')),
|
||||
Carbon::parse($request->input('end_at')),
|
||||
$input,
|
||||
);
|
||||
|
||||
$beforeLedger = $statisticService->ledger(
|
||||
Carbon::parse($request->input('before_start_at')),
|
||||
Carbon::parse($request->input('before_end_at')),
|
||||
$input,
|
||||
);
|
||||
|
||||
// 销售涨幅
|
||||
$salesGrowthRate = 0;
|
||||
|
||||
if (bccomp($beforeLedger['sales'], '0', 2) === 0) {
|
||||
$salesGrowthRate = '-';
|
||||
} else {
|
||||
$diff = bcsub($ledger['sales'], $beforeLedger['sales'], 2);
|
||||
$salesGrowthRate = bcdiv(bcmul($diff, '100'), $beforeLedger['sales'], 2);
|
||||
}
|
||||
|
||||
return array_merge($ledger, [
|
||||
'sales_growth_rate' => $salesGrowthRate,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 门店统计
|
||||
*/
|
||||
public function stores(Request $request)
|
||||
public function stores(Request $request, StatisticService $statisticService)
|
||||
{
|
||||
$request->validate(
|
||||
rules: [
|
||||
|
|
@ -86,46 +127,37 @@ class StatisticsController extends Controller
|
|||
],
|
||||
);
|
||||
|
||||
$storeLedgerStats = Ledger::select(['store_id', DB::raw('SUM(sales) as sales')])
|
||||
->whereBetween('date', [$request->input('start_at'), $request->input('end_at')])
|
||||
->groupBy('store_id');
|
||||
$input = array_merge(
|
||||
$this->defaultFilterInput($request),
|
||||
$request->only(['start_at', 'end_at']),
|
||||
);
|
||||
|
||||
$stores = Store::filter($this->defaultFilterInput($request), StoreFilter::class)
|
||||
->leftJoinSub($storeLedgerStats, 'store_ledger_stats', fn ($join) => $join->on('stores.id', '=', 'store_ledger_stats.store_id'))
|
||||
->orderBy('sales', 'desc')
|
||||
->orderBy('id', 'asc')
|
||||
->get();
|
||||
$sorts = [['sales', 'desc'], ['id', 'asc']];
|
||||
|
||||
return $stores->map(function (Store $store) {
|
||||
return [
|
||||
'store' => StoreResource::make($store),
|
||||
'sales' => trim_zeros($store->sales ?: 0),
|
||||
];
|
||||
});
|
||||
return $statisticService->stores($input, $sorts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备趋势数据
|
||||
* 销售统计
|
||||
*/
|
||||
protected function prepareTrendData(Carbon $start, Carbon $end, Collection $ledgers): array
|
||||
public function sales(Request $request, StatisticService $statisticService): array
|
||||
{
|
||||
$data = collect();
|
||||
$request->validate(
|
||||
rules: [
|
||||
'start_at' => ['bail', 'required', 'date_format:Y-m-d'],
|
||||
'end_at' => ['bail', 'required', 'date_format:Y-m-d'],
|
||||
],
|
||||
attributes: [
|
||||
'start_at' => '开始日期',
|
||||
'end_at' => '结束日期',
|
||||
],
|
||||
);
|
||||
|
||||
do {
|
||||
$ledger = $ledgers->get(
|
||||
$date = $start->format('Y-m-d')
|
||||
);
|
||||
|
||||
$data->push([
|
||||
'date' => $date,
|
||||
'sales' => trim_zeros($ledger->sales ?? 0),
|
||||
'expenditure' => trim_zeros($ledger->expenditure ?? 0),
|
||||
]);
|
||||
|
||||
$start->addDay();
|
||||
} while ($start->lte($end));
|
||||
|
||||
return $data->all();
|
||||
return $statisticService->sales(
|
||||
Carbon::parse($request->input('start_at')),
|
||||
Carbon::parse($request->input('end_at')),
|
||||
$this->defaultFilterInput($request),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ class LedgerItem extends Model
|
|||
use Filterable, HasDateTimeFormatter, HasFactory;
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'date',
|
||||
'approved' => 'bool',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Admin\Filters\LedgerFilter;
|
||||
use App\Admin\Filters\LedgerItemFilter;
|
||||
use App\Admin\Filters\StoreFilter;
|
||||
use App\Models\Keyword;
|
||||
use App\Models\Ledger;
|
||||
use App\Models\LedgerItem;
|
||||
use App\Models\Store;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StatisticService
|
||||
{
|
||||
/**
|
||||
* 总账统计
|
||||
*/
|
||||
public function ledger(Carbon $start, Carbon $end, array $input = []): array
|
||||
{
|
||||
$ledger = Ledger::filter($input, LedgerFilter::class)
|
||||
->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();
|
||||
|
||||
return [
|
||||
'sales' => trim_zeros($ledger->sales ?? 0),
|
||||
'expenditure' => trim_zeros($ledger->expenditure ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 总账数据趋势
|
||||
*/
|
||||
public function ledgerTrends(Carbon $start, Carbon $end, array $input = []): array
|
||||
{
|
||||
$ledgers = Ledger::filter($input, LedgerFilter::class)
|
||||
->whereBetween('date', [$start->format('Y-m-d'), $end->format('Y-m-d')])
|
||||
->get(['date', 'sales', 'expenditure'])
|
||||
->keyBy('date');
|
||||
|
||||
$data = collect();
|
||||
|
||||
while ($start->lte($end)) {
|
||||
$ledger = $ledgers->get(
|
||||
$date = $start->format('Y-m-d')
|
||||
);
|
||||
|
||||
$data->push([
|
||||
'date' => $date,
|
||||
'sales' => trim_zeros($ledger->sales ?? 0),
|
||||
'expenditure' => trim_zeros($ledger->expenditure ?? 0),
|
||||
]);
|
||||
|
||||
$start->addDay();
|
||||
}
|
||||
|
||||
return $data->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 门店统计
|
||||
*/
|
||||
public function stores(array $input = [], array $sorts = []): array
|
||||
{
|
||||
$storeLedgerStats = Ledger::select(['store_id', DB::raw('SUM(sales) as sales'), DB::raw('SUM(expenditure) as expenditure')])
|
||||
->filter(Arr::only($input, ['date_range', 'start_at', 'end_at']), LedgerFilter::class)
|
||||
->groupBy('store_id');
|
||||
|
||||
$stores = Store::filter(Arr::only($input, ['store_id', 'region']), StoreFilter::class)
|
||||
->leftJoinSub($storeLedgerStats, 'store_ledger_stats', fn ($join) => $join->on('stores.id', '=', 'store_ledger_stats.store_id'))
|
||||
->when($sorts, function ($query, $sorts) {
|
||||
foreach ($sorts as $sort) {
|
||||
$query->orderBy($sort[0], $sort[1]);
|
||||
}
|
||||
})
|
||||
->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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销售统计
|
||||
*/
|
||||
public function sales(Carbon $start, Carbon $end, array $input = []): array
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$lotteryTypes = Keyword::where('parent_key', 'lottery_type')->get();
|
||||
|
||||
/** @var \Illuminate\Support\Collection */
|
||||
$ledgerStatistics = Ledger::select([
|
||||
'date',
|
||||
DB::raw('SUM(new_customers) as new_customers'),
|
||||
DB::raw('SUM(sales) as sales'),
|
||||
DB::raw('SUM(expenditure) as expenditure')
|
||||
])
|
||||
->filter($input, LedgerFilter::class)
|
||||
->whereBetween('date', [$start->format('Y-m-d'), $end->format('Y-m-d')])
|
||||
->groupBy(['date'])
|
||||
->get()
|
||||
->keyBy('date');
|
||||
|
||||
/** @var \Illuminate\Support\Collection */
|
||||
$ledgerItemStatistics = LedgerItem::select([
|
||||
'date',
|
||||
'ledger_item_type_id',
|
||||
DB::raw('SUM(sales) as sales'),
|
||||
DB::raw('SUM(expenditure) as expenditure'),
|
||||
])
|
||||
->filter($input, LedgerItemFilter::class)
|
||||
->whereIn('ledger_item_type_id', $lotteryTypes->pluck('key'))
|
||||
->whereBetween('date', [$start->format('Y-m-d'), $end->format('Y-m-d')])
|
||||
->groupBy(['date', 'ledger_item_type_id'])
|
||||
->get()
|
||||
->groupBy('date');
|
||||
|
||||
$data = collect();
|
||||
|
||||
while ($end->gte($start)) {
|
||||
$date = $end->format('Y-m-d');
|
||||
|
||||
$ledgerStatistic = $ledgerStatistics->get($date);
|
||||
/** @var \Illuminate\Support\Collection */
|
||||
$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([
|
||||
'date' => $date,
|
||||
'ledger' => [
|
||||
'new_customers' => $ledgerStatistic->new_customers ?? 0,
|
||||
'sales' => trim_zeros($ledgerStatistic->sales ?? 0),
|
||||
'expenditure' => trim_zeros($ledgerStatistic->expenditure ?? 0),
|
||||
],
|
||||
'lottery_types' => $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),
|
||||
];
|
||||
}),
|
||||
]);
|
||||
|
||||
$end->subDay();
|
||||
}
|
||||
|
||||
return $data->all();
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,10 @@ Route::group([
|
|||
Route::get('/statistics/dashboard', [StatisticsController::class, 'dashboard']);
|
||||
// 统计数据 - 门店统计
|
||||
Route::get('/statistics/stores', [StatisticsController::class, 'stores']);
|
||||
// 统计数据 - 销售统计
|
||||
Route::get('/statistics/sales', [StatisticsController::class, 'sales']);
|
||||
// 统计数据 - 总账统计
|
||||
Route::get('/statistics/ledger', [StatisticsController::class, 'ledger']);
|
||||
|
||||
// 数据上报
|
||||
Route::apiResource('/ledgers', LedgerController::class)->only(['store', 'show']);
|
||||
|
|
|
|||
Loading…
Reference in New Issue