鲜花2.0-定时任务统计每日销售金额(后续统计gmv需要使用)+ sku和spu维度的报表接口开发

This commit is contained in:
杨建炊 2024-08-01 17:53:32 +08:00
parent dba30f1168
commit 86ac32d29f
6 changed files with 305 additions and 24 deletions

View File

@ -53,7 +53,8 @@ class DailyStockRecordReport extends Command
$orderItems = BusinessOrderItem::query() $orderItems = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id") ->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("business_order_items.external_sku_id" ->select("business_order_items.external_sku_id"
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")) , DB::raw("sum(goods_number-already_cancel_number) as goods_total")
, DB::raw("ROUND(sum(goods_amount) / 100,2) as order_total_amount"))
->whereBetween("business_order_items.created_at", [$startDateTime, $endDateTime]) ->whereBetween("business_order_items.created_at", [$startDateTime, $endDateTime])
->where("business_order_items.cancel_status", "=", 0) ->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->pluck(null, "external_sku_id")->toArray(); ->groupBy('external_sku_id')->get()->pluck(null, "external_sku_id")->toArray();
@ -85,6 +86,8 @@ class DailyStockRecordReport extends Command
? $lossRecords[$externalSkuId]["loss_num"] : 0; ? $lossRecords[$externalSkuId]["loss_num"] : 0;
$record->order_goods_num = !empty($orderItems[$externalSkuId]["goods_total"]) $record->order_goods_num = !empty($orderItems[$externalSkuId]["goods_total"])
? $orderItems[$externalSkuId]["goods_total"] : 0; ? $orderItems[$externalSkuId]["goods_total"] : 0;
$record->order_total_amount = !empty($orderItems[$externalSkuId]["order_total_amount"])
? $orderItems[$externalSkuId]["order_total_amount"] : 0;
$record->save(); $record->save();
}); });
} }

View File

