!240 鲜花2.0迭代

Merge pull request !240 from 杨建炊/fix-release-1.0.0/yjc-migrate
This commit is contained in:
杨建炊 2024-11-08 01:49:35 +00:00 committed by Gitee
commit 1bff0bf7e8
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
308 changed files with 12363 additions and 16683 deletions

View File

@ -0,0 +1,66 @@
<?php
namespace App\Console\Commands;
use App\Http\Service\MessageService;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CheckPrice extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:price';
/**
* The console command description.
*
* @var string
*/
protected $description = '检查价格异常';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//定时任务每15min执行一次 只会查询15min内的订单数据
$startTime = Carbon::now()->subMinutes(15)->toDateTimeString();
$endTime = Carbon::now()->toDateTimeString();
//查询价格异常订单
$results = DB::table('business_order_items as a')
->select('b.name 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')
->whereBetween('a.created_at', [$startTime,$endTime])
->whereRaw('a.goods_price / 100 < cost')
->get();
if($results->isNotEmpty()){
Log::info($startTime.'异常订单',$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');
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands;
use App\Http\Service\MessageService;
use App\Models\GoodsSku;
use App\Models\PurchaseRecords;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CheckSkuQualityPeriod extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:sku_quality_period';
/**
* The console command description.
*
* @var string
*/
protected $description = '检查sku保质期';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//定时任务每天执行一次
$endTime = Carbon::now()->endOfDay()->toDateTimeString();
$startTime = Carbon::now()->startOfDay()->toDateTimeString();//目前检查范围是1天
//查询未处理过的快过期的异常订单
$purchaseRecords = DB::table('purchase_records as a')
->select("a.created_at", "a.num", "b.title", "b.stock", "a.id", "b.id as sku_id", "b.external_sku_id")
->leftJoin('goods_skus as b', 'a.sku_id', '=', 'b.id')
->where("a.check_status", "=", 0)
->whereBetween('a.expire_time', [$startTime, $endTime])->get();
Log::info('purchaseRecords', (array)$purchaseRecords);
if ($purchaseRecords->isNotEmpty()) {
$messageService = new MessageService();
$updateIds = [];
foreach ($purchaseRecords as $v) {
// 单独采购单后续总和小于库存表示该sku没有卖完
$totalPurchaseNum = PurchaseRecords::query()->where('date', '>=', $v->date)
->where('external_sku_id', "=", $v->external_sku_id)
->where("status", 1)->sum('num');
if ($totalPurchaseNum < $v->stock) {
$messageService->skuQualityPeriodNoticeMessage((array)$v);
}
$updateIds[] = $v->id;
//更新下状态
}
PurchaseRecords::query()->whereIn('id', $updateIds)->update([
"check_status" => 1
]);
}
Log::info('任务完成:check-CheckSkuQualityPeriod');
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace App\Console\Commands;
use App\Models\BusinessOrderItem;
use App\Models\DailyReport;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\LossRecords;
use App\Models\PurchaseRecords;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class DailyStockRecordReport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'daily:report:stock_record {date?}';
/**
* The console command description.
*
* @var string
*/
protected $description = '每日商品库存记录';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$date = $this->argument('date');
if (is_null($date)) {
$date = Carbon::yesterday()->format('Y-m-d');
}
$startDateTime = Carbon::parse($date)->startOfDay()->toDateTimeString();
$endDateTime = Carbon::parse($date)->endOfDay()->toDateTimeString();
//统计订单数量
$orderItems = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("business_order_items.external_sku_id"
, DB::raw("sum(goods_number-already_cancel_number) as goods_total")
, DB::raw("ROUND(sum(goods_amount) / 100,2) as order_total_amount"))
->where('b.confirm_at', '>=', Carbon::parse($startDateTime)->getPreciseTimestamp(3))
->where('b.confirm_at', '<=', Carbon::parse($endDateTime)->getPreciseTimestamp(3))
->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->pluck(null, "external_sku_id")->toArray();
//统计采购单数量
$purchaseRecords = PurchaseRecords::query()
->select(DB::raw("sum(num) as arrived_today_num"), "external_sku_id")
->whereBetween("check_time", [$startDateTime, $endDateTime])
->where("status",1)
->groupBy("external_sku_id")->get()->pluck(null, "external_sku_id")->toArray();
//统计报损数量
$lossRecords = LossRecords::query()
->select(DB::raw("sum(num) as loss_num"), "external_sku_id")
->whereBetween("created_at", [$startDateTime, $endDateTime])
->groupBy("external_sku_id")->get()->pluck(null, "external_sku_id")->toArray();
Log::info("{$date}每日库存记录", ["orderItems" => $orderItems
, "purchaseRecords" => $purchaseRecords, "lossRecords" => $lossRecords]);
$needUpdateExternalSkuIds = array_merge(array_keys($orderItems), array_keys($purchaseRecords), array_keys($lossRecords));
//开始更新数据
if (!empty($needUpdateExternalSkuIds)) {
$needUpdateSkuIdsMap = GoodsSku::query()->whereIn("external_sku_id", $needUpdateExternalSkuIds)->pluck("external_sku_id", 'id')->toArray();
collect($needUpdateSkuIdsMap)->each(function ($externalSkuId,$skuId) use ($date, $orderItems, $purchaseRecords, $lossRecords) {
$record = DailyStockRecord::query()->firstOrNew([
'sku_id' => $skuId,
'day' => $date,
]);
$record->arrived_today_num = !empty($purchaseRecords[$externalSkuId]["arrived_today_num"])
? $purchaseRecords[$externalSkuId]["arrived_today_num"] : 0;
$record->loss_num = !empty($lossRecords[$externalSkuId]["loss_num"])
? $lossRecords[$externalSkuId]["loss_num"] : 0;
$record->order_goods_num = !empty($orderItems[$externalSkuId]["goods_total"])
? $orderItems[$externalSkuId]["goods_total"] : 0;
$record->order_total_amount = !empty($orderItems[$externalSkuId]["order_total_amount"])
? $orderItems[$externalSkuId]["order_total_amount"] : 0;
$record->save();
});
}
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Console\Commands;
use App\Http\Service\MessageService;
use App\Models\GoodsSku;
use App\Services\Business\MiaoXuan\Goods;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class InitSaleStock extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'init:sale_stock {ids?}';
/**
* The console command description.
*
* @var string
*/
protected $description = '初始化在售库存';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$ids = $this->argument('ids');
if(!empty($ids)){
$ids = explode(',',$ids);
}
GoodsSku::query()->where("stock",'>',0)->when($ids,function($query)use($ids){
$query->whereIn('id',$ids);
})->update(['sale_stock' => DB::raw('stock')]);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use App\Models\DailyStockRecord; use App\Models\DailyStockRecord;
use App\Models\GoodsSku; use App\Models\GoodsSku;
@ -41,8 +42,7 @@ class Inventory extends Command
*/ */
public function handle() public function handle()
{ {
DB::beginTransaction();
try {
$log = new Log(); $log = new Log();
$log->module = 'goods'; $log->module = 'goods';
$log->action = 'PATCH'; $log->action = 'PATCH';
@ -53,7 +53,9 @@ class Inventory extends Command
// 数据库存储过程,7点定时执行 // 数据库存储过程,7点定时执行
$data = []; $data = [];
$date = date('Y-m-d'); $date = date('Y-m-d');
GoodsSku::query()->chunk(500, static function ($skus) use (&$data, $date) { GoodsSku::query()->chunk(500, static function ($skus) use (&$data, $date, $log) {
DB::beginTransaction();
try {
foreach ($skus as $sku) { foreach ($skus as $sku) {
$data[] = [ $data[] = [
'sku_id' => $sku->id, 'sku_id' => $sku->id,
@ -64,7 +66,6 @@ class Inventory extends Command
'two_days_ago_num' => $sku->two_days_ago_num + $sku->yesterday_num, 'two_days_ago_num' => $sku->two_days_ago_num + $sku->yesterday_num,
]); ]);
} }
});
$record = new DailyStockRecord(); $record = new DailyStockRecord();
$record->batchInsert($data); $record->batchInsert($data);
DB::commit(); DB::commit();
@ -73,6 +74,8 @@ class Inventory extends Command
$log->message = '7点数据更新失败' . $exception->getMessage(); $log->message = '7点数据更新失败' . $exception->getMessage();
DB::rollBack(); DB::rollBack();
} }
});
$log->save(); $log->save();
$this->info($log->message); $this->info($log->message);
} }

View File

@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands;
use App\Models\Shop;
use App\Services\Business\BusinessFactory;
use App\Utils\DateTimeUtils;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class KttOrderAfterSaleQuery extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ktt:after_sale_order_query';
/**
* The console command description.
*
* @var string
*/
protected $description = '快团团根据成交时间拉取快团团增量售后单';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
* @throws \Exception
*/
public function handle()
{
$shops = Shop::query()->where('plat_id', Shop::$PLAT_KTT)->where('status', Shop::$STATUS_AUTHORIZED)->get();
$endTime = DateTimeUtils::getMicroTime();
$beginTime = $endTime - (15 * 60 * 1000)-1000;//售后单每15min查询一次 多查询一秒
foreach ($shops as $shop) {
BusinessFactory::init()->make($shop->plat_id)->setShop($shop)->downloadAfterSaleOrdersAndSave($beginTime, $endTime);
}
}
}

View File

