diff --git a/app/Console/Commands/CheckPrice.php b/app/Console/Commands/CheckPrice.php
new file mode 100644
index 0000000..aa05521
--- /dev/null
+++ b/app/Console/Commands/CheckPrice.php
@@ -0,0 +1,66 @@
+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');
+ }
+}
\ No newline at end of file
diff --git a/app/Console/Commands/CheckSkuQualityPeriod.php b/app/Console/Commands/CheckSkuQualityPeriod.php
new file mode 100644
index 0000000..4f76c1e
--- /dev/null
+++ b/app/Console/Commands/CheckSkuQualityPeriod.php
@@ -0,0 +1,80 @@
+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');
+ }
+}
\ No newline at end of file
diff --git a/app/Console/Commands/DailyStockRecordReport.php b/app/Console/Commands/DailyStockRecordReport.php
new file mode 100644
index 0000000..e592591
--- /dev/null
+++ b/app/Console/Commands/DailyStockRecordReport.php
@@ -0,0 +1,101 @@
+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();
+ });
+ }
+ }
+}
diff --git a/app/Console/Commands/InitSaleStock.php b/app/Console/Commands/InitSaleStock.php
new file mode 100644
index 0000000..0394dec
--- /dev/null
+++ b/app/Console/Commands/InitSaleStock.php
@@ -0,0 +1,54 @@
+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')]);
+
+ }
+}
\ No newline at end of file
diff --git a/app/Console/Commands/Inventory.php b/app/Console/Commands/Inventory.php
index 61b12fb..581ca5a 100644
--- a/app/Console/Commands/Inventory.php
+++ b/app/Console/Commands/Inventory.php
@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
+use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
@@ -41,19 +42,20 @@ class Inventory extends Command
*/
public function handle()
{
- DB::beginTransaction();
- try {
- $log = new Log();
- $log->module = 'goods';
- $log->action = 'PATCH';
- $log->target_type = 'goods_sku';
- $log->target_id = 0;
- $log->target_field = 'timingInventory';
- $log->user_id = 999;
- // 数据库存储过程,7点定时执行
- $data = [];
- $date = date('Y-m-d');
- GoodsSku::query()->chunk(500, static function ($skus) use (&$data, $date) {
+
+ $log = new Log();
+ $log->module = 'goods';
+ $log->action = 'PATCH';
+ $log->target_type = 'goods_sku';
+ $log->target_id = 0;
+ $log->target_field = 'timingInventory';
+ $log->user_id = 999;
+ // 数据库存储过程,7点定时执行
+ $data = [];
+ $date = date('Y-m-d');
+ GoodsSku::query()->chunk(500, static function ($skus) use (&$data, $date, $log) {
+ DB::beginTransaction();
+ try {
foreach ($skus as $sku) {
$data[] = [
'sku_id' => $sku->id,
@@ -64,15 +66,16 @@ class Inventory extends Command
'two_days_ago_num' => $sku->two_days_ago_num + $sku->yesterday_num,
]);
}
- });
- $record = new DailyStockRecord();
- $record->batchInsert($data);
- DB::commit();
- $log->message = '7点数据更新成功';
- } catch (\Exception $exception) {
- $log->message = '7点数据更新失败' . $exception->getMessage();
- DB::rollBack();
- }
+ $record = new DailyStockRecord();
+ $record->batchInsert($data);
+ DB::commit();
+ $log->message = '7点数据更新成功';
+ } catch (\Exception $exception) {
+ $log->message = '7点数据更新失败' . $exception->getMessage();
+ DB::rollBack();
+ }
+
+ });
$log->save();
$this->info($log->message);
}
diff --git a/app/Console/Commands/KttOrderAfterSaleQuery.php b/app/Console/Commands/KttOrderAfterSaleQuery.php
new file mode 100644
index 0000000..d6a40d9
--- /dev/null
+++ b/app/Console/Commands/KttOrderAfterSaleQuery.php
@@ -0,0 +1,53 @@
+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);
+ }
+
+ }
+}
diff --git a/app/Console/Commands/KttOrderQuery.php b/app/Console/Commands/KttOrderQuery.php
index 69498a8..d94243e 100644
--- a/app/Console/Commands/KttOrderQuery.php
+++ b/app/Console/Commands/KttOrderQuery.php
@@ -6,6 +6,7 @@ use App\Models\Shop;
use App\Services\Business\BusinessFactory;
use App\Utils\DateTimeUtils;
use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
class KttOrderQuery extends Command
{
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 9a570d5..9ce6b35 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -2,9 +2,12 @@
namespace App\Console;
+use App\Console\Commands\CheckPrice;
+use App\Console\Commands\CheckSkuQualityPeriod;
use App\Console\Commands\DailySalesReport;
use App\Console\Commands\GoodsSkuDailyReport;
use App\Console\Commands\Inventory;
+use App\Console\Commands\KttOrderAfterSaleQuery;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use App\Console\Commands\KttOrderQuery;
@@ -45,6 +48,15 @@ class Kernel extends ConsoleKernel
$schedule->command(DailySalesReport::class, ['S7'])->dailyAt('09:30');
$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');
+
}
/**
diff --git a/app/Events/BatchStockUpdateEvent.php b/app/Events/BatchStockUpdateEvent.php
index a9f2778..0f56371 100644
--- a/app/Events/BatchStockUpdateEvent.php
+++ b/app/Events/BatchStockUpdateEvent.php
@@ -10,6 +10,7 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\Log;
class BatchStockUpdateEvent
{
diff --git a/app/Events/BusinessOrdersUpdate.php b/app/Events/BusinessOrdersUpdate.php
index 1e78768..cf2aac2 100644
--- a/app/Events/BusinessOrdersUpdate.php
+++ b/app/Events/BusinessOrdersUpdate.php
@@ -21,6 +21,8 @@ class BusinessOrdersUpdate
public $businessGoodSku;
public $num;
public $goodsSku;
+ public $type = "BusinessOrdersUpdate";
+
public $combinationGoodsUpdate = true;
/**
@@ -30,39 +32,50 @@ class BusinessOrdersUpdate
*/
public function __construct($businessGoodSku, $num)
{
- $this->businessGoodSku = $businessGoodSku->toArray();
- $this->num = $num;
- $this->updateStock();
+ try {
+ $this->businessGoodSku = $businessGoodSku->toArray();
+ $this->num = $num;
+ $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()
{
$this->goodsSku = GoodsSku::query()
->where('external_sku_id', $this->businessGoodSku['external_sku_id'])
->first();
if (is_null($this->goodsSku)) {
- return false;
+ return true;
}
+ $oldStock = $this->goodsSku->stock;
$stock = $this->goodsSku->stock + $this->num;
-
- if (0 >= $stock) {
- $this->goodsSku->status = GoodsSku::$STATUS_DOWN;
+ $saleStock = $this->goodsSku->sale_stock + $this->num;
+ $saleStock = ($saleStock > 0) ? $saleStock : 0;
+ $updateParam = ["stock" => $stock, "sale_stock" => $saleStock];
+ if (0 >= $saleStock) {
+ $updateParam['status'] = GoodsSku::$STATUS_DOWN;
} else {
- $this->goodsSku->status = GoodsSku::$STATUS_ON_SALE;
+ $updateParam['status'] = GoodsSku::$STATUS_ON_SALE;
}
- // 今日到货 + 1T 大于20,且当前剩余库存小于4时 直接下架
- $arrivedTodayNum = DailyStockRecord::query()
- ->where('day', DateTimeUtils::getToday())
- ->where('sku_id', $this->goodsSku->id)
- ->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();
+ Log::info("sku 业务订单库存更:{$this->goodsSku->id}", $updateParam);
+ //乐观锁更新
+ return GoodsSku::query()->where('external_sku_id', $this->businessGoodSku['external_sku_id'])->
+ where("stock", "=", $oldStock)->update($updateParam);
}
/**
diff --git a/app/Events/StockUpdateEvent.php b/app/Events/StockUpdateEvent.php
index ca84859..31180c2 100644
--- a/app/Events/StockUpdateEvent.php
+++ b/app/Events/StockUpdateEvent.php
@@ -33,22 +33,14 @@ class StockUpdateEvent
private function checkStatusAndStock($goodsSku)
{
- $stock = $goodsSku->stock;
- if (0 >= $goodsSku->stock) {
+ //新版上下架和真实库存无关
+ if (0 >= $goodsSku->sale_stock) {
$status = GoodsSku::$STATUS_DOWN;
} else {
$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->stock = $stock;
$goodsSku->save();
}
diff --git a/app/Exports/BusinessAfterOrderExport.php b/app/Exports/BusinessAfterOrderExport.php
new file mode 100644
index 0000000..e979449
--- /dev/null
+++ b/app/Exports/BusinessAfterOrderExport.php
@@ -0,0 +1,63 @@
+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];
+ }
+}
diff --git a/app/Exports/BusinessOrderExport.php b/app/Exports/BusinessOrderExport.php
new file mode 100644
index 0000000..034ce88
--- /dev/null
+++ b/app/Exports/BusinessOrderExport.php
@@ -0,0 +1,68 @@
+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];
+ }
+}
diff --git a/app/Exports/GoodsSkusExport.php b/app/Exports/GoodsSkusExport.php
index cc5a781..c9d311f 100644
--- a/app/Exports/GoodsSkusExport.php
+++ b/app/Exports/GoodsSkusExport.php
@@ -2,8 +2,11 @@
namespace App\Exports;
+use App\Http\Enum\TargetTypeEnum;
use App\Models\DailyStockRecord;
+use App\Models\Goods;
use App\Models\Log;
+use App\Models\PurchaseRecords;
use App\Utils\ArrayUtils;
use App\Utils\DateTimeUtils;
use Maatwebsite\Excel\Concerns\FromCollection;
@@ -35,56 +38,70 @@ class GoodsSkusExport implements FromCollection, ShouldAutoSize
$headTitle = [
'商品编码',
'商品名称',
- '商品种类',
- '商品品牌',
- '规格编码',
- '规格名称',
+ '可售库存',
+ '创建时间',
];
$map = [
- 'cost' => ['成本', '更新前成本', '更新后成本'],
- 'inventory' => ['库存', '盘点', '上新'],
+ 'cost' => ['当前成本', '更新前成本', "更新后成本"],
+ 'inventory' => ['当前库存', '盘点数', '采购总数'],
];
- $headTitle = array_merge($headTitle, $map[$this->type]);
+ $headTitle = array_merge($headTitle, $map[$this->type] ?? []);
if ('cost' === $this->type) {
$update = $this->getChangeCostLogs();
}
if ('inventory' === $this->type) {
$update = $this->getInventoryRecord();
}
- $ids = array_keys($update);
- if (empty($ids)) {
- return [$headTitle];
- }
- $model = GoodsSku::query()
- ->when($ids, function ($query, $ids) {
- return $query->whereIn('id', $ids);
- })
- ->with(['goods' => function ($query) {
- $query->with(['type:id,name', 'brand:id,name'])
- ->orderBy('type_id')
- ->orderBy('brand_id');
- }]);
- $data = $model->get()->toArray();
- if (empty($data)) {
- return [$headTitle];
+ 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);
+ if (empty($ids)) {
+ return [$headTitle];
+ }
+ $model = GoodsSku::query()
+ ->when($ids, function ($query, $ids) {
+ return $query->whereIn('id', $ids);
+ })
+ ->with(['goods' => function ($query) {
+ $query->with(['type:id,name', 'brand:id,name'])
+ ->orderBy('type_id')
+ ->orderBy('brand_id');
+ }]);
+ $data = $model->get()->toArray();
+ if (empty($data)) {
+ return [$headTitle];
+ }
}
+
$bodyData = [];
foreach ($data as $item) {
- $arr[0] = $item['goods']['goods_code'];
- $arr[1] = $item['goods']['title'];
- $arr[2] = $item['goods']['type']['name'];
- $arr[3] = $item['goods']['brand']['name'];
- $arr[4] = $item['sku_code'];
- $arr[5] = $item['title'];
+ $arr[0] = $item['external_sku_id'];
+ $arr[1] = $item['name'];
+ $arr[2] = $item['sale_stock'] ?? 0;
+ $arr[3] = $item['created_at'];
if ('cost' === $this->type) {
- $arr[6] = (string)$item['cost'];
- $arr[7] = (string)$update[$item['id']]['before_update'];
- $arr[8] = (string)$update[$item['id']]['after_update'];
+ $arr[4] = (string)$item['cost'];
+ $arr[5] = (string)$update[$item['id']]['before_update'];
+ $arr[6] = (string)$update[$item['id']]['after_update'];
}
if ('inventory' === $this->type) {
- $arr[6] = (string)$item['stock'];
- $arr[7] = (string)$update[$item['id']]['inventory'];
- $arr[8] = (string)$update[$item['id']]['arrived_today_num'];
+ $arr[4] = (string)$item['stock'];
+ $arr[5] = (string)$update[$item['id']]['inventory'];
+ $arr[6] = (string)$update[$item['id']]['arrived_today_num'];
}
$bodyData[] = $arr;
}
@@ -97,18 +114,23 @@ class GoodsSkusExport implements FromCollection, ShouldAutoSize
$day = DateTimeUtils::getToday();
$logs = Log::query()
->select(['target_id', 'before_update', 'after_update'])
- ->where('target_type', 'goods_sku')
- ->where('target_field', 'cost')
+ ->where('target_type', TargetTypeEnum::PURCHASE)
+ ->where('target_field', 'stock')
->where('created_at', '>', $day)
- ->orderBy('id', 'asc')
->get();
+
$update = [];
foreach ($logs as $log) {
- if ($log['before_update'] !== $log['after_update'] || (int)$log['after_update']) {
- if (!isset($update[$log['target_id']])) {
- $update[$log['target_id']]['before_update'] = $log['before_update'];
+ if (!isset($update[$log['target_id']])) {
+ $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'];
}
}
diff --git a/app/Exports/WeekDataExport.php b/app/Exports/WeekDataExport.php
index f22d8b1..7147e4b 100644
--- a/app/Exports/WeekDataExport.php
+++ b/app/Exports/WeekDataExport.php
@@ -33,9 +33,8 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
{
$headTitle = [
'商品名称',
- '品类',
- '规格',
- '品牌',
+ '品种',
+ '规格名称',
'商品编码',
'成本',
'销售数量',
@@ -51,8 +50,7 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
->with([
'goods:id,title',
'goodsType:id,name',
- 'goodsSku:id,title',
- 'goodsBrand:id,name',
+ 'goodsSku:id,title,name'
])
->where('date', '>=', $this->startDate)
->where('date', '<=', $this->endDate)
@@ -66,10 +64,9 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
$number = $item->total_goods_number - $item->total_cancel_number;
if (!isset($arr[$item->external_sku_id])) {
$arr[$item->external_sku_id] = [
- 'goodsTitle' => $item->goods->title,
- 'goodsTypeName' => $item->goodsType->name,
- 'goodsSkuTitle' => $item->goodsSku->title,
- 'goodsBrandName' => $item->goodsBrand->name,
+ 'goodsTitle' => !empty($item->goodsSku) ? $item->goodsSku->name:'',
+ 'goodsTypeName' => !empty($item->goodsType) ? $item->goodsType->name : "",
+ 'goodsSkuTitle' => !empty($item->goodsSku) ? $item->goodsSku->title : "",
'external_sku_id' => $item->external_sku_id,
'cost' => [],
'number' => [],
@@ -97,7 +94,6 @@ class WeekDataExport implements FromCollection, ShouldAutoSize
$item['goodsTitle'],
$item['goodsTypeName'],
$item['goodsSkuTitle'],
- $item['goodsBrandName'],
$item['external_sku_id'],
$cost,
array_sum($item['number']),
diff --git a/app/Filters/GoodsSkuFilter.php b/app/Filters/GoodsSkuFilter.php
index 57a8751..ef64a1e 100644
--- a/app/Filters/GoodsSkuFilter.php
+++ b/app/Filters/GoodsSkuFilter.php
@@ -2,6 +2,8 @@
namespace App\Filters;
+use Carbon\Carbon;
+
class GoodsSkuFilter extends Filters
{
protected function skuTitle($value)
@@ -28,4 +30,16 @@ class GoodsSkuFilter extends Filters
{
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);
+ }
}
diff --git a/app/Http/Controllers/Business/BusinessAfterSaleOrderController.php b/app/Http/Controllers/Business/BusinessAfterSaleOrderController.php
new file mode 100644
index 0000000..eb2cca7
--- /dev/null
+++ b/app/Http/Controllers/Business/BusinessAfterSaleOrderController.php
@@ -0,0 +1,67 @@
+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);
+ }
+
+}
diff --git a/app/Http/Controllers/Business/BusinessOrderController.php b/app/Http/Controllers/Business/BusinessOrderController.php
index 21d3406..2545f55 100644
--- a/app/Http/Controllers/Business/BusinessOrderController.php
+++ b/app/Http/Controllers/Business/BusinessOrderController.php
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Business;
+use App\Exports\BusinessOrderExport;
use App\Exports\OrderBlankExport;
use App\Http\Controllers\Controller;
use App\Models\BusinessOrder;
@@ -35,6 +36,24 @@ class BusinessOrderController extends Controller
$ids = BusinessOrderItem::query()->whereIn('external_sku_id', $externalSkuIds)->pluck('business_order_id');
$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')
->paginate($request->get('per_page'));
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 20b7eb9..f735986 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -47,4 +47,23 @@ class Controller extends BaseController
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" => []
+ ];
+ }
}
diff --git a/app/Http/Controllers/DataCenter/DataCenterController.php b/app/Http/Controllers/DataCenter/DataCenterController.php
index b31250a..2513096 100644
--- a/app/Http/Controllers/DataCenter/DataCenterController.php
+++ b/app/Http/Controllers/DataCenter/DataCenterController.php
@@ -3,10 +3,17 @@
namespace App\Http\Controllers\DataCenter;
use App\Http\Controllers\Controller;
+use App\Http\Enum\StaticTypeEnum;
use App\Http\Resources\DailySalesReportResource;
+use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport;
+use App\Models\GoodsSku;
+use App\Services\Statistic\SaleDataService;
use App\Utils\FormatUtils;
+use Carbon\Carbon;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Validator;
class DataCenterController extends Controller
{
@@ -37,4 +44,89 @@ class DataCenterController extends Controller
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));
+ }
}
\ No newline at end of file
diff --git a/app/Http/Controllers/Goods/GoodsCombinationController.php b/app/Http/Controllers/Goods/GoodsCombinationController.php
index f81afdb..553a14b 100644
--- a/app/Http/Controllers/Goods/GoodsCombinationController.php
+++ b/app/Http/Controllers/Goods/GoodsCombinationController.php
@@ -7,10 +7,14 @@ use App\Http\Resources\GoodsSkuResource;
use App\Imports\CombinationGoodsImport;
use App\Models\BusinessOrderItem;
use App\Models\CombinationGood;
+use App\Models\DeveloperConfig;
use App\Models\Goods;
use App\Models\GoodsSku;
+use App\Utils\GeneratorUtils;
+use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
@@ -20,43 +24,82 @@ class GoodsCombinationController extends Controller
public function index(Request $request)
{
// ToDo
- // 可通过子商品查找主商品
- $skus = GoodsSku::query()
- ->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'));
+ $sortField = $request->input('sort_field', 'id');//stock sale_stock order_goods_num
+ $sortValue = $request->input('sort_value', 'desc');
$fields = implode(',', [
'shop_id',
'external_sku_id',
- 'sum(goods_number) as number',
- 'sum(already_cancel_number) as cancel_number',
+ 'SUM(goods_number) - SUM(already_cancel_number) as number',
]);
+ $orderRestTime = DeveloperConfig::query()
+ ->where('key', DeveloperConfig::$ORDER_RESET_TIME)
+ ->value('value');
+ if (is_null($orderRestTime)) {
+ $orderRestTime = date('Y-m-d 07:00:00');
+ }
+
+ $businessOrderItems = BusinessOrderItem::query()
+ ->select(DB::raw($fields))
+ ->with([
+ 'shop:id,name',
+ 'goodsSku:id,external_sku_id,is_combination',
+ 'goodsSku.combinationGoods:id,goods_sku_id,item_id,item_num'
+ ])
+ ->where('created_at', '>', $orderRestTime)
+ ->where('external_sku_id', '<>', '')
+ ->groupBy(['shop_id', 'external_sku_id'])
+ ->orderByDesc('number')
+ ->get()
+ ->toArray();
+ $ids = $externals = [];
+ foreach ($businessOrderItems as $businessOrderItem) {
+ if (is_null($businessOrderItem['goods_sku'])) {
+ continue;
+ }
+ $id = $businessOrderItem['goods_sku']['id'];
+ if (isset($ids[$id])) {
+ $ids[$id] += (int)$businessOrderItem['number'];
+ } else {
+ $ids[$id] = (int)$businessOrderItem['number'];
+ }
+
+ $externals[$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 = [];
- $lastInventoryTime = date('Y-m-d 07:00:00');
- $orderDetail = BusinessOrderItem::query()
- ->select(DB::raw($fields))
- ->with(['shop:id,name'])
- ->where('external_sku_id', $item['external_sku_id'])
- ->when($lastInventoryTime, function ($query) use ($lastInventoryTime) {
- $query->where('created_at', '>', $lastInventoryTime);
- })
- ->groupBy(['shop_id', 'external_sku_id'])
- ->get()
- ->toArray();
- $addOrderGoodsNum = $reduceOrderGoodsNum = 0;
- if ($orderDetail) {
- $addOrderGoodsNum = array_sum(array_column($orderDetail, 'number'));
- $reduceOrderGoodsNum = array_sum(array_column($orderDetail, 'cancel_number'));
+ 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;
}
- $item['order_goods_num'] = $addOrderGoodsNum - $reduceOrderGoodsNum;
- $item['order_detail'] = $orderDetail;
+
$number = BusinessOrderItem::query()
->where('external_sku_id', $item['external_sku_id'])
->sum('goods_number');
@@ -65,6 +108,9 @@ class GoodsCombinationController extends Controller
->sum('already_cancel_number');
$item['total_orders_num'] = $number - $cancelNumber;
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[] = [
'cost' => 0,
'external_sku_id' => $combinationItem['goodsSkuItem']['external_sku_id'],
@@ -75,9 +121,10 @@ class GoodsCombinationController extends Controller
'reference_price' => $combinationItem['goodsSkuItem']['reference_price'],
'status' => $combinationItem['goodsSkuItem']['status'],
'stock' => $combinationItem['goodsSkuItem']['stock'],
- 'thumb_url' => $combinationItem['goodsSkuItem']['goods']['img_url'],
- 'img_url' => $combinationItem['goodsSkuItem']['goods']['img_url'],
- 'title' => $combinationItem['goodsSkuItem']['goods']['title'] . $combinationItem['goodsSkuItem']['title'],
+ 'sale_stock' => $combinationItem['goodsSkuItem']['sale_stock'],
+ 'thumb_url' => null,
+ 'img_url' => null,//图片暂时去掉
+ 'title' => $title,
'updated_at' => $combinationItem['goodsSkuItem']['updated_at'],
'yesterday_num' => $combinationItem['goodsSkuItem']['yesterday_num'],
'order_goods_num' => '请在商品列表查看',
@@ -88,15 +135,18 @@ class GoodsCombinationController extends Controller
$item['children'] = $items;
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)
{
$validator = Validator::make($request->all(), [
'title' => 'required',
- 'external_sku_id' => 'required',
+ 'external_sku_id' => 'sometimes',
'combination_goods.*' => 'required',
'combination_goods.*.item_id' => 'required',
'combination_goods.*.item_num' => 'required|gt:0',
@@ -106,20 +156,33 @@ class GoodsCombinationController extends Controller
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();
try {
$combinationGoods = $request->input('combination_goods');
$itemIds = array_column($combinationGoods, 'item_id');
$skus = GoodsSku::query()
->whereIn('id', $itemIds)
- ->pluck('stock', 'id')
+ ->get()
+ ->pluck(null, 'id')
->toArray();
$stock = [];
+ $saleStock = [];
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);
- $status = $stock ? (5 < $stock ? 1 : 2) : 0;
+ $saleStock = min($saleStock);
+
+ $status = $saleStock ? (5 < $saleStock ? 1 : 2) : 0;
if ($id = $request->input('id')) {
$sku = GoodsSku::query()->findOrFail($id);
} else {
@@ -127,11 +190,15 @@ class GoodsCombinationController extends Controller
$sku->goods_id = 0;
$sku->is_combination = 1;
}
+
+
$sku->status = $status;
$sku->title = $request->input('title');
- $sku->sku_code = $request->input('external_sku_id');
- $sku->external_sku_id = $request->input('external_sku_id');
+ $sku->name = $request->input('title');
+ $sku->sku_code = $externalSkuId;
+ $sku->external_sku_id = $externalSkuId;
$sku->stock = $stock;
+ $sku->sale_stock = $saleStock;
$sku->save();
CombinationGood::query()
->where('goods_sku_id', $sku->id)
@@ -180,16 +247,17 @@ class GoodsCombinationController extends Controller
public function goodsSkus(Request $request, $title)
{
- $goodsIds = Goods::query()
- ->where('title', 'like', '%' . $title . '%')
- ->pluck('id');
$skus = GoodsSku::query()
- ->whereIn('goods_id', $goodsIds)
+ ->where('name', 'like', '%' . $title . '%')
->where('is_combination', 0)
->with('goods:id,title')
- ->get(['id', 'title', 'goods_id']);
+ ->get(['id', 'title', 'goods_id', "name"]);
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);
diff --git a/app/Http/Controllers/Goods/GoodsController.php b/app/Http/Controllers/Goods/GoodsController.php
index 75485ca..d63bdfc 100644
--- a/app/Http/Controllers/Goods/GoodsController.php
+++ b/app/Http/Controllers/Goods/GoodsController.php
@@ -5,10 +5,13 @@ namespace App\Http\Controllers\Goods;
use App\Http\Controllers\Controller;
use App\Http\Requests\GoodsSkuRequest;
use App\Http\Resources\GoodsResource;
+use App\Models\GoodsType;
use App\Models\Log as LogModel;
+use App\Services\Good\GoodService;
use App\Utils\DateTimeUtils;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use App\Models\Goods;
use App\Http\Requests\GoodsRequest;
@@ -42,28 +45,23 @@ class GoodsController extends Controller
return response($this->res, $this->res['httpCode']);
}
+
DB::beginTransaction();
try {
- if (!empty($request->goods_id)) {
- $goods = Goods::query()->find($request->goods_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();
- }
+ $goodService = new GoodService();
+ $goods = $goodService->saveDefaultGoodsByGoodType($request->type_id);
$goodsSkus = [];
+
foreach ($request->skus as $item) {
$item['goods_id'] = $goods->id;
- $item['stock'] = $item['num'];
+ $item['stock'] = $item['num'] ?? 0;
$item['reference_price'] = $item['cost'] * 1.5;
- $item['external_sku_id'] = $goods->goods_code . '_' . $item['sku_code'];
- $item['name'] = $goods->title . $item['title'];
+ $item['sku_code'] = !empty($item['external_sku_id']) ? $item['external_sku_id'] : $goodService->getRandomCode();
+ $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;
}
+
$collection = $goods->skus()->createMany($goodsSkus)->toArray();
$this->setAfterUpdateForLog($collection);
$this->addLog(0, 'add');
@@ -89,13 +87,14 @@ class GoodsController extends Controller
return response($this->res, $this->res['httpCode']);
}
+
public function download()
{
- $file = resource_path('templates/goods_skus_import.xlsx');
+ $file = resource_path('templates/sale_stock_import.xlsx');
$headers = [
'Content-Type: application/xlsx',
];
- return response()->download($file, 'goods_skus_import.xlsx', $headers);
+ return response()->download($file, 'sale_stock_import.xlsx', $headers);
}
}
diff --git a/app/Http/Controllers/Goods/GoodsSkusController.php b/app/Http/Controllers/Goods/GoodsSkusController.php
index f34e979..5a9e5e1 100644
--- a/app/Http/Controllers/Goods/GoodsSkusController.php
+++ b/app/Http/Controllers/Goods/GoodsSkusController.php
@@ -7,16 +7,22 @@ use App\Events\StockUpdateEvent;
use App\Exports\GoodsSkusExport;
use App\Exports\WeekDataExport;
use App\Http\Controllers\Controller;
+use App\Http\Enum\ExcelKeyEnum;
use App\Http\Requests\GoodsRequest;
use App\Http\Requests\GoodsSkuRequest;
use App\Imports\InventoryImport;
use App\Imports\NewSetImport;
+use App\Imports\SaleStockImport;
use App\Models\BusinessOrderItem;
use App\Models\DailySalesReport;
use App\Models\DeveloperConfig;
use App\Models\Goods;
use App\Models\Log;
use App\Models\Log as LogModel;
+use App\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\DateTimeUtils;
use Carbon\Carbon;
@@ -60,7 +66,8 @@ class GoodsSkusController extends Controller
->select(DB::raw($fields))
->with([
'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('external_sku_id', '<>', '')
@@ -74,62 +81,76 @@ class GoodsSkusController extends Controller
continue;
}
$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])) {
$ids[$id] += (int)$businessOrderItem['number'];
} else {
$ids[$id] = (int)$businessOrderItem['number'];
}
- $externals[$businessOrderItem['external_sku_id']][] = $businessOrderItem;
+ $externals[$id][] = $businessOrderItem;
}
- arsort($ids);
+
$builder = GoodsSku::query();
$this->preparQueryGoodsSkus($request, $builder);
$day = DateTimeUtils::getToday();
- $goodsSkus = (clone $builder)->filter()
- ->where('is_combination', 0)
- ->orderByDesc('stock')
- ->pluck('stock', 'id')
- ->toArray();
- $finalIds = [];
- foreach ($ids as $id => $number) {
- if (isset($goodsSkus[$id])) {
- $finalIds[] = $id;
- unset($goodsSkus[$id]);
- }
- }
- $finalIds = array_merge($finalIds, array_keys($goodsSkus));
- $idField = implode(',', $finalIds);
- $goodsSkus = (clone $builder)->with(['goods' => function ($query) {
- $query->with(['type:id,name', 'brand:id,name']);
+ $sortField = $request->input('sort_field', 'id');//stock sale_stock order_goods_num
+ $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) {
$query->where('day', $day);
}])
- ->whereIn('id', $finalIds)
- ->orderByRaw("FIELD(id, {$idField})")
- ->paginate($request->get('per_page'));
+ ->where('is_combination', 0);
+ 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);
+ }
+
+ $goodsSkus = $goodsSkusBuilder->paginate($request->get('per_page'));
$rolesName = $request->user()->getRoleNames()->toArray();
foreach ($goodsSkus as &$sku) {
- $lastInventoryTime = $sku['daily']['inventory_time'] ?: date('Y-m-d 07:00:00');
- if (isset($externals[$sku['external_sku_id']])) {
- $sku['order_detail'] = $externals[$sku['external_sku_id']];
- $sku['order_goods_num'] = array_sum(array_column($externals[$sku['external_sku_id']], 'number'));
+ $lastInventoryTime = !empty($sku['daily']['inventory_time']) ? $sku['daily']['inventory_time'] : date('Y-m-d 07:00:00');
+ if (isset($externals[$sku['id']])) {
+ $sku['order_detail'] = $externals[$sku['id']];
+ $sku['order_goods_num'] = $ids[$sku['id']];
} else {
$sku['order_detail'] = [];
$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;
if ('销售' === $rolesName[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)
@@ -189,22 +210,14 @@ class GoodsSkusController extends Controller
$sku = GoodsSku::query()->find($id);
$this->setBeforeUpdateForLog($sku->toArray());
$skuInfo = $request->sku;
- $skuInfo['external_sku_id'] = $request->goods['goods_code'] . '_' . $request->sku['sku_code'];
- $skuInfo['name'] = $request->goods['title'] . $request->sku['title'];
+ $goodService = new GoodService();
+ $goods = $goodService->saveDefaultGoodsByGoodType($request->goods['type_id']);
+ $skuInfo['goods_id'] = $goods->id;
+ $skuInfo['name'] = $goodService->getSkuName($request->goods['type_id'], $skuInfo);
$sku->update($skuInfo);
$this->setAfterUpdateForLog($sku->toArray());
$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();
} catch (\Exception $exception) {
DB::rollBack();
@@ -221,7 +234,7 @@ class GoodsSkusController extends Controller
public function batchUpdate(Request $request)
{
$appendRules = [
- 'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock'])],
+ 'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock', "saleStock"])],
'skus' => ['required', 'array'],
'skus.*.id' => [
'required',
@@ -240,6 +253,63 @@ class GoodsSkusController extends Controller
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
* @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']);
+
}
/**
@@ -472,7 +543,7 @@ class GoodsSkusController extends Controller
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
- goto end;
+ return response($this->res, $this->res['httpCode']);
}
$updateField = \request('updateField');
$sku = GoodsSku::query()->find($id);
@@ -494,10 +565,11 @@ class GoodsSkusController extends Controller
$changeNum = $sku->reserve - $request->reserve;
if (0 > $changeNum + $sku->stock) {
$this->setValidatorFailResponse('预留量超过库存数量');
- goto end;
+ return response($this->res, $this->res['httpCode']);
}
$sku->stock += $changeNum;
}
+
$sku->$updateField = $request->$updateField;
$sku->save();
$this->setAfterUpdateForLog($sku->$updateField);
@@ -551,6 +623,7 @@ class GoodsSkusController extends Controller
$endDate = Carbon::now()->subWeek()->endOfWeek()->toDateString();
return Excel::download(new WeekDataExport($startDate, $endDate), $startDate . '~' . $endDate . '.xlsx');
}
+
return Excel::download(new GoodsSkusExport($type), $type . '.xlsx');
}
@@ -659,4 +732,25 @@ class GoodsSkusController extends Controller
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']);
+ }
}
diff --git a/app/Http/Controllers/Goods/GoodsTypesController.php b/app/Http/Controllers/Goods/GoodsTypesController.php
index 8a4ae32..10f993c 100644
--- a/app/Http/Controllers/Goods/GoodsTypesController.php
+++ b/app/Http/Controllers/Goods/GoodsTypesController.php
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Http\Resources\GoodsTypeResource;
use App\Models\GoodsType;
use App\Models\Log as LogModel;
+use App\Utils\FormatUtils;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
@@ -23,7 +24,11 @@ class GoodsTypesController extends Controller
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);
}
@@ -31,27 +36,34 @@ class GoodsTypesController extends Controller
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
- 'names' => 'required|array',
- 'names.*' => 'required|string|max:191|unique:goods_types,name',
+ 'name' => 'required|string|max:191',
+ 'parent_id' => 'sometimes',
+ 'show' => 'sometimes|integer',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
- $goodsTypes = [];
- foreach ($request->names as $name) {
- $goodsTypes[] = ['name' => $name];
+ $level = 1;
+ if ($request->parent_id) {
+ $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();
- if (!$goodsType->batchInsert($goodsTypes)) {
- $this->res = [
- 'httpCode' => 500,
- 'errorCode' => 500500,
- 'errorMessage' => '批量添加失败',
- ];
- }
- $this->setAfterUpdateForLog($goodsTypes);
+ $goodsType->parent_id = $request->parent_id ?? 0;
+ $goodsType->show = $request->show ?? 1;
+ $goodsType->name = $request->name;
+ $goodsType->level = $level;
+ $goodsType->save();
+
+ $this->setAfterUpdateForLog($request->all());
$this->addLog(0, 'add');
return response($this->res, $this->res['httpCode']);
@@ -65,12 +77,9 @@ class GoodsTypesController extends Controller
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
- 'name' => [
- 'required',
- 'string',
- 'max:191',
- Rule::unique('goods_types')->ignore($id),
- ]
+ 'name' => 'sometimes|string|max:191',
+ 'parent_id' => 'sometimes',
+ 'show' => 'sometimes|integer',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
@@ -79,7 +88,18 @@ class GoodsTypesController extends Controller
}
$goodsType = GoodsType::query()->find($id);
$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();
$this->setAfterUpdateForLog($goodsType->name);
$this->addLog($id, 'name');
@@ -95,4 +115,11 @@ class GoodsTypesController extends Controller
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);
+ }
}
diff --git a/app/Http/Controllers/Message/WebsiteMessageController.php b/app/Http/Controllers/Message/WebsiteMessageController.php
new file mode 100644
index 0000000..792949c
--- /dev/null
+++ b/app/Http/Controllers/Message/WebsiteMessageController.php
@@ -0,0 +1,77 @@
+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]);
+ }
+ }
+ }
+
+}
diff --git a/app/Http/Controllers/Permission/PermissionsController.php b/app/Http/Controllers/Permission/PermissionsController.php
index 9bba43f..773034d 100644
--- a/app/Http/Controllers/Permission/PermissionsController.php
+++ b/app/Http/Controllers/Permission/PermissionsController.php
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\Log as LogModel;
use App\Utils\ArrayUtils;
use App\Utils\FormatUtils;
+use Illuminate\Support\Facades\Log;
use Spatie\Permission\Models\Permission;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
diff --git a/app/Http/Controllers/Shop/ShopsController.php b/app/Http/Controllers/Shop/ShopsController.php
index fdc64e2..2df0ca0 100644
--- a/app/Http/Controllers/Shop/ShopsController.php
+++ b/app/Http/Controllers/Shop/ShopsController.php
@@ -52,13 +52,16 @@ class ShopsController extends Controller
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:191|unique:shops,name',
'plat_id' => 'required|integer',
- 'ratio' => 'required',
+ 'ratio' => 'sometimes',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
+ if(empty($request->ratio)){
+ $request->ratio = "*1";
+ }
$operator = substr($request->ratio, 0, 1);
if (!in_array($operator, ['+', '-', '*', '/'])) {
$this->res->errorMessage = '运算符号仅允许+,-,*,/';
@@ -203,10 +206,11 @@ class ShopsController extends Controller
} else {
$shops = $builder->where('id', $shopId)->get();
}
+ //同步三方的是在售库存
$skus = GoodsSku::query()
->where('updated_at', '>', date('Y-m-d 07:01:00'))
->whereNotNull('external_sku_id')
- ->pluck('stock', 'external_sku_id')
+ ->pluck('sale_stock', 'external_sku_id')
->toArray();
foreach ($shops as $shop) {
$business = BusinessFactory::init()->make($shop->plat_id);
diff --git a/app/Http/Controllers/Supplier/DailyStockRecordController.php b/app/Http/Controllers/Supplier/DailyStockRecordController.php
new file mode 100644
index 0000000..d44c913
--- /dev/null
+++ b/app/Http/Controllers/Supplier/DailyStockRecordController.php
@@ -0,0 +1,135 @@
+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']);
+ }
+
+
+}
diff --git a/app/Http/Controllers/Supplier/LossRecordController.php b/app/Http/Controllers/Supplier/LossRecordController.php
new file mode 100644
index 0000000..dee5c4a
--- /dev/null
+++ b/app/Http/Controllers/Supplier/LossRecordController.php
@@ -0,0 +1,231 @@
+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']);
+ }
+
+}
diff --git a/app/Http/Controllers/Supplier/PurchaseRecordController.php b/app/Http/Controllers/Supplier/PurchaseRecordController.php
new file mode 100644
index 0000000..aa341ce
--- /dev/null
+++ b/app/Http/Controllers/Supplier/PurchaseRecordController.php
@@ -0,0 +1,315 @@
+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']);
+ }
+
+
+}
diff --git a/app/Http/Controllers/Supplier/SuppliersController.php b/app/Http/Controllers/Supplier/SuppliersController.php
new file mode 100644
index 0000000..f8700ef
--- /dev/null
+++ b/app/Http/Controllers/Supplier/SuppliersController.php
@@ -0,0 +1,109 @@
+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']);
+ }
+}
diff --git a/app/Http/Controllers/User/UsersController.php b/app/Http/Controllers/User/UsersController.php
index 571333a..ac5c8c4 100644
--- a/app/Http/Controllers/User/UsersController.php
+++ b/app/Http/Controllers/User/UsersController.php
@@ -101,4 +101,9 @@ class UsersController extends Controller
return response($this->res, $this->res['httpCode']);
}
+
+ public function userRoles(Request $request)
+ {
+ return $request->user()->toArray();
+ }
}
diff --git a/app/Http/Enum/AfterSaleOrderStatusEnum.php b/app/Http/Enum/AfterSaleOrderStatusEnum.php
new file mode 100644
index 0000000..bc0fbeb
--- /dev/null
+++ b/app/Http/Enum/AfterSaleOrderStatusEnum.php
@@ -0,0 +1,34 @@
+ "未发起售后",
+ 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] ?? '';
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Enum/CacheKeyEnum.php b/app/Http/Enum/CacheKeyEnum.php
new file mode 100644
index 0000000..feb5402
--- /dev/null
+++ b/app/Http/Enum/CacheKeyEnum.php
@@ -0,0 +1,16 @@
+user()->getPermissionsViaRoles()->toArray();
+ return $next($request);
// 获取当前路由名称
$currentRouteName = Route::currentRouteName();
// 引入当前守卫的权限文件
diff --git a/app/Http/Requests/GoodsRequest.php b/app/Http/Requests/GoodsRequest.php
index 142336a..9f7676c 100644
--- a/app/Http/Requests/GoodsRequest.php
+++ b/app/Http/Requests/GoodsRequest.php
@@ -25,11 +25,7 @@ class GoodsRequest extends FormRequest
public function rules()
{
return [
- 'id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
- 'title' => ['required', 'string', 'max:191'],
'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'))],
];
}
diff --git a/app/Http/Requests/GoodsSkuRequest.php b/app/Http/Requests/GoodsSkuRequest.php
index 9b4d9fc..1b912e6 100644
--- a/app/Http/Requests/GoodsSkuRequest.php
+++ b/app/Http/Requests/GoodsSkuRequest.php
@@ -28,10 +28,10 @@ class GoodsSkuRequest extends FormRequest
'id' => ['sometimes', 'required', 'integer', 'exists:goods_skus,id'],
'goods_id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
'title' => ['sometimes', 'required', 'string', 'max:191'],
- 'sku_code' => ['sometimes', 'required', 'distinct', 'alpha_dash', 'max:32'],
'status' => ['sometimes', 'required', 'integer', Rule::in([0, 1, 2])],
'num' => ['sometimes', 'required', 'integer'],
'cost' => ['sometimes', 'required', 'numeric'],
+ 'attribute' => ['sometimes', 'required', 'string'],
'reference_price' => [
'sometimes',
'numeric',
diff --git a/app/Http/Service/MessageService.php b/app/Http/Service/MessageService.php
new file mode 100644
index 0000000..930635c
--- /dev/null
+++ b/app/Http/Service/MessageService.php
@@ -0,0 +1,121 @@
+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);
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/Imports/CombinationGoodsImport.php b/app/Imports/CombinationGoodsImport.php
index f56a137..5492e35 100644
--- a/app/Imports/CombinationGoodsImport.php
+++ b/app/Imports/CombinationGoodsImport.php
@@ -6,6 +6,7 @@ use App\Models\CombinationGood;
use App\Models\GoodsSku;
use App\Utils\ArrayUtils;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
use Maatwebsite\Excel\Concerns\WithStartRow;
@@ -50,18 +51,23 @@ class CombinationGoodsImport implements ToArray, SkipsEmptyRows, WithStartRow
$itemCodes = array_column($info['item'], 'item_code');
$skus = GoodsSku::query()
->whereIn('external_sku_id', $itemCodes)
- ->get(['id', 'external_sku_id', 'stock'])
+ ->get(['id', 'external_sku_id', 'stock', "sale_stock"])
->toArray();
$skus = ArrayUtils::index($skus, 'external_sku_id');
$stock = [];
+ $saleStock = [];
foreach ($info['item'] as $item) {
$stock[] = (int)($skus[$item['item_code']]['stock'] / $item['item_num']);
+ $saleStock[] = (int)($skus[$item['item_code']]['sale_stock'] / $item['item_num']);
+
}
$stock = min($stock);
- $status = $stock ? (5 < $stock ? 1 : 2) : 0;
+ $saleStock = min($saleStock);
+ $status = $saleStock ? (5 < $saleStock ? 1 : 2) : 0;
$sku = GoodsSku::query()->updateOrCreate(
['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()
->where('goods_sku_id', $sku->id)
diff --git a/app/Imports/InventoryImport.php b/app/Imports/InventoryImport.php
index d2e2779..b530ee0 100644
--- a/app/Imports/InventoryImport.php
+++ b/app/Imports/InventoryImport.php
@@ -2,16 +2,12 @@
namespace App\Imports;
-use App\Events\BatchStockUpdateEvent;
-use App\Jobs\SyncCostToMiaoXuan;
-use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
-use App\Models\TodayPrice;
-use App\Utils\DateTimeUtils;
+use App\Services\GoodSku\GoodSkuService;
use Exception;
+use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToArray;
-use App\Utils\ArrayUtils;
class InventoryImport implements ToArray, SkipsEmptyRows
{
@@ -20,85 +16,30 @@ class InventoryImport implements ToArray, SkipsEmptyRows
*/
public function array(array $collection)
{
- $header = $collection[0];
- unset($collection[0]);
- $externalSkuId = [];
- foreach ($collection as &$row) {
- $row = array_map(static function ($v) {
- return trim($v);
- }, $row);
- $externalSkuId[] = $row[0];
- }
- unset($row);
- $updateIds = $todayPrice = [];
- $day = DateTimeUtils::getToday();
- $dateTime = date('Y-m-d H:i:s');
- $hasGoodsSkus = GoodsSku::query()
- ->whereIn('external_sku_id', $externalSkuId)
- ->get(['id', 'status', 'external_sku_id'])
- ->toArray();
- $hasGoodsSkus = ArrayUtils::index($hasGoodsSkus, 'external_sku_id');
- foreach ($collection as $row) {
- if (!isset($hasGoodsSkus[$row[0]])) {
- continue;
+ if (!empty($collection)) {
+ unset($collection[0]);
+ $externalSkuIds = [];
+ $inventoryKeyByExternalSkuIdMap = [];
+ foreach ($collection as &$row) {
+ $row = array_map(static function ($v) {
+ return trim($v);
+ }, $row);
+ $inventoryKeyByExternalSkuIdMap[$row[0]] = $row[2];
+ $externalSkuIds[] = $row[0];
}
- $goodsSku = $hasGoodsSkus[$row[0]];
- if ('下架' === $goodsSku['status']) {
- GoodsSku::query()->where('id', $goodsSku['id'])->update([
- 'stock' => $row[2] + $row[3],
- '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;
+ unset($row);
+ //新版盘点excel字段 编码 商品名称 盘点数
+ $goodsSkus = GoodsSku::query()->with("combinationGoods")->whereIn('external_sku_id', $externalSkuIds)
+ ->get()->toArray();
+ $goodsSkus = collect($goodsSkus)->map(function ($v) use ($inventoryKeyByExternalSkuIdMap) {
+ if (!empty($inventoryKeyByExternalSkuIdMap[$v['external_sku_id']])) {
+ $v['inventory'] = $inventoryKeyByExternalSkuIdMap[$v['external_sku_id']];
+ return $v;
}
- }
- $todayPrice[] = [
- 'day' => $day,
- 'external_sku_id' => $goodsSku['external_sku_id'],
- 'shop_price' => json_encode($shopPrice, 256)
- ];
+ })->toArray();
+ Log::info("goodsSkus",$goodsSkus);
+ $goodSkuService = new GoodSkuService();
+ $goodSkuService->inventory($goodsSkus);
}
- 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));
}
}
diff --git a/app/Imports/LossImport.php b/app/Imports/LossImport.php
new file mode 100644
index 0000000..d9e6ae5
--- /dev/null
+++ b/app/Imports/LossImport.php
@@ -0,0 +1,91 @@
+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()));
+ }
+
+
+ }
+}
diff --git a/app/Imports/PurchaseImport.php b/app/Imports/PurchaseImport.php
new file mode 100644
index 0000000..0525ae5
--- /dev/null
+++ b/app/Imports/PurchaseImport.php
@@ -0,0 +1,100 @@
+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()));
+ }
+ }
+ }
+}
diff --git a/app/Imports/SaleStockImport.php b/app/Imports/SaleStockImport.php
new file mode 100644
index 0000000..30391ed
--- /dev/null
+++ b/app/Imports/SaleStockImport.php
@@ -0,0 +1,83 @@
+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());
+ }
+ }
+ }
+}
diff --git a/app/Listeners/BatchStockUpdateListener.php b/app/Listeners/BatchStockUpdateListener.php
index e07edca..d18fb62 100644
--- a/app/Listeners/BatchStockUpdateListener.php
+++ b/app/Listeners/BatchStockUpdateListener.php
@@ -8,6 +8,8 @@ use App\Models\Shop;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Services\Business\BusinessFactory;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
class BatchStockUpdateListener implements ShouldQueue
{
@@ -35,18 +37,23 @@ class BatchStockUpdateListener implements ShouldQueue
if (empty($shops)) {
return;
}
- foreach ($shops as $shop) {
- foreach ($event->goodsSkus as $goodsSku) {
- $num = $goodsSku->stock;
- $businessGoodsSkus = BusinessGoodsSku::query()
- ->select(['goods_id', 'sku_id', 'external_sku_id'])
- ->where('shop_id', $shop->id)
- ->where('is_sync', 1)
- ->where('external_sku_id', $goodsSku->external_sku_id)
- ->get();
- BusinessFactory::init()->make($shop['plat_id'])->setShopWithId($shop['id'])->batchIncrQuantity($businessGoodsSkus->toArray(), $num, false);
- usleep(140);
+ try {
+ foreach ($shops as $shop) {
+ foreach ($event->goodsSkus as $goodsSku) {
+ //后续同步三方使用在售库存
+ $num = $goodsSku->sale_stock;
+ $businessGoodsSkus = BusinessGoodsSku::query()
+ ->select(['goods_id', 'sku_id', 'external_sku_id'])
+ ->where('shop_id', $shop->id)
+ ->where('is_sync', 1)
+ ->where('external_sku_id', $goodsSku->external_sku_id)
+ ->get();
+ BusinessFactory::init()->make($shop['plat_id'])->setShopWithId($shop['id'])->batchIncrQuantity($businessGoodsSkus->toArray(), $num, false);
+ usleep(140);
+ }
}
+ } catch (\Exception $exception) {
+ Log::error("同步三方库存出现异常", [$exception->getMessage()]);
}
}
}
diff --git a/app/Listeners/BusinessOrderUpdateListener.php b/app/Listeners/BusinessOrderUpdateListener.php
new file mode 100644
index 0000000..c78b7f5
--- /dev/null
+++ b/app/Listeners/BusinessOrderUpdateListener.php
@@ -0,0 +1,70 @@
+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()]);
+ }
+ }
+}
diff --git a/app/Listeners/CombinationGoodsStockUpdateListener.php b/app/Listeners/CombinationGoodsStockUpdateListener.php
index 5443008..a349a6a 100644
--- a/app/Listeners/CombinationGoodsStockUpdateListener.php
+++ b/app/Listeners/CombinationGoodsStockUpdateListener.php
@@ -9,6 +9,8 @@ use App\Utils\DateTimeUtils;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Events\BatchStockUpdateEvent;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
class CombinationGoodsStockUpdateListener implements ShouldQueue
{
@@ -53,20 +55,31 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
}
}
}
- // 减子商品库存
+ $updateIds = [];
+ //拉取三分订单时可能出现组合订单的情况 需要同步扣减库存
if ($combinationGoodsIds) {
$combinationGoods = CombinationGood::query()
->with('goodsSku:id,stock')
->whereIn('goods_sku_id', $combinationGoodsIds)
->get();
+ $num = !empty($event->num) ? $event->num : -1;
foreach ($combinationGoods as $item) {
- $goodsSku = GoodsSku::query()->find($item['item_id']);
- $stock = $goodsSku->stock - $item['item_num'];
- [$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock);
- $goodsSku->status = $status;
- $goodsSku->stock = $stock;
- $goodsSku->save();
- $updateIds[] = $goodsSku->id;
+ DB::transaction(function () use ($item, &$updateIds, $num) {
+ $goodsSku = GoodsSku::query()->lockForUpdate()->find($item['item_id']);
+ $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->stock = $stock;
+ $goodsSku->sale_stock = $saleStock;
+ $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;
+ });
}
}
// 计算主商品库存
@@ -76,22 +89,30 @@ class CombinationGoodsStockUpdateListener implements ShouldQueue
->pluck('goods_sku_id');
foreach ($goodsSkuIds as $goodsSkuId) {
$combinationGoods = CombinationGood::query()
- ->with('goodsSkuItem:id,stock')
+ ->with('goodsSkuItem:id,stock,sale_stock')
->where('goods_sku_id', $goodsSkuId)
->get();
+
$stock = [];
+ $saleStock = [];
foreach ($combinationGoods as $goods) {
$stock[] = (int)($goods['goodsSkuItem']['stock'] / $goods['item_num']);
+ $saleStock[] = (int)($goods['goodsSkuItem']['sale_stock'] / $goods['item_num']);
}
+ //库存和在线可售库存都是通过子商品维护的
$stock = min($stock);
+ $saleStock = min($saleStock);
$goodsSku = GoodsSku::query()->find($goodsSkuId);
- [$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock);
+ //新增在线可售逻辑判断 前置已经完成逻辑扣减或者增加
+ [$status, $stock] = $this->checkStatusAndStock($goodsSku, $stock, $saleStock);
$goodsSku->status = $status;
$goodsSku->stock = $stock;
+ $goodsSku->sale_stock = $saleStock;
$goodsSku->save();
$updateIds[] = $goodsSkuId;
}
}
+
if ($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;
} else {
$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];
}
diff --git a/app/Listeners/StockUpdateListener.php b/app/Listeners/StockUpdateListener.php
index f71014d..5073e0a 100644
--- a/app/Listeners/StockUpdateListener.php
+++ b/app/Listeners/StockUpdateListener.php
@@ -36,7 +36,7 @@ class StockUpdateListener implements ShouldQueue
return;
}
foreach ($shops as $shop) {
- $num = $event->goodsSku->stock;
+ $num = $event->goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query()
->select(['goods_id', 'sku_id', 'external_sku_id'])
->where('shop_id', $shop->id)
diff --git a/app/Listeners/UpdateBusinessGoodsStock.php b/app/Listeners/UpdateBusinessGoodsStock.php
index 5f616dc..937e58e 100644
--- a/app/Listeners/UpdateBusinessGoodsStock.php
+++ b/app/Listeners/UpdateBusinessGoodsStock.php
@@ -60,7 +60,7 @@ class UpdateBusinessGoodsStock implements ShouldQueue
}
foreach ($shops as $shop) {
- $num = $event->goodsSku->stock;
+ $num = $event->goodsSku->sale_stock;
$businessGoodsSkus = BusinessGoodsSku::query()
->where('shop_id', $shop->id)
->where('is_sync', 1)
diff --git a/app/Models/BusinessAfterSaleOrder.php b/app/Models/BusinessAfterSaleOrder.php
new file mode 100644
index 0000000..ecc69b6
--- /dev/null
+++ b/app/Models/BusinessAfterSaleOrder.php
@@ -0,0 +1,37 @@
+belongsTo(Shop::class, 'shop_id', 'id');
+ }
+}
diff --git a/app/Models/DailyStockRecord.php b/app/Models/DailyStockRecord.php
index 00f6161..cbe301a 100644
--- a/app/Models/DailyStockRecord.php
+++ b/app/Models/DailyStockRecord.php
@@ -2,9 +2,23 @@
namespace App\Models;
+use App\Models\traits\Filter;
+
class DailyStockRecord extends Model
{
+ use Filter;
+
protected $hidden = ['created_at', 'updated_at'];
protected $guarded = [];
+
+ public $fieldSearchable = [
+ 'day',
+ ];
+
+ public function goodsSku()
+ {
+ return $this->belongsTo(GoodsSku::class, 'sku_id', 'id');
+ }
+
}
diff --git a/app/Models/GoodsSku.php b/app/Models/GoodsSku.php
index 860c5fd..debf7eb 100644
--- a/app/Models/GoodsSku.php
+++ b/app/Models/GoodsSku.php
@@ -15,6 +15,8 @@ class GoodsSku extends Model
'exclude_ids',
'external_sku_id',
'is_combination',
+ "create_time_start",
+ "create_time_end"
];
protected $fillable = [
@@ -33,10 +35,10 @@ class GoodsSku extends Model
'external_sku_id',
'is_combination',
'name',
+ 'sale_stock',
+ 'attribute'
];
- protected $hidden = ['created_at'];
-
public static $STATUS_ON_SALE = 1;
public static $STATUS_DOWN = 0;
@@ -63,10 +65,17 @@ class GoodsSku extends Model
public function getThumbUrlAttribute($value)
{
-
return json_decode($value, true);
}
+ public function getNameAttribute($value)
+ {
+ if(empty($value)){
+ return $this->attributes['title']??'';
+ }
+ return $value;
+ }
+
/**
* 此规格从属于一个商品
*/
diff --git a/app/Models/GoodsType.php b/app/Models/GoodsType.php
index 53c496b..230638d 100644
--- a/app/Models/GoodsType.php
+++ b/app/Models/GoodsType.php
@@ -2,12 +2,20 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
class GoodsType extends Model
{
+ use SoftDeletes;
/**
* 数组中的属性会被隐藏。
*
* @var array
*/
protected $hidden = ['deleted_at'];
+
+ public function parentType()
+ {
+ return $this->hasOne(GoodsType::class, 'id', 'parent_id');
+ }
}
diff --git a/app/Models/Log.php b/app/Models/Log.php
index eb5c829..6d7ca90 100644
--- a/app/Models/Log.php
+++ b/app/Models/Log.php
@@ -72,6 +72,9 @@ class Log extends Model
'kuaituantuan' => '快团团',
'miaoxuan' => '妙选',
'goods' => '商品',
+ "sku_stock_purchase" => "入库采购",
+ "sku_stock_loss" => "报损记录",
+ "sku_stock_inventory" => "盘点记录",
];
return $map[$value] ?? $value;
@@ -89,6 +92,7 @@ class Log extends Model
'set' => '设置',
'cost' => '成本',
'stock' => '库存',
+ 'sale_stock' => '在售库存',
'inventory' => '库存盘点',
'reserve' => '预留量 ',
'timingInventory' => '7点盘点',
diff --git a/app/Models/LossRecords.php b/app/Models/LossRecords.php
new file mode 100644
index 0000000..4e6d37e
--- /dev/null
+++ b/app/Models/LossRecords.php
@@ -0,0 +1,23 @@
+belongsTo(GoodsSku::class, 'external_sku_id', 'external_sku_id');
+ }
+}
diff --git a/app/Models/PurchaseRecords.php b/app/Models/PurchaseRecords.php
new file mode 100644
index 0000000..143272d
--- /dev/null
+++ b/app/Models/PurchaseRecords.php
@@ -0,0 +1,24 @@
+belongsTo(GoodsSku::class, 'external_sku_id', 'external_sku_id');
+ }
+}
diff --git a/app/Models/Suppliers.php b/app/Models/Suppliers.php
new file mode 100644
index 0000000..61e4350
--- /dev/null
+++ b/app/Models/Suppliers.php
@@ -0,0 +1,11 @@
+ [
UpdateBusinessGoodsStock::class,
CombinationGoodsStockUpdateListener::class,
+ BusinessOrderUpdateListener::class
],
BatchStockUpdateEvent::class => [
BatchStockUpdateListener::class,
diff --git a/app/Services/Business/BusinessClient.php b/app/Services/Business/BusinessClient.php
index 314e6ef..a32a593 100644
--- a/app/Services/Business/BusinessClient.php
+++ b/app/Services/Business/BusinessClient.php
@@ -38,6 +38,8 @@ abstract class BusinessClient
abstract public function downloadOrdersAndSave($beginTime, $endTime, $downloadType = 'default', $page = 1);
+ abstract public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1);
+
public function saveOrders($orders)
{
$shopId = $this->getShop()->id;
@@ -92,6 +94,7 @@ abstract class BusinessClient
}
$orderItem->update($item);
}
+
// 增量更新库存
if ($num && $item['external_sku_id']) {
event(new BusinessOrdersUpdate($orderItem, $num));
@@ -156,7 +159,7 @@ abstract class BusinessClient
if (strlen($paramsJson) > 1024) {
$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->module = 'plat';
$log->action = $method;
@@ -171,7 +174,7 @@ abstract class BusinessClient
}
$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('快团团返回: ' . json_encode($res, 256));
}
diff --git a/app/Services/Business/KuaiTuanTuan/KuaiTuanTuan.php b/app/Services/Business/KuaiTuanTuan/KuaiTuanTuan.php
index c10ee0b..1df6e7d 100644
--- a/app/Services/Business/KuaiTuanTuan/KuaiTuanTuan.php
+++ b/app/Services/Business/KuaiTuanTuan/KuaiTuanTuan.php
@@ -2,6 +2,7 @@
namespace App\Services\Business\KuaiTuanTuan;
+use App\Models\BusinessAfterSaleOrder;
use App\Models\BusinessGoodsSku;
use App\Models\GoodsSku;
use App\Models\GroupGoods;
@@ -9,6 +10,7 @@ use App\Models\Shop;
use App\Models\ShopShip;
use App\Services\Business\BusinessClient;
use App\Models\Groups as GroupsModel;
+use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
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)
{
[$type, $appendParams] = Order::getOrderInfo($orderSn);
@@ -163,6 +215,7 @@ class KuaiTuanTuan extends BusinessClient
$publicParams = array_merge($publicParams, $appendParams);
$publicParams['sign'] = $this->getSign($publicParams);
$res = $this->formDataPostRequest($url, $publicParams);
+ Log::info("快团团请求", ["param" => $publicParams, "res" => $res]);
if (isset($res['error_response'])) {
// "error_msg":"业务服务错误","sub_msg":"该店铺下不存在该商品","sub_code":"11","error_code":50001
// "error_msg":"业务服务错误","sub_msg":"该SKU在快团团中设置的库存为无限库存,不支持修改库存","sub_code":"13","error_code":50001
diff --git a/app/Services/Business/KuaiTuanTuan/Order.php b/app/Services/Business/KuaiTuanTuan/Order.php
index 6d12091..23f1ccf 100644
--- a/app/Services/Business/KuaiTuanTuan/Order.php
+++ b/app/Services/Business/KuaiTuanTuan/Order.php
@@ -92,5 +92,21 @@ class Order
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];
+ }
}
diff --git a/app/Services/Business/MiaoXuan/MiaoXuan.php b/app/Services/Business/MiaoXuan/MiaoXuan.php
index 4ddeee1..6831623 100644
--- a/app/Services/Business/MiaoXuan/MiaoXuan.php
+++ b/app/Services/Business/MiaoXuan/MiaoXuan.php
@@ -33,6 +33,10 @@ class MiaoXuan extends BusinessClient
{
}
+ public function downloadAfterSaleOrdersAndSave($beginTime, $endTime, $page = 1){
+
+ }
+
public function batchIncrQuantity($businessGoodsSkus, $num, $incremental)
{
$batchAppendParams = [];
diff --git a/app/Services/DeveloperConfig/DeveloperConfigService.php b/app/Services/DeveloperConfig/DeveloperConfigService.php
new file mode 100644
index 0000000..a924707
--- /dev/null
+++ b/app/Services/DeveloperConfig/DeveloperConfigService.php
@@ -0,0 +1,35 @@
+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) ?? [];
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/app/Services/Good/GoodService.php b/app/Services/Good/GoodService.php
new file mode 100644
index 0000000..7449d62
--- /dev/null
+++ b/app/Services/Good/GoodService.php
@@ -0,0 +1,106 @@
+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;
+ }
+
+}
diff --git a/app/Services/GoodSku/GoodSkuService.php b/app/Services/GoodSku/GoodSkuService.php
new file mode 100644
index 0000000..cfcbdac
--- /dev/null
+++ b/app/Services/GoodSku/GoodSkuService.php
@@ -0,0 +1,193 @@
+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;
+ }
+
+}
diff --git a/app/Services/Statistic/SaleDataService.php b/app/Services/Statistic/SaleDataService.php
new file mode 100644
index 0000000..def519b
--- /dev/null
+++ b/app/Services/Statistic/SaleDataService.php
@@ -0,0 +1,410 @@
+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();
+ }
+
+
+}
diff --git a/app/Utils/DateTimeUtils.php b/app/Utils/DateTimeUtils.php
index e80f314..aef7f25 100644
--- a/app/Utils/DateTimeUtils.php
+++ b/app/Utils/DateTimeUtils.php
@@ -2,6 +2,8 @@
namespace App\Utils;
+use Illuminate\Support\Facades\Log;
+
class DateTimeUtils
{
/**
@@ -42,4 +44,28 @@ class DateTimeUtils
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;
+
+ }
+
}
diff --git a/app/Utils/GeneratorUtils.php b/app/Utils/GeneratorUtils.php
new file mode 100644
index 0000000..167ef86
--- /dev/null
+++ b/app/Utils/GeneratorUtils.php
@@ -0,0 +1,44 @@
+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);
+ }
+}
diff --git a/composer.json b/composer.json
index 4bbc05d..59875f9 100644
--- a/composer.json
+++ b/composer.json
@@ -17,6 +17,7 @@
"laravel/framework": "^6.20.26",
"laravel/tinker": "^2.5",
"maatwebsite/excel": "^3.1",
+ "predis/predis": "^2.2",
"spatie/laravel-permission": "*"
},
"require-dev": {
diff --git a/composer.lock b/composer.lock
index 7144aa9..46407ce 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "037b06c1b26399725a1d9c0687402942",
+ "content-hash": "964631bbee47f895975146a783331c50",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
@@ -2587,6 +2587,73 @@
],
"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",
"version": "1.1.1",
diff --git a/config/logging.php b/config/logging.php
index 088c204..d5687fc 100644
--- a/config/logging.php
+++ b/config/logging.php
@@ -52,6 +52,7 @@ return [
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
+ "permission" => 0777
],
'slack' => [
diff --git a/database/migrations/2022_08_05_093629_create_business_orders_table.php b/database/migrations/2022_08_05_093629_create_business_orders_table.php
index 360fe04..60590fa 100644
--- a/database/migrations/2022_08_05_093629_create_business_orders_table.php
+++ b/database/migrations/2022_08_05_093629_create_business_orders_table.php
@@ -61,7 +61,8 @@ class CreateBusinessOrdersTable extends Migration
// 索引
$table->unique(['shop_id', 'order_sn']);
$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");
});
}
diff --git a/database/migrations/2024_07_24_144851_create_purchase_records_table.php b/database/migrations/2024_07_24_144851_create_purchase_records_table.php
new file mode 100644
index 0000000..9733836
--- /dev/null
+++ b/database/migrations/2024_07_24_144851_create_purchase_records_table.php
@@ -0,0 +1,46 @@
+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');
+ }
+}
diff --git a/database/migrations/2024_07_24_144925_create_loss_records_table.php b/database/migrations/2024_07_24_144925_create_loss_records_table.php
new file mode 100644
index 0000000..eb92973
--- /dev/null
+++ b/database/migrations/2024_07_24_144925_create_loss_records_table.php
@@ -0,0 +1,45 @@
+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');
+ }
+}
diff --git a/database/migrations/2024_07_24_144950_create_website_messages_table.php b/database/migrations/2024_07_24_144950_create_website_messages_table.php
new file mode 100644
index 0000000..b01f672
--- /dev/null
+++ b/database/migrations/2024_07_24_144950_create_website_messages_table.php
@@ -0,0 +1,42 @@
+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');
+ }
+}
diff --git a/database/migrations/2024_07_24_145032_create_suppliers_table.php b/database/migrations/2024_07_24_145032_create_suppliers_table.php
new file mode 100644
index 0000000..6831c23
--- /dev/null
+++ b/database/migrations/2024_07_24_145032_create_suppliers_table.php
@@ -0,0 +1,41 @@
+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');
+ }
+}
diff --git a/database/migrations/2024_07_24_145201_add_sale_stock_and_quality_period_to_goods_skus_table.php b/database/migrations/2024_07_24_145201_add_sale_stock_and_quality_period_to_goods_skus_table.php
new file mode 100644
index 0000000..f051e43
--- /dev/null
+++ b/database/migrations/2024_07_24_145201_add_sale_stock_and_quality_period_to_goods_skus_table.php
@@ -0,0 +1,40 @@
+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');
+ });
+ }
+}
diff --git a/database/migrations/2024_08_01_152352_add_order_total_amount_to_daily_stock_record.php b/database/migrations/2024_08_01_152352_add_order_total_amount_to_daily_stock_record.php
new file mode 100644
index 0000000..c4084a3
--- /dev/null
+++ b/database/migrations/2024_08_01_152352_add_order_total_amount_to_daily_stock_record.php
@@ -0,0 +1,41 @@
+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');
+ });
+ }
+}
diff --git a/database/migrations/2024_08_02_175020_create_business_after_sale_orders.php b/database/migrations/2024_08_02_175020_create_business_after_sale_orders.php
new file mode 100644
index 0000000..27951db
--- /dev/null
+++ b/database/migrations/2024_08_02_175020_create_business_after_sale_orders.php
@@ -0,0 +1,51 @@
+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');
+ }
+}
diff --git a/database/migrations/2024_08_06_143056_add_fields_to_goods_types.php b/database/migrations/2024_08_06_143056_add_fields_to_goods_types.php
new file mode 100644
index 0000000..acd90ad
--- /dev/null
+++ b/database/migrations/2024_08_06_143056_add_fields_to_goods_types.php
@@ -0,0 +1,40 @@
+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');
+ });
+ }
+}
diff --git a/database/migrations/2024_08_06_143056_remove_index_to_goods_types.php b/database/migrations/2024_08_06_143056_remove_index_to_goods_types.php
new file mode 100644
index 0000000..c1d4daa
--- /dev/null
+++ b/database/migrations/2024_08_06_143056_remove_index_to_goods_types.php
@@ -0,0 +1,35 @@
+dropUnique('goods_types_name_unique');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('goods_types', function (Blueprint $table) {
+ //
+ $table->unique('name');
+
+ });
+ }
+}
diff --git a/database/migrations/2024_08_29_150829_add_attribute_to_goods_skus.php b/database/migrations/2024_08_29_150829_add_attribute_to_goods_skus.php
new file mode 100644
index 0000000..166473e
--- /dev/null
+++ b/database/migrations/2024_08_29_150829_add_attribute_to_goods_skus.php
@@ -0,0 +1,38 @@
+string('attribute',100)->default("")->comment('属性名称');
+
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ //
+ Schema::table('goods_skus', function (Blueprint $table) {
+ $table->dropColumn("attribute");
+ });
+ }
+}
diff --git a/database/migrations/2024_08_29_151908_add_field_to_purchase_records_table.php b/database/migrations/2024_08_29_151908_add_field_to_purchase_records_table.php
new file mode 100644
index 0000000..e4dc8b9
--- /dev/null
+++ b/database/migrations/2024_08_29_151908_add_field_to_purchase_records_table.php
@@ -0,0 +1,43 @@
+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');
+
+ });
+ }
+}
diff --git a/public/.htaccess b/public/.htaccess
index b75525b..e69de29 100644
--- a/public/.htaccess
+++ b/public/.htaccess
@@ -1,21 +0,0 @@
-
{{ scope.row.title }}
\r\n{{ scope.row.external_sku_id }}
\r\n{{ scope.row.created_at }}
\r\n\r\n ERP管理系统\r\n
登录
\r\n{{ scope.row.created_at }}
\r\n \r\n{{ scope.row.external_sku_id }}
\r\n{{ scope.row.created_at }}
\r\n{{ scope.row.external_sku_id }}
\r\n>>0,o=0,l=0;while(0==(1&n))if(n=ie(e,r),r+=3,n>>>1!=0)for(n>>1==1?(o=9,l=5):(r=xe(e,r),o=_e,l=Oe);;){!t&&s>>1==1?ve[f]:Se[f];if(r+=15&c,c>>>=4,0===(c>>>8&255))a[i++]=c;else{if(256==c)break;c-=257;var h=c<8?0:c-4>>2;h>5&&(h=0);var u=i+q[c];h>0&&(u+=fe(e,r,h),r+=h),f=fe(e,r,l),c=n>>>1==1?Te[f]:Ae[f],r+=15&c,c>>>=4;var p=c<4?0:c-2>>1,d=Z[c];p>0&&(d+=fe(e,r,p),r+=p),!t&&s>>3]|e[1+(r>>>3)]<<8;if(r+=32,m>0){!t&&s0)a[i++]=e[r>>>3],r+=8}}return t?[a,r+7>>>3]:[a.slice(0,i),r+7>>>3]}function Re(e,t){var r=e.slice(e.l||0),n=Ce(r,t);return e.l+=n[1],n[0]}function ke(e,t){if(!e)throw new Error(t);"undefined"!==typeof console&&console.error(t)}function Ie(e,t){var r=e;Ir(r,0);var n=[],a=[],i={FileIndex:n,FullPaths:a};R(i,{root:t.root});var s=r.length-4;while((80!=r[s]||75!=r[s+1]||5!=r[s+2]||6!=r[s+3])&&s>=0)--s;r.l=s+4,r.l+=4;var l=r.read_shift(2);r.l+=6;var f=r.read_shift(4);for(r.l=f,s=0;s
").replace(Ct,(function(e){return""+("000"+e.charCodeAt(0).toString(16)).slice(-4)+";"}))}function kt(e){var t=e+"";return t.replace(yt,(function(e){return At[e]})).replace(Ct,(function(e){return""+e.charCodeAt(0).toString(16).toUpperCase()+";"}))}function It(e){return e.replace(/(\r\n|[\r\n])/g,"
")}function Nt(e){switch(e){case 1:case!0:case"1":case"true":case"TRUE":return!0;default:return!1}}function Dt(e){var t="",r=0,n=0,a=0,i=0,s=0,o=0;while(rs[0].e.c)&&!(o.rs[0].e.r)){h.push(yl(s[1],c,I,n,a)),D=!0;break}D||h.push(w[1])}break;case"PtgArray":h.push("{"+wl(w[1])+"}");break;case"PtgMemArea":break;case"PtgAttrSpace":case"PtgAttrSpaceSemi":m=v;break;case"PtgTbl":break;case"PtgMemErr":break;case"PtgMissArg":h.push("");break;case"PtgAreaErr":h.push("#REF!");break;case"PtgAreaErr3d":h.push("#REF!");break;case"PtgList":h.push("Table"+w[1].idx+"[#"+w[1].rt+"]");break;case"PtgMemAreaN":case"PtgMemNoMemN":case"PtgAttrNoop":case"PtgSheet":case"PtgEndSheet":break;case"PtgMemFunc":break;case"PtgMemNoMem":break;case"PtgElfCol":case"PtgElfColS":case"PtgElfColSV":case"PtgElfColV":case"PtgElfLel":case"PtgElfRadical":case"PtgElfRadicalLel":case"PtgElfRadicalS":case"PtgElfRw":case"PtgElfRwV":throw new Error("Unsupported ELFs");case"PtgSxName":throw new Error("Unrecognized Formula Token: "+String(w));default:throw new Error("Unrecognized Formula Token: "+String(w))}var P=["PtgAttrSpace","PtgAttrSpaceSemi","PtgAttrGoto"];if(3!=a.biff&&m>=0&&-1==P.indexOf(e[0][v][0])){w=e[0][m];var L=!0;switch(w[1][0]){case 4:L=!1;case 0:g=mt(" ",w[1][1]);break;case 5:L=!1;case 1:g=mt("\r",w[1][1]);break;default:if(g="",a.WTF)throw new Error("Unexpected PtgAttrSpaceType "+w[1][0])}h.push((L?g:"")+h.pop()+(L?"":g)),m=-1}}if(h.length>1&&a.WTF)throw new Error("bad formula stack");return h[0]}function _l(e){if(null==e){var t=Dr(8);return t.write_shift(1,3),t.write_shift(1,0),t.write_shift(2,0),t.write_shift(2,0),t.write_shift(2,65535),t}return Pn("number"==typeof e?e:0)}function Ol(e,t,r,n,a){var i=Va(t,r,a),s=_l(e.v),o=Dr(6),l=33;o.write_shift(2,l),o.write_shift(4,0);for(var f=Dr(e.bf.length),c=0;c"+s+"
"),n.push(Kc(i,t,e,r)),n.join("")}function th(e,t){t||(t={}),e.SSF||(e.SSF=dt(X)),e.SSF&&(Ge(),He(e.SSF),t.revssf=tt(e.SSF),t.revssf[e.SSF[65535]]=0,t.ssf=e.SSF,t.cellXfs=[],Hl(t.cellXfs,{},{revssf:{General:0}}));var r=[];r.push(jc(e,t)),r.push(Vc(e,t)),r.push(""),r.push("");for(var n=0;n";return d+i.join("")+" "}var Oh='"}function Rh(e,t){var r=t||{},n=null!=r.header?r.header:Oh,a=null!=r.footer?r.footer:xh,i=[n],s=qr(e["!ref"]);r.dense=Array.isArray(e),i.push(Ch(e,s,r));for(var o=s.s.r;o<=s.e.r;++o)i.push(_h(e,s,o,r));return i.push("
"+a),i.join("")}function kh(e,t,r){var n=r||{};null!=v&&(n.dense=v);var a=0,i=0;if(null!=n.origin)if("number"==typeof n.origin)a=n.origin;else{var s="string"==typeof n.origin?Kr(n.origin):n.origin;a=s.r,i=s.c}var o=t.getElementsByTagName("tr"),l=Math.min(n.sheetRows||1e7,o.length),f={s:{r:0,c:0},e:{r:a,c:i}};if(e["!ref"]){var c=qr(e["!ref"]);f.s.r=Math.min(f.s.r,c.s.r),f.s.c=Math.min(f.s.c,c.s.c),f.e.r=Math.max(f.e.r,c.e.r),f.e.c=Math.max(f.e.c,c.e.c),-1==a&&(f.e.r=a=c.e.r+1)}var h=[],u=0,p=e["!rows"]||(e["!rows"]=[]),d=0,m=0,g=0,T=0,w=0,b=0;for(e["!cols"]||(e["!cols"]=[]);d*/ = [], split = encoded.split(\"\\r\\n\");\n\tfor(var si = 0; si < split.length; ++si) {\n\t\tvar str = split[si];\n\t\tif(str.length == 0) { o.push(\"\"); continue; }\n\t\tfor(var i = 0; i < str.length;) {\n\t\t\tvar end = 76;\n\t\t\tvar tmp = str.slice(i, i + end);\n\t\t\tif(tmp.charAt(end - 1) == \"=\") end --;\n\t\t\telse if(tmp.charAt(end - 2) == \"=\") end -= 2;\n\t\t\telse if(tmp.charAt(end - 3) == \"=\") end -= 3;\n\t\t\ttmp = str.slice(i, i + end);\n\t\t\ti += end;\n\t\t\tif(i < str.length) tmp += \"=\";\n\t\t\to.push(tmp);\n\t\t}\n\t}\n\n\treturn o.join(\"\\r\\n\");\n}\nfunction parse_quoted_printable(data/*:Array
\").replace(htmlcharegex,function(s) { return \"\" + (\"000\"+s.charCodeAt(0).toString(16)).slice(-4) + \";\"; });\n}\n\nfunction escapexlml(text/*:string*/)/*:string*/{\n\tvar s = text + '';\n\treturn s.replace(decregex, function(y) { return rencoding[y]; }).replace(htmlcharegex,function(s) { return \"\" + (s.charCodeAt(0).toString(16)).toUpperCase() + \";\"; });\n}\n\n/* TODO: handle codepages */\nvar xlml_fixstr/*:StringConv*/ = /*#__PURE__*/(function() {\n\tvar entregex = /(\\d+);/g;\n\tfunction entrepl($$/*:string*/,$1/*:string*/)/*:string*/ { return String.fromCharCode(parseInt($1,10)); }\n\treturn function xlml_fixstr(str/*:string*/)/*:string*/ { return str.replace(entregex,entrepl); };\n})();\nfunction xlml_unfixstr(str/*:string*/)/*:string*/ { return str.replace(/(\\r\\n|[\\r\\n])/g,\"\\
\"); }\n\nfunction parsexmlbool(value/*:any*/)/*:boolean*/ {\n\tswitch(value) {\n\t\tcase 1: case true: case '1': case 'true': case 'TRUE': return true;\n\t\t/* case '0': case 'false': case 'FALSE':*/\n\t\tdefault: return false;\n\t}\n}\n\nfunction utf8reada(orig/*:string*/)/*:string*/ {\n\tvar out = \"\", i = 0, c = 0, d = 0, e = 0, f = 0, w = 0;\n\twhile (i < orig.length) {\n\t\tc = orig.charCodeAt(i++);\n\t\tif (c < 128) { out += String.fromCharCode(c); continue; }\n\t\td = orig.charCodeAt(i++);\n\t\tif (c>191 && c<224) { f = ((c & 31) << 6); f |= (d & 63); out += String.fromCharCode(f); continue; }\n\t\te = orig.charCodeAt(i++);\n\t\tif (c < 240) { out += String.fromCharCode(((c & 15) << 12) | ((d & 63) << 6) | (e & 63)); continue; }\n\t\tf = orig.charCodeAt(i++);\n\t\tw = (((c & 7) << 18) | ((d & 63) << 12) | ((e & 63) << 6) | (f & 63))-65536;\n\t\tout += String.fromCharCode(0xD800 + ((w>>>10)&1023));\n\t\tout += String.fromCharCode(0xDC00 + (w&1023));\n\t}\n\treturn out;\n}\n\nfunction utf8readb(data) {\n\tvar out = new_raw_buf(2*data.length), w, i, j = 1, k = 0, ww=0, c;\n\tfor(i = 0; i < data.length; i+=j) {\n\t\tj = 1;\n\t\tif((c=data.charCodeAt(i)) < 128) w = c;\n\t\telse if(c < 224) { w = (c&31)*64+(data.charCodeAt(i+1)&63); j=2; }\n\t\telse if(c < 240) { w=(c&15)*4096+(data.charCodeAt(i+1)&63)*64+(data.charCodeAt(i+2)&63); j=3; }\n\t\telse { j = 4;\n\t\t\tw = (c & 7)*262144+(data.charCodeAt(i+1)&63)*4096+(data.charCodeAt(i+2)&63)*64+(data.charCodeAt(i+3)&63);\n\t\t\tw -= 65536; ww = 0xD800 + ((w>>>10)&1023); w = 0xDC00 + (w&1023);\n\t\t}\n\t\tif(ww !== 0) { out[k++] = ww&255; out[k++] = ww>>>8; ww = 0; }\n\t\tout[k++] = w%256; out[k++] = w>>>8;\n\t}\n\treturn out.slice(0,k).toString('ucs2');\n}\n\nfunction utf8readc(data) { return Buffer_from(data, 'binary').toString('utf8'); }\n\nvar utf8corpus = \"foo bar baz\\u00e2\\u0098\\u0083\\u00f0\\u009f\\u008d\\u00a3\";\nvar utf8read = has_buf && (/*#__PURE__*/utf8readc(utf8corpus) == /*#__PURE__*/utf8reada(utf8corpus) && utf8readc || /*#__PURE__*/utf8readb(utf8corpus) == /*#__PURE__*/utf8reada(utf8corpus) && utf8readb) || utf8reada;\n\nvar utf8write/*:StringConv*/ = has_buf ? function(data) { return Buffer_from(data, 'utf8').toString(\"binary\"); } : function(orig/*:string*/)/*:string*/ {\n\tvar out/*:Array 0 ? __utf8(b, i+4,i+4+len-1) : \"\";};\nvar __lpstr = ___lpstr;\n\nvar ___cpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : \"\";};\nvar __cpstr = ___cpstr;\n\nvar ___lpwstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = 2*__readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : \"\";};\nvar __lpwstr = ___lpwstr;\n\nvar ___lpp4 = function lpp4_(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? __utf16le(b, i+4,i+4+len) : \"\";};\nvar __lpp4 = ___lpp4;\n\nvar ___8lpp4 = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len) : \"\";};\nvar __8lpp4 = ___8lpp4;\n\nvar ___double = function(b/*:RawBytes|CFBlob*/, idx/*:number*/) { return read_double_le(b, idx);};\nvar __double = ___double;\n\nvar is_buf = function is_buf_a(a) { return Array.isArray(a) || (typeof Uint8Array !== \"undefined\" && a instanceof Uint8Array); };\n\nif(has_buf/*:: && typeof Buffer !== 'undefined'*/) {\n\t__lpstr = function lpstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___lpstr(b, i); var len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8',i+4,i+4+len-1) : \"\";};\n\t__cpstr = function cpstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___cpstr(b, i); var len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8',i+4,i+4+len-1) : \"\";};\n\t__lpwstr = function lpwstr_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___lpwstr(b, i); var len = 2*b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len-1);};\n\t__lpp4 = function lpp4_b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len);};\n\t__8lpp4 = function lpp4_8b(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(!Buffer.isBuffer(b)/*:: || !(b instanceof Buffer)*/) return ___8lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf8',i+4,i+4+len);};\n\t__double = function double_(b/*:RawBytes|CFBlob*/, i/*:number*/) { if(Buffer.isBuffer(b)/*::&& b instanceof Buffer*/) return b.readDoubleLE(i); return ___double(b,i); };\n\tis_buf = function is_buf_b(a) { return Buffer.isBuffer(a) || Array.isArray(a) || (typeof Uint8Array !== \"undefined\" && a instanceof Uint8Array); };\n}\n\n/* from js-xls */\nfunction cpdoit() {\n\t__utf16le = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return $cptable.utils.decode(1200, b.slice(s,e)).replace(chr0, ''); };\n\t__utf8 = function(b/*:RawBytes|CFBlob*/,s/*:number*/,e/*:number*/) { return $cptable.utils.decode(65001, b.slice(s,e)); };\n\t__lpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(current_ansi, b.slice(i+4, i+4+len-1)) : \"\";};\n\t__cpstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(current_codepage, b.slice(i+4, i+4+len-1)) : \"\";};\n\t__lpwstr = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = 2*__readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(1200, b.slice(i+4,i+4+len-1)) : \"\";};\n\t__lpp4 = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(1200, b.slice(i+4,i+4+len)) : \"\";};\n\t__8lpp4 = function(b/*:RawBytes|CFBlob*/,i/*:number*/) { var len = __readUInt32LE(b,i); return len > 0 ? $cptable.utils.decode(65001, b.slice(i+4,i+4+len)) : \"\";};\n}\nif(typeof $cptable !== 'undefined') cpdoit();\n\nvar __readUInt8 = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return b[idx]; };\nvar __readUInt16LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return (b[idx+1]*(1<<8))+b[idx]; };\nvar __readInt16LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { var u = (b[idx+1]*(1<<8))+b[idx]; return (u < 0x8000) ? u : ((0xffff - u + 1) * -1); };\nvar __readUInt32LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return b[idx+3]*(1<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx]; };\nvar __readInt32LE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return (b[idx+3]<<24)|(b[idx+2]<<16)|(b[idx+1]<<8)|b[idx]; };\nvar __readInt32BE = function(b/*:RawBytes|CFBlob*/, idx/*:number*/)/*:number*/ { return (b[idx]<<24)|(b[idx+1]<<16)|(b[idx+2]<<8)|b[idx+3]; };\n\nfunction ReadShift(size/*:number*/, t/*:?string*/)/*:number|string*/ {\n\tvar o=\"\", oI/*:: :number = 0*/, oR, oo=[], w, vv, i, loc;\n\tswitch(t) {\n\t\tcase 'dbcs':\n\t\t\tloc = this.l;\n\t\t\tif(has_buf && Buffer.isBuffer(this)) o = this.slice(this.l, this.l+2*size).toString(\"utf16le\");\n\t\t\telse for(i = 0; i < size; ++i) { o+=String.fromCharCode(__readUInt16LE(this, loc)); loc+=2; }\n\t\t\tsize *= 2;\n\t\t\tbreak;\n\n\t\tcase 'utf8': o = __utf8(this, this.l, this.l + size); break;\n\t\tcase 'utf16le': size *= 2; o = __utf16le(this, this.l, this.l + size); break;\n\n\t\tcase 'wstr':\n\t\t\tif(typeof $cptable !== 'undefined') o = $cptable.utils.decode(current_codepage, this.slice(this.l, this.l+2*size));\n\t\t\telse return ReadShift.call(this, size, 'dbcs');\n\t\t\tsize = 2 * size; break;\n\n\t\t/* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */\n\t\tcase 'lpstr-ansi': o = __lpstr(this, this.l); size = 4 + __readUInt32LE(this, this.l); break;\n\t\tcase 'lpstr-cp': o = __cpstr(this, this.l); size = 4 + __readUInt32LE(this, this.l); break;\n\t\t/* [MS-OLEDS] 2.1.5 LengthPrefixedUnicodeString */\n\t\tcase 'lpwstr': o = __lpwstr(this, this.l); size = 4 + 2 * __readUInt32LE(this, this.l); break;\n\t\t/* [MS-OFFCRYPTO] 2.1.2 Length-Prefixed Padded Unicode String (UNICODE-LP-P4) */\n\t\tcase 'lpp4': size = 4 + __readUInt32LE(this, this.l); o = __lpp4(this, this.l); if(size & 0x02) size += 2; break;\n\t\t/* [MS-OFFCRYPTO] 2.1.3 Length-Prefixed UTF-8 String (UTF-8-LP-P4) */\n\t\tcase '8lpp4': size = 4 + __readUInt32LE(this, this.l); o = __8lpp4(this, this.l); if(size & 0x03) size += 4 - (size & 0x03); break;\n\n\t\tcase 'cstr': size = 0; o = \"\";\n\t\t\twhile((w=__readUInt8(this, this.l + size++))!==0) oo.push(_getchar(w));\n\t\t\to = oo.join(\"\"); break;\n\t\tcase '_wstr': size = 0; o = \"\";\n\t\t\twhile((w=__readUInt16LE(this,this.l +size))!==0){oo.push(_getchar(w));size+=2;}\n\t\t\tsize+=2; o = oo.join(\"\"); break;\n\n\t\t/* sbcs and dbcs support continue records in the SST way TODO codepages */\n\t\tcase 'dbcs-cont': o = \"\"; loc = this.l;\n\t\t\tfor(i = 0; i < size; ++i) {\n\t\t\t\tif(this.lens && this.lens.indexOf(loc) !== -1) {\n\t\t\t\t\tw = __readUInt8(this, loc);\n\t\t\t\t\tthis.l = loc + 1;\n\t\t\t\t\tvv = ReadShift.call(this, size-i, w ? 'dbcs-cont' : 'sbcs-cont');\n\t\t\t\t\treturn oo.join(\"\") + vv;\n\t\t\t\t}\n\t\t\t\too.push(_getchar(__readUInt16LE(this, loc)));\n\t\t\t\tloc+=2;\n\t\t\t} o = oo.join(\"\"); size *= 2; break;\n\n\t\tcase 'cpstr':\n\t\t\tif(typeof $cptable !== 'undefined') {\n\t\t\t\to = $cptable.utils.decode(current_codepage, this.slice(this.l, this.l + size));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t/* falls through */\n\t\tcase 'sbcs-cont': o = \"\"; loc = this.l;\n\t\t\tfor(i = 0; i != size; ++i) {\n\t\t\t\tif(this.lens && this.lens.indexOf(loc) !== -1) {\n\t\t\t\t\tw = __readUInt8(this, loc);\n\t\t\t\t\tthis.l = loc + 1;\n\t\t\t\t\tvv = ReadShift.call(this, size-i, w ? 'dbcs-cont' : 'sbcs-cont');\n\t\t\t\t\treturn oo.join(\"\") + vv;\n\t\t\t\t}\n\t\t\t\too.push(_getchar(__readUInt8(this, loc)));\n\t\t\t\tloc+=1;\n\t\t\t} o = oo.join(\"\"); break;\n\n\t\tdefault:\n\tswitch(size) {\n\t\tcase 1: oI = __readUInt8(this, this.l); this.l++; return oI;\n\t\tcase 2: oI = (t === 'i' ? __readInt16LE : __readUInt16LE)(this, this.l); this.l += 2; return oI;\n\t\tcase 4: case -4:\n\t\t\tif(t === 'i' || ((this[this.l+3] & 0x80)===0)) { oI = ((size > 0) ? __readInt32LE : __readInt32BE)(this, this.l); this.l += 4; return oI; }\n\t\t\telse { oR = __readUInt32LE(this, this.l); this.l += 4; } return oR;\n\t\tcase 8: case -8:\n\t\t\tif(t === 'f') {\n\t\t\t\tif(size == 8) oR = __double(this, this.l);\n\t\t\t\telse oR = __double([this[this.l+7],this[this.l+6],this[this.l+5],this[this.l+4],this[this.l+3],this[this.l+2],this[this.l+1],this[this.l+0]], 0);\n\t\t\t\tthis.l += 8; return oR;\n\t\t\t} else size = 8;\n\t\t/* falls through */\n\t\tcase 16: o = __hexlify(this, this.l, size); break;\n\t}}\n\tthis.l+=size; return o;\n}\n\nvar __writeUInt32LE = function(b/*:RawBytes|CFBlob*/, val/*:number*/, idx/*:number*/)/*:void*/ { b[idx] = (val & 0xFF); b[idx+1] = ((val >>> 8) & 0xFF); b[idx+2] = ((val >>> 16) & 0xFF); b[idx+3] = ((val >>> 24) & 0xFF); };\nvar __writeInt32LE = function(b/*:RawBytes|CFBlob*/, val/*:number*/, idx/*:number*/)/*:void*/ { b[idx] = (val & 0xFF); b[idx+1] = ((val >> 8) & 0xFF); b[idx+2] = ((val >> 16) & 0xFF); b[idx+3] = ((val >> 24) & 0xFF); };\nvar __writeUInt16LE = function(b/*:RawBytes|CFBlob*/, val/*:number*/, idx/*:number*/)/*:void*/ { b[idx] = (val & 0xFF); b[idx+1] = ((val >>> 8) & 0xFF); };\n\nfunction WriteShift(t/*:number*/, val/*:string|number*/, f/*:?string*/)/*:any*/ {\n\tvar size = 0, i = 0;\n\tif(f === 'dbcs') {\n\t\t/*:: if(typeof val !== 'string') throw new Error(\"unreachable\"); */\n\t\tfor(i = 0; i != val.length; ++i) __writeUInt16LE(this, val.charCodeAt(i), this.l + 2 * i);\n\t\tsize = 2 * val.length;\n\t} else if(f === 'sbcs') {\n\t\tif(typeof $cptable !== 'undefined' && current_ansi == 874) {\n\t\t\t/* TODO: use tables directly, don't encode */\n\t\t\t/*:: if(typeof val !== \"string\") throw new Error(\"unreachable\"); */\n\t\t\tfor(i = 0; i != val.length; ++i) {\n\t\t\t\tvar cppayload = $cptable.utils.encode(current_ansi, val.charAt(i));\n\t\t\t\tthis[this.l + i] = cppayload[0];\n\t\t\t}\n\t\t} else {\n\t\t\t/*:: if(typeof val !== 'string') throw new Error(\"unreachable\"); */\n\t\t\tval = val.replace(/[^\\x00-\\x7F]/g, \"_\");\n\t\t\t/*:: if(typeof val !== 'string') throw new Error(\"unreachable\"); */\n\t\t\tfor(i = 0; i != val.length; ++i) this[this.l + i] = (val.charCodeAt(i) & 0xFF);\n\t\t}\n\t\tsize = val.length;\n\t} else if(f === 'hex') {\n\t\tfor(; i < t; ++i) {\n\t\t\t/*:: if(typeof val !== \"string\") throw new Error(\"unreachable\"); */\n\t\t\tthis[this.l++] = (parseInt(val.slice(2*i, 2*i+2), 16)||0);\n\t\t} return this;\n\t} else if(f === 'utf16le') {\n\t\t\t/*:: if(typeof val !== \"string\") throw new Error(\"unreachable\"); */\n\t\t\tvar end/*:number*/ = Math.min(this.l + t, this.length);\n\t\t\tfor(i = 0; i < Math.min(val.length, t); ++i) {\n\t\t\t\tvar cc = val.charCodeAt(i);\n\t\t\t\tthis[this.l++] = (cc & 0xff);\n\t\t\t\tthis[this.l++] = (cc >> 8);\n\t\t\t}\n\t\t\twhile(this.l < end) this[this.l++] = 0;\n\t\t\treturn this;\n\t} else /*:: if(typeof val === 'number') */ switch(t) {\n\t\tcase 1: size = 1; this[this.l] = val&0xFF; break;\n\t\tcase 2: size = 2; this[this.l] = val&0xFF; val >>>= 8; this[this.l+1] = val&0xFF; break;\n\t\tcase 3: size = 3; this[this.l] = val&0xFF; val >>>= 8; this[this.l+1] = val&0xFF; val >>>= 8; this[this.l+2] = val&0xFF; break;\n\t\tcase 4: size = 4; __writeUInt32LE(this, val, this.l); break;\n\t\tcase 8: size = 8; if(f === 'f') { write_double_le(this, val, this.l); break; }\n\t\t/* falls through */\n\t\tcase 16: break;\n\t\tcase -4: size = 4; __writeInt32LE(this, val, this.l); break;\n\t}\n\tthis.l += size; return this;\n}\n\nfunction CheckField(hexstr/*:string*/, fld/*:string*/)/*:void*/ {\n\tvar m = __hexlify(this,this.l,hexstr.length>>1);\n\tif(m !== hexstr) throw new Error(fld + 'Expected ' + hexstr + ' saw ' + m);\n\tthis.l += hexstr.length>>1;\n}\n\nfunction prep_blob(blob, pos/*:number*/)/*:void*/ {\n\tblob.l = pos;\n\tblob.read_shift = /*::(*/ReadShift/*:: :any)*/;\n\tblob.chk = CheckField;\n\tblob.write_shift = WriteShift;\n}\n\nfunction parsenoop(blob, length/*:: :number, opts?:any */) { blob.l += length; }\n\nfunction new_buf(sz/*:number*/)/*:Block*/ {\n\tvar o = new_raw_buf(sz);\n\tprep_blob(o, 0);\n\treturn o;\n}\n\n/* [MS-XLSB] 2.1.4 Record */\nfunction recordhopper(data, cb/*:RecordHopperCB*/, opts/*:?any*/) {\n\tif(!data) return;\n\tvar tmpbyte, cntbyte, length;\n\tprep_blob(data, data.l || 0);\n\tvar L = data.length, RT = 0, tgt = 0;\n\twhile(data.l < L) {\n\t\tRT = data.read_shift(1);\n\t\tif(RT & 0x80) RT = (RT & 0x7F) + ((data.read_shift(1) & 0x7F)<<7);\n\t\tvar R = XLSBRecordEnum[RT] || XLSBRecordEnum[0xFFFF];\n\t\ttmpbyte = data.read_shift(1);\n\t\tlength = tmpbyte & 0x7F;\n\t\tfor(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte);\n\t\ttgt = data.l + length;\n\t\tvar d = R.f && R.f(data, length, opts);\n\t\tdata.l = tgt;\n\t\tif(cb(d, R, RT)) return;\n\t}\n}\n\n/* control buffer usage for fixed-length buffers */\nfunction buf_array()/*:BufArray*/ {\n\tvar bufs/*:Array