diff --git a/app/Console/Commands/CheckPrice.php b/app/Console/Commands/CheckPrice.php new file mode 100644 index 0000000..76b5bd8 --- /dev/null +++ b/app/Console/Commands/CheckPrice.php @@ -0,0 +1,68 @@ +startOfDay()->toDateTimeString(); + $yesterdayEndTime = Carbon::yesterday()->endOfDay()->toDateTimeString(); + + //查询价格异常订单 + $results = DB::table('business_order_items as a') + ->select('c.title as cn_name', 'b.title as title', DB::raw('ROUND(a.goods_price / 100,2) AS goods_price') + , 'b.cost','a.created_at','a.business_order_id') + ->leftJoin('goods_skus as b', 'a.external_sku_id', '=', 'b.external_sku_id') + ->leftJoin('goods as c', 'b.goods_id', '=', 'c.id') + ->whereBetween('a.created_at', [$yesterdayStartTime,$yesterdayEndTime]) + ->havingRaw('goods_price < cost') + ->get(); + if($results->isNotEmpty()){ + Log::info($yesterdayStartTime.'异常订单',$results->toArray()); + $messageService = new MessageService(); + foreach ($results as $v){ + $messageService->createPriceExceptionMessage($v->business_order_id, + $v->cn_name,$v->title,$v->goods_price,$v->cost); + } + } + + Log::info('任务完成:check-CheckPrice'); + } +} \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 9a570d5..3fffb72 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,6 +2,7 @@ namespace App\Console; +use App\Console\Commands\CheckPrice; use App\Console\Commands\DailySalesReport; use App\Console\Commands\GoodsSkuDailyReport; use App\Console\Commands\Inventory; @@ -45,6 +46,8 @@ class Kernel extends ConsoleKernel $schedule->command(DailySalesReport::class, ['S7'])->dailyAt('09:30'); $schedule->command(DeleteKttQuery::class)->daily(); + //新增价格校验 + $schedule->command(CheckPrice::class)->dailyAt('07:00'); } /** diff --git a/app/Events/StockUpdateEvent.php b/app/Events/StockUpdateEvent.php index ca84859..a217336 100644 --- a/app/Events/StockUpdateEvent.php +++ b/app/Events/StockUpdateEvent.php @@ -33,12 +33,14 @@ class StockUpdateEvent private function checkStatusAndStock($goodsSku) { - $stock = $goodsSku->stock; - if (0 >= $goodsSku->stock) { + //新版上下架和真实库存无关 + $stock = $goodsSku->sale_stock; + if (0 >= $goodsSku->sale_stock) { $status = GoodsSku::$STATUS_DOWN; } else { $status = GoodsSku::$STATUS_ON_SALE; } + //TODO 待确认逻辑 $arrivedTodayNum = DailyStockRecord::query() ->where('day', DateTimeUtils::getToday()) ->where('sku_id', $goodsSku->id) diff --git a/app/Http/Controllers/Goods/WareHouseSkusController.php b/app/Http/Controllers/Goods/WareHouseSkusController.php new file mode 100644 index 0000000..362b980 --- /dev/null +++ b/app/Http/Controllers/Goods/WareHouseSkusController.php @@ -0,0 +1,270 @@ +log = new LogModel([ + 'module' => 'goods', + 'action' => $request->getMethod(), + 'target_type' => 'goods_sku', + ]); + } + + public function index(Request $request) + { + $fields = implode(',', [ + 'shop_id', + 'external_sku_id', + 'SUM(goods_number) - SUM(already_cancel_number) as number', + ]); + $orderRestTime = DeveloperConfig::query() + ->where('key', DeveloperConfig::$ORDER_RESET_TIME) + ->value('value'); + if (is_null($orderRestTime)) { + $orderRestTime = date('Y-m-d 07:00:00'); + } + $businessOrderItems = BusinessOrderItem::query() + ->select(DB::raw($fields)) + ->with([ + 'shop:id,name', + 'goodsSku:id,external_sku_id' + ]) + ->where('created_at', '>', $orderRestTime) + ->where('external_sku_id', '<>', '') + ->groupBy(['shop_id', 'external_sku_id']) + ->orderByDesc('number') + ->get() + ->toArray(); + $ids = $externals = []; + foreach ($businessOrderItems as $businessOrderItem) { + if (is_null($businessOrderItem['goods_sku'])) { + continue; + } + $id = $businessOrderItem['goods_sku']['id']; + if (isset($ids[$id])) { + $ids[$id] += (int)$businessOrderItem['number']; + } else { + $ids[$id] = (int)$businessOrderItem['number']; + } + + $externals[$businessOrderItem['external_sku_id']][] = $businessOrderItem; + } + arsort($ids); + + $builder = GoodsSku::query(); + $this->preparQueryGoodsSkus($request, $builder); + $day = DateTimeUtils::getToday(); + $goodsSkus = (clone $builder)->filter() + ->where('is_combination', 0) + ->orderByDesc('stock') + ->pluck('stock', 'id') + ->toArray(); + $finalIds = []; + foreach ($ids as $id => $number) { + if (isset($goodsSkus[$id])) { + $finalIds[] = $id; + unset($goodsSkus[$id]); + } + } + $finalIds = array_merge($finalIds, array_keys($goodsSkus)); + $idField = implode(',', $finalIds); + + $goodsSkus = (clone $builder)->with(['goods' => function ($query) { + $query->with(['type:id,name', 'brand:id,name']); + }]) + ->with(['daily' => function ($query) use ($day) { + $query->where('day', $day); + }]) + ->whereIn('id', $finalIds) + ->orderByRaw("FIELD(id, {$idField})") + ->paginate($request->get('per_page')); + $rolesName = $request->user()->getRoleNames()->toArray(); + foreach ($goodsSkus as &$sku) { + $lastInventoryTime = $sku['daily']['inventory_time'] ?: date('Y-m-d 07:00:00'); + if (isset($externals[$sku['external_sku_id']])) { + $sku['order_detail'] = $externals[$sku['external_sku_id']]; + $sku['order_goods_num'] = array_sum(array_column($externals[$sku['external_sku_id']], 'number')); + } else { + $sku['order_detail'] = []; + $sku['order_goods_num'] = 0; + } + $sku['order_goods_num'] -= $sku['daily']['reissue_num']; + $sku['inventory_time'] = $lastInventoryTime; + if ('销售' === $rolesName[0]) { + $sku['cost'] = 0; + } + } + return GoodsSkuResource::collection($goodsSkus); + } + + public function PurchaseBatchStore(Request $request) + { + $validator = Validator::make($request->all(), [ + 'purchaseOrders' => 'required|array', + 'purchaseOrders.*.sku_id' => 'required|integer', + 'purchaseOrders.*.external_sku_id' => 'required|string', + 'purchaseOrders.*.num' => 'required|integer', + 'purchaseOrders.*.cost' => 'required|string', + ]); + if ($validator->fails()) { + $this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); + return response($this->res, $this->res['httpCode']); + } + $purchaseOrders = $request->input('purchaseOrders') ?? []; + $externalSkuIds = collect($purchaseOrders)->pluck("external_sku_id")->toArray(); + $goodsSku = GoodsSku::query()->whereIn('external_sku_id', $externalSkuIds)->get(); + if ($goodsSku->pluck("external_sku_id")->diff($externalSkuIds)->isNotEmpty()) { + $content = implode(',', $goodsSku->pluck("external_sku_id")->diff($externalSkuIds)->toArray()); + $content .= "以上sku编码数据库中不存在"; + return response($content, $this->res['httpCode']); + } + $goodsSkuMap = $goodsSku->pluck('external_sku_id',null)->toArray(); + //执行库存操作 + foreach ($purchaseOrders as $v) { + $goodsSkuItem = $goodsSkuMap[$v['external_sku_id']]; + //保存記錄 + $purchaseRecords = new PurchaseRecords(); + $purchaseRecords->sku_id = $goodsSkuItem['id'] ?? 0; + $purchaseRecords->external_sku_id = $v['external_sku_id']; + $purchaseRecords->num = $v['num']; + $purchaseRecords->cost = $v['cost']; + $purchaseRecords->buyer_name = $v['buyer_name'] ?? ''; + $purchaseRecords->supplier_name = $v['supplier_name'] ?? ''; + $purchaseRecords->supplier_id = $v['supplier_id'] ?? 0; + $purchaseRecords->save(); + + //更新库存 + GoodsSku::query()->where('external_sku_id', $v['external_sku_id'])->update([ + 'stock' => $goodsSkuItem['stock']+$v['num'], + 'sale_stock' => $goodsSkuItem['sale_stock']+$v['num'], + 'cost' => $v['cost'], + 'status' => 1, + ]); + event(new StockUpdateEvent($goodsSkuItem)); + } + } + + public function updateField($id, Request $request) + { + $rules = [ + 'updateField' => [ + 'required', + Rule::in(['reference_price', 'reserve', 'loss_num', 'status', 'goal_rate']) + ], + 'reference_price' => [ + 'sometimes', + 'numeric', + 'gt:0' + ], + 'reserve' => [ + 'sometimes', + 'integer', + ], + 'loss_num' => [ + 'sometimes', + 'integer', + ], + 'reason' => [ + 'sometimes', + 'required', + 'string' + ], + 'status' => [ + 'sometimes', + 'required', + 'integer', + Rule::in([0, 1, 2]) + ], + 'goal_rate' => [ + 'sometimes', + 'numeric', + ], + ]; + $validator = Validator::make($request->all(), $rules); + if ($validator->fails()) { + $this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); + goto end; + } + $updateField = \request('updateField'); + $sku = GoodsSku::query()->find($id); + if ('loss_num' === $updateField) { + $record = DailyStockRecord::query() + ->where('sku_id', $id) + ->where('day', DateTimeUtils::getToday()) + ->first(['id', 'loss_num']); + $this->log->message = $request->get('reason'); + $this->setBeforeUpdateForLog($record->loss_num); + $record->loss_num += $request->loss_num; + $record->save(); + $this->setAfterUpdateForLog($record->loss_num); + $sku->stock -= $request->loss_num; + $sku->save(); + } else { + $this->setBeforeUpdateForLog($sku->$updateField); + if ('reserve' === $updateField) { + $changeNum = $sku->reserve - $request->reserve; + if (0 > $changeNum + $sku->stock) { + $this->setValidatorFailResponse('预留量超过库存数量'); + goto end; + } + $sku->stock += $changeNum; + } + + $sku->$updateField = $request->$updateField; + $sku->save(); + $this->setAfterUpdateForLog($sku->$updateField); + } + if (in_array($updateField, ['reserve', 'loss_num'])) { + event(new StockUpdateEvent($sku)); + } + // 更新目标去化率 + if ('goal_rate' === $updateField) { + DailySalesReport::query() + ->where('date', date('Y-m-d')) + ->where('goods_sku_id', $sku->id) + ->update([ + 'goal_rate' => $request->$updateField + ]); + } + + $this->addLog($id, $updateField); + end: + + return response($this->res, $this->res['httpCode']); + } + +} diff --git a/app/Http/Controllers/Message/WebsiteMessageController.php b/app/Http/Controllers/Message/WebsiteMessageController.php new file mode 100644 index 0000000..33c5408 --- /dev/null +++ b/app/Http/Controllers/Message/WebsiteMessageController.php @@ -0,0 +1,46 @@ +user()->roles)->pluck('id')->toArray()??[]; + $status = $request->input('status')?? 0; + $websiteMessage = WebsiteMessages::query()->where('status','=',$status); + + if(!empty($roleIds)){ + $websiteMessagePage = $websiteMessage->whereIn('role_id',$roleIds) + ->paginate($request->get('per_page')); + return JsonResource::collection($websiteMessagePage); + } + + return JsonResource::collection([]); + } + + public function update($id, Request $request) + { + $validator = Validator::make($request->all(), [ + 'status' => ['required', 'integer', 'in:0,1'] + ]); + if ($validator->fails()) { + $this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); + return response($this->res, $this->res['httpCode']); + } + //更新站內信狀態 + $websiteMessages = WebsiteMessages::query()->find($id); + $websiteMessages->status = $request->status; + Log::info("管理員更新站內信",(array)$request->user()); + $websiteMessages->save(); + return $websiteMessages; + } +} diff --git a/app/Http/Controllers/Shop/ShopsController.php b/app/Http/Controllers/Shop/ShopsController.php index fdc64e2..43f407a 100644 --- a/app/Http/Controllers/Shop/ShopsController.php +++ b/app/Http/Controllers/Shop/ShopsController.php @@ -203,10 +203,11 @@ class ShopsController extends Controller } else { $shops = $builder->where('id', $shopId)->get(); } + //同步三方的是在售库存 $skus = GoodsSku::query() ->where('updated_at', '>', date('Y-m-d 07:01:00')) ->whereNotNull('external_sku_id') - ->pluck('stock', 'external_sku_id') + ->pluck('sale_stock', 'external_sku_id') ->toArray(); foreach ($shops as $shop) { $business = BusinessFactory::init()->make($shop->plat_id); diff --git a/app/Http/Controllers/Supplier/SuppliersController.php b/app/Http/Controllers/Supplier/SuppliersController.php index 657760f..a627893 100644 --- a/app/Http/Controllers/Supplier/SuppliersController.php +++ b/app/Http/Controllers/Supplier/SuppliersController.php @@ -2,36 +2,13 @@ namespace App\Http\Controllers\Supplier; -use App\Events\BatchStockUpdateEvent; -use App\Events\StockUpdateEvent; -use App\Exports\GoodsSkusExport; -use App\Exports\WeekDataExport; use App\Http\Controllers\Controller; -use App\Http\Requests\GoodsRequest; -use App\Http\Requests\GoodsSkuRequest; -use App\Imports\InventoryImport; -use App\Imports\NewSetImport; -use App\Models\BusinessOrderItem; -use App\Models\DailySalesReport; -use App\Models\DeveloperConfig; -use App\Models\Goods; use App\Models\Log; use App\Models\Log as LogModel; use App\Models\Suppliers; -use App\Utils\ArrayUtils; -use App\Utils\DateTimeUtils; -use Carbon\Carbon; use Illuminate\Http\Request; -use App\Models\GoodsSku; -use App\Http\Resources\GoodsSkuResource; -use App\Imports\GoodsSkusImport; -use Illuminate\Support\Facades\DB; +use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Support\Facades\Validator; -use Illuminate\Validation\Rule; -use Illuminate\Validation\ValidationException; -use Maatwebsite\Excel\Facades\Excel; -use App\Models\DailyStockRecord; -use App\Models\Shop; class SuppliersController extends Controller { @@ -47,7 +24,7 @@ class SuppliersController extends Controller public function index(Request $request) { $suppliers = Suppliers::query()->paginate($request->get('per_page')); - return GoodsSkuResource::collection($suppliers); + return JsonResource::collection($suppliers); } public function store(Request $request) { diff --git a/app/Http/Service/MessageService.php b/app/Http/Service/MessageService.php new file mode 100644 index 0000000..ff106cb --- /dev/null +++ b/app/Http/Service/MessageService.php @@ -0,0 +1,21 @@ +format('Y-m-d')."订单号:{$businessOrderId}-商品{$productName} + 规格{$skuName}价格有异常,当前售价{$goodsPrice}/支,当前成本价{$cost}/支"; + $arr['created_at'] = Carbon::now()->toDateTimeString(); + $arr['updated_at'] = Carbon::now()->toDateTimeString(); + return WebsiteMessages::insert($arr); + } + +} \ No newline at end of file diff --git a/app/Listeners/BatchStockUpdateListener.php b/app/Listeners/BatchStockUpdateListener.php index e07edca..54c3fda 100644 --- a/app/Listeners/BatchStockUpdateListener.php +++ b/app/Listeners/BatchStockUpdateListener.php @@ -37,7 +37,7 @@ class BatchStockUpdateListener implements ShouldQueue } foreach ($shops as $shop) { foreach ($event->goodsSkus as $goodsSku) { - $num = $goodsSku->stock; + $num = $goodsSku->sale_stock; $businessGoodsSkus = BusinessGoodsSku::query() ->select(['goods_id', 'sku_id', 'external_sku_id']) ->where('shop_id', $shop->id) diff --git a/app/Listeners/StockUpdateListener.php b/app/Listeners/StockUpdateListener.php index f71014d..612c50e 100644 --- a/app/Listeners/StockUpdateListener.php +++ b/app/Listeners/StockUpdateListener.php @@ -36,7 +36,7 @@ class StockUpdateListener implements ShouldQueue return; } foreach ($shops as $shop) { - $num = $event->goodsSku->stock; + $num = $event->goodsSku->sale_stock; $businessGoodsSkus = BusinessGoodsSku::query() ->select(['goods_id', 'sku_id', 'external_sku_id']) ->where('shop_id', $shop->id) diff --git a/app/Models/PurchaseRecords.php b/app/Models/PurchaseRecords.php index 085c172..4e9357b 100644 --- a/app/Models/PurchaseRecords.php +++ b/app/Models/PurchaseRecords.php @@ -8,4 +8,5 @@ class PurchaseRecords extends Model { // protected $table = 'purchase_records'; + } diff --git a/database/migrations/2024_07_24_144950_create_website_messages_table.php b/database/migrations/2024_07_24_144950_create_website_messages_table.php index 2191bab..c6588b7 100644 --- a/database/migrations/2024_07_24_144950_create_website_messages_table.php +++ b/database/migrations/2024_07_24_144950_create_website_messages_table.php @@ -15,13 +15,14 @@ class CreateWebsiteMessagesTable extends Migration { Schema::create('website_messages', function (Blueprint $table) { $table->bigIncrements('id'); + $table->string('title')->comment('消息内容标题'); $table->string('content')->comment('消息内容'); - $table->string('role')->comment('角色名称'); - $table->Integer('uid')->comment('用户id 存在非0值表示当前uid可见'); - $table->tinyInteger('status')->comment('消息状态 0未读 1已读'); - $table->string('extend')->comment('拓展字段 后续拓展用'); + $table->string('role_id')->nullable()->comment('角色名称'); + $table->Integer('uid')->nullable()->comment('用户id 存在非0值表示当前uid可见'); + $table->tinyInteger('status')->default(0)->comment('消息状态 0未读 1已读'); + $table->string('extend')->nullable()->comment('拓展字段 后续拓展用'); - $table->index(["role","uid"]); + $table->index(["role_id","uid"]); $table->timestamps(); }); } diff --git a/database/migrations/2024_07_24_145201_add_sale_stock_and_quality_period_to_goods_skus_table.php b/database/migrations/2024_07_24_145201_add_sale_stock_and_quality_period_to_goods_skus_table.php index f051e43..0441c0a 100644 --- a/database/migrations/2024_07_24_145201_add_sale_stock_and_quality_period_to_goods_skus_table.php +++ b/database/migrations/2024_07_24_145201_add_sale_stock_and_quality_period_to_goods_skus_table.php @@ -18,7 +18,7 @@ class AddSaleStockAndQualityPeriodToGoodsSkusTable extends Migration } Schema::table('goods_skus', function (Blueprint $table) { // - $table->integer('sale_stock')->default(0)->comment("在售库存数"); + $table->integer('sale_stock')->default(9999)->comment("在售库存数"); $table->integer('quality_period')->nullable()->comment("保质期时间,单位天"); }); diff --git a/public/.htaccess b/public/.htaccess index b75525b..e69de29 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,21 +0,0 @@ - - - Options -MultiViews -Indexes - - - RewriteEngine On - - # Handle Authorization Header - RewriteCond %{HTTP:Authorization} . - RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] - - # Redirect Trailing Slashes If Not A Folder... - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_URI} (.+)/$ - RewriteRule ^ %1 [L,R=301] - - # Handle Front Controller... - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - diff --git a/resources/lang/zh-CN/permission.php b/resources/lang/zh-CN/permission.php index 204b062..6f1b3cf 100644 --- a/resources/lang/zh-CN/permission.php +++ b/resources/lang/zh-CN/permission.php @@ -509,4 +509,20 @@ return [ 'name' => '供应商删除', 'parent_id' => 190, ], + 'MESSAGE' => [ + 'id' => 191, + 'name' => '站內信', + 'parent_id' => 19, + 'show' => 1, + ], + 'website_message.index' => [ + 'id' => 1911, + 'name' => '站內信', + 'parent_id' => 191 + ], + 'website_message.update' => [ + 'id' => 1912, + 'name' => '站內信', + 'parent_id' => 191 + ], ]; diff --git a/routes/api.php b/routes/api.php index 4fd0820..e6ec167 100644 --- a/routes/api.php +++ b/routes/api.php @@ -74,7 +74,7 @@ Route::middleware(['auth:api', 'check.permissions'])->group(function () { Route::get('data_center/sales_report', [DataCenterController::class, 'salesReport'])->name('sales_report.index'); Route::resource('supplier', 'Supplier\SuppliersController', ['only' => ['index', 'update', 'store','destroy']]); - + Route::resource('website_message', 'Message\WebsiteMessageController', ['only' => ['index', 'update']]); }); Route::get('stock/goods_skus', [GoodsSkusController::class, 'stockNum'])->middleware('auth:api'); Route::get('goods/filter/{title}', [GoodsCombinationController::class, 'goodsSkus'])->middleware('auth:api'); diff --git a/tests/Feature/CheckPriceTest.php b/tests/Feature/CheckPriceTest.php new file mode 100644 index 0000000..7c1b03e --- /dev/null +++ b/tests/Feature/CheckPriceTest.php @@ -0,0 +1,20 @@ +handle(); + assertTrue(true); + } +}