@ -6,6 +6,7 @@ use App\Models\Shop;
use App\Services\Business\BusinessFactory; use App\Services\Business\BusinessFactory;
use App\Utils\DateTimeUtils; use App\Utils\DateTimeUtils;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class KttOrderQuery extends Command class KttOrderQuery extends Command
{ {

View File

@ -2,9 +2,12 @@
namespace App\Console; namespace App\Console;
use App\Console\Commands\CheckPrice;
use App\Console\Commands\CheckSkuQualityPeriod;
use App\Console\Commands\DailySalesReport; use App\Console\Commands\DailySalesReport;
use App\Console\Commands\GoodsSkuDailyReport; use App\Console\Commands\GoodsSkuDailyReport;
use App\Console\Commands\Inventory; use App\Console\Commands\Inventory;
use App\Console\Commands\KttOrderAfterSaleQuery;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Console\Commands\KttOrderQuery; use App\Console\Commands\KttOrderQuery;
@ -45,6 +48,15 @@ class Kernel extends ConsoleKernel
$schedule->command(DailySalesReport::class, ['S7'])->dailyAt('09:30'); $schedule->command(DailySalesReport::class, ['S7'])->dailyAt('09:30');
$schedule->command(DeleteKttQuery::class)->daily(); $schedule->command(DeleteKttQuery::class)->daily();
//新增价格校验
$schedule->command(CheckPrice::class)->everyFifteenMinutes();
//保质期
$schedule->command(CheckSkuQualityPeriod::class)->dailyAt('05:30');
//快团团售后单拉取
$schedule->command(KttOrderAfterSaleQuery::class)->everyFifteenMinutes();
//同步售卖信息和报损相关数据
$schedule->command(GoodsSkuDailyReport::class)->dailyAt('03:30');
} }
/** /**

View File

@ -10,6 +10,7 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class BatchStockUpdateEvent class BatchStockUpdateEvent
{ {

View File

@ -21,6 +21,8 @@ class BusinessOrdersUpdate
public $businessGoodSku; public $businessGoodSku;
public $num; public $num;
public $goodsSku; public $goodsSku;
public $type = "BusinessOrdersUpdate";
public $combinationGoodsUpdate = true; public $combinationGoodsUpdate = true;
/** /**
@ -30,10 +32,26 @@ class BusinessOrdersUpdate
*/ */
public function __construct($businessGoodSku, $num) public function __construct($businessGoodSku, $num)
{ {
try {
$this->businessGoodSku = $businessGoodSku->toArray(); $this->businessGoodSku = $businessGoodSku->toArray();
$this->num = $num; $this->num = $num;
$this->updateStock(); $updateResult = false;
//暂时设定重试3次
for ($i = 0; $i < 3; $i++) {
if ($this->updateStock()) {
$updateResult = true;
break;
} }
usleep(140);
}
if (!$updateResult) {
Log::error("sku 业务更新失败", (array)$this->businessGoodSku);
}
} catch (\Exception $exception) {
Log::error("sku 业务更新发生异常", ["error" => $exception->getMessage()]);
}
}
private function updateStock() private function updateStock()
{ {
@ -41,28 +59,23 @@ class BusinessOrdersUpdate
->where('external_sku_id', $this->businessGoodSku['external_sku_id']) ->where('external_sku_id', $this->businessGoodSku['external_sku_id'])
->first(); ->first();
if (is_null($this->goodsSku)) { if (is_null($this->goodsSku)) {
return false; return true;
} }
$oldStock = $this->goodsSku->stock;
$stock = $this->goodsSku->stock + $this->num; $stock = $this->goodsSku->stock + $this->num;
$saleStock = $this->goodsSku->sale_stock + $this->num;
if (0 >= $stock) { $saleStock = ($saleStock > 0) ? $saleStock : 0;
$this->goodsSku->status = GoodsSku::$STATUS_DOWN; $updateParam = ["stock" => $stock, "sale_stock" => $saleStock];
if (0 >= $saleStock) {
$updateParam['status'] = GoodsSku::$STATUS_DOWN;
} else { } else {
$this->goodsSku->status = GoodsSku::$STATUS_ON_SALE; $updateParam['status'] = GoodsSku::$STATUS_ON_SALE;
} }
// 今日到货 + 1T 大于20,且当前剩余库存小于4时 直接下架 Log::info("sku 业务订单库存更:{$this->goodsSku->id}", $updateParam);
$arrivedTodayNum = DailyStockRecord::query() //乐观锁更新
->where('day', DateTimeUtils::getToday()) return GoodsSku::query()->where('external_sku_id', $this->businessGoodSku['external_sku_id'])->
->where('sku_id', $this->goodsSku->id) where("stock", "=", $oldStock)->update($updateParam);
->value('arrived_today_num');
if (20 < $arrivedTodayNum + $this->goodsSku->yesterday_num && 4 > $stock) {
$this->goodsSku->status = GoodsSku::$STATUS_DOWN;
$stock = 0;
}
$this->goodsSku->stock = $stock;
$this->goodsSku->save();
} }
/** /**

View File

@ -33,22 +33,14 @@ class StockUpdateEvent
private function checkStatusAndStock($goodsSku) private function checkStatusAndStock($goodsSku)
{ {
$stock = $goodsSku->stock; //新版上下架和真实库存无关
if (0 >= $goodsSku->stock) { if (0 >= $goodsSku->sale_stock) {
$status = GoodsSku::$STATUS_DOWN; $status = GoodsSku::$STATUS_DOWN;
} else { } else {
$status = GoodsSku::$STATUS_ON_SALE; $status = GoodsSku::$STATUS_ON_SALE;
} }
$arrivedTodayNum = DailyStockRecord::query()
->where('day', DateTimeUtils::getToday())
->where('sku_id', $goodsSku->id)
->value('arrived_today_num');
if (20 < $arrivedTodayNum + $goodsSku->yesterday_num && 4 > $goodsSku->stock) {
$status = GoodsSku::$STATUS_DOWN;
$stock = 0;
}
$goodsSku->status = $status; $goodsSku->status = $status;
$goodsSku->stock = $stock;
$goodsSku->save(); $goodsSku->save();
} }

View File

@ -0,0 +1,63 @@
<?php
namespace App\Exports;
use App\Http\Enum\AfterSaleOrderStatusEnum;
use App\Models\DailyReport;
use App\Models\Shop;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Illuminate\Support\Collection;
class BusinessAfterOrderExport implements FromCollection, ShouldAutoSize
{
private $data;
public function __construct($orders)
{
$this->data = $this->createData($orders);
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return new Collection($this->data);
}
private function createData($orders)
{
$headTitle = [
'父订单编号',
'店铺名称',
'退款金额',
'用户申请退运费金额',
'退款原因',
'描述',
'申请类型',
'售后单状态',
'售后单创建时间',
'图片链接',
];
$bodyData = [];
foreach ($orders as $order) {
$bodyData[] = [
$order['order_sn'],
$order['shop']['name'],
bcdiv($order['refund_amount'], 100, 2),
bcdiv($order['refund_shipping_amount'], 100, 2),
$order['reason'],
$order['description'],
empty($order['apply_type']) ? "仅退款" : "退货退款",
AfterSaleOrderStatusEnum::getData($order['after_sales_status']),
$order['after_sale_created_at'],
implode(",", $order['image_list'])
];
}
return [$headTitle, $bodyData];
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Exports;
use App\Models\DailyReport;
use App\Models\Shop;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Illuminate\Support\Collection;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class BusinessOrderExport implements FromCollection, ShouldAutoSize
{
private $data;
public function __construct($orders)
{
$this->data = $this->createData($orders);
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return new Collection($this->data);
}
private function createData($orders)
{
$headTitle = [
'订单id/编号',
'商品信息|个数',
'店铺名称',
'跟团号',
'收件人',
'收件地址',
'下单时间',
'发货状态',
'订单状态',
'售后状态',
];
$bodyData = [];
foreach ($orders as $order) {
$productInfo = "";
foreach ($order['items'] as $item) {
$productInfo .= $item['goods_name']. "|" . $item['goods_number'] . ",";
}
rtrim($productInfo, ",");
$bodyData[] = [
$order['id'] . "/" . $order['order_sn'],
$productInfo,
$order['shop']['name'],
$order['is_supplier'].":".$order['participate_no'],
$order['receiver_name'],
$order['receiver_address_province']." ". $order['receiver_address_city']." ".$order['receiver_address_district']." ".$order['receiver_address_detail'],
$order['confirm_at'],
$order['shipping_status'],
$order['cancel_status'],
$order['after_sales_status']
];
}
return [$headTitle, $bodyData];
}
}

View File

@ -2,8 +2,11 @@
namespace App\Exports; namespace App\Exports;
use App\Http\Enum\TargetTypeEnum;
use App\Models\DailyStockRecord; use App\Models\DailyStockRecord;
use App\Models\Goods;
use App\Models\Log; use App\Models\Log;
use App\Models\PurchaseRecords;
use App\Utils\ArrayUtils; use App\Utils\ArrayUtils;
use App\Utils\DateTimeUtils; use App\Utils\DateTimeUtils;
use Maatwebsite\Excel\Concerns\FromCollection; use Maatwebsite\Excel\Concerns\FromCollection;
@ -35,22 +38,36 @@ class GoodsSkusExport implements FromCollection, ShouldAutoSize
$headTitle = [ $headTitle = [
'商品编码', '商品编码',
'商品名称', '商品名称',
'商品种类', '可售库存',
'商品品牌', '创建时间',
'规格编码',
'规格名称',
]; ];
$map = [ $map = [
'cost' => ['成本', '更新前成本', '更新后成本'], 'cost' => ['当前成本', '更新前成本', "更新后成本"],
'inventory' => ['库存', '盘点', '上新'], 'inventory' => ['当前库存', '盘点数', '采购总数'],
]; ];
$headTitle = array_merge($headTitle, $map[$this->type]); $headTitle = array_merge($headTitle, $map[$this->type] ?? []);
if ('cost' === $this->type) { if ('cost' === $this->type) {
$update = $this->getChangeCostLogs(); $update = $this->getChangeCostLogs();
} }
if ('inventory' === $this->type) { if ('inventory' === $this->type) {
$update = $this->getInventoryRecord(); $update = $this->getInventoryRecord();
} }
if ($this->type === "goods_sku" || $this->type === "goods_combination") {
$builder = GoodsSku::query();
if (request()->get('type_id')) {
$goodsIds = Goods::query()->filter()->pluck('id')->toArray();
$builder->whereIn('goods_id', $goodsIds);
}
if (request()->get('goods_title')) {
$builder->where('name', 'like', '%' . request()->get('goods_title') . '%');
}
if ($this->type === "goods_combination") {
$builder->where('is_combination', 1);
} else {
$builder->where('is_combination', 0);
}
$data = $builder->filter()->orderByDesc('id')->get()->toArray();
} else {
$ids = array_keys($update); $ids = array_keys($update);
if (empty($ids)) { if (empty($ids)) {
return [$headTitle]; return [$headTitle];
@ -68,23 +85,23 @@ class GoodsSkusExport implements FromCollection, ShouldAutoSize
if (empty($data)) { if (empty($data)) {
return [$headTitle]; return [$headTitle];
} }
}
$bodyData = []; $bodyData = [];
foreach ($data as $item) { foreach ($data as $item) {
$arr[0] = $item['goods']['goods_code']; $arr[0] = $item['external_sku_id'];
$arr[1] = $item['goods']['title']; $arr[1] = $item['name'];
$arr[2] = $item['goods']['type']['name']; $arr[2] = $item['sale_stock'] ?? 0;
$arr[3] = $item['goods']['brand']['name']; $arr[3] = $item['created_at'];
$arr[4] = $item['sku_code'];
$arr[5] = $item['title'];
if ('cost' === $this->type) { if ('cost' === $this->type) {
$arr[6] = (string)$item['cost']; $arr[4] = (string)$item['cost'];
$arr[7] = (string)$update[$item['id']]['before_update']; $arr[5] = (string)$update[$item['id']]['before_update'];
$arr[8] = (string)$update[$item['id']]['after_update']; $arr[6] = (string)$update[$item['id']]['after_update'];
} }
if ('inventory' === $this->type) { if ('inventory' === $this->type) {
$arr[6] = (string)$item['stock']; $arr[4] = (string)$item['stock'];
$arr[7] = (string)$update[$item['id']]['inventory']; $arr[5] = (string)$update[$item['id']]['inventory'];
$arr[8] = (string)$update[$item['id']]['arrived_today_num']; $arr[6] = (string)$update[$item['id']]['arrived_today_num'];
} }
$bodyData[] = $arr; $bodyData[] = $arr;
} }
@ -97,18 +114,23 @@ class GoodsSkusExport implements FromCollection, ShouldAutoSize
$day = DateTimeUtils::getToday(); $day = DateTimeUtils::getToday();
$logs = Log::query() $logs = Log::query()
->select(['target_id', 'before_update', 'after_update']) ->select(['target_id', 'before_update', 'after_update'])
->where('target_type', 'goods_sku') ->where('target_type', TargetTypeEnum::PURCHASE)
->where('target_field', 'cost') ->where('target_field', 'stock')
->where('created_at', '>', $day) ->where('created_at', '>', $day)
->orderBy('id', 'asc')
->get(); ->get();
$update = []; $update = [];
foreach ($logs as $log) { foreach ($logs as $log) {
if ($log['before_update'] !== $log['after_update'] || (int)$log['after_update']) {
if (!isset($update[$log['target_id']])) { if (!isset($update[$log['target_id']])) {
$update[$log['target_id']]['before_update'] = $log['before_update']; $beforeData = json_decode($log['before_update'], true);
if (!empty($beforeData['cost'])) {
$update[$log['target_id']]['before_update'] = $beforeData['cost'];
} }
$update[$log['target_id']]['after_update'] = $log['after_update'];
}
$afterData = json_decode($log['after_update'], true);
if (!empty($afterData['cost']) && $afterData['cost'] != $beforeData['cost']) {
$update[$log['target_id']]['after_update'] = $afterData['cost'];
} }
} }

View File

@ -33,9 +33,8 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
{ {
$headTitle = [ $headTitle = [
'商品名称', '商品名称',
'品类', '品种',
'规格', '规格名称',
'品牌',
'商品编码', '商品编码',
'成本', '成本',
'销售数量', '销售数量',
@ -51,8 +50,7 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
->with([ ->with([
'goods:id,title', 'goods:id,title',
'goodsType:id,name', 'goodsType:id,name',
'goodsSku:id,title', 'goodsSku:id,title,name'
'goodsBrand:id,name',
]) ])
->where('date', '>=', $this->startDate) ->where('date', '>=', $this->startDate)
->where('date', '<=', $this->endDate) ->where('date', '<=', $this->endDate)
@ -66,10 +64,9 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
$number = $item->total_goods_number - $item->total_cancel_number; $number = $item->total_goods_number - $item->total_cancel_number;
if (!isset($arr[$item->external_sku_id])) { if (!isset($arr[$item->external_sku_id])) {
$arr[$item->external_sku_id] = [ $arr[$item->external_sku_id] = [
'goodsTitle' => $item->goods->title, 'goodsTitle' => !empty($item->goodsSku) ? $item->goodsSku->name:'',
'goodsTypeName' => $item->goodsType->name, 'goodsTypeName' => !empty($item->goodsType) ? $item->goodsType->name : "",
'goodsSkuTitle' => $item->goodsSku->title, 'goodsSkuTitle' => !empty($item->goodsSku) ? $item->goodsSku->title : "",
'goodsBrandName' => $item->goodsBrand->name,
'external_sku_id' => $item->external_sku_id, 'external_sku_id' => $item->external_sku_id,
'cost' => [], 'cost' => [],
'number' => [], 'number' => [],
@ -97,7 +94,6 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
$item['goodsTitle'], $item['goodsTitle'],
$item['goodsTypeName'], $item['goodsTypeName'],
$item['goodsSkuTitle'], $item['goodsSkuTitle'],
$item['goodsBrandName'],
$item['external_sku_id'], $item['external_sku_id'],
$cost, $cost,
array_sum($item['number']), array_sum($item['number']),

View File

@ -2,6 +2,8 @@
namespace App\Filters; namespace App\Filters;
use Carbon\Carbon;
class GoodsSkuFilter extends Filters class GoodsSkuFilter extends Filters
{ {
protected function skuTitle($value) protected function skuTitle($value)
@ -28,4 +30,16 @@ class GoodsSkuFilter extends Filters
{ {
return $this->builder->where('is_combination', $value); return $this->builder->where('is_combination', $value);
} }
protected function createTimeStart($value)
{
return $this->builder->where('created_at', ">=", $value);
}
protected function createTimeEnd($value)
{
$end = Carbon::parse($value)->endOfDay()->toDateTimeString();
return $this->builder->where('created_at', "<=", $end);
}
} }

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Business;
use App\Exports\BusinessAfterOrderExport;
use App\Exports\BusinessOrderExport;
use App\Exports\OrderBlankExport;
use App\Http\Controllers\Controller;
use App\Models\BusinessAfterSaleOrder;
use App\Models\BusinessOrder;
use App\Models\BusinessOrderItem;
use App\Models\GoodsSku;
use App\Models\Shop;
use App\Services\Ship\WayBillService;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Resources\BusinessOrderResource;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
class BusinessAfterSaleOrderController extends Controller
{
public function index(Request $request)
{
$shopIds = Shop::query()
->where('plat_id', Shop::$PLAT_KTT)
->pluck('id');
$builder = BusinessAfterSaleOrder::query()
->with(["shop:id,name"])
->whereIn('shop_id', $shopIds)
->filter();
if (!empty($request->created_at_start) & !empty($request->created_at_end)) {
$builder = $builder->whereBetween("after_sale_created_at"
, [$request->created_at_start, $request->created_at_end]);
}
if ($request->get("is_export")) {
$params = $request->validate([
'created_at_start' => 'required',
'created_at_end' => 'required',
], [
'created_at_start.required' => '请输入开始确认时间',
'created_at_end.required' => '请输入结束确认时间',
]);
$startDate = Carbon::parse($params['created_at_start']);
$endDate = Carbon::parse($params['created_at_end']);
if ($endDate->gt($startDate->copy()->addMonth())) {
throw new \Exception("导出时间超出一个月");
}
return Excel::download(new BusinessAfterOrderExport($builder->get()->toArray())
, $startDate . '~' . $endDate . "售后订单数据" . '.xlsx');
}
$businessOrders = $builder->orderByDesc('after_sale_created_at')
->paginate($request->get('per_page'));
$businessOrders->getCollection()->map(function ($v) {
$v->refund_amount = bcdiv($v->refund_amount,100,2);
$v->refund_shipping_amount = bcdiv($v->refund_shipping_amount,100,2);
});
return JsonResource::collection($businessOrders);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Business; namespace App\Http\Controllers\Business;
use App\Exports\BusinessOrderExport;
use App\Exports\OrderBlankExport; use App\Exports\OrderBlankExport;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\BusinessOrder; use App\Models\BusinessOrder;
@ -35,6 +36,24 @@ class BusinessOrderController extends Controller
$ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id'); $ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id');
$builder->whereIn('id', $ids); $builder->whereIn('id', $ids);
} }
if($request->get("is_export")){
$params = $request->validate([
'confirm_at_start' => 'required',
'confirm_at_end' => 'required',
], [
'confirm_at_start.required' => '请输入开始确认时间',
'confirm_at_end.required' => '请输入结束确认时间',
]);
$startDate = Carbon::parse($params['confirm_at_start']);
$endDate = Carbon::parse($params['confirm_at_end']);
if ($endDate->gt($startDate->copy()->addMonth())) {
throw new \Exception("导出时间超出一个月");
}
return Excel::download(new BusinessOrderExport($builder->get()->toArray()), $startDate . '~' . $endDate."订单数据" . '.xlsx');
}
$businessOrders = $builder->orderByDesc('confirm_at') $businessOrders = $builder->orderByDesc('confirm_at')
->paginate($request->get('per_page')); ->paginate($request->get('per_page'));

View File

@ -47,4 +47,23 @@ class Controller extends BaseController
return $this->log->add($targetId, $targetField); return $this->log->add($targetId, $targetField);
} }
public function success($data, $msg = "")
{
return [
"success" => true,
"msg" => $msg,
"data" => $data
];
}
public function error($errorCode, $msg = "")
{
return [
"success" => false,
"msg" => $msg,
"errorCode" => $errorCode,
"data" => []
];
}
} }

View File

@ -3,10 +3,17 @@
namespace App\Http\Controllers\DataCenter; namespace App\Http\Controllers\DataCenter;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Enum\StaticTypeEnum;
use App\Http\Resources\DailySalesReportResource; use App\Http\Resources\DailySalesReportResource;
use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport; use App\Models\DailySalesReport;
use App\Models\GoodsSku;
use App\Services\Statistic\SaleDataService;
use App\Utils\FormatUtils; use App\Utils\FormatUtils;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class DataCenterController extends Controller class DataCenterController extends Controller
{ {
@ -37,4 +44,89 @@ class DataCenterController extends Controller
return DailySalesReportResource::collection($dailySalesReports); return DailySalesReportResource::collection($dailySalesReports);
} }
/**
* 销售报表
* @param Request $request
* @return void
*/
public function saleStatistics(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'type' => 'required|integer', //1表示今日
'start_day' => 'sometimes|date_format:Y-m-d',
'end_day' => 'sometimes|date_format:Y-m-d',
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::saleStatistics($request));
}
/**
* spu 维度的数据统计
* @param Request $request
* @return array|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|mixed
*/
public function spuSaleStatistics(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',
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::spuSaleStatistics($request));
}
public function gmvStatistics(Request $request)
{
//进行校验验证
$validator = Validator::make($request->all(), [
'type' => 'required|integer', //1表示今日
'start_day' => 'required_unless:type,1|date_format:Y-m-d',
'end_day' => 'required_unless:type,1|date_format:Y-m-d',
"interval" => 'sometimes|integer|max:120|min:1',
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s',
'sku_id' => 'sometimes|integer'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::gmvStatistics($request));
}
public function lossRecordStatistics(Request $request)
{
//进行校验验证
$validator = Validator::make($request->all(), [
'start_time' => 'sometimes|date_format:Y-m-d H:i:s',
'end_time' => 'sometimes|date_format:Y-m-d H:i:s',
'sku_id' => 'sometimes|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
return $this->success(SaleDataService::lossRecordStatistics($request));
}
} }

View File

@ -7,10 +7,14 @@ use App\Http\Resources\GoodsSkuResource;
use App\Imports\CombinationGoodsImport; use App\Imports\CombinationGoodsImport;
use App\Models\BusinessOrderItem; use App\Models\BusinessOrderItem;
use App\Models\CombinationGood; use App\Models\CombinationGood;
use App\Models\DeveloperConfig;
use App\Models\Goods; use App\Models\Goods;
use App\Models\GoodsSku; use App\Models\GoodsSku;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
@ -20,43 +24,82 @@ class GoodsCombinationController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
// ToDo // ToDo
// 可通过子商品查找主商品 $sortField = $request->input('sort_field', 'id');//stock sale_stock order_goods_num
$skus = GoodsSku::query() $sortValue = $request->input('sort_value', 'desc');
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num',
'combinationGoods.goodsSkuItem:id,goods_id,title,stock,external_sku_id,updated_at,yesterday_num,reference_price,status',
'combinationGoods.goodsSkuItem.goods:id,title,img_url',
])
->where('is_combination', 1)
->filter()
->orderBy('stock', 'desc')
->paginate($request->get('per_page'));
$fields = implode(',', [ $fields = implode(',', [
'shop_id', 'shop_id',
'external_sku_id', 'external_sku_id',
'sum(goods_number) as number', 'SUM(goods_number) - SUM(already_cancel_number) as number',
'sum(already_cancel_number) as cancel_number',
]); ]);
foreach ($skus as &$item) { $orderRestTime = DeveloperConfig::query()
$items = []; ->where('key', DeveloperConfig::$ORDER_RESET_TIME)
$lastInventoryTime = date('Y-m-d 07:00:00'); ->value('value');
$orderDetail = BusinessOrderItem::query() if (is_null($orderRestTime)) {
$orderRestTime = date('Y-m-d 07:00:00');
}
$businessOrderItems = BusinessOrderItem::query()
->select(DB::raw($fields)) ->select(DB::raw($fields))
->with(['shop:id,name']) ->with([
->where('external_sku_id', $item['external_sku_id']) 'shop:id,name',
->when($lastInventoryTime, function ($query) use ($lastInventoryTime) { 'goodsSku:id,external_sku_id,is_combination',
$query->where('created_at', '>', $lastInventoryTime); 'goodsSku.combinationGoods:id,goods_sku_id,item_id,item_num'
}) ])
->where('created_at', '>', $orderRestTime)
->where('external_sku_id', '<>', '')
->groupBy(['shop_id', 'external_sku_id']) ->groupBy(['shop_id', 'external_sku_id'])
->orderByDesc('number')
->get() ->get()
->toArray(); ->toArray();
$addOrderGoodsNum = $reduceOrderGoodsNum = 0; $ids = $externals = [];
if ($orderDetail) { foreach ($businessOrderItems as $businessOrderItem) {
$addOrderGoodsNum = array_sum(array_column($orderDetail, 'number')); if (is_null($businessOrderItem['goods_sku'])) {
$reduceOrderGoodsNum = array_sum(array_column($orderDetail, 'cancel_number')); continue;
} }
$item['order_goods_num'] = $addOrderGoodsNum - $reduceOrderGoodsNum; $id = $businessOrderItem['goods_sku']['id'];
$item['order_detail'] = $orderDetail; if (isset($ids[$id])) {
$ids[$id] += (int)$businessOrderItem['number'];
} else {
$ids[$id] = (int)$businessOrderItem['number'];
}
$externals[$id][] = $businessOrderItem;
}
arsort($ids);
// 可通过子商品查找主商品
$goodsSkusBuilder = GoodsSku::query()
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num',
'combinationGoods.goodsSkuItem:id,name,goods_id,title,stock,sale_stock,external_sku_id,updated_at,yesterday_num,reference_price,status',
'combinationGoods.goodsSkuItem.goods:id,title,img_url',
])
->where('is_combination', 1)
->filter();
if ($sortField == "order_goods_num") {
$finalIds = [];
asort($ids);
foreach ($ids as $id => $number) {
$finalIds[] = $id;
}
$idField = implode(',', $finalIds);
$goodsSkusBuilder->orderByRaw("FIELD(id,{$idField}) {$sortValue}");
} else {
$goodsSkusBuilder->orderBy($sortField, $sortValue);
}
$skus = $goodsSkusBuilder
->paginate($request->get('per_page'));
foreach ($skus as &$item) {
$items = [];
if (isset($externals[$item['id']])) {
$item['order_detail'] = $externals[$item['id']];
$item['order_goods_num'] = $ids[$item['id']];
} else {
$item['order_detail'] = [];
$item['order_goods_num'] = 0;
}
$number = BusinessOrderItem::query() $number = BusinessOrderItem::query()
->where('external_sku_id', $item['external_sku_id']) ->where('external_sku_id', $item['external_sku_id'])
->sum('goods_number'); ->sum('goods_number');
@ -65,6 +108,9 @@ class GoodsCombinationController extends Controller
->sum('already_cancel_number'); ->sum('already_cancel_number');
$item['total_orders_num'] = $number - $cancelNumber; $item['total_orders_num'] = $number - $cancelNumber;
foreach ($item['combinationGoods'] as $combinationItem) { foreach ($item['combinationGoods'] as $combinationItem) {
$title = !empty($combinationItem['goodsSkuItem']['name']) ? $combinationItem['goodsSkuItem']['name'] :
(!empty($combinationItem['goodsSkuItem']['goods']) ? $combinationItem['goodsSkuItem']['goods']['title']
. " " . $combinationItem['goodsSkuItem']['title'] : $combinationItem['goodsSkuItem']['title']);
$items[] = [ $items[] = [
'cost' => 0, 'cost' => 0,
'external_sku_id' => $combinationItem['goodsSkuItem']['external_sku_id'], 'external_sku_id' => $combinationItem['goodsSkuItem']['external_sku_id'],
@ -75,9 +121,10 @@ class GoodsCombinationController extends Controller
'reference_price' => $combinationItem['goodsSkuItem']['reference_price'], 'reference_price' => $combinationItem['goodsSkuItem']['reference_price'],
'status' => $combinationItem['goodsSkuItem']['status'], 'status' => $combinationItem['goodsSkuItem']['status'],
'stock' => $combinationItem['goodsSkuItem']['stock'], 'stock' => $combinationItem['goodsSkuItem']['stock'],
'thumb_url' => $combinationItem['goodsSkuItem']['goods']['img_url'], 'sale_stock' => $combinationItem['goodsSkuItem']['sale_stock'],
'img_url' => $combinationItem['goodsSkuItem']['goods']['img_url'], 'thumb_url' => null,
'title' => $combinationItem['goodsSkuItem']['goods']['title'] . $combinationItem['goodsSkuItem']['title'], 'img_url' => null,//图片暂时去掉
'title' => $title,
'updated_at' => $combinationItem['goodsSkuItem']['updated_at'], 'updated_at' => $combinationItem['goodsSkuItem']['updated_at'],
'yesterday_num' => $combinationItem['goodsSkuItem']['yesterday_num'], 'yesterday_num' => $combinationItem['goodsSkuItem']['yesterday_num'],
'order_goods_num' => '请在商品列表查看', 'order_goods_num' => '请在商品列表查看',
@ -88,15 +135,18 @@ class GoodsCombinationController extends Controller
$item['children'] = $items; $item['children'] = $items;
unset($item['combinationGoods']); unset($item['combinationGoods']);
} }
$rolesName = $request->user()->getRoleNames()->toArray();
$data = ["manage" => ["is_admin" => in_array($rolesName[0]
, ["运营", "超级管理员", "管理员", "系统管理员", "店铺运营"]) ? 1 : 0]];
return GoodsSkuResource::collection($skus); return GoodsSkuResource::collection($skus)->additional($data);
} }
public function store(Request $request) public function store(Request $request)
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'title' => 'required', 'title' => 'required',
'external_sku_id' => 'required', 'external_sku_id' => 'sometimes',
'combination_goods.*' => 'required', 'combination_goods.*' => 'required',
'combination_goods.*.item_id' => 'required', 'combination_goods.*.item_id' => 'required',
'combination_goods.*.item_num' => 'required|gt:0', 'combination_goods.*.item_num' => 'required|gt:0',
@ -106,20 +156,33 @@ class GoodsCombinationController extends Controller
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
$externalSkuId = $request->input('external_sku_id') ??
GeneratorUtils::generateCombinationGoodNumber($request->combination_goods);
$hasCodeSku = GoodsSku::query()->where("external_sku_id", $externalSkuId)->first();
if (!empty($hasCodeSku)) {
throw new \Exception("该组合商品编码已存在");
}
DB::beginTransaction(); DB::beginTransaction();
try { try {
$combinationGoods = $request->input('combination_goods'); $combinationGoods = $request->input('combination_goods');
$itemIds = array_column($combinationGoods, 'item_id'); $itemIds = array_column($combinationGoods, 'item_id');
$skus = GoodsSku::query() $skus = GoodsSku::query()
->whereIn('id', $itemIds) ->whereIn('id', $itemIds)
->pluck('stock', 'id') ->get()
->pluck(null, 'id')
->toArray(); ->toArray();
$stock = []; $stock = [];
$saleStock = [];
foreach ($combinationGoods as $item) { foreach ($combinationGoods as $item) {
$stock[] = (int)($skus[$item['item_id']] / $item['item_num']); if (!empty($skus[$item['item_id']])) {
$stock[] = (int)($skus[$item['item_id']]['stock'] / $item['item_num']);
$saleStock[] = (int)($skus[$item['item_id']]['sale_stock'] / $item['item_num']);
}
} }
$stock = min($stock); $stock = min($stock);
$status = $stock ? (5 < $stock ? 1 : 2) : 0; $saleStock = min($saleStock);
$status = $saleStock ? (5 < $saleStock ? 1 : 2) : 0;
if ($id = $request->input('id')) { if ($id = $request->input('id')) {
$sku = GoodsSku::query()->findOrFail($id); $sku = GoodsSku::query()->findOrFail($id);
} else { } else {
@ -127,11 +190,15 @@ class GoodsCombinationController extends Controller
$sku->goods_id = 0; $sku->goods_id = 0;
$sku->is_combination = 1; $sku->is_combination = 1;
} }
$sku->status = $status; $sku->status = $status;
$sku->title = $request->input('title'); $sku->title = $request->input('title');
$sku->sku_code = $request->input('external_sku_id'); $sku->name = $request->input('title');
$sku->external_sku_id = $request->input('external_sku_id'); $sku->sku_code = $externalSkuId;
$sku->external_sku_id = $externalSkuId;
$sku->stock = $stock; $sku->stock = $stock;
$sku->sale_stock = $saleStock;
$sku->save(); $sku->save();
CombinationGood::query() CombinationGood::query()
->where('goods_sku_id', $sku->id) ->where('goods_sku_id', $sku->id)
@ -180,16 +247,17 @@ class GoodsCombinationController extends Controller
public function goodsSkus(Request $request, $title) public function goodsSkus(Request $request, $title)
{ {
$goodsIds = Goods::query()
->where('title', 'like', '%' . $title . '%')
->pluck('id');
$skus = GoodsSku::query() $skus = GoodsSku::query()
->whereIn('goods_id', $goodsIds) ->where('name', 'like', '%' . $title . '%')
->where('is_combination', 0) ->where('is_combination', 0)
->with('goods:id,title') ->with('goods:id,title')
->get(['id', 'title', 'goods_id']); ->get(['id', 'title', 'goods_id', "name"]);
foreach ($skus as &$sku) { foreach ($skus as &$sku) {
$sku['title'] = $sku['goods']['title'] . $sku['title']; if (!empty($sku['name'])) {
$sku['title'] = $sku['name'];
} else {
$sku['title'] = ($sku['goods']['title'] ?? "") . $sku['title'];
}
} }
return GoodsSkuResource::collection($skus); return GoodsSkuResource::collection($skus);

View File

@ -5,10 +5,13 @@ namespace App\Http\Controllers\Goods;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\GoodsSkuRequest; use App\Http\Requests\GoodsSkuRequest;
use App\Http\Resources\GoodsResource; use App\Http\Resources\GoodsResource;
use App\Models\GoodsType;
use App\Models\Log as LogModel; use App\Models\Log as LogModel;
use App\Services\Good\GoodService;
use App\Utils\DateTimeUtils; use App\Utils\DateTimeUtils;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use App\Models\Goods; use App\Models\Goods;
use App\Http\Requests\GoodsRequest; use App\Http\Requests\GoodsRequest;
@ -42,28 +45,23 @@ class GoodsController extends Controller
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
DB::beginTransaction(); DB::beginTransaction();
try { try {
if (!empty($request->goods_id)) { $goodService = new GoodService();
$goods = Goods::query()->find($request->goods_id); $goods = $goodService->saveDefaultGoodsByGoodType($request->type_id);
} else {
$goods = new Goods();
$goods->title = $request->title;
$goods->img_url = $request->img_url;
$goods->type_id = $request->type_id;
$goods->brand_id = $request->brand_id;
$goods->goods_code = $request->goods_code;
$goods->save();
}
$goodsSkus = []; $goodsSkus = [];
foreach ($request->skus as $item) { foreach ($request->skus as $item) {
$item['goods_id'] = $goods->id; $item['goods_id'] = $goods->id;
$item['stock'] = $item['num']; $item['stock'] = $item['num'] ?? 0;
$item['reference_price'] = $item['cost'] * 1.5; $item['reference_price'] = $item['cost'] * 1.5;
$item['external_sku_id'] = $goods->goods_code . '_' . $item['sku_code']; $item['sku_code'] = !empty($item['external_sku_id']) ? $item['external_sku_id'] : $goodService->getRandomCode();
$item['name'] = $goods->title . $item['title']; $item['external_sku_id'] = !empty($item['external_sku_id']) ? $item['external_sku_id'] : ($goods->goods_code . '_' . $item['sku_code']);
$item['name'] = $goodService->getSkuName($request->type_id,$item);
$goodsSkus[] = $item; $goodsSkus[] = $item;
} }
$collection = $goods->skus()->createMany($goodsSkus)->toArray(); $collection = $goods->skus()->createMany($goodsSkus)->toArray();
$this->setAfterUpdateForLog($collection); $this->setAfterUpdateForLog($collection);
$this->addLog(0, 'add'); $this->addLog(0, 'add');
@ -89,13 +87,14 @@ class GoodsController extends Controller
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
public function download() public function download()
{ {
$file = resource_path('templates/goods_skus_import.xlsx'); $file = resource_path('templates/sale_stock_import.xlsx');
$headers = [ $headers = [
'Content-Type: application/xlsx', 'Content-Type: application/xlsx',
]; ];
return response()->download($file, 'goods_skus_import.xlsx', $headers); return response()->download($file, 'sale_stock_import.xlsx', $headers);
} }
} }

View File

@ -7,16 +7,22 @@ use App\Events\StockUpdateEvent;
use App\Exports\GoodsSkusExport; use App\Exports\GoodsSkusExport;
use App\Exports\WeekDataExport; use App\Exports\WeekDataExport;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Http\Requests\GoodsRequest; use App\Http\Requests\GoodsRequest;
use App\Http\Requests\GoodsSkuRequest; use App\Http\Requests\GoodsSkuRequest;
use App\Imports\InventoryImport; use App\Imports\InventoryImport;
use App\Imports\NewSetImport; use App\Imports\NewSetImport;
use App\Imports\SaleStockImport;
use App\Models\BusinessOrderItem; use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport; use App\Models\DailySalesReport;
use App\Models\DeveloperConfig; use App\Models\DeveloperConfig;
use App\Models\Goods; use App\Models\Goods;
use App\Models\Log; use App\Models\Log;
use App\Models\Log as LogModel; use App\Models\Log as LogModel;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\Good\GoodService;
use App\Services\GoodSku\GoodSkuService;
use App\Services\Ship\WayBillService;
use App\Utils\ArrayUtils; use App\Utils\ArrayUtils;
use App\Utils\DateTimeUtils; use App\Utils\DateTimeUtils;
use Carbon\Carbon; use Carbon\Carbon;
@ -60,7 +66,8 @@ class GoodsSkusController extends Controller
->select(DB::raw($fields)) ->select(DB::raw($fields))
->with([ ->with([
'shop:id,name', 'shop:id,name',
'goodsSku:id,external_sku_id' 'goodsSku:id,external_sku_id,is_combination',
'goodsSku.combinationGoods:id,goods_sku_id,item_id,item_num'
]) ])
->where('created_at', '>', $orderRestTime) ->where('created_at', '>', $orderRestTime)
->where('external_sku_id', '<>', '') ->where('external_sku_id', '<>', '')
@ -74,62 +81,76 @@ class GoodsSkusController extends Controller
continue; continue;
} }
$id = $businessOrderItem['goods_sku']['id']; $id = $businessOrderItem['goods_sku']['id'];
if ($businessOrderItem['goods_sku']['is_combination']) {
foreach ($businessOrderItem['goods_sku']['combination_goods'] ?? [] as $combinationGoods) {
$ids[$combinationGoods['item_id']] = $ids[$combinationGoods['item_id']] ?? 0
+ ((int)$businessOrderItem['number']) * $combinationGoods['item_num'];
$externals[$combinationGoods['item_id']][] = $businessOrderItem;
}
}
if (isset($ids[$id])) { if (isset($ids[$id])) {
$ids[$id] += (int)$businessOrderItem['number']; $ids[$id] += (int)$businessOrderItem['number'];
} else { } else {
$ids[$id] = (int)$businessOrderItem['number']; $ids[$id] = (int)$businessOrderItem['number'];
} }
$externals[$businessOrderItem['external_sku_id']][] = $businessOrderItem; $externals[$id][] = $businessOrderItem;
} }
arsort($ids);
$builder = GoodsSku::query(); $builder = GoodsSku::query();
$this->preparQueryGoodsSkus($request, $builder); $this->preparQueryGoodsSkus($request, $builder);
$day = DateTimeUtils::getToday(); $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) { $sortField = $request->input('sort_field', 'id');//stock sale_stock order_goods_num
$query->with(['type:id,name', 'brand:id,name']); $sortValue = $request->input('sort_value', 'desc');
$goodsSkusBuilder = (clone $builder)->filter()->with(['goods' => function ($query) {
$query->with(['type' => function ($query) {
$query->with("parentType:id,name")->select(["id", "name", "parent_id"]);
}]);
}]) }])
->with(['daily' => function ($query) use ($day) { ->with(['daily' => function ($query) use ($day) {
$query->where('day', $day); $query->where('day', $day);
}]) }])
->whereIn('id', $finalIds) ->where('is_combination', 0);
->orderByRaw("FIELD(id, {$idField})") if ($sortField == "order_goods_num") {
->paginate($request->get('per_page')); $finalIds = [];
asort($ids);
foreach ($ids as $id => $number) {
$finalIds[] = $id;
}
$idField = implode(',', $finalIds);
$goodsSkusBuilder->orderByRaw("FIELD(id,{$idField}) {$sortValue}");
} else {
$goodsSkusBuilder->orderBy($sortField, $sortValue);
}
$goodsSkus = $goodsSkusBuilder->paginate($request->get('per_page'));
$rolesName = $request->user()->getRoleNames()->toArray(); $rolesName = $request->user()->getRoleNames()->toArray();
foreach ($goodsSkus as &$sku) { foreach ($goodsSkus as &$sku) {
$lastInventoryTime = $sku['daily']['inventory_time'] ?: date('Y-m-d 07:00:00'); $lastInventoryTime = !empty($sku['daily']['inventory_time']) ? $sku['daily']['inventory_time'] : date('Y-m-d 07:00:00');
if (isset($externals[$sku['external_sku_id']])) { if (isset($externals[$sku['id']])) {
$sku['order_detail'] = $externals[$sku['external_sku_id']]; $sku['order_detail'] = $externals[$sku['id']];
$sku['order_goods_num'] = array_sum(array_column($externals[$sku['external_sku_id']], 'number')); $sku['order_goods_num'] = $ids[$sku['id']];
} else { } else {
$sku['order_detail'] = []; $sku['order_detail'] = [];
$sku['order_goods_num'] = 0; $sku['order_goods_num'] = 0;
} }
$sku['order_goods_num'] -= $sku['daily']['reissue_num']; $sku['order_goods_num'] -= $sku['daily']['reissue_num'] ?? 0;
$sku['inventory_time'] = $lastInventoryTime; $sku['inventory_time'] = $lastInventoryTime;
if ('销售' === $rolesName[0]) { if ('销售' === $rolesName[0]) {
$sku['cost'] = 0; $sku['cost'] = 0;
} }
}
return GoodsSkuResource::collection($goodsSkus); if (!empty($sku['yesterday_num'])) {
$sku['sale_ratio'] = round($sku['stock'] / $sku['yesterday_num'], 2) * 100;
} else {
$sku['sale_ratio'] = 0;
}
}
$data = ["manage" => ["is_admin" => in_array($rolesName[0], ["运营", "超级管理员", "管理员", "系统管理员", "店铺运营"]) ? 1 : 0]];
return GoodsSkuResource::collection($goodsSkus)->additional($data);
} }
private function preparQueryGoodsSkus(Request $request, &$builder) private function preparQueryGoodsSkus(Request $request, &$builder)
@ -189,22 +210,14 @@ class GoodsSkusController extends Controller
$sku = GoodsSku::query()->find($id); $sku = GoodsSku::query()->find($id);
$this->setBeforeUpdateForLog($sku->toArray()); $this->setBeforeUpdateForLog($sku->toArray());
$skuInfo = $request->sku; $skuInfo = $request->sku;
$skuInfo['external_sku_id'] = $request->goods['goods_code'] . '_' . $request->sku['sku_code']; $goodService = new GoodService();
$skuInfo['name'] = $request->goods['title'] . $request->sku['title']; $goods = $goodService->saveDefaultGoodsByGoodType($request->goods['type_id']);
$skuInfo['goods_id'] = $goods->id;
$skuInfo['name'] = $goodService->getSkuName($request->goods['type_id'], $skuInfo);
$sku->update($skuInfo); $sku->update($skuInfo);
$this->setAfterUpdateForLog($sku->toArray()); $this->setAfterUpdateForLog($sku->toArray());
$this->addLog($id, 'update'); $this->addLog($id, 'update');
// 商品更新
$goods = Goods::query()->find($sku->goods_id);
$this->log = new LogModel([
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods',
]);
$this->setBeforeUpdateForLog($goods->toArray());
$goods->update($request->goods);
$this->setAfterUpdateForLog($goods->toArray());
$this->addLog($sku->goods_id, 'update');
DB::commit(); DB::commit();
} catch (\Exception $exception) { } catch (\Exception $exception) {
DB::rollBack(); DB::rollBack();
@ -221,7 +234,7 @@ class GoodsSkusController extends Controller
public function batchUpdate(Request $request) public function batchUpdate(Request $request)
{ {
$appendRules = [ $appendRules = [
'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock'])], 'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock', "saleStock"])],
'skus' => ['required', 'array'], 'skus' => ['required', 'array'],
'skus.*.id' => [ 'skus.*.id' => [
'required', 'required',
@ -240,6 +253,63 @@ class GoodsSkusController extends Controller
return $this->$function($request); return $this->$function($request);
} }
/**
* 新版本运营管理 主要修改在线库存和成本价(修订) 看看是否需要单独提供修改
*
* @param $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
private function saleStock($request)
{
$updateIds = [];
DB::beginTransaction();
try {
$logs = [];
foreach ($request->skus as $sku) {
if ($sku['sale_stock'] < 0) {
throw new \Exception("商品id{$sku['id']}在售库存数不能小于0");
}
$costLog = [
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods_sku',
'target_id' => $sku['id'],
'user_id' => $request->user()->id
];
// 成本
$goodsSku = GoodsSku::query()->where('id', $sku['id'])->first(['id', 'cost', 'sale_stock']);
$costLog['target_field'] = 'sale_stock';
$costLog['before_update'] = $goodsSku->sale_stock;
$goodsSku->sale_stock = $sku['sale_stock'];
if (0 >= $goodsSku->sale_stock) {
$status = GoodsSku::$STATUS_DOWN;
} else {
$status = GoodsSku::$STATUS_ON_SALE;
}
$goodsSku->status = $status;
$goodsSku->save();
$costLog['after_update'] = $goodsSku->sale_stock;
$logs[] = $costLog;
$updateIds[] = $sku['id'];
}
$log = new LogModel();
$log->batchInsert($logs);
DB::commit();
if (!empty($updateIds)) {
event(new BatchStockUpdateEvent($updateIds));
}
} catch (\Exception $exception) {
DB::rollBack();
$this->res = [
'httpCode' => 400,
'errorCode' => 400500,
'errorMessage' => $exception->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
/** /**
* 上新 * 上新
* *
@ -297,7 +367,7 @@ class GoodsSkusController extends Controller
} }
/** /**
* 库存盘点 * 库存盘点 准备废弃中
* *
* @param $request * @param $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
@ -377,6 +447,7 @@ class GoodsSkusController extends Controller
} }
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
/** /**
@ -472,7 +543,7 @@ class GoodsSkusController extends Controller
$validator = Validator::make($request->all(), $rules); $validator = Validator::make($request->all(), $rules);
if ($validator->fails()) { if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); $this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
goto end; return response($this->res, $this->res['httpCode']);
} }
$updateField = \request('updateField'); $updateField = \request('updateField');
$sku = GoodsSku::query()->find($id); $sku = GoodsSku::query()->find($id);
@ -494,10 +565,11 @@ class GoodsSkusController extends Controller
$changeNum = $sku->reserve - $request->reserve; $changeNum = $sku->reserve - $request->reserve;
if (0 > $changeNum + $sku->stock) { if (0 > $changeNum + $sku->stock) {
$this->setValidatorFailResponse('预留量超过库存数量'); $this->setValidatorFailResponse('预留量超过库存数量');
goto end; return response($this->res, $this->res['httpCode']);
} }
$sku->stock += $changeNum; $sku->stock += $changeNum;
} }
$sku->$updateField = $request->$updateField; $sku->$updateField = $request->$updateField;
$sku->save(); $sku->save();
$this->setAfterUpdateForLog($sku->$updateField); $this->setAfterUpdateForLog($sku->$updateField);
@ -551,6 +623,7 @@ class GoodsSkusController extends Controller
$endDate = Carbon::now()->subWeek()->endOfWeek()->toDateString(); $endDate = Carbon::now()->subWeek()->endOfWeek()->toDateString();
return Excel::download(new WeekDataExport($startDate, $endDate), $startDate . '~' . $endDate . '.xlsx'); return Excel::download(new WeekDataExport($startDate, $endDate), $startDate . '~' . $endDate . '.xlsx');
} }
return Excel::download(new GoodsSkusExport($type), $type . '.xlsx'); return Excel::download(new GoodsSkusExport($type), $type . '.xlsx');
} }
@ -659,4 +732,25 @@ class GoodsSkusController extends Controller
return $data; return $data;
} }
public function saleStockImport(Request $request)
{
if (!$request->hasFile(ExcelKeyEnum::SALE_STOCK_FILE)) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found inventory file',
];
}
try {
$import = new SaleStockImport();
$path = $request->file(ExcelKeyEnum::SALE_STOCK_FILE);
Excel::import($import, $path);
$this->addLog(0, 'import', ExcelKeyEnum::SALE_STOCK_FILE);
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
} }

View File

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Http\Resources\GoodsTypeResource; use App\Http\Resources\GoodsTypeResource;
use App\Models\GoodsType; use App\Models\GoodsType;
use App\Models\Log as LogModel; use App\Models\Log as LogModel;
use App\Utils\FormatUtils;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
@ -23,7 +24,11 @@ class GoodsTypesController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$goodsTypes = GoodsType::query()->paginate($request->get('per_page')); $build = GoodsType::query();
if(!empty($request->level)){
$build->where("level","=",$request->level);
}
$goodsTypes = $build->paginate($request->get('per_page'));
return GoodsTypeResource::collection($goodsTypes); return GoodsTypeResource::collection($goodsTypes);
} }
@ -31,27 +36,34 @@ class GoodsTypesController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'names' => 'required|array', 'name' => 'required|string|max:191',
'names.*' => 'required|string|max:191|unique:goods_types,name', 'parent_id' => 'sometimes',
'show' => 'sometimes|integer',
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); $this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
$goodsTypes = []; $level = 1;
foreach ($request->names as $name) { if ($request->parent_id) {
$goodsTypes[] = ['name' => $name]; $parentGoodsType = GoodsType::query()->where("id", "=", $request->parent_id)->get()->toArray();
$level = $parentGoodsType['level'] ?? 1 + 1;
$existTypeName = GoodsType::query()->where("parent_id", "=", $request->parent_id)
->where("name",$request->name)->first();
if($existTypeName){
throw new \Exception("当前品类下该名称已存在");
} }
}
$goodsType = new GoodsType(); $goodsType = new GoodsType();
if (!$goodsType->batchInsert($goodsTypes)) { $goodsType->parent_id = $request->parent_id ?? 0;
$this->res = [ $goodsType->show = $request->show ?? 1;
'httpCode' => 500, $goodsType->name = $request->name;
'errorCode' => 500500, $goodsType->level = $level;
'errorMessage' => '批量添加失败', $goodsType->save();
];
} $this->setAfterUpdateForLog($request->all());
$this->setAfterUpdateForLog($goodsTypes);
$this->addLog(0, 'add'); $this->addLog(0, 'add');
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
@ -65,12 +77,9 @@ class GoodsTypesController extends Controller
public function update($id, Request $request) public function update($id, Request $request)
{ {
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'name' => [ 'name' => 'sometimes|string|max:191',
'required', 'parent_id' => 'sometimes',
'string', 'show' => 'sometimes|integer',
'max:191',
Rule::unique('goods_types')->ignore($id),
]
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); $this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
@ -79,7 +88,18 @@ class GoodsTypesController extends Controller
} }
$goodsType = GoodsType::query()->find($id); $goodsType = GoodsType::query()->find($id);
$this->setBeforeUpdateForLog($goodsType->name); $this->setBeforeUpdateForLog($goodsType->name);
$goodsType->name = request('name'); $level= 1;
if ($request->parent_id) {
$parentGoodsType = GoodsType::query()->where("id", "=", $request->parent_id)->get()->toArray();
$level = $parentGoodsType['level'] ?? 1 + 1;
}
$goodsType->parent_id = $request->parent_id ?? 0;
$goodsType->show = $request->show ?? 1;
if($request->name){
$goodsType->name = $request->name;
}
$goodsType->level = $level;
$goodsType->save(); $goodsType->save();
$this->setAfterUpdateForLog($goodsType->name); $this->setAfterUpdateForLog($goodsType->name);
$this->addLog($id, 'name'); $this->addLog($id, 'name');
@ -95,4 +115,11 @@ class GoodsTypesController extends Controller
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
public function tree(Request $request)
{
$goodsTypes = GoodsType::query()->get()->toArray();
$menus = FormatUtils::formatTreeData($goodsTypes, $request->parent_id??0);
return $this->success($menus);
}
} }

View File

@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers\Message;
use App\Http\Controllers\Controller;
use App\Http\Enum\Goods\SkuStatusEnum;
use App\Http\Enum\Message\MessageTypeEnum;
use App\Models\GoodsSku;
use App\Models\WebsiteMessages;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class WebsiteMessageController extends Controller
{
public function index(Request $request)
{
$roleIds = collect($request->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);
if (empty($websiteMessages)) {
$this->res = [
'httpCode' => 400,
'errorCode' => 400404,
'errorMessage' => "站内信数据不存在",
];
return response($this->res, "400");
}
$websiteMessages->status = $request->status;
Log::info("管理員更新站內信", (array)$request->user());
$websiteMessages->save();
if ($request->status == 1) {
//标记已读触发
$this->hook($websiteMessages);
}
return $websiteMessages;
}
public function hook($websiteMessages)
{
if ($websiteMessages->type == MessageTypeEnum::LOW_STOCK_NOTICE) {
$keyArray = explode("-", $websiteMessages->unique_key);//时间Ymd-对应id-角色id
$skuId = $keyArray[1] ?? 0;
if (!empty($skuId)) {
//变更回上架状态
GoodsSku::query()->where("id", "=", $skuId)->update(["status" => SkuStatusEnum::UP]);
}
}
}
}

View File

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\Log as LogModel; use App\Models\Log as LogModel;
use App\Utils\ArrayUtils; use App\Utils\ArrayUtils;
use App\Utils\FormatUtils; use App\Utils\FormatUtils;
use Illuminate\Support\Facades\Log;
use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Permission;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;

View File

@ -52,13 +52,16 @@ class ShopsController extends Controller
$validator = Validator::make($request->all(), [ $validator = Validator::make($request->all(), [
'name' => 'required|string|max:191|unique:shops,name', 'name' => 'required|string|max:191|unique:shops,name',
'plat_id' => 'required|integer', 'plat_id' => 'required|integer',
'ratio' => 'required', 'ratio' => 'sometimes',
]); ]);
if ($validator->fails()) { if ($validator->fails()) {
$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(empty($request->ratio)){
$request->ratio = "*1";
}
$operator = substr($request->ratio, 0, 1); $operator = substr($request->ratio, 0, 1);
if (!in_array($operator, ['+', '-', '*', '/'])) { if (!in_array($operator, ['+', '-', '*', '/'])) {
$this->res->errorMessage = '运算符号仅允许+,-,*,/'; $this->res->errorMessage = '运算符号仅允许+,-,*,/';
@ -203,10 +206,11 @@ class ShopsController extends Controller
} else { } else {
$shops = $builder->where('id', $shopId)->get(); $shops = $builder->where('id', $shopId)->get();
} }
//同步三方的是在售库存
$skus = GoodsSku::query() $skus = GoodsSku::query()
->where('updated_at', '>', date('Y-m-d 07:01:00')) ->where('updated_at', '>', date('Y-m-d 07:01:00'))
->whereNotNull('external_sku_id') ->whereNotNull('external_sku_id')
->pluck('stock', 'external_sku_id') ->pluck('sale_stock', 'external_sku_id')
->toArray(); ->toArray();
foreach ($shops as $shop) { foreach ($shops as $shop) {
$business = BusinessFactory::init()->make($shop->plat_id); $business = BusinessFactory::init()->make($shop->plat_id);

View File

@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Events\BatchStockUpdateEvent;
use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Imports\InventoryImport;
use App\Imports\LossImport;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\Log as LogModel;
use App\Services\GoodSku\GoodSkuService;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
class DailyStockRecordController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'dailyStockRecord',
]);
}
public function index(Request $request)
{
$build = DailyStockRecord::query()->whereNotNull("inventory_time")->filter()->with("goodsSku:id,name,title,external_sku_id");
if (!empty($request->title)) {
$build->whereHas('goodsSku', function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->title . '%');
});
}
if (!empty($request->get('external_sku_id'))) {
$build->whereHas('goodsSku', function ($query) use ($request) {
$query->where('external_sku_id', '=', $request->external_sku_id);
});
}
$dailyStockRecord = $build->orderByDesc("id")->paginate($request->get('per_page'));
return JsonResource::collection($dailyStockRecord);
}
/**
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'external_sku_id' => 'required|string',
'inventory' => 'required|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->with("combinationGoods")->where('external_sku_id', "=", $request->external_sku_id)->first();
if (empty($goodsSku)) {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
return response($this->res, $this->res['httpCode']);
}
$goodsSkuWithInventory = array_merge($goodsSku->toArray(),["inventory"=>$request->inventory]);
//同批量逻辑操作
$goodSkuService = new GoodSkuService();
$goodSkuService->inventory([$goodsSkuWithInventory]);
return response($this->res, $this->res['httpCode']);
}
/**
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function inventoryBatchStore(Request $request)
{
$validator = Validator::make($request->all(), [
'inventoryOrders' => 'required|array',
'inventoryOrders.*.external_sku_id' => 'required|string',
'inventoryOrders.*.inventory' => 'required|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$inventoryKeyByExternalSkuIdMap = collect($request->inventoryOrders)->pluck(null,"external_sku_id")->toArray();
$goodsSkus = GoodsSku::query()->with("combinationGoods")->whereIn('external_sku_id', array_keys($inventoryKeyByExternalSkuIdMap))
->get()->toArray();
$goodsSkus = collect($goodsSkus)->map(function ($v) use ($inventoryKeyByExternalSkuIdMap) {
if (!empty($inventoryKeyByExternalSkuIdMap[$v['external_sku_id']])) {
$v['inventory'] = $inventoryKeyByExternalSkuIdMap[$v['external_sku_id']]['inventory'];
return $v;
}
})->toArray();
Log::info("goodsSkus",$goodsSkus);
$goodSkuService = new GoodSkuService();
$goodSkuService->inventory($goodsSkus);
return response($this->res, $this->res['httpCode']);
}
public function inventoryImport(Request $request)
{
if (!$request->hasFile('inventoryFile')) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found inventory file',
];
}
try {
$import = new InventoryImport();
$path = $request->file('inventoryFile');
Excel::import($import, $path);
$this->addLog(0, 'import', 'inventory');
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,231 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Events\BatchStockUpdateEvent;
use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Imports\LossImport;
use App\Models\DailyStockRecord;
use App\Models\Goods;
use App\Models\GoodsSku;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Models\LossRecords;
use App\Models\Suppliers;
use App\Models\User;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
class LossRecordController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'loss_record',
]);
}
public function index(Request $request)
{
$builder = LossRecords::query()->filter()->with("goodsSku:name,external_sku_id,title");
if (!empty($request->start_time) && !empty($request->end_time)) {
$builder->whereBetween('created_at', [$request->start_time, $request->end_time]);
}
if (!empty($request->title)) {
$builder->whereHas('goodsSku', function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->title . '%');
});
}
$dailyStockRecord = $builder->orderByDesc("id")->paginate($request->get('per_page'));
return JsonResource::collection($dailyStockRecord);
}
public function store(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'external_sku_id' => 'required|string',
'num' => 'required|integer',
'reason' => 'sometimes|string',
'buyer_name' => 'sometimes|string'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->where('external_sku_id', "=", $request->external_sku_id)->first();
if (!empty($goodsSku)) {
$buyerUserId = User::query()->where("name", $allParams['buyer_name'] ?? '')
->pluck("id")->first();
$today = DateTimeUtils::getToday();
DB::beginTransaction();
try {
//保存記錄
$lossRecords = new LossRecords();
$lossRecords->batch_number = GeneratorUtils::generateBatchNumber();
$lossRecords->external_sku_id = $allParams['external_sku_id'];
$lossRecords->num = $allParams['num'];
$lossRecords->cost = $allParams['cost'];
$lossRecords->date = $allParams['date'] ?? $today;
$lossRecords->buyer_user_id = $allParams['buyer_user_id'] ?? ($buyerUserId ?? 0);
$lossRecords->buyer_name = $allParams['buyer_name'] ?? '';
$lossRecords->reason = $allParams['reason'] ?? '';
$lossRecords->phenomenon = $v['phenomenon'] ?? '';
$lossRecords->save();
$updateIds = GoodSkuService::computeSkuStock($goodsSku->toArray()
, ['num' => 0 - $allParams['num'], "cost" => $allParams['cost'], "user_id" => $request->user()->id]);
DB::commit();
event(new BatchStockUpdateEvent($updateIds));
} catch (\Exception $exception) {
DB::rollBack();
}
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
public function update($id, Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'reason' => 'sometimes|string',
'buyer_name' => 'sometimes|string',
'buyer_user_id' => 'sometimes|integer'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$lossRecords = LossRecords::query()->find($id);
if (!empty($lossRecords)) {
//更新記錄
$lossRecords->buyer_name = $allParams['buyer_name'] ?? '';
$lossRecords->buyer_user_id = $allParams['buyer_user_id'] ?? 0;
$lossRecords->reason = $allParams['reason'] ?? '';
$lossRecords->phenomenon = $allParams['phenomenon'] ?? '';
$lossRecords->save();
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
public function lossBatchStore(Request $request)
{
$validator = Validator::make($request->all(), [
'lossOrders' => 'required|array',
'lossOrders.*.external_sku_id' => 'required|string',
'lossOrders.*.num' => 'required|integer',
'lossOrders.*.reason' => 'sometimes|string',
'lossOrders.*.buyer_name' => 'sometimes|string',//采购商
'lossOrders.*.phenomenon' => 'sometimes|string',
'lossOrders.*.date' => 'sometimes|date_format:Y-m-d'
]);
//参数校验
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$lossOrders = $request->input('lossOrders') ?? [];
$externalSkuIds = collect($lossOrders)->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(null, 'external_sku_id')->toArray();
$allUpdateIds = [];
$batchNumber = GeneratorUtils::generateBatchNumber();
$today = DateTimeUtils::getToday();
//开始保存数据
foreach ($lossOrders as $v) {
DB::beginTransaction();
try {
$goodsSkuItem = $goodsSkuMap[$v['external_sku_id']];
//保存記錄
$lossRecords = new LossRecords();
$lossRecords->batch_number = $batchNumber;
$lossRecords->external_sku_id = $v['external_sku_id'];
$lossRecords->num = $v['num'];
$lossRecords->cost = $v['cost'];
$lossRecords->date = $v['date'] ?? $today;
$lossRecords->buyer_user_id = $v['buyer_user_id'] ?? 0;
$lossRecords->buyer_name = $v['buyer_name'] ?? '';
$lossRecords->reason = $v['reason'] ?? '';
$lossRecords->phenomenon = $v['phenomenon'] ?? '';
$lossRecords->save();
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem
, ['num' => 0 - $v['num'], "cost" => $v['cost'], "user_id" => $request->user()->id]);
DB::commit();
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
} catch (\Exception $exception) {
DB::rollBack();
}
}
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
return response($this->res, $this->res['httpCode']);
}
/**
* 报损单单后台批量导入
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function lossImport(Request $request)
{
if (!$request->hasFile(ExcelKeyEnum::LOSS_KEY)) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found loss file',
];
}
try {
$import = new LossImport();
$path = $request->file(ExcelKeyEnum::LOSS_KEY);
Excel::import($import, $path);
$this->addLog(0, 'import', ExcelKeyEnum::LOSS_KEY);
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,315 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Events\BatchStockUpdateEvent;
use App\Http\Controllers\Controller;
use App\Http\Enum\ExcelKeyEnum;
use App\Http\Enum\Purchase\PurchaseConfigEnum;
use App\Http\Enum\Purchase\PurchaseStatusEnum;
use App\Http\Enum\TargetTypeEnum;
use App\Http\Resources\GoodsSkuResource;
use App\Http\Resources\RolesResource;
use App\Imports\PurchaseImport;
use App\Models\GoodsSku;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Models\PurchaseRecords;
use App\Models\Suppliers;
use App\Models\User;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
class PurchaseRecordController extends Controller
{
public $isAutoCheck = 0;
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'purchase_record',
]);
}
public function index(Request $request)
{
$builder = PurchaseRecords::query()->filter()->with("goodsSku:id,name,external_sku_id,title");
if (!empty($request->start_time) && !empty($request->end_time)) {
$builder->whereBetween('created_at', [$request->start_time, $request->end_time]);
}
if (!empty($request->title)) {
$builder->whereHas('goodsSku', function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->title . '%');
});
}
$dailyStockRecord = $builder->orderByDesc("id")->paginate($request->get('per_page'));
return JsonResource::collection($dailyStockRecord);
}
public function store(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'external_sku_id' => 'required|string',
'num' => 'required|integer',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->where('external_sku_id', "=", $request->external_sku_id)->first();
if (!empty($goodsSku)) {
$expireDay = DeveloperConfigService::getDefaultExpireDay();
$today = DateTimeUtils::getToday();
if (empty($allParams['buyer_user_id'])) {
$buyerUserId = User::query()->where("name", $allParams['buyer_name'] ?? '')
->pluck("id")->first();
}
if (empty($allParams['supplier_id'])) {
$supplierId = Suppliers::query()->where("supplier_name", $allParams['supplier_name'] ?? '')
->pluck("id")->first();
}
//保存記錄
$purchaseRecords = new PurchaseRecords();
$purchaseRecords->external_sku_id = $allParams['external_sku_id'];
$purchaseRecords->batch_number = GeneratorUtils::generateBatchNumber();
$purchaseRecords->num = $allParams['num'];
$purchaseRecords->cost = $allParams['cost'];
$purchaseRecords->date = $today;
$purchaseRecords->buyer_user_id = $allParams['buyer_user_id'] ?? ($buyerUserId ?? 0);
$purchaseRecords->buyer_name = $allParams['buyer_name'] ?? '';
$purchaseRecords->supplier_name = $allParams['supplier_name'] ?? '';
$purchaseRecords->supplier_id = $allParams['supplier_id'] ?? ($supplierId ?? 0);
if (!empty($allParams['expire_time'])) {
$purchaseRecords->expire_time = Carbon::parse($allParams['expire_time'])->toDateTimeString();
} else {
$purchaseRecords->expire_time = Carbon::now()->addDays($expireDay)->toDateTimeString();
}
$purchaseRecords->save();
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
$allParams['user_id'] = $request->user()->id;
$updateIds = GoodSkuService::computeSkuStock($goodsSku->toArray(), $allParams, TargetTypeEnum::PURCHASE);
event(new BatchStockUpdateEvent($updateIds));
}
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
public function update($id, Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'buyer_name' => 'sometimes|string',
'supplier_name' => 'sometimes|string',
"expire_time" => 'sometimes|string'
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsSku = GoodsSku::query()->where('id', "=", $id)->get();
if (!empty($goodsSku)) {
//可以修改的記錄
$purchaseRecords = PurchaseRecords::query()->find($id);
$purchaseRecords->buyer_user_id = $allParams['buyer_user_id'] ?? 0;
$purchaseRecords->buyer_name = $allParams['buyer_name'] ?? '';
$purchaseRecords->supplier_name = $allParams['supplier_name'] ?? '';
$purchaseRecords->supplier_id = $allParams['supplier_id'] ?? 0;
if ($purchaseRecords->status == PurchaseStatusEnum::WAIT_CHECK) {
$purchaseRecords->cost = $allParams['cost'] ?? 0;
$purchaseRecords->num = $allParams['num'] ?? 0;
}
if (!empty($allParams['expire_time'])) {
$purchaseRecords->expire_time = Carbon::parse($allParams['expire_time'])->toDateTimeString();
}
if (!empty($allParams['date'])) {
$purchaseRecords->date = Carbon::parse($allParams['date'])->toDateString();
}
if (!empty($allParams['arrived_time'])) {
$purchaseRecords->arrived_time = Carbon::parse($allParams['arrived_time'])->toDateTimeString();
}
$purchaseRecords->save();
} else {
$this->res = [
'httpCode' => 400,
'message' => '查询不到sku信息',
'errorCode' => "ERP001",
'errorMessage' => '查询不到sku信息',
];
}
return response($this->res, $this->res['httpCode']);
}
/**
* 采购单后台批量存储
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function purchaseBatchStore(Request $request)
{
$validator = Validator::make($request->all(), [
'purchaseOrders' => 'required|array',
'purchaseOrders.*.external_sku_id' => 'required|string',
'purchaseOrders.*.num' => 'required|integer',
'purchaseOrders.*.cost' => 'required',]);
//参数校验
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 (collect($externalSkuIds)->diff($goodsSku->pluck("external_sku_id"))->isNotEmpty()) {
$content = implode(',', collect($externalSkuIds)->diff($goodsSku->pluck("external_sku_id"))->toArray());
$content .= "以上sku编码数据库中不存在";
return response($content, 400);
}
$goodsSkuMap = $goodsSku->pluck(null, 'external_sku_id')->toArray();
$allUpdateIds = [];
$expireDay = DeveloperConfigService::getDefaultExpireDay();
$today = DateTimeUtils::getToday();
$batchNumber = GeneratorUtils::generateBatchNumber();
//开始保存数据
foreach ($purchaseOrders as $v) {
$goodsSkuItem = $goodsSkuMap[$v['external_sku_id']];
//保存記錄
$purchaseRecords = new PurchaseRecords();
$purchaseRecords->batch_number = $batchNumber;
$purchaseRecords->external_sku_id = $v['external_sku_id'];
$purchaseRecords->num = $v['num'];
$purchaseRecords->cost = $v['cost'];
$purchaseRecords->date = $v['date'] ?? $today;
$purchaseRecords->buyer_user_id = $v['buyer_user_id'] ?? 0;
$purchaseRecords->buyer_name = $v['buyer_name'] ?? '';
$purchaseRecords->supplier_name = $v['supplier_name'] ?? '';
$purchaseRecords->supplier_id = $v['supplier_id'] ?? 0;
$purchaseRecords->arrived_time = $v['arrived_time'] ?? Carbon::now()->toDateTimeString();
$purchaseRecords->expire_time = Carbon::now()->addDays($expireDay)->toDateTimeString();
$purchaseRecords->save();
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
$v['user_id'] = $request->user()->id;
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem, $v, TargetTypeEnum::PURCHASE);
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
}
}
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
//如果是組合商品会触发重算逻辑
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
return response($this->res, $this->res['httpCode']);
}
/**
* 报损单后台批量存储
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function purchaseImport(Request $request)
{
if (!$request->hasFile(ExcelKeyEnum::PURCHASE_KEY)) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found purchase file',
];
}
try {
$import = new PurchaseImport();
$path = $request->file(ExcelKeyEnum::PURCHASE_KEY);
Excel::import($import, $path);
$this->addLog(0, 'import', ExcelKeyEnum::PURCHASE_KEY);
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
/**
* 报损单后台批量存储
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|void
*/
public function purchaseBatchCheck(Request $request)
{
$params = $request->validate([
'purchaseOrders' => 'required|array',
'purchaseOrders.*.id' => 'required|integer',
'purchaseOrders.*.status' => 'required|in:1,2',
'purchaseOrders.*.num' => 'sometimes|integer',
]);
$allUpdateIds = [];
$now = Carbon::now()->toDateTimeString();
foreach ($params['purchaseOrders'] as $v) {
$purchaseRecordBuilder = PurchaseRecords::query()->with("goodsSku")->where("id", $v['id'])->first();
if (empty($purchaseRecordBuilder)) {
continue;
}
$purchaseRecords = $purchaseRecordBuilder->toArray();
if (empty($purchaseRecords['goods_sku'])) {
continue;
}
if ($purchaseRecords['status'] == $v['status']) {
continue;
}
if (!empty($v['num'])) {
$purchaseRecordBuilder->num = $v['num'];
$purchaseRecords['num'] = $v['num'];
}
DB::beginTransaction();
try {
if (PurchaseStatusEnum::CHECK_SUCCESS == $v['status']) {
$updateIds = GoodSkuService::computeSkuStock($purchaseRecords['goods_sku'], $purchaseRecords, TargetTypeEnum::PURCHASE);
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
}
$purchaseRecordBuilder->status = $v['status'];
$purchaseRecordBuilder->check_time = $now;
$purchaseRecordBuilder->save();
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
\Illuminate\Support\Facades\Log::error("质检审核事务异常", ["error" => $exception->getMessage()]);
}
}
if (!empty($allUpdateIds)) {
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace App\Http\Controllers\Supplier;
use App\Http\Controllers\Controller;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Models\Suppliers;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Validator;
class SuppliersController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'supplier',
'action' => $request->getMethod(),
'target_type' => 'supplier',
]);
}
public function index(Request $request)
{
$suppliers = Suppliers::query()->paginate($request->get('per_page'));
return JsonResource::collection($suppliers);
}
public function store(Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'supplier_name' => 'required|string',
'company_name' => 'required|string',
'address' => 'sometimes|string',
'link_tel' => 'sometimes|string',
'payment_account' => 'sometimes|string',
'supply_type' => 'sometimes|string',
'agent_id' => 'sometimes|integer',
'agent_name' => 'sometimes|string',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
//保存数据
$suppliers = new Suppliers();
$suppliers->supplier_name = $request->supplier_name;
$suppliers->company_name = $request->company_name ?? '';
$suppliers->link_tel = $request->link_tel ?? '';
$suppliers->address = $request->address ?? '';
$suppliers->payment_account = $request->payment_account ?? '';
$suppliers->supply_type = $request->supply_type ?? '';
$suppliers->agent_id = $request->agent_id ?? 0;
$suppliers->agent_name = $request->agent_name ?? '';
$suppliers->save();
return response($this->res, $this->res['httpCode']);
}
public function update($id, Request $request)
{
//获取所有参数
$allParams = $request->all();
//进行校验验证
$validator = Validator::make($allParams, [
'supplier_name' => 'required|string',
'company_name' => 'required|string',
'address' => 'sometimes|string',
'link_tel' => 'sometimes|string',
'payment_account' => 'sometimes|string',
'supply_type' => 'sometimes|string',
'agent_id' => 'sometimes|integer',
'agent_name' => 'sometimes|string',
]);
if ($validator->fails()) {
//校验失败返回异常
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
//保存数据
$suppliers = Suppliers::query()->find($id);
$suppliers->supplier_name = $request->supplier_name;
$suppliers->company_name = $request->company_name ?? '';
$suppliers->link_tel = $request->link_tel ?? '';
$suppliers->address = $request->address ?? '';
$suppliers->payment_account = $request->payment_account ?? '';
$suppliers->supply_type = $request->supply_type ?? '';
$suppliers->agent_id = $request->agent_id ?? 0;
$suppliers->agent_name = $request->agent_name ?? '';
$suppliers->save();
return response($this->res, $this->res['httpCode']);
}
public function destroy($id)
{
$goodsType = Suppliers::query()->find($id);
$goodsType->delete();
$this->addLog($id, 'status');
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -101,4 +101,9 @@ class UsersController extends Controller
return response($this->res, $this->res['httpCode']); return response($this->res, $this->res['httpCode']);
} }
public function userRoles(Request $request)
{
return $request->user()->toArray();
}
} }

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Enum;
class AfterSaleOrderStatusEnum
{
const WAIT = 0;
const REFUNDING = 1;
const REFUNDED_SUCCESS = 2;
const WAIT_HANDLE = 3;
const REFUND_REFUSE = 4;
const WAIT_RETURN_GOOD = 6;
const WAIT_CONFIRM_RETURN_GOOD = 7;
const USER_CANCEL = 8;
const CLOSE = 9;
const MAP = [
self::WAIT => "未发起售后",
self::REFUNDING => "退款中",
self::REFUNDED_SUCCESS => "退款成功",
self::WAIT_HANDLE => "待处理",
self::REFUND_REFUSE => "拒绝退款",
self::WAIT_RETURN_GOOD => "待退货",
self::WAIT_CONFIRM_RETURN_GOOD => "待团长确认退货",
self::USER_CANCEL => "用户取消",
self::CLOSE => "系统已关闭",
];
public static function getData($key)
{
return self::MAP[$key] ?? '';
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Enum;
class CacheKeyEnum
{
const STOCK_RULE_PROPORTION = "stock_rule_proportion";
const DEFAULT_EXPIRE_DAY = "default_expire_day";
const SPU_STATISTIC_BY_DATE = "spu_statistic_by_date";
const SKU_ADMIN_ROLE_IDS = "sku_admin_role_ids";
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Enum;
class DevConfigKeyEnum
{
const STOCK_RULE_PROPORTION = "stock_rule_proportion";
const DEFAULT_STOCK_RULE_PROPORTION = 0.2;
const SKU_EXPIRE_DAY = "sku_expire_day";
const DEFAULT_EXPIRE_DAY = 3;
const SKU_ADMIN_ROLE_IDS = "sku_admin_role_ids";
const DEFAULT_SKU_ADMIN_ROLE_IDS = "1,2";
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Http\Enum;
class ExcelKeyEnum
{
const PURCHASE_KEY = "purchaseFile";
const LOSS_KEY = "lossFile";
const SALE_STOCK_FILE = "saleStockFile";
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Http\Enum\Goods;
class SkuStatusEnum
{
const DOWN = 0;
const UP = 1;
const NOTICE = 2;
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Enum\Message;
class MessageTypeEnum
{
const PRICE_EXCEPTION_NOTICE = "price_exception_notice";
const LOW_STOCK_NOTICE = "low_stock_notice";
const QUALITY_PERIOD_EXPIRE_NOTICE = "quality_period_expire_notice";
const DEFAULT_ROLE_IDS = [11];
const MESSAGE_ALL_TYPE = [
self::LOW_STOCK_NOTICE,
self::PRICE_EXCEPTION_NOTICE,
self::QUALITY_PERIOD_EXPIRE_NOTICE
];
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Enum\Purchase;
class PurchaseConfigEnum
{
const IS_AUTO_CHECK = 0;
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Enum\Purchase;
class PurchaseStatusEnum
{
const WAIT_CHECK = 0;
const CHECK_SUCCESS = 1;
const CHECK_FAILED = 2;
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Enum;
class StaticTypeEnum
{
const TODAY = 1;
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Http\Enum;
class TargetTypeEnum
{
const PURCHASE = "sku_stock_purchase";
const LOSS = "sku_stock_loss";
const INVENTORY = "sku_stock_inventory";
}

View File

@ -16,6 +16,9 @@ class CheckPermissions
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
//目前permission仅菜单权限和接口权限耦合 这里先放开
$permissions = $request->user()->getPermissionsViaRoles()->toArray();
return $next($request);
// 获取当前路由名称 // 获取当前路由名称
$currentRouteName = Route::currentRouteName(); $currentRouteName = Route::currentRouteName();
// 引入当前守卫的权限文件 // 引入当前守卫的权限文件

View File

@ -25,11 +25,7 @@ class GoodsRequest extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
'title' => ['required', 'string', 'max:191'],
'type_id' => ['required', 'integer', 'exists:goods_types,id'], 'type_id' => ['required', 'integer', 'exists:goods_types,id'],
'brand_id' => ['integer', 'exists:goods_brands,id'],
'goods_code' => ['required', 'alpha_dash', 'max:32', Rule::unique('goods')->ignore(request('goods_id'))],
]; ];
} }

View File

@ -28,10 +28,10 @@ class GoodsSkuRequest extends FormRequest
'id' => ['sometimes', 'required', 'integer', 'exists:goods_skus,id'], 'id' => ['sometimes', 'required', 'integer', 'exists:goods_skus,id'],
'goods_id' => ['sometimes', 'required', 'integer', 'exists:goods,id'], 'goods_id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
'title' => ['sometimes', 'required', 'string', 'max:191'], 'title' => ['sometimes', 'required', 'string', 'max:191'],
'sku_code' => ['sometimes', 'required', 'distinct', 'alpha_dash', 'max:32'],
'status' => ['sometimes', 'required', 'integer', Rule::in([0, 1, 2])], 'status' => ['sometimes', 'required', 'integer', Rule::in([0, 1, 2])],
'num' => ['sometimes', 'required', 'integer'], 'num' => ['sometimes', 'required', 'integer'],
'cost' => ['sometimes', 'required', 'numeric'], 'cost' => ['sometimes', 'required', 'numeric'],
'attribute' => ['sometimes', 'required', 'string'],
'reference_price' => [ 'reference_price' => [
'sometimes', 'sometimes',
'numeric', 'numeric',

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Service;
use App\Http\Enum\Message\MessageTypeEnum;
use App\Models\DeveloperConfig;
use App\Models\WebsiteMessages;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class MessageService
{
public $roleIdsMapKeyByNoticeType = [];
public function __construct()
{
$DeveloperConfig = DeveloperConfig::query()->whereIn("key", MessageTypeEnum::MESSAGE_ALL_TYPE)
->pluck("value", "key")->toArray();
$this->roleIdsMapKeyByNoticeType = [
MessageTypeEnum::PRICE_EXCEPTION_NOTICE => !empty($DeveloperConfig[MessageTypeEnum::PRICE_EXCEPTION_NOTICE])
? explode(",", $DeveloperConfig[MessageTypeEnum::PRICE_EXCEPTION_NOTICE]) : MessageTypeEnum::DEFAULT_ROLE_IDS,
MessageTypeEnum::LOW_STOCK_NOTICE => !empty($DeveloperConfig[MessageTypeEnum::LOW_STOCK_NOTICE])
? explode(",", $DeveloperConfig[MessageTypeEnum::LOW_STOCK_NOTICE]) : MessageTypeEnum::DEFAULT_ROLE_IDS,
MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE => !empty($DeveloperConfig[MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE])
? explode(",", $DeveloperConfig[MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE]) : MessageTypeEnum::DEFAULT_ROLE_IDS
];
}
//值为角色ids
public function createPriceExceptionMessage(string $businessOrderId, string $productName, string $skuName, string $goodsPrice, string $cost)
{
$roleIds = $this->roleIdsMapKeyByNoticeType[MessageTypeEnum::PRICE_EXCEPTION_NOTICE] ?? [];
if (empty($roleIds)) {
Log::error("消息配置异常", $this->roleIdsMapKeyByNoticeType);
}
$date = Carbon::now()->format('Ymd');
foreach ($roleIds as $v) {
$arr['title'] = "订单价格异常告警";
$arr['role_id'] = $v;
$arr['unique_key'] = $date . "-" . $businessOrderId . "-" . $v;
$arr['type'] = MessageTypeEnum::PRICE_EXCEPTION_NOTICE;
$arr['content'] = $date . "订单号:{$businessOrderId}-商品{$productName}
规格{$skuName}价格有异常,当前售价{$goodsPrice}/支,当前成本价{$cost}/";
$this->saveWebsiteMessages($arr);
}
}
/**
* 低库存告警
* @param $inventory
* @param $goodsSku
* @return void
*/
public function createLowerStockNoticeMessage($inventory, $goodsSku)
{
$roleIds = $this->roleIdsMapKeyByNoticeType[MessageTypeEnum::LOW_STOCK_NOTICE] ?? [];
if (empty($roleIds)) {
Log::error("消息配置异常", $this->roleIdsMapKeyByNoticeType);
}
$date = Carbon::now()->format('Ymd');
foreach ($roleIds as $v) {
$arr['title'] = "商品库存不足告警";
$arr['role_id'] = $v;
$arr['unique_key'] = $date . "-" . $goodsSku['id'] . "-" . $v;
$arr['type'] = MessageTypeEnum::LOW_STOCK_NOTICE;
$arr['content'] = $date . "规格{$goodsSku['title']}库存可能需要补货,当前实际库存{$goodsSku['stock']},上次库存盘点数{$inventory}";
$this->saveWebsiteMessages($arr);
}
}
/**
* 保質期告警
* @param $goodsSku
* @return void
*/
public function skuQualityPeriodNoticeMessage($goodsSku)
{
$roleIds = $this->roleIdsMapKeyByNoticeType[MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE] ?? [];
if (empty($roleIds)) {
Log::error("消息配置异常", $this->roleIdsMapKeyByNoticeType);
}
$date = Carbon::now()->format('Ymd');
foreach ($roleIds as $v) {
$arr['title'] = "商品保质期告警";
$arr['role_id'] = $v;
$arr['unique_key'] = $date . "-" . $goodsSku['id'] . "-" . $v;//这个场景下实际是采购单的id
$arr['type'] = MessageTypeEnum::QUALITY_PERIOD_EXPIRE_NOTICE;
$arr['content'] = $date . "规格{$goodsSku['title']}编码{$goodsSku['external_sku_id']},即将过期,目前实际库存{$goodsSku['stock']},当时采购数量为{$goodsSku['num']},录入采购时间为{$goodsSku['created_at']}";
$this->saveWebsiteMessages($arr);
}
}
public function saveWebsiteMessages($arr)
{
$hasMessage = WebsiteMessages::query()->where("type", "=", $arr['type'])
->where("unique_key", "=", $arr['unique_key'])->first();
if (!empty($hasMessage)) {
//已经写入过了
return true;
}
$websiteMessages = new WebsiteMessages();
$websiteMessages->title = $arr['title'];
$websiteMessages->type = $arr['type'];
$websiteMessages->role_id = $arr['role_id'];
$websiteMessages->content = $arr['content'];
$websiteMessages->unique_key = $arr['unique_key'];
if (!empty($arr['uid'])) {
$websiteMessages->uid = $arr['uid'];
}
Log::info("站内消息保存", (array)$arr);
return $websiteMessages->save($arr);
}
}

View File

@ -6,6 +6,7 @@ use App\Models\CombinationGood;
use App\Models\GoodsSku; use App\Models\GoodsSku;
use App\Utils\ArrayUtils; use App\Utils\ArrayUtils;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows; use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray; use Maatwebsite\Excel\Concerns\ToArray;
use Maatwebsite\Excel\Concerns\WithStartRow; use Maatwebsite\Excel\Concerns\WithStartRow;
@ -50,18 +51,23 @@ class CombinationGoodsImport implements ToArray, SkipsEmptyRows, WithStartRow
$itemCodes = array_column($info['item'], 'item_code'); $itemCodes = array_column($info['item'], 'item_code');
$skus = GoodsSku::query() $skus = GoodsSku::query()
->whereIn('external_sku_id', $itemCodes) ->whereIn('external_sku_id', $itemCodes)
->get(['id', 'external_sku_id', 'stock']) ->get(['id', 'external_sku_id', 'stock', "sale_stock"])
->toArray(); ->toArray();
$skus = ArrayUtils::index($skus, 'external_sku_id'); $skus = ArrayUtils::index($skus, 'external_sku_id');
$stock = []; $stock = [];
$saleStock = [];
foreach ($info['item'] as $item) { foreach ($info['item'] as $item) {
$stock[] = (int)($skus[$item['item_code']]['stock'] / $item['item_num']); $stock[] = (int)($skus[$item['item_code']]['stock'] / $item['item_num']);
$saleStock[] = (int)($skus[$item['item_code']]['sale_stock'] / $item['item_num']);
} }
$stock = min($stock); $stock = min($stock);
$status = $stock ? (5 < $stock ? 1 : 2) : 0; $saleStock = min($saleStock);
$status = $saleStock ? (5 < $saleStock ? 1 : 2) : 0;
$sku = GoodsSku::query()->updateOrCreate( $sku = GoodsSku::query()->updateOrCreate(
['external_sku_id' => $info['external_sku_id'], 'is_combination' => 1], ['external_sku_id' => $info['external_sku_id'], 'is_combination' => 1],
['title' => $info['title'], 'goods_id' => 0, 'sku_code' => $info['external_sku_id'], 'stock' => $stock, 'status' => $status] ['title' => $info['title'],'name' => $info['title'], 'goods_id' => 0, 'sku_code' => $info['external_sku_id']
, 'stock' => $stock, "sale_stock" => $saleStock, 'status' => $status]
); );
CombinationGood::query() CombinationGood::query()
->where('goods_sku_id', $sku->id) ->where('goods_sku_id', $sku->id)

View File

@ -2,16 +2,12 @@
namespace App\Imports; namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Jobs\SyncCostToMiaoXuan;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku; use App\Models\GoodsSku;
use App\Models\TodayPrice; use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use Exception; use Exception;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows; use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray; use Maatwebsite\Excel\Concerns\ToArray;
use App\Utils\ArrayUtils;
class InventoryImport implements ToArray, SkipsEmptyRows class InventoryImport implements ToArray, SkipsEmptyRows
{ {
@ -20,85 +16,30 @@ class InventoryImport implements ToArray, SkipsEmptyRows
*/ */
public function array(array $collection) public function array(array $collection)
{ {
$header = $collection[0]; if (!empty($collection)) {
unset($collection[0]); unset($collection[0]);
$externalSkuId = []; $externalSkuIds = [];
$inventoryKeyByExternalSkuIdMap = [];
foreach ($collection as &$row) { foreach ($collection as &$row) {
$row = array_map(static function ($v) { $row = array_map(static function ($v) {
return trim($v); return trim($v);
}, $row); }, $row);
$externalSkuId[] = $row[0]; $inventoryKeyByExternalSkuIdMap[$row[0]] = $row[2];
$externalSkuIds[] = $row[0];
} }
unset($row); unset($row);
$updateIds = $todayPrice = []; //新版盘点excel字段 编码 商品名称 盘点数
$day = DateTimeUtils::getToday(); $goodsSkus = GoodsSku::query()->with("combinationGoods")->whereIn('external_sku_id', $externalSkuIds)
$dateTime = date('Y-m-d H:i:s'); ->get()->toArray();
$hasGoodsSkus = GoodsSku::query() $goodsSkus = collect($goodsSkus)->map(function ($v) use ($inventoryKeyByExternalSkuIdMap) {
->whereIn('external_sku_id', $externalSkuId) if (!empty($inventoryKeyByExternalSkuIdMap[$v['external_sku_id']])) {
->get(['id', 'status', 'external_sku_id']) $v['inventory'] = $inventoryKeyByExternalSkuIdMap[$v['external_sku_id']];
->toArray(); return $v;
$hasGoodsSkus = ArrayUtils::index($hasGoodsSkus, 'external_sku_id');
foreach ($collection as $row) {
if (!isset($hasGoodsSkus[$row[0]])) {
continue;
} }
$goodsSku = $hasGoodsSkus[$row[0]]; })->toArray();
if ('下架' === $goodsSku['status']) { Log::info("goodsSkus",$goodsSkus);
GoodsSku::query()->where('id', $goodsSku['id'])->update([ $goodSkuService = new GoodSkuService();
'stock' => $row[2] + $row[3], $goodSkuService->inventory($goodsSkus);
'cost' => $row[4],
'status' => 1,
]);
} else {
GoodsSku::query()->where('id', $goodsSku['id'])->update([
'stock' => $row[2] + $row[3],
'cost' => $row[4],
]);
} }
SyncCostToMiaoXuan::dispatch($row[0], $row[4]);
$updateIds[] = $goodsSku['id'];
DailyStockRecord::query()->where('sku_id', $goodsSku['id'])->where('day', $day)->update([
'arrived_today_num' => $row[3],
'inventory' => $row[2],
'inventory_time' => $dateTime
]);
$shopPrice = [];
foreach ($row as $i => $v) {
if ($i > 4) {
$shopPrice[$header[$i]] = $v;
}
}
$todayPrice[] = [
'day' => $day,
'external_sku_id' => $goodsSku['external_sku_id'],
'shop_price' => json_encode($shopPrice, 256)
];
}
if ($todayPrice) {
TodayPrice::query()->delete();
$model = new TodayPrice();
$model->batchInsert($todayPrice);
}
sleep(2);
$onSkuIds = GoodsSku::query()
->where('is_combination', 0)
->where('status', '>', 0)
->pluck('id')
->toArray();
$downSkuIds = array_diff($onSkuIds, $updateIds);
if ($downSkuIds) {
$goodsSkus = GoodsSku::query()->whereIn('id', $downSkuIds)
->get(['id', 'yesterday_num', 'stock'])
->toArray();
foreach ($goodsSkus as $goodsSku) {
GoodsSku::query()->where('id', $goodsSku['id'])->update([
'yesterday_num' => $goodsSku['yesterday_num'] - $goodsSku['stock'],
'stock' => 0,
]);
}
}
sleep(2);
// 批量更新
event(new BatchStockUpdateEvent($onSkuIds));
} }
} }

View File

@ -0,0 +1,91 @@
<?php
namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Jobs\SyncCostToMiaoXuan;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\LossRecords;
use App\Models\PurchaseRecords;
use App\Models\User;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
use App\Utils\ArrayUtils;
class LossImport implements ToArray, SkipsEmptyRows
{
/**
* @throws Exception
*/
public function array(array $collection)
{
if (!empty($collection)) {
unset($collection[0]);
$externalSkuIds = [];
$buyerNames = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
$externalSkuIds[] = $row[0];
$buyerNames[] = $row[4];
}
unset($row);
$allUpdateIds = [];
$hasGoodsSkus = GoodsSku::query()
->whereIn('external_sku_id', $externalSkuIds)
->get(['id', 'status', 'external_sku_id', 'stock', "sale_stock", "cost", "is_combination"])
->toArray();
$hasGoodsSkus = ArrayUtils::index($hasGoodsSkus, 'external_sku_id');
$buyerUserIdKeyByNameMap = User::query()->whereIn("name", $buyerNames)->get()
->pluck("id", "name")->toArray();
$today = DateTimeUtils::getToday();
$batchNumber = GeneratorUtils::generateBatchNumber("import");
//excel字段排序 編碼 商品名稱 报损數量 成本价 采购人名称 报损现象 报损原因 报损日期
foreach ($collection as $row) {
if (!isset($hasGoodsSkus[$row[0]])) {
continue;
}
DB::beginTransaction();
try {
//执行库存操作
$goodsSkuItem = $hasGoodsSkus[$row[0]];
//保存記錄
$lossRecords = new LossRecords();
$lossRecords->batch_number = $batchNumber;
$lossRecords->external_sku_id = $row[0];
$lossRecords->num = $row[2];
$lossRecords->cost = $row[3];
$lossRecords->buyer_user_id = $buyerUserIdKeyByNameMap[$row[4]] ?? 0;
$lossRecords->buyer_name = $row[4] ?? '';
$lossRecords->phenomenon = $row[5] ?? '';
$lossRecords->reason = $row[6] ?? '';
$lossRecords->date = $today;
if (!empty($row[7])) {
$lossRecords->date = DateTimeUtils::excelUploadDateToString($row[7], $today);
}
$lossRecords->save();
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem, ["num" => 0 - $row[2], 'cost' => $row[3]]);
DB::commit();
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
} catch (\Exception $exception) {
DB::rollBack();
}
}
Log::info("报损导入内容:", $collection);
// 批量更新
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Http\Enum\Purchase\PurchaseConfigEnum;
use App\Http\Enum\TargetTypeEnum;
use App\Jobs\SyncCostToMiaoXuan;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\PurchaseRecords;
use App\Models\Suppliers;
use App\Models\User;
use App\Services\DeveloperConfig\DeveloperConfigService;
use App\Services\GoodSku\GoodSkuService;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
use App\Utils\ArrayUtils;
class PurchaseImport implements ToArray, SkipsEmptyRows
{
/**
* @throws Exception
*/
public function array(array $collection)
{
if (!empty($collection)) {
unset($collection[0]);
$externalSkuIds = [];
$supplierNames = [];
$buyerNames = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
$externalSkuIds[] = $row[0];
$buyerNames[] = $row[4];
$supplierNames[] = $row[5];
}
unset($row);
$allUpdateIds = [];
$hasGoodsSkus = GoodsSku::query()
->whereIn('external_sku_id', $externalSkuIds)
->get(['id', 'status', 'external_sku_id', 'stock', "sale_stock", "cost", "is_combination"])
->toArray();
$hasGoodsSkus = ArrayUtils::index($hasGoodsSkus, 'external_sku_id');
//获取供货商
$supplierIdKeyByNameMap = Suppliers::query()->whereIn("supplier_name", $supplierNames)->get()
->pluck("id", "supplier_name")->toArray();
$buyerUserIdKeyByNameMap = User::query()->whereIn("name", $buyerNames)->get()
->pluck("id", "name")->toArray();
$expireDay = DeveloperConfigService::getDefaultExpireDay();
$today = DateTimeUtils::getToday();
$batch_number = GeneratorUtils::generateBatchNumber("import");
//excel字段排序 編碼 商品名稱 导购數量 成本价 采购人名称 供应商名称 采购日期
foreach ($collection as $row) {
if (!isset($hasGoodsSkus[$row[0]])) {
continue;
}
//执行库存操作
$goodsSkuItem = $hasGoodsSkus[$row[0]];
//保存記錄
$purchaseRecords = new PurchaseRecords();
$purchaseRecords->external_sku_id = $row[0];
$purchaseRecords->batch_number = $batch_number;
$purchaseRecords->num = $row[2];
$purchaseRecords->cost = $row[3];
$purchaseRecords->date = $today;
if (!empty($row[6])) {
$purchaseRecords->date = DateTimeUtils::excelUploadDateToString($row[6], $today);
}
if (!empty($row[7])) {
$purchaseRecords->arrived_time = DateTimeUtils::excelUploadDateToString($row[7], $today,"Y-m-d H:i:s");
}else{
$purchaseRecords->arrived_time = $today;
}
$purchaseRecords->buyer_user_id = $buyerUserIdKeyByNameMap[$row[4]] ?? 0;
$purchaseRecords->buyer_name = $row[4] ?? '';
$purchaseRecords->supplier_name = $row[5] ?? '';
$purchaseRecords->supplier_id = $supplierIdKeyByNameMap[$row[5]] ?? 0;
$purchaseRecords->expire_time = Carbon::now()->addDays($expireDay)->toDateTimeString();
$purchaseRecords->save();
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
$updateIds = GoodSkuService::computeSkuStock($goodsSkuItem, ["num" => $row[2], 'cost' => $row[3]], TargetTypeEnum::PURCHASE);
$allUpdateIds = array_merge($allUpdateIds, $updateIds);
}
}
Log::info("采购导入内容:", $collection);
if (PurchaseConfigEnum::IS_AUTO_CHECK) {
// 批量更新
event(new BatchStockUpdateEvent(collect($allUpdateIds)->unique()->toArray()));
}
}
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Imports;
use App\Events\BatchStockUpdateEvent;
use App\Models\GoodsSku;
use App\Services\GoodSku\GoodSkuService;
use Exception;
use App\Models\Log as LogModel;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
class SaleStockImport implements ToArray, SkipsEmptyRows
{
/**
* @throws Exception
*/
public function array(array $collection)
{
if (!empty($collection)) {
unset($collection[0]);
$externalSkuIds = [];
$inventoryKeyByExternalSkuIdMap = [];
foreach ($collection as &$row) {
$row = array_map(static function ($v) {
return trim($v);
}, $row);
if ($row[2] < 0) {
throw new Exception("商品编码{$row[0]}在售库存数不能小于0");
}
$inventoryKeyByExternalSkuIdMap[$row[0]] = $row[2];
$externalSkuIds[] = $row[0];
}
unset($row);
//新版盘点excel字段 编码 在售库存值 商品名称
$updateIds = [];
DB::beginTransaction();
try {
$logs = [];
foreach ($externalSkuIds as $externalSkuId) {
// 成本
$goodsSku = GoodsSku::query()->where('external_sku_id', $externalSkuId)->first(['id', 'cost', 'sale_stock']);
Log::info("SKU", [$goodsSku]);
if (empty($goodsSku)) {
continue;
}
$costLog = [
'module' => 'goods',
'action' => "SaleStockImport",
'target_type' => 'goods_sku',
'target_id' => $goodsSku['id'],
'user_id' => Auth::id() ?? 999
];
$costLog['target_field'] = 'sale_stock';
$costLog['before_update'] = $goodsSku->sale_stock;
$goodsSku->sale_stock = $inventoryKeyByExternalSkuIdMap[$externalSkuId] ?? 0;
if (0 >= $goodsSku->sale_stock) {
$status = GoodsSku::$STATUS_DOWN;
} else {
$status = GoodsSku::$STATUS_ON_SALE;
}
$goodsSku->status = $status;
$goodsSku->save();
$costLog['after_update'] = $goodsSku->sale_stock;
$logs[] = $costLog;
$updateIds[] = $goodsSku['id'];
}
$log = new LogModel();
$log->batchInsert($logs);
DB::commit();
if (!empty($updateIds)) {
event(new BatchStockUpdateEvent($updateIds));
}
} catch (\Exception $exception) {
DB::rollBack();
throw new Exception($exception->getMessage());
}
}
}
}

View File

@ -8,6 +8,8 @@ use App\Models\Shop;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use App\Services\Business\BusinessFactory; use App\Services\Business\BusinessFactory;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class BatchStockUpdateListener implements ShouldQueue class BatchStockUpdateListener implements ShouldQueue
{ {
@ -35,9 +37,11 @@ class BatchStockUpdateListener implements ShouldQueue
if (empty($shops)) { if (empty($shops)) {
return; return;
} }
try {
foreach ($shops as $shop) { foreach ($shops as $shop) {
foreach ($event->goodsSkus as $goodsSku) { foreach ($event->goodsSkus as $goodsSku) {
$num = $goodsSku->stock; //后续同步三方使用在售库存
$num = $goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query() $businessGoodsSkus = BusinessGoodsSku::query()
->select(['goods_id', 'sku_id', 'external_sku_id']) ->select(['goods_id', 'sku_id', 'external_sku_id'])
->where('shop_id', $shop->id) ->where('shop_id', $shop->id)
@ -48,5 +52,8 @@ class BatchStockUpdateListener implements ShouldQueue
usleep(140); usleep(140);
} }
} }
} catch (\Exception $exception) {
Log::error("同步三方库存出现异常", [$exception->getMessage()]);
}
} }
} }

View File

@ -0,0 +1,70 @@
<?php
namespace App\Listeners;
use App\Events\BatchStockUpdateEvent;
use App\Events\BusinessOrdersUpdate;
use App\Http\Enum\CacheKeyEnum;
use App\Http\Enum\DevConfigKeyEnum;
use App\Http\Enum\Goods\SkuStatusEnum;
use App\Http\Service\MessageService;
use App\Models\BusinessGoodsSku;
use App\Models\DailyStockRecord;
use App\Models\DeveloperConfig;
use App\Models\GoodsSku;
use App\Models\Shop;
use Carbon\Carbon;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Services\Business\BusinessFactory;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class BusinessOrderUpdateListener implements ShouldQueue
{
use InteractsWithQueue;
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param BusinessOrdersUpdate $event
* @return void
*/
public function handle(BusinessOrdersUpdate $event)
{
try {
if (!empty($event->goodsSku)) {
//查询库存是否满足告警规则
//查找昨日统计的库存数据
$inventory = $event->goodsSku['yesterday_num'] ?? 0;
$expireTime = Carbon::now()->addMinutes(30)->toDateTimeString();
$proportion = Cache::remember(CacheKeyEnum::STOCK_RULE_PROPORTION, $expireTime, function () {
$developerConfig = DeveloperConfig::query()->where("key", "=", DevConfigKeyEnum::STOCK_RULE_PROPORTION)->first();
return $developerConfig['value'] ?? DevConfigKeyEnum::DEFAULT_STOCK_RULE_PROPORTION;
});
//库存比例小于最近盘点多少告警 一天也只是告警一次
if ($inventory > 10 && $inventory * $proportion > $event->goodsSku->stock) {
$messageService = new MessageService();
$messageService->createLowerStockNoticeMessage($inventory, $event->goodsSku->toArray());
//更新库存状态
GoodsSku::query()->where("id", "=", $event->goodsSku->id)->update([
"status" => SkuStatusEnum::NOTICE
]);
}
}
} catch (\Exception $exception) {
Log::error("库存告警发生异常", ["error" => $exception->getMessage()]);
}
}
}

View File

@ -9,6 +9,8 @@ use App\Utils\DateTimeUtils;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use App\Events\BatchStockUpdateEvent; use App\Events\BatchStockUpdateEvent;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class CombinationGoodsStockUpdateListener implements ShouldQueue class CombinationGoodsStockUpdateListener implements ShouldQueue
{ {
@ -53,20 +55,31 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
} }
} }
} }
// 减子商品库存 $updateIds = [];
//拉取三分订单时可能出现组合订单的情况 需要同步扣减库存
if ($combinationGoodsIds) { if ($combinationGoodsIds) {
$combinationGoods = CombinationGood::query() $combinationGoods = CombinationGood::query()
->with('goodsSku:id,stock') ->with('goodsSku:id,stock')
->whereIn('goods_sku_id', $combinationGoodsIds) ->whereIn('goods_sku_id', $combinationGoodsIds)
->get(); ->get();
$num = !empty($event->num) ? $event->num : -1;
foreach ($combinationGoods as $item) { foreach ($combinationGoods as $item) {
$goodsSku = GoodsSku::query()->find($item['item_id']); DB::transaction(function () use ($item, &$updateIds, $num) {
$stock = $goodsSku->stock - $item['item_num']; $goodsSku = GoodsSku::query()->lockForUpdate()->find($item['item_id']);
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock); $stock = $goodsSku->stock + $item['item_num'] * $num;
//新增逻辑 在线库存同步扣减
$saleStock = max($goodsSku->sale_stock + $item['item_num'] * $num, 0);
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock, $saleStock);
$goodsSku->status = $status; $goodsSku->status = $status;
$goodsSku->stock = $stock; $goodsSku->stock = $stock;
$goodsSku->sale_stock = $saleStock;
$goodsSku->save(); $goodsSku->save();
$mainGoodsSku = GoodsSku::query()->find($item['goods_sku_id']);
$mainGoodsSku->stock = min($mainGoodsSku->stock,(int)($stock / $item['item_num']));
$mainGoodsSku->sale_stock = min($mainGoodsSku->sale_stock,(int)($saleStock / $item['item_num']));
$mainGoodsSku->save();
$updateIds[] = $goodsSku->id; $updateIds[] = $goodsSku->id;
});
} }
} }
// 计算主商品库存 // 计算主商品库存
@ -76,22 +89,30 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
->pluck('goods_sku_id'); ->pluck('goods_sku_id');
foreach ($goodsSkuIds as $goodsSkuId) { foreach ($goodsSkuIds as $goodsSkuId) {
$combinationGoods = CombinationGood::query() $combinationGoods = CombinationGood::query()
->with('goodsSkuItem:id,stock') ->with('goodsSkuItem:id,stock,sale_stock')
->where('goods_sku_id', $goodsSkuId) ->where('goods_sku_id', $goodsSkuId)
->get(); ->get();
$stock = []; $stock = [];
$saleStock = [];
foreach ($combinationGoods as $goods) { foreach ($combinationGoods as $goods) {
$stock[] = (int)($goods['goodsSkuItem']['stock'] / $goods['item_num']); $stock[] = (int)($goods['goodsSkuItem']['stock'] / $goods['item_num']);
$saleStock[] = (int)($goods['goodsSkuItem']['sale_stock'] / $goods['item_num']);
} }
//库存和在线可售库存都是通过子商品维护的
$stock = min($stock); $stock = min($stock);
$saleStock = min($saleStock);
$goodsSku = GoodsSku::query()->find($goodsSkuId); $goodsSku = GoodsSku::query()->find($goodsSkuId);
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock); //新增在线可售逻辑判断 前置已经完成逻辑扣减或者增加
[$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock, $saleStock);
$goodsSku->status = $status; $goodsSku->status = $status;
$goodsSku->stock = $stock; $goodsSku->stock = $stock;
$goodsSku->sale_stock = $saleStock;
$goodsSku->save(); $goodsSku->save();
$updateIds[] = $goodsSkuId; $updateIds[] = $goodsSkuId;
} }
} }
if ($updateIds) { if ($updateIds) {
$updateIds = array_unique($updateIds); $updateIds = array_unique($updateIds);
// 批量更新 // 批量更新
@ -99,21 +120,14 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
} }
} }
private function checkStatusAndStock($goodsSku, $stock) private function checkStatusAndStock($goodsSku, $stock, $saleStock)
{ {
if (0 >= $stock) { //下线库存判断以在线可售库存为准
if (0 >= $saleStock) {
$status = GoodsSku::$STATUS_DOWN; $status = GoodsSku::$STATUS_DOWN;
} else { } else {
$status = GoodsSku::$STATUS_ON_SALE; $status = GoodsSku::$STATUS_ON_SALE;
} }
$arrivedTodayNum = DailyStockRecord::query()
->where('day', DateTimeUtils::getToday())
->where('sku_id', $goodsSku->id)
->value('arrived_today_num');
if (20 < $arrivedTodayNum + $goodsSku->yesterday_num && 4 > $stock) {
$status = GoodsSku::$STATUS_DOWN;
$stock = 0;
}
return [$status, $stock]; return [$status, $stock];
} }