@ -8,6 +8,7 @@ use App\Http\Resources\DailySalesReportResource;
use App\Models\BusinessOrderItem; use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport; use App\Models\DailySalesReport;
use App\Models\GoodsSku; use App\Models\GoodsSku;
use App\Services\Service\SaleDataService;
use App\Utils\FormatUtils; use App\Utils\FormatUtils;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -49,7 +50,7 @@ class DataCenterController extends Controller
* @param Request $request * @param Request $request
* @return void * @return void
*/ */
public function skuSalesReport(Request $request) public function saleStatistics(Request $request)
{ {
//获取所有参数 //获取所有参数
$allParams = $request->all(); $allParams = $request->all();
@ -57,8 +58,7 @@ class DataCenterController extends Controller
$validator = Validator::make($allParams, [ $validator = Validator::make($allParams, [
'type' => 'required|integer', //1表示今日 'type' => 'required|integer', //1表示今日
'start_day' => 'sometimes|string', 'start_day' => 'sometimes|string',
'end_day' => 'sometimes|string', 'end_day' => 'sometimes|string'
'title' => 'sometimes|string'
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
@ -66,25 +66,41 @@ class DataCenterController extends Controller
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); $this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
if (StaticTypeEnum::TODAY == $allParams->type) { return SaleDataService::saleStatistics($request);
//实时统计 }
$orderItems = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id") /**
->select("business_order_items.external_sku_id" * spu 维度的数据统计
, DB::raw("sum(CASE WHEN b.shipping_status>0 THEN goods_number-already_cancel_number ELSE 0 END) as shipping_num") * @param Request $request
, DB::raw("sum(CASE WHEN b.shipping_status=0 THEN goods_number-already_cancel_number ELSE 0 END) as unshipping_num") * @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|mixed
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")) */
->where("business_order_items.created_at", ">",Carbon::now()->startOfDay()->toDateTimeString()) public function spuSaleStatistics(Request $request){
->where("business_order_items.cancel_status", "=", 0) //进行校验验证
->groupBy('external_sku_id') $validator = Validator::make($request->all(), [
->orderBy("goods_total","DESC") 'type' => 'required|integer', //1表示今日
->paginate($request->get('per_page')); 'start_day' => 'sometimes|date_format:Y-m-d',
return $orderItems; 'end_day' => 'sometimes|date_format:Y-m-d'
} else { ]);
$startTime = Carbon::parse($request->input("start_day"))->toDateTimeString(); if ($validator->fails()) {
$endTime = Carbon::parse($request->input("end_day"))->endOfDay()->toDateTimeString(); //校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
} }
return SaleDataService::spuSaleStatistics($request);
}
public function gmvStatistics(Request $request){
//进行校验验证
$validator = Validator::make($request->all(), [
'type' => 'required|integer', //1表示今日
'start_day' => 'sometimes|date_format:Y-m-d',
'end_day' => 'sometimes|date_format:Y-m-d'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return SaleDataService::gmvStatistics($request);
} }
} }

View File

@ -9,4 +9,6 @@ class CacheKeyEnum
const STOCK_RULE_PROPORTION = "stock_rule_proportion"; const STOCK_RULE_PROPORTION = "stock_rule_proportion";
const DEFAULT_EXPIRE_DAY = "default_expire_day"; const DEFAULT_EXPIRE_DAY = "default_expire_day";
const SPU_STATISTIC_BY_DATE = "spu_statistic_by_date";
} }

View File

@ -0,0 +1,243 @@
<?php
namespace App\Services\Service;
use App\Events\BatchStockUpdateEvent;
use App\Http\Enum\CacheKeyEnum;
use App\Http\Enum\StaticTypeEnum;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class SaleDataService
{
public static function saleStatistics(Request $request)
{
if (StaticTypeEnum::TODAY == $request->type) {
//实时统计 sku维度
return static::skuSaleStatisticsByToday($request);
} else {
//历史数据查询
return static::skuSaleStatisticsByHistory($request);
}
}
public static function skuSaleStatisticsByToday(Request $request)
{
$orderItems = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("business_order_items.external_sku_id"
, DB::raw("sum(CASE WHEN b.shipping_status>0 THEN goods_number-already_cancel_number ELSE 0 END) as shipping_num")
, DB::raw("sum(CASE WHEN b.shipping_status=0 THEN goods_number-already_cancel_number ELSE 0 END) as unshipping_num")
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")
, DB::raw("ROUND(sum(goods_amount) / 100,2) as goods_total_amount"))
->where("business_order_items.created_at", ">", Carbon::now()->subDays(2)->startOfDay()->toDateTimeString())
->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')
->orderBy("goods_total", "DESC")
->paginate($request->get('per_page'));
if (!empty($orderItems->items())) {
$externalSkuIds = collect($orderItems->items())->pluck("external_sku_id")->toArray();
$goodsSkus = GoodsSku::query()->whereIn("external_sku_id", $externalSkuIds)
->select("id", "title", "name", "stock", "sale_stock", "status", "external_sku_id")->get()
->pluck(null, "id")->toArray();
return static::addSaleDataToGoodsSku($goodsSkus, $orderItems);
}
return $orderItems;
}
public static function skuSaleStatisticsByHistory(Request $request)
{
$startTime = Carbon::parse($request->input("start_day"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_day"))->endOfDay()->toDateTimeString();
$dailyRecord = DailyStockRecord::query()
->select("sku_id", DB::raw("sum(order_goods_num) as goods_total")
, DB::raw("ROUND(sum(order_total_amount) / 100,2) as goods_total_amount"))
->whereBetween("created_at", [$startTime, $endTime])
->groupBy("sku_id")
->having(DB::raw("sum(order_goods_num)"), ">", 0)
->orderBy("goods_total", "DESC")
->paginate($request->get('per_page'));
$skuIds = collect($dailyRecord->items())->pluck('sku_id')->toArray();
$goodsSkusMapKeyBySkuId = GoodsSku::query()->select("id", "title", "name", "stock", "sale_stock"
, "status", "external_sku_id")->whereIn("id", $skuIds)
->get()->pluck(null, "id")->toArray();
$dailyRecord->getCollection()->map(function ($v) use ($goodsSkusMapKeyBySkuId) {
if (!empty($goodsSkusMapKeyBySkuId[$v['sku_id']])) {
foreach ($goodsSkusMapKeyBySkuId[$v['sku_id']] as $key => $val) {
$v->$key = $val;
}
} else {
//出现异常的skuid
$v->title = "未知商品";
$v->name = "未知商品";
$v->stock = 0;
$v->sale_stock = 0;
$v->status = "下架";
$v->id = 0;
}
});
return $dailyRecord;
}
public static function addSaleDataToGoodsSku($goodsSku, $orderItems)
{
$skuIds = collect($goodsSku)->pluck("id")->toArray();
//查询8天内-昨天的数据
$startTime = Carbon::now()->subDays(8)->startOfDay()->toDateTimeString();
$endTime = Carbon::yesterday()->endOfDay()->toDateTimeString();
$dailyRecord = DailyStockRecord::query()->whereIn("sku_id", $skuIds)
->whereBetween("created_at", [$startTime, $endTime])
->get()->toArray();
$dailyRecordGroupBySkuId = collect($dailyRecord)->groupBy("sku_id")->toArray();
Log::info("dailyRecordGroupBySkuId", $dailyRecordGroupBySkuId);
$combineGoodsSkus = collect($goodsSku)->map(function ($v) use ($dailyRecordGroupBySkuId, $orderItems) {
$v['yesterday_avg_num'] = round(collect($dailyRecordGroupBySkuId[$v['id']] ?? [])->sortByDesc("day")
->take(1)->avg("order_goods_num") ?? 0, 2);
$v['three_day_avg_num'] = round(collect($dailyRecordGroupBySkuId[$v['id']] ?? [])->sortByDesc("day")
->take(3)->avg("order_goods_num") ?? 0, 2);
$v['seven_day_avg_num'] = round(collect($dailyRecordGroupBySkuId[$v['id']] ?? [])->sortByDesc("day")
->take(7)->avg("order_goods_num") ?? 0, 2);
return $v;
})->pluck(null, "external_sku_id")->toArray();
Log::info("combineGoodsSkus", $combineGoodsSkus);
$orderItems->getCollection()->map(function ($v) use ($combineGoodsSkus) {
if (!empty($combineGoodsSkus[$v['external_sku_id']])) {
foreach ($combineGoodsSkus[$v['external_sku_id']] as $key => $val) {
$v->$key = $val;
}
} else {
//出现异常售卖的编码
$v->title = "未知商品";
$v->name = "未知商品";
$v->stock = 0;
$v->sale_stock = 0;
$v->status = "下架";
$v->yesterday_avg_num = 0;
$v->three_day_avg_num = 0;
$v->id = 0;
$v->seven_day_avg_num = 0;
}
});
return $orderItems;
}
public static function spuSaleStatistics(Request $request)
{
//spu 基本就是全统计了
if (StaticTypeEnum::TODAY == $request->type) {
return static::spuSaleStatisticsByToday($request);
} else {
//统计历史数据 这里走缓存
return static::spuSaleStatisticsByHistoryCache($request);
}
}
public static function spuSaleStatisticsByToday(Request $request)
{
//实时统计 sku维度
$orderItems = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("business_order_items.external_sku_id"
, DB::raw("sum(CASE WHEN b.shipping_status>0 THEN goods_number-already_cancel_number ELSE 0 END) as shipping_num")
, DB::raw("sum(CASE WHEN b.shipping_status=0 THEN goods_number-already_cancel_number ELSE 0 END) as unshipping_num")
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")
, DB::raw("ROUND(sum(goods_amount) / 100,2) as goods_total_amount"))
->where("business_order_items.created_at", ">", Carbon::now()->subDays(2)->startOfDay()->toDateTimeString())
->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->toArray();
$externalSkuIds = collect($orderItems)->pluck("external_sku_id")->toArray();
$orderItemsKeyByExternalSkuId = collect($orderItems)->pluck(null, "external_sku_id")->toArray();
$goodsSkuWithTypes = GoodsSku::query()
->Join("goods", "goods_id", "=", "goods.id")
->Join("goods_types", "goods.type_id", "=", "goods_types.id")
->whereIn("external_sku_id", $externalSkuIds)
->select("goods_skus.id", "goods_types.id as type_id", "goods_types.name", "external_sku_id", "stock", "sale_stock")->get()
->toArray();
return collect($goodsSkuWithTypes)->map(function ($v) use ($orderItemsKeyByExternalSkuId) {
if (!empty($orderItemsKeyByExternalSkuId[$v['external_sku_id']])) {
return array_merge($v, $orderItemsKeyByExternalSkuId[$v['external_sku_id']]);
}
})->filter()->values()->groupBy('type_id')->map(function ($v, $key) {
return [
"type_id" => $key,
"type_name" => $v[0]['name'] ?? '',
"stock" => $v->sum("stock"),
"sale_stock" => $v->sum("sale_stock"),
"shipping_num" => $v->sum("shipping_num"),
"unshipping_num" => $v->sum("unshipping_num"),
"goods_total" => $v->sum("goods_total"),
"goods_total_amount" => $v->sum("goods_total_amount"),
];
})->values()->toArray();
}
public static function spuSaleStatisticsByHistoryCache(Request $request)
{
$startTime = Carbon::parse($request->input("start_day"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_day"))->endOfDay()->toDateTimeString();
$cacheKey = CacheKeyEnum::SPU_STATISTIC_BY_DATE . $request->input("start_day") . "_" . $request->input("end_day");
$expireTime = Carbon::now()->addHour();
return Cache::remember($cacheKey, $expireTime, function () use ($startTime, $endTime) {
return static::spuSaleStatisticsByHistory($startTime, $endTime);
});
}
public static function spuSaleStatisticsByHistory($startTime, $endTime)
{
$dailyAllRecord = DailyStockRecord::query()
->select("sku_id", DB::raw("sum(order_goods_num) as goods_total")
, DB::raw("ROUND(sum(order_total_amount) / 100,2) as goods_total_amount"))
->whereBetween("created_at", [$startTime, $endTime])
->groupBy("sku_id")
->having(DB::raw("sum(order_goods_num)"), ">", 0)
->get()->toArray();
$skuIds = collect($dailyAllRecord)->pluck('sku_id')->toArray();
$dailyRecordMapKeyBySkuId = collect($dailyAllRecord)->pluck(null, 'sku_id')->toArray();
$goodsSkuWithTypes = GoodsSku::query()
->Join("goods", "goods_id", "=", "goods.id")
->Join("goods_types", "goods.type_id", "=", "goods_types.id")
->select("goods_skus.id", "goods_types.id as type_id", "goods_types.name", "external_sku_id", "stock", "sale_stock")
->whereIn("goods_skus.id", $skuIds)
->get()->toArray();
return collect($goodsSkuWithTypes)->map(function ($v) use ($dailyRecordMapKeyBySkuId) {
if (!empty($dailyRecordMapKeyBySkuId[$v['id']])) {
return array_merge($v, $dailyRecordMapKeyBySkuId[$v['id']]);
}
})->filter()->values()->groupBy('type_id')->map(function ($v, $key) {
return [
"type_id" => $key,
"type_name" => $v[0]['name'] ?? '',
"stock" => $v->sum("stock"),
"sale_stock" => $v->sum("sale_stock"),
"goods_total" => $v->sum("goods_total"),
"goods_total_amount" => $v->sum("goods_total_amount"),
];
})->values()->toArray();
}
public static function gmvStatistics(Request $request){
//gmv
if (StaticTypeEnum::TODAY == $request->type) {
} else {
//统计历史数据 这里走缓存
$startTime = Carbon::parse($request->input("start_day"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_day"))->endOfDay()->toDateTimeString();
}
}
}

View File

@ -513,7 +513,7 @@ return [
'id' => 191, 'id' => 191,
'name' => '站內信', 'name' => '站內信',
'parent_id' => 19, 'parent_id' => 19,
'show' => 1, 'show' => 0,
], ],
'website_message.index' => [ 'website_message.index' => [
'id' => 1911, 'id' => 1911,
@ -598,4 +598,20 @@ return [
'name' => '盘点记录批量导入', 'name' => '盘点记录批量导入',
'parent_id' => 193 'parent_id' => 193
], ],
'sale_statistics' => [
'id' => 181,
'name' => '销售统计',
'parent_id' => 18,
'show' => 1,
],
'data_center.sale_statistics' => [
'id' => 1811,
'name' => '销售统计',
'parent_id' => 18
],
'data_center.spu_sale_statistics' => [
'id' => 1812,
'name' => 'spu销售统计',
'parent_id' => 18
],
]; ];

View File

@ -87,7 +87,8 @@ Route::middleware(['auth:api', 'check.permissions'])->group(function () {
Route::resource('supplier/daily_stock_record', 'Supplier\DailyStockRecordController',['only' => ['index', 'store']]); Route::resource('supplier/daily_stock_record', 'Supplier\DailyStockRecordController',['only' => ['index', 'store']]);
// 数据中心 销售报表 // 数据中心 销售报表
Route::get('data_center/sku_sales_report', [DataCenterController::class, 'skuSalesReport'])->name('sku_sales_report.index'); Route::get('data_center/sale_statistics', [DataCenterController::class, 'saleStatistics'])->name('data_center.sale_statistics');
Route::get('data_center/spu_sale_statistics', [DataCenterController::class, 'spuSaleStatistics'])->name('data_center.spu_sale_statistics');
}); });
Route::get('stock/goods_skus', [GoodsSkusController::class, 'stockNum'])->middleware('auth:api'); Route::get('stock/goods_skus', [GoodsSkusController::class, 'stockNum'])->middleware('auth:api');