View File

@ -36,7 +36,7 @@ class StockUpdateListener implements ShouldQueue
return; return;
} }
foreach ($shops as $shop) { foreach ($shops as $shop) {
$num = $event->goodsSku->stock; $num = $event->goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query() $businessGoodsSkus = BusinessGoodsSku::query()
->select(['goods_id', 'sku_id', 'external_sku_id']) ->select(['goods_id', 'sku_id', 'external_sku_id'])
->where('shop_id', $shop->id) ->where('shop_id', $shop->id)

View File

@ -60,7 +60,7 @@ class UpdateBusinessGoodsStock implements ShouldQueue
} }
foreach ($shops as $shop) { foreach ($shops as $shop) {
$num = $event->goodsSku->stock; $num = $event->goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query() $businessGoodsSkus = BusinessGoodsSku::query()
->where('shop_id', $shop->id) ->where('shop_id', $shop->id)
->where('is_sync', 1) ->where('is_sync', 1)

View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
class BusinessAfterSaleOrder extends Model
{
use Filter;
protected $table = 'business_after_sale_orders';
protected $fillable = [
'after_sales_biz_sn',
];
public $fieldSearchable = [
'after_sales_status',
"shop_id",
"order_sn",
"after_sales_biz_sn"
];
public function getImageListAttribute($value)
{
return !empty($value) ? json_decode($value, true) : $value;
}
public function getSubExtensionsAttribute($value)
{
return !empty($value) ? json_decode($value, true) : $value;
}
public function shop()
{
return $this->belongsTo(Shop::class, 'shop_id', 'id');
}
}

View File

@ -2,9 +2,23 @@
namespace App\Models; namespace App\Models;
use App\Models\traits\Filter;
class DailyStockRecord extends Model class DailyStockRecord extends Model
{ {
use Filter;
protected $hidden = ['created_at', 'updated_at']; protected $hidden = ['created_at', 'updated_at'];
protected $guarded = []; protected $guarded = [];
public $fieldSearchable = [
'day',
];
public function goodsSku()
{
return $this->belongsTo(GoodsSku::class, 'sku_id', 'id');
}
} }

View File

@ -15,6 +15,8 @@ class GoodsSku extends Model
'exclude_ids', 'exclude_ids',
'external_sku_id', 'external_sku_id',
'is_combination', 'is_combination',
"create_time_start",
"create_time_end"
]; ];
protected $fillable = [ protected $fillable = [
@ -33,10 +35,10 @@ class GoodsSku extends Model
'external_sku_id', 'external_sku_id',
'is_combination', 'is_combination',
'name', 'name',
'sale_stock',
'attribute'
]; ];
protected $hidden = ['created_at'];
public static $STATUS_ON_SALE = 1; public static $STATUS_ON_SALE = 1;
public static $STATUS_DOWN = 0; public static $STATUS_DOWN = 0;
@ -63,10 +65,17 @@ class GoodsSku extends Model
public function getThumbUrlAttribute($value) public function getThumbUrlAttribute($value)
{ {
return json_decode($value, true); return json_decode($value, true);
} }
public function getNameAttribute($value)
{
if(empty($value)){
return $this->attributes['title']??'';
}
return $value;
}
/** /**
* 此规格从属于一个商品 * 此规格从属于一个商品
*/ */

View File

@ -2,12 +2,20 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
class GoodsType extends Model class GoodsType extends Model
{ {
use SoftDeletes;
/** /**
* 数组中的属性会被隐藏。 * 数组中的属性会被隐藏。
* *
* @var array * @var array
*/ */
protected $hidden = ['deleted_at']; protected $hidden = ['deleted_at'];
public function parentType()
{
return $this->hasOne(GoodsType::class, 'id', 'parent_id');
}
} }

View File

@ -72,6 +72,9 @@ class Log extends Model
'kuaituantuan' => '快团团', 'kuaituantuan' => '快团团',
'miaoxuan' => '妙选', 'miaoxuan' => '妙选',
'goods' => '商品', 'goods' => '商品',
"sku_stock_purchase" => "入库采购",
"sku_stock_loss" => "报损记录",
"sku_stock_inventory" => "盘点记录",
]; ];
return $map[$value] ?? $value; return $map[$value] ?? $value;
@ -89,6 +92,7 @@ class Log extends Model
'set' => '设置', 'set' => '设置',
'cost' => '成本', 'cost' => '成本',
'stock' => '库存', 'stock' => '库存',
'sale_stock' => '在售库存',
'inventory' => '库存盘点', 'inventory' => '库存盘点',
'reserve' => '预留量 ', 'reserve' => '预留量 ',
'timingInventory' => '7点盘点', 'timingInventory' => '7点盘点',

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
use Illuminate\Database\Eloquent\Model;
class LossRecords extends Model
{
use Filter;
protected $table = 'loss_records';
protected $guarded = [];
public $fieldSearchable = [
'external_sku_id',
];
public function goodsSku()
{
return $this->belongsTo(GoodsSku::class, 'external_sku_id', 'external_sku_id');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
use Illuminate\Database\Eloquent\Model;
class PurchaseRecords extends Model
{
use Filter;
protected $table = 'purchase_records';
protected $guarded = [];
public $fieldSearchable = [
'external_sku_id',
'status'
];
public function goodsSku()
{
return $this->belongsTo(GoodsSku::class, 'external_sku_id', 'external_sku_id');
}
}

11
app/Models/Suppliers.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Suppliers extends Model
{
//
protected $table = 'suppliers';
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WebsiteMessages extends Model
{
//
protected $table = 'website_messages';
}

View File

@ -7,6 +7,7 @@ use App\Events\StockUpdateEvent;
use App\Events\GroupSetEvent; use App\Events\GroupSetEvent;
use App\Events\BatchStockUpdateEvent; use App\Events\BatchStockUpdateEvent;
use App\Listeners\BatchStockUpdateListener; use App\Listeners\BatchStockUpdateListener;
use App\Listeners\BusinessOrderUpdateListener;
use App\Listeners\CreateLogisticListener; use App\Listeners\CreateLogisticListener;
use App\Listeners\GroupQueryListener; use App\Listeners\GroupQueryListener;
use App\Listeners\StockUpdateListener; use App\Listeners\StockUpdateListener;
@ -28,6 +29,7 @@ class EventServiceProvider extends ServiceProvider
BusinessOrdersUpdate::class => [ BusinessOrdersUpdate::class => [
UpdateBusinessGoodsStock::class, UpdateBusinessGoodsStock::class,
CombinationGoodsStockUpdateListener::class, CombinationGoodsStockUpdateListener::class,
BusinessOrderUpdateListener::class
], ],
BatchStockUpdateEvent::class => [ BatchStockUpdateEvent::class => [
BatchStockUpdateListener::class, BatchStockUpdateListener::class,

View File

@ -38,6 +38,8 @@ abstract class BusinessClient
abstract public function downloadOrdersAndSave($beginTime, $endTime, $downloadType = 'default', $page = 1); abstract public function downloadOrdersAndSave($beginTime, $endTime, $downloadType = 'default', $page = 1);
abstract public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1);
public function saveOrders($orders) public function saveOrders($orders)
{ {
$shopId = $this->getShop()->id; $shopId = $this->getShop()->id;
@ -92,6 +94,7 @@ abstract class BusinessClient
} }
$orderItem->update($item); $orderItem->update($item);
} }
// 增量更新库存 // 增量更新库存
if ($num && $item['external_sku_id']) { if ($num && $item['external_sku_id']) {
event(new BusinessOrdersUpdate($orderItem, $num)); event(new BusinessOrdersUpdate($orderItem, $num));
@ -156,7 +159,7 @@ abstract class BusinessClient
if (strlen($paramsJson) > 1024) { if (strlen($paramsJson) > 1024) {
$paramsJson = ''; $paramsJson = '';
} }
if (!in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list'], true)) { if (!in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list',"pdd.ktt.after.sales.increment.list"], true)) {
$log = new Log(); $log = new Log();
$log->module = 'plat'; $log->module = 'plat';
$log->action = $method; $log->action = $method;
@ -171,7 +174,7 @@ abstract class BusinessClient
} }
$log->save(); $log->save();
} }
if (in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list'], true)) { if (in_array($params['type'], ['pdd.ktt.increment.order.query', 'pdd.ktt.order.list',"pdd.ktt.after.sales.increment.list"], true)) {
LogFile::info('快团团请求: ' . $paramsJson); LogFile::info('快团团请求: ' . $paramsJson);
LogFile::info('快团团返回: ' . json_encode($res, 256)); LogFile::info('快团团返回: ' . json_encode($res, 256));
} }

View File

@ -2,6 +2,7 @@
namespace App\Services\Business\KuaiTuanTuan; namespace App\Services\Business\KuaiTuanTuan;
use App\Models\BusinessAfterSaleOrder;
use App\Models\BusinessGoodsSku; use App\Models\BusinessGoodsSku;
use App\Models\GoodsSku; use App\Models\GoodsSku;
use App\Models\GroupGoods; use App\Models\GroupGoods;
@ -9,6 +10,7 @@ use App\Models\Shop;
use App\Models\ShopShip; use App\Models\ShopShip;
use App\Services\Business\BusinessClient; use App\Services\Business\BusinessClient;
use App\Models\Groups as GroupsModel; use App\Models\Groups as GroupsModel;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class KuaiTuanTuan extends BusinessClient class KuaiTuanTuan extends BusinessClient
@ -123,6 +125,56 @@ class KuaiTuanTuan extends BusinessClient
} }
} }
public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1)
{
[$type, $appendParams] = Order::downloadIncrementAfterSaleOrders($beginTime, $endTime, $page);
$responseName = 'ktt_after_sales_incermet_list_response';
$res = $this->doRequest($type, $appendParams);
if (!isset($res[$responseName])) {
return;
}
$this->saveAfterSaleOrdersServer($res[$responseName]['list']);
if ($res[$responseName]['has_next'] == false) {
return;
}
$pageNum = ceil($res[$responseName]['total_count'] / $appendParams['page_size']);
if ($pageNum > $page && 30 >= $page) {
$this->downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page + 1);
}
}
public function saveAfterSaleOrdersServer($orders)
{
$shopId = $this->getShop()->id;
$now = Carbon::now()->toDateTimeString();
foreach ($orders as $k => $v) {
$model = BusinessAfterSaleOrder::query()->where("shop_id", "=", $shopId)
->where("after_sales_biz_sn", "=", $v['after_sales_biz_sn'])->first();
if (empty($model)) {
$model = new BusinessAfterSaleOrder();
} else {
$model->updated_at = $now;
}
$model->shop_id = $shopId;
$model->refund_amount = $v['apply_extension']['refund_amount'];
$model->refund_shipping_amount = $v['apply_extension']['refund_shipping_amount'];
$model->description = $v['apply_extension']['description'] ?? '';
$model->reason = $v['apply_extension']['reason'] ?? '';
$model->sub_extensions = isset($v['apply_extension']['sub_extensions']) ? json_encode($v['apply_extension']['sub_extensions']) : null;
$model->return_goods_extension = isset($v['return_goods_extension']) ? json_encode($v['return_goods_extension']) : null;
$model->image_list = isset($v['apply_extension']['image_list']) ? json_encode($v['apply_extension']['image_list']) : null;
$model->apply_type = $v['apply_type'];
$model->order_sn = $v['order_sn'];
$model->after_sales_biz_sn = $v['after_sales_biz_sn'];
$model->after_sales_status = $v['after_sales_status'];
$model->after_sale_created_at = !empty($v['created_at']) ? Carbon::createFromTimestamp($v['created_at'] / 1000)->toDateTimeString() : "";
$model->after_sale_updated_at = !empty($v['updated_at']) ? Carbon::createFromTimestamp($v['created_at'] / 1000)->toDateTimeString() : "";
$model->save();
}
}
public function getOrderInfo($orderSn) public function getOrderInfo($orderSn)
{ {
[$type, $appendParams] = Order::getOrderInfo($orderSn); [$type, $appendParams] = Order::getOrderInfo($orderSn);
@ -163,6 +215,7 @@ class KuaiTuanTuan extends BusinessClient
$publicParams = array_merge($publicParams, $appendParams); $publicParams = array_merge($publicParams, $appendParams);
$publicParams['sign'] = $this->getSign($publicParams); $publicParams['sign'] = $this->getSign($publicParams);
$res = $this->formDataPostRequest($url, $publicParams); $res = $this->formDataPostRequest($url, $publicParams);
Log::info("快团团请求", ["param" => $publicParams, "res" => $res]);
if (isset($res['error_response'])) { if (isset($res['error_response'])) {
// "error_msg":"业务服务错误","sub_msg":"该店铺下不存在该商品","sub_code":"11","error_code":50001 // "error_msg":"业务服务错误","sub_msg":"该店铺下不存在该商品","sub_code":"11","error_code":50001
// "error_msg":"业务服务错误","sub_msg":"该SKU在快团团中设置的库存为无限库存不支持修改库存","sub_code":"13","error_code":50001 // "error_msg":"业务服务错误","sub_msg":"该SKU在快团团中设置的库存为无限库存不支持修改库存","sub_code":"13","error_code":50001

View File

@ -92,5 +92,21 @@ class Order
return [$type, $appendParams]; return [$type, $appendParams];
} }
/**
* 快团团增量查售后单订单
*/
public static function downloadIncrementAfterSaleOrders($beginTime, $endTime, $page = 1)
{
$type = 'pdd.ktt.after.sales.increment.list';
$appendParams = [
'start_updated_at' => $beginTime, // 更新起始时间
'end_updated_at' => $endTime, // 更新结束时间
'page_number' => $page, // 页码
'page_size' => 100, // 数量
];
return [$type, $appendParams];
}
} }

View File

@ -33,6 +33,10 @@ class MiaoXuan extends BusinessClient
{ {
} }
public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1){
}
public function batchIncrQuantity($businessGoodsSkus, $num, $incremental) public function batchIncrQuantity($businessGoodsSkus, $num, $incremental)
{ {
$batchAppendParams = []; $batchAppendParams = [];

View File

@ -0,0 +1,35 @@
<?php
namespace App\Services\DeveloperConfig;
use App\Http\Enum\CacheKeyEnum;
use App\Http\Enum\DevConfigKeyEnum;
use App\Models\DeveloperConfig;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
class DeveloperConfigService
{
public static function getDefaultExpireDay()
{
$expireTime = Carbon::now()->addHour();
return Cache::remember(CacheKeyEnum::DEFAULT_EXPIRE_DAY, $expireTime, function () {
$developerConfig = DeveloperConfig::query()->where("key",
"=", DevConfigKeyEnum::SKU_EXPIRE_DAY)->first();
return $developerConfig['value'] ?? DevConfigKeyEnum::DEFAULT_EXPIRE_DAY;
});
}
public static function getSkuAdminRoleIds()
{
$expireTime = Carbon::now()->addHour();
return Cache::remember(CacheKeyEnum::SKU_ADMIN_ROLE_IDS, $expireTime, function () {
$developerConfig = DeveloperConfig::query()->where("key",
"=", DevConfigKeyEnum::SKU_ADMIN_ROLE_IDS)->first();
$roleIdsStr = $developerConfig['value'] ?? DevConfigKeyEnum::DEFAULT_SKU_ADMIN_ROLE_IDS;
return explode(",", $roleIdsStr) ?? [];
});
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace App\Services\Good;
use App\Events\BatchStockUpdateEvent;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
use App\Models\DailyStockRecord;
use App\Models\Goods;
use App\Models\GoodsSku;
use App\Models\GoodsType;
use App\Utils\DateTimeUtils;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Predis\Command\Redis\SUBSCRIBE;
class GoodService
{
public function saveDefaultGoodsByGoodType($typeId)
{
$goodsType = GoodsType::query()->with("parentType")->where('id', "=", $typeId)
->first()->toArray();
$params['type_id'] = $typeId;
$parentName = "";
$params['goods_code'] = static::getChCode($goodsType['id']);
if (!empty($goodsType['parent_type']['id'])) {
$params['goods_code'] .= "Z". static::getChCode($goodsType['parent_type']['id']);
$parentName = $goodsType['parent_type']['name'] ?? '';
}
$saveData = $params;
$saveData['title'] = $goodsType['name'] . " " .$parentName;
return Goods::query()->firstOrCreate($params, $saveData);
}
public function getTypeFormatName($typeId)
{
$goodsType = GoodsType::query()->with("parentType")->where('id', "=", $typeId)
->first()->toArray();
$parentName = "";
if (!empty($goodsType['parent_type']['name'])) {
$parentName = $goodsType['parent_type']['name'] ?? '';
}
return $goodsType['name'] . " " .$parentName;
}
public function getSkuName($typeId,$sku)
{
$goodsType = GoodsType::query()->with("parentType")->where('id', "=", $typeId)
->first()->toArray();
$skuName = $goodsType['name'];
if(!empty($sku['attribute'])){
$skuName.=" ".$sku['attribute'];
}
if (!empty($goodsType['parent_type']['name'])) {
$skuName.= " ".$goodsType['parent_type']['name'] ?? '';
}
return $skuName." ".$sku['title'];
}
public function getRandomCode()
{
$time = time();
return substr($time, -7) . static::randomString(3);
}
public static function randomString($len = 32)
{
$chars = [
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
"3", "4", "5", "6", "7", "8", "9"
];
// 将数组打乱
shuffle($chars);
$charsLen = count($chars) - 1;
$output = "";
for ($i = 0; $i < $len; $i++) {
$output .= $chars[mt_rand(0, $charsLen)];
}
return $output;
}
/**
* 获取非z的字符串
* @param $intValue
* @return string
*/
public function getChCode($intValue)
{
$ch = range('A', 'Y');
$result = '';
while ($intValue) {
$result = $ch[$intValue % 25] . $result;
$intValue = intval($intValue / 25);
}
return $result;
}
}

View File

@ -0,0 +1,193 @@
<?php
namespace App\Services\GoodSku;
use App\Events\BatchStockUpdateEvent;
use App\Http\Enum\TargetTypeEnum;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\Log as LogModel;
use App\Utils\DateTimeUtils;
use App\Utils\GeneratorUtils;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class GoodSkuService
{
/**
* $skusWithCombinationGoods 除了携带组合商品的字段 还拼接了盘点的具体库存值inventory
* @param array $skusWithCombinationGoods
* @return void
*/
public function inventory(array $skusWithCombinationGoods)
{
$skusWithCombinationGoods = $this->handleSkusWithCombinationGoods($skusWithCombinationGoods);
//传进来的sku可能包含组合商品 所以这里需要事先计算好数据
$inventoryKeyBySkuIdMap = collect($skusWithCombinationGoods)->where('is_combination', "=", 0)
->pluck("real_stock", "id")->filter()->toArray();
Log::info("库存原始操作map", $inventoryKeyBySkuIdMap);
//计算组合商品
foreach ($skusWithCombinationGoods as $sku) {
if (!empty($sku['is_combination'])) {
foreach ($sku['combination_goods'] as $combinationGoods) {
if (!isset($inventoryKeyBySkuIdMap[$combinationGoods["item_id"]])) {
//没有盘点到的sku需要在原先的sku库存
$inventoryKeyBySkuIdMap[$combinationGoods["item_id"]] = 0;
}
$inventoryKeyBySkuIdMap[$combinationGoods["item_id"]] += $combinationGoods['item_num'] * $sku['real_stock'];
}
}
}
$today = DateTimeUtils::getToday();
$dateTime = date('Y-m-d H:i:s');
$updateIds = [];
Log::info("库存盘点前完整信息", $skusWithCombinationGoods);
Log::info("需要操作的库存数据", $inventoryKeyBySkuIdMap);
$batchNumber = GeneratorUtils::generateBatchNumber("import");
DB::beginTransaction();
try {
foreach ($skusWithCombinationGoods as $sku) {
// 更新每日数据
DailyStockRecord::query()->updateOrCreate([
'sku_id' => $sku['id'],
'day' => $today,
], [
"inventory" => $sku['inventory'],
"inventory_time" => $dateTime,
"batch_number" => $batchNumber
]);
}
$costLogs = [];
//更新库存并记录数据
foreach ($inventoryKeyBySkuIdMap as $skuId => $realStock) {
//库存修改 盘点是直接覆盖
$skuData = GoodsSku::query()->where('id', $skuId)->first();
if (!empty($skuData)) {
$costLogs[] = static::addStockLog($skuData
, TargetTypeEnum::INVENTORY, ['stock' => $realStock]);
$skuData->stock = $realStock;
$skuData->save();
$updateIds[] = $skuId;
}
}
$log = new LogModel();
$log->batchInsert($costLogs);
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
Log::error("库存盘点异常", ["error" => $exception->getMessage()]);
}
// 批量更新
event(new BatchStockUpdateEvent($updateIds));
}
/**
* 库存盘点 需要扣减未发货未取消的订单数据
* 目前只查询了7日内的数据
* @param array $skusWithCombinationGoods
* @return \Illuminate\Support\Collection
*/
public function handleSkusWithCombinationGoods(array $skusWithCombinationGoods)
{
//查询sku当前未发货的数量 需要扣减
$externalSkuIds = collect($skusWithCombinationGoods)->pluck("external_sku_id")->toArray();
//默认只查15天内未发货的数据
$startTime = Carbon::now()->subDays(15)->startOfDay()->toDateTimeString();
$unshippedDataCollect = BusinessOrderItem::query()
->leftJoin("business_orders as b", "business_order_id", "=", "b.id")
->select("external_sku_id", DB::raw("sum(goods_number) as goods_total"), DB::raw("sum(already_cancel_number) as cancel_total"))
->whereIn("external_sku_id", $externalSkuIds)->where("b.shipping_status", "=", 0)
->where("business_order_items.created_at", ">=", $startTime)->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->toArray();
$unshippedData = collect($unshippedDataCollect)->pluck(null, "external_sku_id")->toArray();
Log::info("15日内未发货数据", $unshippedData);
return collect($skusWithCombinationGoods)->map(function ($v) use ($unshippedData) {
$v['real_stock'] = $v['inventory'] ?? null;
if (!empty($unshippedData[$v['external_sku_id']]) && isset($v['inventory'])) {
$v['real_stock'] = $v['inventory'] - $unshippedData[$v['external_sku_id']]['goods_total']
+ $unshippedData[$v['external_sku_id']]['cancel_total'];
}
return $v;
})->toArray();
}
public static function computeSkuStock(array $goodsSkuItem, array $changeData, $targetType = TargetTypeEnum::LOSS)
{
$updateIds = [];
$updateParams = [];
//添加系统日志
$costLogs = [];
// 成本
if (empty($goodsSkuItem['is_combination'])) {
$updateParam = [
'stock' => $goodsSkuItem['stock'] + $changeData['num'],
'sale_stock' => max($goodsSkuItem['sale_stock'] + $changeData['num'], 0),
];
if ($targetType == TargetTypeEnum::PURCHASE) {
$updateParam['cost'] = $changeData['cost'];
}
if ($updateParam['sale_stock'] <= 0) {
$updateParam['status'] = GoodsSku::$STATUS_DOWN;
}
GoodsSku::query()->where('external_sku_id', "=", $goodsSkuItem['external_sku_id'])
->update($updateParam);
$updateIds[] = $goodsSkuItem['id'];
$updateParams[] = $updateParam;
$costLogs[] = static::addStockLog($goodsSkuItem, $targetType, $updateParam);
} else {
$combinationGood = CombinationGood::query()->with('goodsSkuItem:id,stock,sale_stock')
->where('goods_sku_id', $goodsSkuItem['id'])->get();
foreach ($combinationGood as $item) {
$updateParam = [
'stock' => $item['goodsSkuItem']['stock'] + $changeData['num'] * $item['item_num'],
'sale_stock' => max($item['goodsSkuItem']['sale_stock'] + $changeData['num'] * $item['item_num'], 0),
];
if ($updateParam['sale_stock'] <= 0) {
$updateParam['status'] = GoodsSku::$STATUS_DOWN;
}
GoodsSku::query()->where('id', $item['goodsSkuItem']['id'])->update($updateParam);
$updateIds[] = $item['goodsSkuItem']['id'];
$updateParams[] = $updateParam;
$costLogs[] = static::addStockLog($item['goodsSkuItem'], $targetType, $updateParam);
}
}
$log = new LogModel();
$log->batchInsert($costLogs);
Log::info("本次请求更新参数", $updateParams);
return $updateIds;
}
public static function addStockLog($goodsSkuItem, $targetType = TargetTypeEnum::LOSS, $updateParam)
{
$userId = Auth::id();
$costLog = [
'module' => 'goods',
'action' => "POST",
'target_type' => $targetType,
'target_id' => $goodsSkuItem['id'] ?? 0,
'user_id' => $userId ?? 999,
"target_field" => "stock"
];
$costLog['before_update'] = json_encode($goodsSkuItem);
$costLog['after_update'] = json_encode($updateParam);
return $costLog;
}
}

View File

@ -0,0 +1,410 @@
<?php
namespace App\Services\Statistic;
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\Models\LossRecords;
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
{
/**
* sku维度的统计数据
*/
public static function saleStatistics(Request $request)
{
if (StaticTypeEnum::TODAY == $request->type) {
//实时统计 sku维度
return static::skuSaleStatisticsByToday($request);
} else {
//历史数据查询
return static::skuSaleStatisticsByHistory($request);
}
}
/**
* sku维度 今日实时统计
*/
public static function skuSaleStatisticsByToday(Request $request)
{
[$startTime, $endTime] = static::getTimeRange($request);
$build = BusinessOrderItem::query();
if (!empty($request->sku_id)) {
$externalSkuId = GoodsSku::query()->where("id", "=", $request->sku_id)
->pluck("external_sku_id")->first();
if (!empty($externalSkuId)) {
$build->where("external_sku_id", "=", $externalSkuId);
} else {
return [];
}
}
$orderItems = $build
->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"))
->whereBetween("business_order_items.created_at", [$startTime, $endTime])
->where("external_sku_id", "!=", "")
->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, $endTime] = static::getTimeRange($request);
$build = DailyStockRecord::query();
if (!empty($request->sku_id)) {
$build->where("sku_id", "=", $request->sku_id);
}
$dailyRecord = $build->select("sku_id", DB::raw("sum(order_goods_num) as goods_total")
, DB::raw("sum(order_total_amount) 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("day", [$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 getTimeRange(Request $request)
{
if (!empty($request->input("start_time")) && !empty($request->input("start_time"))) {
$startTime = Carbon::parse($request->input("start_time"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_time"))->toDateTimeString();
} else {
$startTime = Carbon::parse($request->input("start_day"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_day"))->endOfDay()->toDateTimeString();
}
return [$startTime, $endTime];
}
public static function spuSaleStatisticsByToday(Request $request)
{
[$startTime, $endTime] = static::getTimeRange($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('b.confirm_at', '>=', Carbon::parse($startTime)->getPreciseTimestamp(3))
->where('b.confirm_at', '<=', Carbon::parse($endTime)->getPreciseTimestamp(3))
->where("external_sku_id", "!=", "")
->where("business_order_items.cancel_status", "=", 0)
->groupBy('external_sku_id')->get()->toArray();
$externalSkuIds = collect($orderItems)->pluck("external_sku_id")->toArray();
$goodsSkus = GoodsSku::query()
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num',
'combinationGoods.goodsSkuItem:id,name,goods_id,title,stock,sale_stock,external_sku_id,updated_at,yesterday_num,reference_price,status',
])
->whereIn("external_sku_id", $externalSkuIds)->get()->pluck(null, "external_sku_id")
->toArray();
$skus = [];
//组合商品
foreach ($orderItems as $orderItem) {
$sku = $goodsSkus[$orderItem['external_sku_id']] ?? [];
if (!empty($sku['is_combination'])) {
foreach ($sku['combination_goods'] as $combinationGood) {
$skuItem = $goodsSkus[$combinationGood['goods_sku_item']['external_sku_id']] ?? [];
$skuItem['shipping_num'] = $skuItem['shipping_num'] ?? 0 + $orderItem['shipping_num'] * $combinationGood['item_num'];
$skuItem['unshipping_num'] = $skuItem['unshipping_num'] ?? 0 + $orderItem['unshipping_num'] * $combinationGood['item_num'];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $orderItem['goods_total'] * $combinationGood['item_num'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $orderItem['goods_total_amount'] * $combinationGood['item_num'];
$skus[$combinationGood['goods_sku_item']['external_sku_id']] = $skuItem;
}
} else {
$skuItem = $skus[$orderItem['external_sku_id']] ?? [];
$skuItem['shipping_num'] = $skuItem['shipping_num'] ?? 0 + $orderItem['shipping_num'];
$skuItem['unshipping_num'] = $skuItem['unshipping_num'] ?? 0 + $orderItem['unshipping_num'];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $orderItem['goods_total'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $orderItem['goods_total_amount'];
$skus[$orderItem['external_sku_id']] = $skuItem;
}
}
$goodsSkuWithTypes = GoodsSku::query()
->Join("goods", "goods_id", "=", "goods.id")
->Join("goods_types", "goods.type_id", "=", "goods_types.id")
->whereIn("external_sku_id", array_keys($skus))
->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 ($skus) {
if (!empty($skus[$v['external_sku_id']])) {
return array_merge($v, $skus[$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"),
];
})->sortByDesc('goods_total')->values()->toArray();
}
/**
* 通过历史报表统计spu数据-缓存
* @param Request $request
* @return mixed
*/
public
static function spuSaleStatisticsByHistoryCache(Request $request)
{
[$startTime, $endTime] = static::getTimeRange($request);
$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);
});
}
/**
* 通过历史报表统计spu数据
* @param $startTime
* @param $endTime
* @return array
*/
public static function spuSaleStatisticsByHistory($startTime, $endTime)
{
$dailyAllRecord = DailyStockRecord::query()
->select("sku_id", DB::raw("sum(order_goods_num) as goods_total")
, DB::raw("sum(order_total_amount) as goods_total_amount"))
->whereBetween("day", [$startTime, $endTime])
->groupBy("sku_id")
->having(DB::raw("sum(order_goods_num)"), ">", 0)
->get()->toArray();
$skuIds = collect($dailyAllRecord)->pluck('sku_id')->toArray();
$goodsSkus = GoodsSku::query()
->with([
'combinationGoods:id,goods_sku_id,item_id,item_num'
])
->whereIn("id", $skuIds)->get()->pluck(null, "id")
->toArray();
$skus = [];
//组合商品需要分散到sku维度进行统计
foreach ($dailyAllRecord as $skuRecord) {
$sku = $goodsSkus[$skuRecord['sku_id']] ?? [];
if (!empty($sku['is_combination'])) {
foreach ($sku['combination_goods'] as $combinationGood) {
$skuItem = $goodsSkus[$combinationGood['item_id']] ?? [];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $skuRecord['goods_total'] * $combinationGood['item_num'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $skuRecord['goods_total_amount'] * $combinationGood['item_num'];
$skus[$combinationGood['item_id']] = $skuItem;
}
} else {
$skuItem = $skus[$skuRecord['sku_id']] ?? [];
$skuItem['goods_total'] = $skuItem['goods_total'] ?? 0 + $skuRecord['goods_total'];
$skuItem['goods_total_amount'] = $skuItem['goods_total_amount'] ?? 0 + $skuRecord['goods_total_amount'];
$skus[$skuRecord['sku_id']] = $skuItem;
}
}
$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", array_keys($skus))
->get()->toArray();
return collect($goodsSkuWithTypes)->map(function ($v) use ($skus) {
if (!empty($skus[$v['id']])) {
return array_merge($v, $skus[$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"),
];
})->sortByDesc('goods_total')->values()->toArray();
}
/**
* gmv 统计
* @param Request $request
* @return array
*/
public static function gmvStatistics(Request $request)
{
if (StaticTypeEnum::TODAY == $request->type) {
[$startTime, $endTime] = static::getTimeRange($request);
$build = BusinessOrderItem::query();
if (!empty($request->sku_id)) {
$externalSkuId = GoodsSku::query()->where("id", "=", $request->sku_id)
->pluck("external_sku_id")->first();
if (!empty($externalSkuId)) {
$build->where("external_sku_id", "=", $externalSkuId);
} else {
return [];
}
}
$orderItems = $build->select("business_order_items.external_sku_id"
, "goods_number", "already_cancel_number", "goods_amount", "created_at")
->whereBetween("business_order_items.created_at", [$startTime, $endTime])
->where("business_order_items.cancel_status", "=", 0)
->get()->toArray();
$interval = $request->input("interval", 30);
return collect($orderItems)->groupBy(function ($v) use ($startTime, $interval) {
$diff = Carbon::parse($v['created_at'])->diffInMinutes(Carbon::parse($startTime));
return (int)floor($diff / $interval);
})->map(function ($v, $key) use ($startTime, $interval) {
return [
"sort_key" => $key,
"interval" => $interval,
"time_start" => Carbon::parse($startTime)->addMinutes($key * $interval)->toTimeString(),
"time_end" => Carbon::parse($startTime)->addMinutes(($key + 1) * $interval)->toTimeString(),
"goods_total" => $v->sum("goods_number") - $v->sum("already_cancel_number"),
"goods_total_amount" => round($v->sum("goods_amount") / 100, 2),
];
})->sort()->values()->toArray();
} else {
//gmv 统计历史数据
[$startTime, $endTime] = static::getTimeRange($request);
$build = DailyStockRecord::query();
if (!empty($request->sku_id)) {
$build->where("sku_id", "=", $request->sku_id);
}
return $build->select("day", DB::raw("sum(order_total_amount) as goods_total_amount")
, DB::raw("sum(order_goods_num) as goods_total"))
->whereBetween("day", [$startTime, $endTime])
->groupBy("day")->orderBy("day")
->get()->toArray();
}
}
/**
* 报损统计
* @param Request $request
* @return array
*/
public static function lossRecordStatistics(Request $request)
{
$startTime = Carbon::parse($request->input("start_time"))->toDateTimeString();
$endTime = Carbon::parse($request->input("end_time"))->toDateTimeString();
$build = LossRecords::query();
if (!empty($request->sku_id)) {
$externalSkuId = GoodsSku::query()->where("id", "=", $request->sku_id)
->pluck("external_sku_id")->first();
if (!empty($externalSkuId)) {
$build->where("external_sku_id", "=", $externalSkuId);
} else {
return [];
}
}
return $build->select("date", DB::raw("sum(num) as total_num")
, DB::raw("sum(num*cost) as total_loss_amount"))
->whereBetween("date", [$startTime, $endTime])
->groupBy("date")->orderBy("date")
->get()->toArray();
}
}

View File

@ -2,6 +2,8 @@
namespace App\Utils; namespace App\Utils;
use Illuminate\Support\Facades\Log;
class DateTimeUtils class DateTimeUtils
{ {
/** /**
@ -42,4 +44,28 @@ class DateTimeUtils
return (int)ceil($time); return (int)ceil($time);
} }
public static function validateDate($date, $format = 'Y-m-d')
{
$d = \DateTime::createFromFormat($format, $date);
return $d && $d->format($format) === $date;
}
public static function excelUploadDateToString($excelData, $defaultTime, $format = "Y-m-d")
{
try {
$time = ($excelData - 25569) * 24 * 3600;
if ($format == "Y-m-d H:i:s") {
$time = $time - 8 * 3600;
}
return date($format, $time);
} catch (\Exception $exception) {
Log::error("时间转化出错", [$exception->getMessage()]);
}
return $defaultTime;
}
} }

View File

@ -0,0 +1,44 @@
<?php
namespace App\Utils;
use App\Utils\NumberUtils;
use Yeepay\Yop\Sdk\Utils\Http\HttpUtils;
class GeneratorUtils
{
public static function generateBatchNumber($type = "add")
{
$code = "A";//批量添加
if ($type != 1) {
$code = "I";//导入
}
return $code.date("YmdHis") . rand(1000, 10000);
}
public static function generateCombinationGoodNumber($goods)
{
$code = "";
foreach ($goods as $v){
$code.=static::getChCode($v['item_id'])."Z".static::getChCode($v['item_num'])."Z";
}
rtrim($code,"Z");
return $code;
}
/**
* 获取非z的字符串
* @param $intValue
* @return string
*/
public static function getChCode($intValue)
{
$ch = range('A', 'Y');
$result = '';
while ($intValue) {
$result = $ch[$intValue % 25] . $result;
$intValue = intval($intValue / 25);
}
return $result;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Utils;
use App\Exceptions\ServiceException;
use Illuminate\Support\Facades\Redis;
class RedisLockUtils
{
private static $prefix = 'lock';
public static function getKey($keyType, $extend = '')
{
return collect([
self::$prefix,
$keyType,
$extend
])->filter()->implode(':');
}
public static function getLock($lockKey, $expireSeconds = 1, $throwException = false)
{
$lock = Redis::setnx($lockKey, 1);
if (!$lock) {
if ($throwException) {
throw new ServiceException('操作频繁');
}
return false;
}
Redis::expire($lockKey, $expireSeconds);
return true;
}
public static function deleteLock($lockKey)
{
return Redis::del($lockKey);
}
}

View File

@ -17,6 +17,7 @@
"laravel/framework": "^6.20.26", "laravel/framework": "^6.20.26",
"laravel/tinker": "^2.5", "laravel/tinker": "^2.5",
"maatwebsite/excel": "^3.1", "maatwebsite/excel": "^3.1",
"predis/predis": "^2.2",
"spatie/laravel-permission": "*" "spatie/laravel-permission": "*"
}, },
"require-dev": { "require-dev": {

69
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "037b06c1b26399725a1d9c0687402942", "content-hash": "964631bbee47f895975146a783331c50",
"packages": [ "packages": [
{ {
"name": "aliyuncs/oss-sdk-php", "name": "aliyuncs/oss-sdk-php",
@ -2587,6 +2587,73 @@
], ],
"time": "2022-07-30T15:51:26+00:00" "time": "2022-07-30T15:51:26+00:00"
}, },
{
"name": "predis/predis",
"version": "v2.2.2",
"source": {
"type": "git",
"url": "https://github.com/predis/predis.git",
"reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/predis/predis/zipball/b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1",
"reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^8.0 || ~9.4.4"
},
"suggest": {
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
},
"type": "library",
"autoload": {
"psr-4": {
"Predis\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Till Krüss",
"homepage": "https://till.im",
"role": "Maintainer"
}
],
"description": "A flexible and feature-complete Redis client for PHP.",
"homepage": "http://github.com/predis/predis",
"keywords": [
"nosql",
"predis",
"redis"
],
"support": {
"issues": "https://github.com/predis/predis/issues",
"source": "https://github.com/predis/predis/tree/v2.2.2"
},
"funding": [
{
"url": "https://github.com/sponsors/tillkruss",
"type": "github"
}
],
"time": "2023-09-13T16:42:03+00:00"
},
{ {
"name": "psr/container", "name": "psr/container",
"version": "1.1.1", "version": "1.1.1",

View File

@ -52,6 +52,7 @@ return [
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
'level' => 'debug', 'level' => 'debug',
'days' => 14, 'days' => 14,
"permission" => 0777
], ],
'slack' => [ 'slack' => [

View File

@ -61,7 +61,8 @@ class CreateBusinessOrdersTable extends Migration
// 索引 // 索引
$table->unique(['shop_id', 'order_sn']); $table->unique(['shop_id', 'order_sn']);
$table->index(['shop_id', 'participate_no']); $table->index(['shop_id', 'participate_no']);
$table->index(['shop_id', 'confirm_at', 'after_sales_status', 'cancel_status', 'is_supplier']); $table->index(['shop_id', 'confirm_at', 'after_sales_status', 'cancel_status', 'is_supplier']
,"shop_id_confirm_at_composite_index");
}); });
} }

View File

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePurchaseRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('purchase_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('external_sku_id',60)->comment('唯一sku标识');
$table->string('batch_number',60)->nullable()->comment('批次号');
$table->date('date')->nullable()->comment('日期');;
$table->Integer('num')->default(0)->comment('采购数量');
$table->unsignedDecimal('cost')->default(0)->comment('成本');
$table->Integer('buyer_user_id')->default(0)->comment('购买人用户id');;
$table->string('buyer_name')->nullable()->comment('采购人');
$table->Integer('check_status')->default(0)->comment('盘点完近似状态 0未完成1已售卖完成');
$table->string('expire_time')->nullable()->comment('保质期时间');
$table->string('supplier_name')->nullable()->comment('供应商名称');
$table->Integer('supplier_id')->nullable()->comment('供应商id');
// 索引
$table->index('external_sku_id');
$table->index('created_at');
$table->index('batch_number');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('purchase_records');
}
}

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLossRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('loss_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('external_sku_id',60)->comment('唯一sku标识');
$table->string('batch_number',60)->nullable()->comment('批次号');
$table->date('date')->nullable()->comment('日期');
$table->Integer('num')->default(0)->comment('报损数量');
$table->unsignedDecimal('cost')->default(0)->comment('成本');
$table->Integer('buyer_user_id')->default(0)->comment('购买人用户id');;
$table->string('buyer_name')->nullable()->comment('采购人');
$table->string('reason')->nullable()->comment('报损原因');
$table->string('phenomenon')->nullable()->comment('报损现象');
// 索引
$table->index('external_sku_id');
$table->index('created_at');
$table->index('batch_number');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('loss_records');
}
}

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateWebsiteMessagesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('website_messages', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title')->comment('消息内容标题');
$table->string('content')->comment('消息内容');
$table->string('type',60)->comment('消息类型');
$table->string('unique_key',100)->comment('类型下唯一的key避免重复写入');
$table->Integer('role_id')->nullable()->comment('角色id');
$table->Integer('uid')->nullable()->comment('用户id 存在非0值表示当前uid可见');
$table->tinyInteger('status')->default(0)->comment('消息状态 0未读 1已读');
$table->string('extend')->nullable()->comment('拓展字段 后续拓展用');
$table->index(["role_id","uid"]);
$table->unique(["type","unique_key"]);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('website_messages');
}
}

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSuppliersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('suppliers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('supplier_name',100)->comment('供应商名称');
$table->string('company_name',100)->nullable()->comment('公司名称');
$table->string('address')->nullable()->comment('地址');
$table->string('link_tel',100)->nullable()->comment('联系方式');
$table->string('payment_account',100)->nullable()->comment('支付方式');
$table->string('supply_type')->nullable()->comment('供应类型');
$table->string('agent_name')->nullable()->comment('开发维护人');
$table->Integer('agent_id')->nullable()->comment('开发维护人id');
$table->index('supplier_name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('suppliers');
}
}

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddSaleStockAndQualityPeriodToGoodsSkusTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasColumns('goods_skus', ['sale_stock', 'quality_period'])) {
return;
}
Schema::table('goods_skus', function (Blueprint $table) {
//
$table->integer('sale_stock')->default(0)->comment("在售库存数");
$table->integer('quality_period')->nullable()->comment("保质期时间,单位天");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('goods_skus', function (Blueprint $table) {
//
$table->dropColumn('sale_stock');
$table->dropColumn('quality_period');
});
}
}

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddOrderTotalAmountToDailyStockRecord extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasColumns('daily_stock_records', ['order_total_amount',"batch_number"])) {
return;
}
Schema::table('daily_stock_records', function (Blueprint $table) {
$table->decimal('order_total_amount')->default(0)->comment('订单总金额');
$table->string('batch_number',60)->nullable()->comment('批次号');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (Schema::hasColumns('daily_stock_records', ['order_total_amount',"batch_number"])) {
return;
}
Schema::table('daily_stock_records', function (Blueprint $table) {
//
$table->dropColumn('order_total_amount');
$table->dropColumn('batch_number');
});
}
}

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBusinessAfterSaleOrders extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('business_after_sale_orders', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('after_sales_biz_sn',100)->comment('售后单编号');
$table->string('order_sn',100)->comment('父单编号');
$table->integer('shop_id')->comment('门店id');
$table->integer('after_sales_status')->default(0)->comment('售后单状态
0-未发起售后 1-退款中 2-退款成功 3-待处理 4-拒绝退款 6-(顾客)退货 7-(团长)确认退货 8-(顾客)撤销 9-(系统)关闭');
$table->bigInteger('refund_amount')->default(0)->comment('退款金额');
$table->bigInteger('refund_shipping_amount')->default(0)->comment('用户申请退运费金额');
$table->string('description')->nullable()->comment('描述');
$table->string('reason')->nullable()->comment('原因');
$table->text('sub_extensions')->nullable()->comment('子单信息');
$table->text('image_list')->nullable()->comment('图片列表');
$table->text('return_goods_extension')->nullable()->comment('退款物流信息');
$table->integer('apply_type')->default(0)->comment('售后单类型 0-仅退款 1-退货退款');
$table->dateTime('after_sale_created_at')->nullable()->comment('售后单三方创建时间');
$table->dateTime('after_sale_updated_at')->nullable()->comment('售后单三方更新时间');
$table->unique(["shop_id",'order_sn'],"unique_shop_id_order_sn");
$table->index('order_sn');
$table->index(['after_sales_biz_sn',"apply_type"],"index_biz_sn_apply_type");
$table->index('after_sale_created_at');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('business_after_sale_orders');
}
}

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFieldsToGoodsTypes extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasColumns('goods_types', ['parent_id', 'show',"level"])) {
return;
}
Schema::table('goods_types', function (Blueprint $table) {
$table->integer('parent_id')->default(0)->comment('父id');
$table->integer('show')->default(1)->comment('是否显示 0隐藏 1显示');
$table->integer('level')->default(1)->comment('层级 目前仅记录使用 默认1');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('goods_types', function (Blueprint $table) {
//
$table->dropColumn('parent_id');
$table->dropColumn('show');
$table->dropColumn('level');
});
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class RemoveIndexToGoodsTypes extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('goods_types', function (Blueprint $table) {
$table->dropUnique('goods_types_name_unique');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('goods_types', function (Blueprint $table) {
//
$table->unique('name');
});
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddAttributeToGoodsSkus extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasColumn('goods_skus', "attribute")) {
return;
}
Schema::table('goods_skus', function (Blueprint $table) {
$table->string('attribute',100)->default("")->comment('属性名称');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
Schema::table('goods_skus', function (Blueprint $table) {
$table->dropColumn("attribute");
});
}
}

View File

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddFieldToPurchaseRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (Schema::hasColumns('purchase_records', ["arrived_time", "status","check_time"])) {
return;
}
Schema::table('purchase_records', function (Blueprint $table) {
$table->dateTime('arrived_time')->nullable()->comment('到货时间');
$table->integer('status')->default(0)->comment('采购单状态 0待审核 1已审核 2审核失败');
$table->dateTime('check_time')->nullable()->comment('审核时间');;
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('purchase_records', function (Blueprint $table) {
//
$table->dropColumn('arrived_time');
$table->dropColumn('status');
$table->dropColumn('check_time');
});
}
}

View File

@ -1,21 +0,0 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
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]
</IfModule>

1
public/dist/css/107.fd5b2517.css vendored Normal file
View File

@ -0,0 +1 @@
#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.searchBox[data-v-41a7827f]{display:flex;align-items:center;flex-wrap:wrap;white-space:nowrap}.searchBox .row[data-v-41a7827f]{font-size:14px;margin-bottom:20px;margin-right:15px;display:flex;align-items:center}.searchBox .time[data-v-41a7827f]{margin-left:20px;color:#999;font-size:12px}.opaBox[data-v-41a7827f]{margin-bottom:15px}

1
public/dist/css/147.610fa777.css vendored Normal file
View File

@ -0,0 +1 @@
#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.searchBox[data-v-6c4b71cd]{display:flex;align-items:center;flex-wrap:wrap;white-space:nowrap}.searchBox .row[data-v-6c4b71cd]{font-size:14px;margin-bottom:20px;margin-right:15px;display:flex;align-items:center}.imgBox .img[data-v-6c4b71cd]{width:55px;height:55px;border-radius:4px;margin:3px}

View File

@ -1 +0,0 @@
#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.aside-show[data-v-c25279e8]{transition:all .3s;opacity:0;width:0!important}.aside-hide[data-v-c25279e8]{transition:all .3s;opacity:1;width:200px!important}.el-container[data-v-c25279e8]{height:100vh}.el-aside[data-v-c25279e8]{background-color:#d3dce6;color:#333;overflow-x:hidden}.el-aside[data-v-c25279e8]::-webkit-scrollbar{width:8px}.el-aside[data-v-c25279e8]::-webkit-scrollbar-thumb{background-color:hsla(220,4%,58%,.3);border-radius:20px}.el-main[data-v-c25279e8]{background-color:#f0f2f5;color:#333;padding:0 0!important}.el-main[data-v-c25279e8]::-webkit-scrollbar{width:10px}.el-main[data-v-c25279e8]::-webkit-scrollbar-thumb{background-color:hsla(220,4%,58%,.3)}.box-card[data-v-c25279e8]{min-height:calc(100vh - 120px);margin:10px}.conent[data-v-c25279e8]{width:100%;min-height:calc(100vh - 200px);position:relative}.add[data-v-c25279e8]{cursor:pointer;font-size:25px;color:#606266}.head[data-v-c25279e8]{padding:10px;background-color:#fff;border-bottom:1px solid #f6f6f6;box-shadow:0 1px 4px rgba(0,21,41,.08)}.head ul[data-v-c25279e8]{display:flex;justify-content:space-between}.head ul li[data-v-c25279e8]{display:flex;align-items:center}.head ul li .right[data-v-c25279e8]{margin-left:20px}.head ul li .token[data-v-c25279e8]{cursor:pointer}.el-aside[data-v-c25279e8]{background:#282c34;box-shadow:2px 0 6px rgba(0,21,41,.35)}[data-v-c25279e8] .el-menu{border:none}.el-menu-item[data-v-c25279e8]:hover{outline:0!important;background:#5470c6!important;border-radius:5px!important}.el-menu-item.is-active[data-v-c25279e8]{color:#fff!important;background:#5470c6!important;border-radius:5px!important}.el-menu-item-group__title[data-v-c25279e8]{padding:0 0!important}

1
public/dist/css/197.e8b8bfee.css vendored Normal file
View File

@ -0,0 +1 @@
#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.searchBox[data-v-825b4586]{display:flex;align-items:center;flex-wrap:wrap;white-space:nowrap;.row[data-v-825b4586]{font-size:14px;margin-bottom:20px;margin-right:15px;display:flex;align-items:center}}.opaBox[data-v-825b4586]{margin-bottom:15px}.btn[data-v-825b4586]{float:right}.commodityimg[data-v-825b4586]{width:59px;height:59px;background:hsla(0,0%,89%,.39);opacity:1;display:block;margin-right:12px}.Img[data-v-825b4586]{width:100%;height:100%}[data-v-825b4586] .flex .cell{display:flex;align-items:center}[data-v-825b4586] .btn11{padding:0;width:14px;height:14px}[data-v-825b4586] .btn11 img{width:100%;height:100%}.page[data-v-825b4586]{margin-top:20px}

1
public/dist/css/199.fc892afb.css vendored Normal file
View File

@ -0,0 +1 @@
#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.searchBox[data-v-42163996]{display:flex;align-items:center;flex-wrap:wrap;white-space:nowrap}.searchBox .row[data-v-42163996]{font-size:14px;margin-bottom:20px;margin-right:15px;display:flex;align-items:center}.searchBox .time[data-v-42163996]{margin-left:20px;color:#999;font-size:12px}.echartBox[data-v-42163996]{width:100%;height:480px}

Some files were not shown because too many files have changed in this diff Show More