Merge pull request !3 from feature
This commit is contained in:
赵世界 2022-08-08 02:46:06 +00:00 committed by Gitee
commit c847b42653
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
137 changed files with 21898 additions and 33 deletions

View File

@ -1,15 +1,15 @@
APP_NAME=Laravel APP_NAME=ERP
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://erp.test
LOG_CHANNEL=stack LOG_CHANNEL=stack
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3306 DB_PORT=3306
DB_DATABASE=laravel DB_DATABASE=erp
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD= DB_PASSWORD=

View File

@ -2,21 +2,27 @@
#### 介绍 #### 介绍
主要为鲜花售卖提供一个统一的商品管理平台,支持对接第三方平台,包括商品管理,库存同步,订单管理,发货等
#### 软件架构 #### 软件架构
- laravel 6.* - laravel 6.*
- vue2.* - vue2.*
- element-ui - element-ui
#### 安装教程 #### 本地开发安装教程
1. xxxx 1. `composer install`
2. xxxx 2. `cp .env.example .env`
3. xxxx 3. 修改 .env 配置项为本地配置
4. 创建数据库 `CREATE DATABASE IF NOT EXISTS `erp` DEFAULT CHARACTER SET utf8;`
5. `php artisan migrate` 如果数据填充没有执行成功,则需要再次执行 `php artisan migrate:fresh --seed`
6. `php artisan key:generate`
7. `php artisan update:super_admin_permissions` 更新超级管理员角色权限
#### 使用说明 #### 使用说明
1. xxxx 1. 阅读并遵守<<[Laravel项目开发规范](https://learnku.com/docs/laravel-specification/9.x/whats-the-use-of-standards/12720)>>
2. xxxx 2. xxxx
3. xxxx 3. xxxx

View File

@ -0,0 +1,79 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use App\Models\DailyStockRecord;
use App\Models\GoodsSku;
use App\Models\Log;
class Inventory extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'timing:inventory';
/**
* The console command description.
*
* @var string
*/
protected $description = '定时盘点';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
DB::beginTransaction();
try {
// 数据库存储过程,7点定时执行
$skus = GoodsSku::query()->get(['id', 'stock', 'two_days_ago_num', 'yesterday_num']);
$data = [];
$date = date('Y-m-d');
foreach ($skus as $sku) {
$data[] = [
'sku_id' => $sku->id,
'day' => $date,
];
GoodsSku::where('id', $sku->id)->update([
'stock' => $sku->stock + $sku->two_days_ago_num + $sku->yesterday_num,
'yesterday_num' => $sku->stock,
'two_days_ago_num' => $sku->two_days_ago_num + $sku->yesterday_num,
]);
}
$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 = 1;
$record = new DailyStockRecord();
$record->batchInsert($data);
$log->message = '成功';
DB::commit();
} catch (\Exception $exception) {
$log->message = '失败';
DB::rollBack();
}
$log->save();
$this->info($log->message);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class UpdateSuperPermissions extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'update:super_admin_permissions';
/**
* The console command description.
*
* @var string
*/
protected $description = '更新超级管理员权限';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$roleName = '超级管理员';
$role = Role::query()->where('name', $roleName)->find(1);
$permissions = Permission::query()->get();
$role->syncPermissions($permissions);
$user = User::query()->find(1);
$user->assignRole($role);
$this->info('更新成功');
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace App\Exports;
use App\Models\Log;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use App\Models\GoodsSku;
use Illuminate\Support\Collection;
class GoodsSkusExport implements FromCollection, ShouldAutoSize
{
private $data;
private $type;
public function __construct($type)
{
$this->type = $type;
$this->data = $this->createData();
}
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return new Collection($this->data);
}
private function createData()
{
$headTitle = [
'商品编码',
'商品名称',
'商品种类',
'商品品牌',
'规格编码',
'规格名称',
'成本',
'库存',
];
$inventoryTime = strtotime(date('Y-m-d 07:00:00'));
$ids = Log::query()->where('target_type', 'sku')
->where('target_field', $this->type)
->where('created_at', '>', $inventoryTime)
->pluck('sku_id')
->toArray();
$data = GoodsSku::query()
->when($ids, function ($query, $ids) {
return $query->whereIn('id', $ids);
})
->with(['goods' => function ($query) {
$query->with(['type:id,name', 'brand:id,name']);
}])
->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['goods']['sku_code'];
$arr[5] = $item['goods']['title'];
$arr[6] = $item['goods']['cost'];
$arr[7] = $item['goods']['stock'];
$bodyData[] = $arr;
}
unset($arr);
return [$headTitle, $bodyData];
}
}

121
app/Filters/Filters.php Normal file
View File

@ -0,0 +1,121 @@
<?php
namespace App\Filters;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
class Filters
{
/**
* @var Request
*/
protected $request;
/**
* The Eloquent builder.
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $builder;
/**
* Registered filters to operate upon.
*
* @var array
*/
protected $filters = [];
/**
* Create a new ThreadFilters instance.
*
* @param Request $request
*/
public function __construct()
{
$this->request = Request();
}
/**
* Apply the filters.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return \Illuminate\Database\Eloquent\Builder
*/
public function apply($builder)
{
$this->builder = $builder;
$fieldsSearchable = $this->getFieldsSearchable();
$fields = $this->getFilters($fieldsSearchable);
if (is_array($fields) && count($fields) && is_array($fieldsSearchable) && count($fieldsSearchable)) {
foreach ($fields as $field => $value) {
$method = Str::camel($field);
if (method_exists($this, $method)) {
$this->$method($value);
} elseif (isset($fieldsSearchable[$field])) {
$condition = $fieldsSearchable[$field];
$value = $condition == "like" ? "%{$value}%" : $value;
$relation = null;
if (stripos($field, '.')) {
$explode = explode('.', $field);
$field = array_pop($explode);
$relation = implode('.', $explode);
}
$modelTableName = $builder->getModel()->getTable();
$field = Str::snake($field);
if (!is_null($value)) {
if (!is_null($relation)) {
$builder->whereHas($relation, function ($query) use ($field, $condition, $value) {
$query->where($field, $condition, $value);
});
} else {
$builder->where($modelTableName . '.' . $field, $condition, $value);
}
}
}
}
return $this->builder;
}
}
/**
* Fetch all relevant filters from the request.
*
* @return array
*/
public function getFilters($fieldsSearchable)
{
if (!is_array($fieldsSearchable) || count($fieldsSearchable) == 0) {
return [];
}
$keys = array_keys($fieldsSearchable);
//$this->request->only() 会把user.nickname参数里包含"." 转换成数组
//Arr::dot 方法使用「.」号将将多维数组转化为一维数组:
return array_filter(Arr::dot($this->request->only($keys)), function ($value) {
return !is_null($value) && $value != '';
});
}
//获取model 可以查询的参数
protected function getFieldsSearchable()
{
$model = $this->builder->getModel();
if (!property_exists($model, 'fieldSearchable')) {
return [];
}
$fieldSearchable = $model->fieldSearchable;
$fields = [];
foreach ($fieldSearchable as $field => $condition) {
if (is_numeric($field)) {
$field = $condition;
$condition = "=";
}
$fields[$field] = $condition;
}
return $fields;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Filters;
class GoodsFilter extends Filters
{
protected function goodsTitle($value)
{
return $this->builder->where('title', 'like', "%$value%");
}
protected function typeId($value)
{
return $this->builder->where('type_id', '=', $value);
}
protected function brandId($value)
{
return $this->builder->where('brand_id', '=', $value);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Filters;
class GoodsSkuFilter extends Filters
{
protected function skuTitle($value)
{
return $this->builder->where('title', '=', $value);
}
protected function status($value)
{
return $this->builder->where('status', '=', $value);
}
}

46
app/Filters/LogFilter.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace App\Filters;
class LogFilter extends Filters
{
protected function module($value)
{
return $this->builder->where('module', '=', $value);
}
protected function action($value)
{
return $this->builder->where('action', '=', $value);
}
protected function targetType($value)
{
return $this->builder->where('target_type', '=', $value);
}
protected function targetId($value)
{
return $this->builder->where('target_id', '=', $value);
}
protected function targetField($value)
{
return $this->builder->where('target_field', '=', $value);
}
protected function userId($value)
{
return $this->builder->where('user_id', '=', $value);
}
protected function startTime($value)
{
return $this->builder->where('created_at', '>=', $value);
}
protected function endTime($value)
{
return $this->builder->where('created_at', '<=', $value);
}
}

View File

@ -4,6 +4,8 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller class LoginController extends Controller
@ -37,4 +39,21 @@ class LoginController extends Controller
{ {
$this->middleware('guest')->except('logout'); $this->middleware('guest')->except('logout');
} }
public function username()
{
return 'name';
}
public function login(Request $request)
{
$credentials = $request->only('name', 'password');
if (Auth::attempt($credentials)) {
// 通过认证..
return response()->json(['token' => $request->user()->api_token]);
}
return response()->json(['error' => 'auth login fail']);
}
} }

View File

@ -3,11 +3,13 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Faker\Generator as Faker;
use Illuminate\Support\Str;
class RegisterController extends Controller class RegisterController extends Controller
{ {
@ -50,9 +52,10 @@ class RegisterController extends Controller
protected function validator(array $data) protected function validator(array $data)
{ {
return Validator::make($data, [ return Validator::make($data, [
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'unique:users', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'email' => ['string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'], 'password' => ['required', 'string', 'min:8', 'confirmed'],
'role_id' => ['required', 'numeric', 'exists:roles,id'],
]); ]);
} }
@ -60,14 +63,17 @@ class RegisterController extends Controller
* Create a new user instance after a valid registration. * Create a new user instance after a valid registration.
* *
* @param array $data * @param array $data
* @return \App\User * @return User
*/ */
protected function create(array $data) protected function create(array $data)
{ {
$faker = new Faker();
return User::create([ return User::create([
'name' => $data['name'], 'name' => $data['name'],
'email' => $data['email'], 'email' => $data['email'] ?? $faker->unique()->safeEmail,
'password' => Hash::make($data['password']), 'password' => Hash::make($data['password']),
'api_token' => Str::random(60),
]); ]);
} }
} }

View File

@ -10,4 +10,40 @@ use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController class Controller extends BaseController
{ {
use AuthorizesRequests, DispatchesJobs, ValidatesRequests; use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
protected $res = [
'httpCode' => 200,
'errorCode' => 0,
'errorMessage' => '',
];
protected $log;
protected function setValidatorFailResponse($errorMessage)
{
return $this->res = [
'httpCode' => 400,
'errorCode' => 400416,
'errorMessage' => $errorMessage,
];
}
protected function setBeforeUpdate($data)
{
$this->log->before_update = is_array($data) ? json_encode($data, 256) : $data;
}
protected function setAfterUpdate($data)
{
$this->log->after_update = is_array($data) ? json_encode($data, 256) : $data;
}
protected function addLog($targetId = 0, $targetField = '', $targetType = '')
{
if ($targetType) {
$this->log->target_type = $targetType;
}
return $this->log->add($targetId, $targetField);
}
} }

View File

@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers\Goods;
use App\Http\Controllers\Controller;
use App\Http\Resources\GoodsBrandResource;
use App\Models\GoodsBrand;
use App\Models\Log as LogModel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class GoodsBrandsController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods_brand',
]);
}
public function index()
{
$goodsBrands = GoodsBrand::query()->paginate();
return GoodsBrandResource::collection($goodsBrands);
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'names' => 'required|array',
'names.*' => 'required|string|max:255|unique:goods_brands,name',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsBrands = [];
foreach ($request->names as $name) {
$goodsBrands[] = ['name' => $name];
}
$goodsBrand = new GoodsBrand();
if (!$goodsBrand->batchInsert($goodsBrands)) {
$this->res = [
'httpCode' => 500,
'errorCode' => 500500,
'errorMessage' => '批量添加失败',
];
}
$this->setAfterUpdate($goodsBrands);
$this->addLog(0, 'add');
return response($this->res, $this->res['httpCode']);
}
public function show($id)
{
return new GoodsBrandResource(GoodsBrand::query()->find($id));
}
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'name' => [
'required',
'string',
'max:255',
Rule::unique('goods_brands')->ignore($id),
]
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsBrand = GoodsBrand::query()->find($id);
$this->setBeforeUpdate($goodsBrand->name);
$goodsBrand->name = request('name');
$goodsBrand->save();
$this->setAfterUpdate($goodsBrand->name);
$this->addLog($id, 'name');
return new GoodsBrandResource($goodsBrand);
}
public function destroy($id)
{
$goodsBrand = GoodsBrand::query()->find($id);
try {
$goodsBrand->delete();
$this->addLog($id, 'status');
} catch (\Exception $e) {
$this->res = [
'httpCode' => 500,
'errorCode' => 500416,
'errorMessage' => $e->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers\Goods;
use App\Http\Controllers\Controller;
use App\Http\Requests\GoodsSkuRequest;
use App\Http\Resources\GoodsResource;
use App\Models\Log as LogModel;
use App\Utils\FormatUtils;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use App\Models\Goods;
use App\Http\Requests\GoodsRequest;
use App\Models\DailyStockRecord;
use Illuminate\Support\Facades\Storage;
class GoodsController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods_sku',
]);
}
public function index(Request $request)
{
$goods = Goods::query()->get(['id', 'title', 'img_url', 'type_id', 'brand_id', 'goods_code']);
return GoodsResource::collection($goods);
}
public function store(Request $request)
{
$goodsRules = (new GoodsRequest())->rules();
$skuRules = (new GoodsSkuRequest())->arrayRules('skus.*.');
$validator = Validator::make($request->all(), array_merge($goodsRules, ['skus' => ['required', 'array']], $skuRules));
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
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();
}
$goodsSkus = [];
foreach ($request->skus as $item) {
$item['goods_id'] = $goods->id;
$item['reference_price'] = $item['cost'] * 1.5;
$goodsSkus[] = $item;
}
$collection = $goods->skus()->createMany($goodsSkus)->toArray();
$this->setAfterUpdate($collection);
$this->addLog(0, 'add');
$newRecords = [];
foreach ($collection as $sku) {
$newRecords[] = [
'sku_id' => $sku['id'],
'day' => FormatUtils::date(),
];
}
$record = new DailyStockRecord();
$record->batchInsert($newRecords);
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
$this->res = [
'httpCode' => 400,
'errorCode' => 400416,
'errorMessage' => $exception->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
public function download()
{
return Storage::download(resource_path('templates/goods_skus_import.xlsx'));
}
}

View File

@ -0,0 +1,350 @@
<?php
namespace App\Http\Controllers\Goods;
use App\Exports\GoodsSkusExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\GoodsRequest;
use App\Http\Requests\GoodsSkuRequest;
use App\Models\Goods;
use App\Models\Log;
use App\Models\Log as LogModel;
use App\Utils\FormatUtils;
use Illuminate\Http\Request;
use App\Models\GoodsSku;
use App\Http\Resources\GoodsSkuResource;
use App\Imports\GoodsSkusImport;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
use App\Models\DailyStockRecord;
class GoodsSkusController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods_sku',
]);
}
public function index(Request $request)
{
$goods = Goods::query()->filter()->get()->toArray();
$goodsIds = array_column($goods, 'id');
// 状态变更时间查询,日志
$ids = [];
if ($request->get('keyword_type', '') && $request->get('keyword_value', '')) {
$ids = Log::query()->where('target_type', 'sku')
->where('target_field', $request->keyword_type)
->whereBetween('created_at', explode(' - ', $request->keyword_value))
->pluck('sku_id')
->toArray();
}
$day = FormatUtils::date();
$goodsSkus = GoodsSku::query()
->whereIn('goods_id', $goodsIds)
->when($ids, function ($query, $ids) {
return $query->whereIn('id', $ids);
})
->filter()
->with(['goods' => function ($query) {
$query->with(['type:id,name', 'brand:id,name']);
}])
->with(['daily' => function ($query) use ($day) {
return $query->where('day', $day)->first(['id', 'sku_id', 'day', 'arrived_today_num', 'loss_num', 'inventory']);
}])
->paginate();
return GoodsSkuResource::collection($goodsSkus);
}
public function show($id)
{
return new GoodsSkuResource(GoodsSku::query()
->with(['goods' => function ($query) {
$query->with(['type:id,name', 'brand:id,name']);
}])
->find($id));
}
public function update($id, Request $request)
{
$goodsRules = (new GoodsRequest())->arrayRules('goods.');
$skuRules = (new GoodsSkuRequest())->arrayRules('sku.');
$validator = Validator::make($request->all(), array_merge($goodsRules, $skuRules));
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
DB::beginTransaction();
try {
// 商品规格更新
$sku = GoodsSku::query()->find($id);
$this->setBeforeUpdate($sku->toArray());
$sku->update($request->sku);
$this->setAfterUpdate($sku->toArray());
$this->addLog($id, 'update');
// 商品更新
$goods = Goods::query()->find($sku->goods_id);
$this->setBeforeUpdate($goods->toArray());
$goods->update($request->goods);
$this->setAfterUpdate($goods->toArray());
$this->addLog($sku->goods_id, 'update', 'goods');
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
$this->res = [
'httpCode' => 400,
'errorCode' => 400416,
'errorMessage' => $exception->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
public function batchUpdate(Request $request)
{
$appendRules = [
'updateType' => ['required', 'string', Rule::in(['newest', 'inventory', 'stock'])],
'skus' => ['required', 'array'],
'skus.*.id' => [
'required',
Rule::exists('goods_skus', 'id'),
],
];
$validator = $this->validateUpdate($request->all(), $appendRules);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$function = $request->updateType;
return $this->$function($request);
}
private function newest($request)
{
DB::beginTransaction();
try {
$logs = [];
foreach ($request->skus as $sku) {
$costLog = $arrivedLog = [
'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', 'stock', 'num']);
$costLog['target_field'] = 'cost';
$costLog['before_update'] = $goodsSku->cost;
$goodsSku->cost = $sku['cost'];
$goodsSku->reference_price = $sku['cost'] * 1.5;
$goodsSku->stock += $sku['arrived_today_num'];
$goodsSku->num += $sku['arrived_today_num'];
$goodsSku->save();
$costLog['after_update'] = $goodsSku->cost;
$logs[] = $costLog;
// 今日到货
$record = DailyStockRecord::query()->where('sku_id', $sku['id'])->where('day', FormatUtils::date())->first(['id', 'arrived_today_num']);
$arrivedLog['target_field'] = 'arrived_today_num';
$arrivedLog['before_update'] = $record->arrived_today_num;
$record->arrived_today_num += $sku['arrived_today_num'];
$record->save();
$arrivedLog['after_update'] = $record->arrived_today_num;
$logs[] = $arrivedLog;
}
$log = new LogModel();
$log->batchInsert($logs);
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
$this->res = [
'httpCode' => 400,
'errorCode' => 400500,
'errorMessage' => $exception->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
private function inventory($request)
{
DB::beginTransaction();
try {
$logs = [];
foreach ($request->skus as $sku) {
$inventoryLog = [
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods_sku',
'target_id' => $sku['id'],
'user_id' => $request->user()->id
];
// 今日到货
$record = DailyStockRecord::query()->where('sku_id', $sku['id'])->where('day', FormatUtils::date())->first(['id', 'inventory']);
$inventoryLog['target_field'] = 'inventory';
$inventoryLog['before_update'] = $record->inventory;
$record->inventory = $sku['inventory'];
$record->save();
$inventoryLog['after_update'] = $record->inventory;
$logs[] = $inventoryLog;
}
$log = new LogModel();
$log->batchInsert($logs);
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
$this->res = [
'httpCode' => 400,
'errorCode' => 400500,
'errorMessage' => $exception->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
private function stock($skus)
{
$update = reset($skus);
DB::beginTransaction();
try {
$sku = GoodsSku::query()->where('id', $update['id'])->get(['id', 'two_days_ago_num', 'yesterday_num', 'num', 'stock']);
$record = DailyStockRecord::query()
->where('sku_id', $sku->id)
->where('day', FormatUtils::date())
->first();
$this->setBeforeUpdate([
'two_days_ago_num' => $sku->two_days_ago_num,
'yesterday_num' => $sku->yesterday_num,
'num' => $sku->num,
'arrived_today_num' => $record->arrived_today_num,
]);
$sku->two_days_ago_num = $update['two_days_ago_num'];
$sku->yesterday_num = $update['yesterday_num'];
$sku->num = $update['two_days_ago_num'] + $update['yesterday_num'] + $sku->stock;
$sku->save();
$record->arrived_today_num = $update['arrived_today_num'];
$record->save();
$this->setAfterUpdate([
'two_days_ago_num' => $sku->two_days_ago_num,
'yesterday_num' => $sku->yesterday_num,
'arrived_today_num' => $record->arrived_today_num,
]);
$this->addLog($sku->id, 'stock');
DB::commit();
} catch (\Exception $exception) {
DB::rollBack();
$this->res = [
'httpCode' => 400,
'errorCode' => 400416,
'errorMessage' => $exception->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
public function updateField($id, Request $request)
{
$rules = [
'updateField' => [
'required',
Rule::in(['reference_price', 'reserve', 'loss_num', 'status'])
],
'reference_price' => [
'sometimes',
'numeric',
'gt:0'
],
'reserve' => [
'sometimes',
'integer',
],
'loss_num' => [
'sometimes',
'integer',
],
'reason' => [
'sometimes',
'required',
'string'
],
'status' => [
'sometimes',
'required',
'integer',
Rule::in([0, 1, 2])],
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$updateField = \request('updateField');
if ('loss_num' === $updateField) {
$model = DailyStockRecord::query()
->where('sku_id', $id)
->where('day', FormatUtils::date())
->first(['id', 'loss_num']);
$this->log->message = $request->get('reason');
$this->setBeforeUpdate($model->loss_num);
$model->loss_num += $request->loss_num;
} else {
$model = GoodsSku::query()->find($id);
$this->setBeforeUpdate($model->$updateField);
if ('reserve' === $updateField) {
$changeNum = $model->reserve - $request->reserve;
$model->stock += $changeNum;
$model->reserve = $request->reserve;
$model->num += $changeNum;
} else {
$model->$updateField = $request->$updateField;
}
}
$model->save();
$this->setAfterUpdate($model->$updateField);
$this->addLog($id, $updateField);
return response($this->res, $this->res['httpCode']);
}
public function store(Request $request)
{
if (!$request->hasFile('goodsSkus')) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found goodsSkus file',
];
}
try {
$collection = Excel::import(new GoodsSkusImport(), $request->file('goodsSkus'));
$this->setAfterUpdate($collection->toArray());
$this->addLog(0, 'import');
} catch (ValidationException $exception) {
$this->setValidatorFailResponse($exception->validator->getMessageBag()->getMessages());
}
return response($this->res, $this->res['httpCode']);
}
public function export(Request $request)
{
$type = $request->get('exportType');
ob_end_clean();
return Excel::download(new GoodsSkusExport($type), $type, '.xlsx');
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Goods;
use App\Http\Controllers\Controller;
use App\Http\Resources\GoodsTypeResource;
use App\Models\GoodsType;
use App\Models\Log as LogModel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class GoodsTypesController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'goods',
'action' => $request->getMethod(),
'target_type' => 'goods_type',
]);
}
public function index()
{
$goodsTypes = GoodsType::query()->paginate();
return GoodsTypeResource::collection($goodsTypes);
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'names' => 'required|array',
'names.*' => 'required|string|max:255|unique:goods_types,name',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsTypes = [];
foreach ($request->names as $name) {
$goodsTypes[] = ['name' => $name];
}
$goodsType = new GoodsType();
if (!$goodsType->batchInsert($goodsTypes)) {
$this->res = [
'httpCode' => 500,
'errorCode' => 500500,
'errorMessage' => '批量添加失败',
];
}
$this->setAfterUpdate($goodsTypes);
$this->addLog(0, 'add');
return response($this->res, $this->res['httpCode']);
}
public function show($id)
{
return new GoodsTypeResource(GoodsType::query()->find($id));
}
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'name' => [
'required',
'string',
'max:255',
Rule::unique('goods_types')->ignore($id),
]
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$goodsType = GoodsType::query()->find($id);
$this->setBeforeUpdate($goodsType->name);
$goodsType->name = request('name');
$goodsType->save();
$this->setAfterUpdate($goodsType->name);
$this->addLog($id, 'name');
return new GoodsTypeResource($goodsType);
}
public function destroy($id)
{
$goodsType = GoodsType::query()->find($id);
$goodsType->delete();
$this->addLog($id, 'status');
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Log;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Log;
use App\Http\Resources\LogsResource;
class LogsController extends Controller
{
public function index(Request $request)
{
$res = Log::query()
->with(['user:id,name'])
->filter()
->paginate();
return LogsResource::collection($res);
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers\Menu;
use App\Http\Controllers\Controller;
use App\Models\Log as LogModel;
use App\Models\Menu;
use App\Http\Resources\MenusResource;
use App\Models\User;
use App\Utils\ArrayUtils;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Utils\FormatUtils;
class MenusController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'menu',
'action' => $request->getMethod(),
'target_type' => 'menu',
]);
}
public function index(Request $request)
{
$permissions = $request->user()->getPermissionsViaRoles()->toArray();
$permissions = array_column($permissions, 'name');
$menus = Menu::query()->get()->toArray();
$hasPermissionMenus = [];
foreach ($menus as $menu) {
if (in_array($menu['code'], $permissions, true)) {
$hasPermissionMenus[] = $menu;
}
}
$menus = FormatUtils::formatTreeData($hasPermissionMenus, 0);
return MenusResource::collection($menus);
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:32',
'code' => 'required|string|max:32|unique:menus,code',
'parent_id' => 'required|integer',
'seq' => 'required|integer',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$menu = new Menu();
$menu->name = $request->name;
$menu->code = $request->code;
$menu->parent_id = $request->parent_id;
$menu->seq = $request->seq;
$menu->save();
$this->setAfterUpdate($menu->toArray());
$this->addLog($menu->id, 'add');
return response($this->res, $this->res['httpCode']);
}
public function show($id)
{
return new MenusResource(Menu::query()->find($id));
}
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'name' => ['required', 'string', 'max:32',],
'code' => ['required', 'string', 'max:32', Rule::unique('menus')->ignore($id),],
'parent_id' => ['required', 'integer',],
'seq' => ['required', 'integer',],
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$menu = Menu::query()->find($id);
$this->setBeforeUpdate($menu->toArray());
$menu->name = $request->name;
$menu->code = $request->code;
$menu->parent_id = $request->parent_id;
$menu->seq = $request->seq;
$menu->save();
$this->setAfterUpdate($menu->toArray());
$this->addLog($id, 'update');
return new MenusResource($menu);
}
public function destroy($id)
{
$menu = Menu::query()->find($id);
try {
$menu->delete();
} catch (\Exception $e) {
$this->res = [
'httpCode' => 500,
'errorCode' => 500416,
'errorMessage' => $e->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\Permission;
use App\Http\Controllers\Controller;
use App\Models\Log as LogModel;
use App\Utils\ArrayUtils;
use App\Utils\FormatUtils;
use Spatie\Permission\Models\Permission;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Http\Resources\PermissionsResource;
class PermissionsController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'permission',
'action' => $request->getMethod(),
'target_type' => 'permission',
]);
}
public function index()
{
$permissions = Permission::query()->get()->toArray();
$permissions = ArrayUtils::index($permissions, 'name');
$routes = include(resource_path('lang/zh-CN/permission.php'));
foreach ($routes as $key => &$route) {
$route['id'] = $permissions[$key]['id'];
}
$routes = FormatUtils::formatTreeData($routes, 0);
return PermissionsResource::collection($routes);
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255|unique:permissions,name',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$permission = new Permission();
$permission->name = $request->name;
$permission->save();
$this->setAfterUpdate($permission->name);
$this->addLog($permission->id, 'add');
return response($this->res, $this->res['httpCode']);
}
public function show($id)
{
return new PermissionsResource(Permission::query()->find($id));
}
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'name' => ['required', 'string', 'max:255', Rule::unique('permissions')->ignore($id),]
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$permission = Permission::query()->find($id);
$this->setBeforeUpdate($permission->name);
$permission->name = $request->name;
$permission->save();
$this->setAfterUpdate($permission->name);
$this->addLog($id, 'name');
return new PermissionsResource($permission);
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers\Role;
use App\Http\Controllers\Controller;
use App\Models\Log as LogModel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use App\Http\Resources\RolesResource;
class RolesController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'role',
'action' => $request->getMethod(),
'target_type' => 'role',
]);
}
public function index()
{
$roles = Role::query()->with('permissions')->get()->toArray();
$routes = include(resource_path('lang/zh-CN/permission.php'));
foreach ($roles as &$role) {
$permissions = [];
foreach ($role['permissions'] as $item) {
$permissions[] = $routes[$item['name']]['name'];
}
$role['permissions'] = $permissions;
}
return RolesResource::collection($roles);
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255|unique:roles,name',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$role = new Role();
$role->name = $request->name;
$role->save();
$this->setAfterUpdate($role->name);
$this->addLog($role->id, 'add');
return new RolesResource($role);
}
public function addPermissions($id, Request $request)
{
$role = Role::query()->findOrFail($id);
$permissions = Permission::query()->findOrFail($request->permissionIds);
$role->syncPermissions($permissions);
$this->setAfterUpdate($permissions->toArray());
$this->addLog($id, 'set', 'permission');
return response($this->res, $this->res['httpCode']);
}
public function show($id)
{
return new RolesResource(Role::query()->find($id));
}
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'name' => ['required', 'string', 'max:255', Rule::unique('roles')->ignore($id),]
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$role = Role::query()->find($id);
$this->setBeforeUpdate($role->name);
$role->name = $request->name;
$role->save();
$this->setAfterUpdate($role->name);
$this->addLog($id, 'name');
return new RolesResource($role);
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers\Shop;
use App\Http\Controllers\Controller;
use App\Listeners\BindBusinessGoods;
use App\Listeners\UpdateBusinessGoodsStock;
use App\Models\Shop;
use App\Http\Resources\ShopsResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Services\Business\BusinessFactory;
use Illuminate\Validation\Rule;
class ShopsController extends Controller
{
public function index()
{
$shops = Shop::query()->paginate();
foreach ($shops as $shop) {
$shop->authUrl = '';
if ('妙选' !== $shop->plat_id && '未授权' === $shop->status) {
$shop->authUrl = BusinessFactory::init()->make($shop->plat_id)->getAuthUrl($shop->id, $shop->getOriginal('plat_id'));
}
}
return ShopsResource::collection($shops);
}
public function getPlatList()
{
$shop = new Shop();
return new ShopsResource($shop->getPlatList());
}
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255|unique:shops,name',
'plat_id' => 'required|integer',
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$shop = new Shop();
$shop->name = $request->name;
$shop->plat_id = $request->plat_id;
$shop->save();
return response($this->res, $this->res['httpCode']);
}
public function authBind(Request $request)
{
[$shopId, $platId] = explode('_', $request->get('state'));
$shop = new Shop();
$platList = $shop->getPlatList();
$shop = $shop->find($shopId);
if ($platList[$platId] === $shop->plat_id) {
BusinessFactory::init()->make($shop->plat_id)->authCallback($request->get('code'), $shop->id);
} else {
$this->res = [
'httpCode' => 403,
'errorCode' => 403400,
'errorMessage' => '信息不匹配',
];
}
return response($this->res, $this->res['httpCode']);
}
public function business(Request $request)
{
$validator = Validator::make($request->all(), [
'type' => ['required', 'string', Rule::in(['goods', 'orders'])],
'erp_shop_id' => ['required', 'integer', 'exists:shops,id'],
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$shop = new Shop();
$shop = $shop->find($request->get('erp_shop_id'));
$business = BusinessFactory::init()->make($shop->plat_id);
$business->setShop($shop);
if ('goods' === $request->get('type')) {
$business->bindGoods($request->get('data'));
event(new BindBusinessGoods($shop));
}
if ('orders' === $request->get('type')) {
$business->saveOrders();
event(new UpdateBusinessGoodsStock($shop));
}
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers;
use App\Models\Log as LogModel;
use App\Utils\UploadUtils;
use Illuminate\Http\Request;
class UploadController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'file',
'action' => $request->getMethod(),
'target_type' => 'upload',
]);
}
public function store(Request $request)
{
if (!$request->hasFile('uploadFile')) {
$this->res = [
'httpCode' => 404,
'errorCode' => 404404,
'errorMessage' => 'not found file',
];
}
$this->addLog(0, 'add');
$this->res['resource'] = UploadUtils::putForUploadedFile('image', $request->uploadFile);
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\Log as LogModel;
use App\Models\User;
use Illuminate\Http\Request;
use Faker\Generator as Faker;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use App\Http\Resources\UsersResource;
use Illuminate\Validation\Rule;
class UsersController extends Controller
{
public function __construct(Request $request)
{
$this->log = new LogModel([
'module' => 'user',
'action' => $request->getMethod(),
'target_type' => 'user',
]);
}
public function index()
{
$users = User::query()->where('id', '<>', 1)->with('roles:id,name,guard_name')->paginate();
return UsersResource::collection($users);
}
public function store(Request $request, Faker $faker)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255|unique:users,name',
'password' => 'required|string|min:8|confirmed',
'email' => 'email',
'role_name' => 'required|string|exists:roles,name'
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$user = new User();
$user->name = $request->name;
$user->email = \request('email', $faker->unique()->safeEmail);
$user->password = $request->password;
$user->api_token = Str::random(60);
$user->save();
$this->setAfterUpdate($user->toArray());
$this->addLog($user->id, 'add');
$user->assignRole($request->role_name);
return new UsersResource($user);
}
public function show($id)
{
return new UsersResource(User::query()->with('roles:id,name,guard_name')->find($id));
}
public function update($id, Request $request)
{
$validator = Validator::make($request->all(), [
'name' => [
'required',
'string',
'max:255',
Rule::unique('users')->ignore($id),
],
// 'old_password' => 'sometimes|required|string|min:8',
'password' => 'sometimes|string|min:8|confirmed',
'email' => 'sometimes|email',
'role_name' => 'sometimes|required|string|exists:roles,name'
]);
if ($validator->fails()) {
$this->setValidatorFailResponse($validator->getMessageBag()->getMessages());
return response($this->res, $this->res['httpCode']);
}
$user = User::query()->find($id);
$user->update($request->toArray());
if ($request->has('role_name')) {
$user->syncRoles($request->role_name);
}
return new UsersResource($user);
}
public function destory($id)
{
$user = User::query()->find($id);
try {
$user->delete();
$this->addLog($id, 'status');
} catch (\Exception $e) {
$this->res = [
'httpCode' => 500,
'errorCode' => 500416,
'errorMessage' => $e->getMessage(),
];
}
return response($this->res, $this->res['httpCode']);
}
}

View File

@ -61,6 +61,9 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
'check.permissions' => \App\Http\Middleware\CheckPermissions::class,
]; ];
/** /**

View File

@ -0,0 +1,38 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Route;
class CheckPermissions
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// 获取当前路由名称
$currentRouteName = Route::currentRouteName();
// 引入当前守卫的权限文件
$routes = include(resource_path('lang/zh-CN/permission.php'));
if (is_array($routes) && array_key_exists($currentRouteName, $routes)) {
$permissions = $request->user()->getPermissionsViaRoles()->toArray();
$permissions = array_column($permissions, 'name');
if (in_array($currentRouteName, $permissions, true)) {
return $next($request);
}
}
$res = [
'httpCode' => 403,
'errorCode' => 403403,
'errorMessage' => '您没有使用此功能的权限' . $currentRouteName,
];
return response($res, 403);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class GoodsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
'title' => ['required', 'string', 'max:255'],
'img_url' => ['required', 'string', 'max:255'],
'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'))],
];
}
public function arrayRules($arrayName)
{
$arrayRules = [];
$rules = $this->rules();
foreach ($rules as $key => $val) {
$arrayRules[$arrayName . $key] = $val;
}
return $arrayRules;
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class GoodsSkuRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'id' => ['sometimes', 'required', 'integer', 'exists:goods_skus,id'],
'goods_id' => ['sometimes', 'required', 'integer', 'exists:goods,id'],
'title' => ['required', 'string', 'max:255'],
'sku_code' => ['required', 'distinct', 'alpha_dash', 'max:32'],
'status' => ['required', 'integer', Rule::in([0, 1, 2])],
'num' => ['required', 'integer'],
'cost' => ['required', 'numeric'],
'reference_price' => [
'sometimes',
'numeric',
'gt:0'
],
'reserve' => [
'sometimes',
'integer',
],
'loss_num' => [
'sometimes',
'integer',
],
'inventory' => [
'sometimes',
'integer',
],
];
}
public function arrayRules($arrayName)
{
$arrayRules = [];
$rules = $this->rules();
foreach ($rules as $key => $val) {
$arrayRules[$arrayName . $key] = $val;
}
return $arrayRules;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GoodsBrandResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GoodsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GoodsSkuResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GoodsTypeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class LogsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class MenusResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PermissionsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class RolesResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ShopsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UsersResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Imports;
use App\Models\Goods;
use App\Models\GoodsBrand;
use App\Models\GoodsSku;
use App\Models\GoodsType;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToCollection;
use Illuminate\Support\Facades\Validator;
use App\Utils\ArrayUtils;
class GoodsSkusImport implements ToCollection, SkipsEmptyRows
{
private $statusMap = [
'下架' => 0,
'在售' => 1,
'预警' => 2,
];
/**
* @throws ValidationException
*/
public function collection(Collection $collection)
{
unset($collection[0], $collection[1]);
$arr = $collection->toArray();
$validator = Validator::make($arr, [
'*.0' => ['required', 'string', 'max:255'],
'*.1' => ['required', 'string', 'max:255', 'exists:goods_types,name'],
'*.2' => ['string', 'max:255', 'exists:goods_brands,name'],
'*.3' => ['required', 'alpha_dash', 'max:32', 'unique:goods,goods_code'],
'*.4' => ['required', 'string', 'max:255'],
'*.5' => ['required', 'distinct', 'alpha_dash', 'max:32'],
'*.6' => ['required', 'string', Rule::in(['下架', '在售', '预警'])],
'*.7' => ['required', 'max:10'],
'*.8' => ['required', 'max:10'],
]);
if ($validator->fails()) {
throw new ValidationException($validator);
}
$types = array_column($arr, 1);
$types = GoodsType::query()->whereIn('name', $types)->get(['id', 'name'])->toArray();
$types = ArrayUtils::index($types, 'name');
$brands = array_column($arr, 2);
$brands = GoodsBrand::query()->whereIn('name', $brands)->get(['id', 'name'])->toArray();
$brands = ArrayUtils::index($brands, 'name');
$goodsCodes = array_column($arr, 3);
$hasGoods = Goods::query()->whereIn('goods_code', $goodsCodes)->get(['id', 'goods_code'])->toArray();
$hasGoods = ArrayUtils::index($hasGoods, 'goods_code');
$newGoods = $skus = [];
foreach ($arr as $item) {
$sku = [
'goods_id' => $item[3],
'title' => $item[4],
'sku_code' => $item[5],
'status' => $this->statusMap[$item[6]],
'num' => $item[7],
'cost' => $item[8],
];
// 主商品已存在
if (isset($hasGoods[$item[3]])) {
$sku['goods_id'] = $hasGoods[$item[3]]['id'];
} else {
// 新商品
$newGoods[$item[3]] = [
'title' => $item[0],
'type_id' => $types[$item[1]]['id'],
'brand_id' => $brands[$item[2]]['id'],
'goods_code' => $item[3],
];
}
$skus[] = $sku;
}
if ($newGoods) {
$goods = new Goods();
$goods->batchInsert(array_values($newGoods));
$hasGoods = Goods::query()->whereIn('goods_code', array_column($newGoods, 'goods_code'))->get(['id', 'goods_code'])->toArray();
$hasGoods = ArrayUtils::index($hasGoods, 'goods_code');
foreach ($skus as &$sku) {
$sku['goods_id'] = isset($hasGoods[$sku['goods_id']]) ? $hasGoods[$sku['goods_id']]['id'] : $sku['goods_id'];
}
}
$goodsSku = new GoodsSku();
$goodsSku->batchInsert(array_values($skus));
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Listeners;
use App\Models\Shop;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class BindBusinessGoods
{
protected $shop;
/**
* Create the event listener.
*
* @return void
*/
public function __construct(Shop $shop)
{
$this->shop = $shop;
}
/**
* Handle the event.
*
* @param Registered $event
* @return void
*/
public function handle(Registered $event)
{
//
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Listeners;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendDatabaseNotification
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param Registered $event
* @return void
*/
public function handle(Registered $event)
{
//
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Listeners;
use App\Models\Shop;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class UpdateBusinessGoodsStock
{
protected $shop;
/**
* Create the event listener.
*
* @return void
*/
public function __construct(Shop $shop)
{
$this->shop = $shop;
}
/**
* Handle the event.
*
* @param Registered $event
* @return void
*/
public function handle(Registered $event)
{
//
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
class BusinessGoodsSku extends Model
{
/**
* 不可批量赋值的属性。为空则所有熟悉都可以批量赋值
*
* @var array
*/
protected $guarded = [];
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
class BusinessOrder extends Model
{
/**
* 不可批量赋值的属性。为空则所有熟悉都可以批量赋值
*
* @var array
*/
protected $guarded = [];
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
class BusinessOrderItem extends Model
{
/**
* 不可批量赋值的属性。为空则所有熟悉都可以批量赋值
*
* @var array
*/
protected $guarded = [];
public function order()
{
return $this->hasOne(BusinessOrder::class, 'id', 'business_order_id');
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Models;
class DailyStockRecord extends Model
{
}

8
app/Models/FailedJob.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace App\Models;
class FailedJob extends Model
{
}

37
app/Models/Goods.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
class Goods extends Model
{
use Filter;
//查询字段
public $fieldSearchable = [
'goods_title',
'type_id',
'brand_id',
];
protected $guarded = [];
/**
* 多规格
*/
public function skus()
{
return $this->hasMany(GoodsSku::class, 'goods_id');
}
public function brand()
{
return $this->hasOne(GoodsBrand::class, 'id', 'brand_id');
}
public function type()
{
return $this->hasOne(GoodsType::class, 'id', 'type_id');
}
}

13
app/Models/GoodsBrand.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
class GoodsBrand extends Model
{
/**
* 数组中的属性会被隐藏。
*
* @var array
*/
protected $hidden = ['deleted_at'];
}

62
app/Models/GoodsSku.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
class GoodsSku extends Model
{
use Filter;
//查询字段
public $fieldSearchable = [
'sku_title',
'status',
];
/**
* 不可批量赋值的属性。为空则所有熟悉都可以批量赋值
*
* @var array
*/
protected $guarded = [];
protected $hidden = ['created_at'];
/**
* 获取状态
*
* @param string $value
* @return string
*/
public function getStatusAttribute($value)
{
$map = [
0 => '下架',
1 => '在售',
2 => '预警',
];
return $map[$value];
}
/**
* 此规格从属于一个商品
*/
public function goods()
{
return $this->hasOne(Goods::class, 'id', 'goods_id');
}
/**
* 此规格每日记录
*/
public function daily()
{
return $this->hasOne(DailyStockRecord::class, 'sku_id', 'id');
}
public function order()
{
return $this->hasOne(BusinessOrderItem::class, 'external_sku_id', 'id');
}
}

13
app/Models/GoodsType.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
class GoodsType extends Model
{
/**
* 数组中的属性会被隐藏。
*
* @var array
*/
protected $hidden = ['deleted_at'];
}

114
app/Models/Log.php Normal file
View File

@ -0,0 +1,114 @@
<?php
namespace App\Models;
use App\Models\traits\Filter;
use Illuminate\Support\Facades\Auth;
class Log extends Model
{
use Filter;
//查询字段
public $fieldSearchable = [
'module',
'action',
'target_type',
'target_id',
'target_field',
'user_id',
'created_at',
];
public $fillable = [
'module',
'action',
'target_type',
'target_id',
'target_field',
'before_update',
'after_update',
];
public function getModuleAttribute($value)
{
$map = [
'goods' => '商品',
'menu' => '菜单',
'permission' => '权限',
'role' => '角色',
'user' => '用户',
'plat' => '平台',
'file' => '文件',
];
return $map[$value];
}
public function getActionAttribute($value)
{
$map = [
'GET' => '查看',
'POST' => '新增',
'PATCH' => '更新',
'DELETE' => '删除',
];
return $map[$value];
}
public function getTargetTypeAttribute($value)
{
$map = [
'goods_sku' => '商品&规格',
'goods_type' => '商品种类',
'goods_brand' => '商品品牌',
'permission' => '权限',
'role' => '角色',
'menu' => '菜单',
'user' => '用户',
'upload' => '上传',
'kuaituantuan' => '快团团',
'miaoxuan' => '妙选',
];
return $map[$value];
}
public function getTargetFieldAttribute($value)
{
$map = [
'add' => '创建',
'status' => '状态',
'name' => '名称',
'title' => '名称',
'import' => '导入',
'export' => '导出',
'set' => '设置',
'cost' => '成本',
'stock' => '库存',
'inventory' => '库存盘点',
];
return $map[$value];
}
public function setUserIdAttribute($value)
{
$this->attributes['user_id'] = Auth::id() ?: $value;
}
public function add($targetId = 0, $targetField = '')
{
$this->attributes['user_id'] = Auth::id();
$this->attributes['target_id'] = $targetId;
$this->attributes['target_field'] = $targetField;
return $this->save();
}
public function user()
{
return $this->hasOne(User::class, 'id', 'user_id');
}
}

8
app/Models/Menu.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace App\Models;
class Menu extends Model
{
protected $hidden = ['created_at', 'updated_at'];
}

35
app/Models/Model.php Normal file
View File

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Support\Facades\DB;
class Model extends EloquentModel
{
public function scopeRecent($query)
{
return $query->orderBy('id', 'desc');
}
public function scopeOlder($query)
{
return $query->orderBy('id', 'asc');
}
public function scopeByUser($query, User $user)
{
return $query->where('user_id', $user->id);
}
public function batchInsert(array $data)
{
$time = date('Y-m-d H:i:s');
foreach ($data as &$val) {
$val['created_at'] = $val['created_at'] ?? $time;
$val['updated_at'] = $val['updated_at'] ?? $time;
}
return Db::table($this->getTable())->insert($data);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Models;
class PasswordReset extends Model
{
}

44
app/Models/Shop.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Shop extends Model
{
protected $hidden = [
'access_token',
'expires_at',
'expires_in',
'refresh_token',
'refresh_token_expires_at',
'refresh_token_expires_in',
'pop_auth_token_create_response',
];
protected $guarded = [];
public function getStatusAttribute($value)
{
$map = [
0 => '未授权',
1 => '已授权',
2 => '无需授权',
3 => '停用',
];
return $map[$value];
}
public function getPlatList()
{
return ['妙选', '快团团'];
}
public function getPlatIdAttribute($value)
{
$map = $this->getPlatList();
return $map[$value];
}
}

View File

@ -1,14 +1,16 @@
<?php <?php
namespace App; namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable class User extends Authenticatable
{ {
use Notifiable; use Notifiable;
use HasRoles;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -25,7 +27,7 @@ class User extends Authenticatable
* @var array * @var array
*/ */
protected $hidden = [ protected $hidden = [
'password', 'remember_token', 'password', 'remember_token', 'api_token', 'email_verified_at',
]; ];
/** /**
@ -36,4 +38,14 @@ class User extends Authenticatable
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
]; ];
public function setPasswordAttribute($value)
{
$this->attributes['password'] = Hash::make($value);
}
public function isRoot()
{
return $this->name === 'erpAdmin';
}
} }

View File

@ -0,0 +1,52 @@
<?php
namespace App\Models\traits;
trait Filter
{
/**
* 过滤
*/
public function scopeFilter($query)
{
$filters = $this->getFilterClass($query);
if ($filters) {
return $filters->apply($query);
}
return $query;
}
//自动获取过滤的类
private function getFilterClass($query)
{
$prefix = 'App\Filters\\';
$filterClass = $this->getModelFilterClass($query);
if (!$filterClass) {
$currentClass = get_class($this);
$className = substr(strrchr($currentClass, '\\'), 1). 'Filter';
}
$filterClass = $prefix.$className;
if (class_exists($filterClass)) {
return new $filterClass();
}
$filterClass = $prefix.'Filters';
return new $filterClass();
}
//查看 model是否有 getModelClassName 方法
private function getModelFilterClass($query)
{
$model = $query->getModel();
$className = '';
if (method_exists($model, 'getFilterClassName')) {
$className = $model->getFilterClassName();
}
return $className;
}
}

View File

@ -6,6 +6,9 @@ use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use App\Listeners\SendDatabaseNotification;
use App\Listeners\BindBusinessGoods;
use App\Listeners\UpdateBusinessGoodsStock;
class EventServiceProvider extends ServiceProvider class EventServiceProvider extends ServiceProvider
{ {
@ -17,6 +20,9 @@ class EventServiceProvider extends ServiceProvider
protected $listen = [ protected $listen = [
Registered::class => [ Registered::class => [
SendEmailVerificationNotification::class, SendEmailVerificationNotification::class,
SendDatabaseNotification::class,
BindBusinessGoods::class,
UpdateBusinessGoodsStock::class,
], ],
]; ];

View File

@ -0,0 +1,90 @@
<?php
namespace App\Services\Business;
use App\Models\Log;
use App\Models\Shop;
use Facade\FlareClient\Http\Client;
abstract class BusinessClient
{
protected $redirectUri = 'http://erp.chutang66.com/callback';
protected $code;
protected $shop;
abstract public function auth();
abstract public function downloadGoods();
abstract public function bindGoods($goods);
abstract public function incrQuantity($skuId);
abstract public function downloadOrders($beginTime, $endTime, $activityNo, $downloadType = 'default');
abstract public function saveOrders($orders);
public function authCallback($code, $shopId)
{
$this->setCode($code);
$this->setShop($shopId);
$this->auth();
return $this;
}
public function setShopWithId($shopId)
{
$this->shop = Shop::query()->find($shopId);
return $this;
}
public function setShop(Shop $shop)
{
$this->shop = $shop;
return $this;
}
public function getShop()
{
return $this->shop;
}
protected function setCode($code)
{
$this->code = $code;
return $this;
}
protected function getCode()
{
return $this->code;
}
public function formDataPostRequest($url, $params)
{
$headers = [
'headers' => ['Content-type' => 'multipart/form-data;charset=UTF-8'],
'form_params' => $params
];
$res = (new Client())->makeCurlRequest('POST', $url, $headers);
$res = json_decode($res->getBody()->getContents(), true);
if (isset($res['error_response'])) {
$log = new Log();
$log->module = 'plat';
$log->action = 'POST';
$log->target_type = 'kuaituantuan';
$log->target_id = 0;
$log->target_field = '更新库存';
$log->user_id = 1;
$log->message = json_encode($res, 256);
$log->save();
throw new \Exception($res['error_response']['error_msg'], $res['error_response']['error_code']);
}
return $res;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Services\Business;
use App\Services\Business\KuaiTuanTuan\KuaiTuanTuan;
class BusinessFactory
{
private $platList;
public function __construct()
{
$this->platList['快团团'] = KuaiTuanTuan::class;
}
public function make($platName)
{
return new $this->platList[$platName];
}
public static function init()
{
return new self();
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Services\Business\KuaiTuanTuan;
use App\Models\BusinessGoodsSku;
class Goods
{
public static function downloadGoods($activityNo, $page = 1, $size = 100)
{
$type = 'pdd.ktt.goods.query.list';
$appendParams = [
'activity_no' => $activityNo, // 非必填,团号(团号和创建时间只能传一个)
'page' => $page,
'size' => $size,
// 'update_time_end' => '', // 非必填,结束最后更新时间(毫秒级时间戳)
// 'update_time_start' => '', // 非必填,起始最后更新时间(毫秒级时间戳)
// 'create_time_end' => '', // 非必填,开始时间结束(毫秒级时间戳)
// 'create_time_start' => '' // 非必填, 开始时间起始(毫秒级时间戳)
];
return [$type, $appendParams];
}
public static function bindGoods(array $goodsList, $shopId)
{
foreach ($goodsList as $businessGood) {
$skuList = $businessGood['sku_list'];
unset($businessGood['sku_list']);
foreach ($skuList as $sku) {
$sku['spec_list'] = json_encode($sku['spec_list'], 256);
$data = array_merge($businessGood, $sku);
BusinessGoodsSku::updateOrCreate(
['shop_id' => $shopId, 'activity_no' => $businessGood['activity_no'], 'goods_id' => $businessGood['goods_id'], 'sku_id' => $sku['sku_id']],
$data
);
}
}
}
public function downloadSingle($goodsId)
{
$type = 'pdd.ktt.goods.query.single';
$appendParams = [
'goods_id' => $goodsId
];
}
public static function incrQuantity($goodsId, $quantity, $skuId, $modifyType = 2)
{
$type = 'pdd.ktt.goods.incr.quantity';
$appendParams = [
'goods_id' => $goodsId,
'quantity_delta' => $quantity,
'sku_id' => $skuId,
// 非必填
'modify_quantity_type' => $modifyType, // 修改库存的类型不传或1代表增量修改2代表全量修改
];
return [$type, $appendParams];
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace App\Services\Business\KuaiTuanTuan;
use App\Listeners\BindBusinessGoods;
use App\Models\BusinessGoodsSku;
use App\Models\GoodsSku;
use App\Services\Business\BusinessClient;
use App\Models\Log;
class KuaiTuanTuan extends BusinessClient
{
// 所有的请求和响应数据编码皆为utf-8格式url里的所有参数值请做urlencode编码。
// 如果请求的content-type是 application/x-www-form-urlencoded所有参数值也做urlencode编码
// 如果是multipart/form-data格式每个表单字段的参数值无需编码但每个表单字段的charset需要指定为utf-8
// 如果指定接口返回数据格式为JSON请指明header头content-type: application/json
protected $clientId = '';
protected $clientSecret = '';
protected $publicParams = [
'type' => '',
'client_id' => '',
'access_token' => '', // 非必填,通过code获取的access_token
'timestamp' => '',
'data_type' => '', // 非必填,响应格式即返回数据的格式JSON或者XML二选一默认JSON注意是大写
'version' => '', // 非必填, API协议版本号默认为V1可不填
'sign' => ''
];
public function auth()
{
$accessToken = $this->getAccessTokenWithCode();
$accessToken['pop_auth_token_create_response'] = json_encode($accessToken, 256);
$this->shop->update($accessToken);
return $this->shop;
}
public function downloadGoods($page = 1)
{
[$type, $appendParams] = Goods::downloadGoods($this->shop->owner_name, $page);
$res = $this->formDataPostRequest($type, $appendParams);
$goods = $res['ktt_goods_query_list_response']['goods_list'];
$this->bindGoods($goods);
$pageNum = ceil($res['total'] / $appendParams['size']);
if ($pageNum > $page && 10 >= $page) {
$this->downloadGoods($page + 1);
}
}
public function bindGoods($goods)
{
Goods::bindGoods($goods, $this->shop->id);
}
public function incrQuantity($skuId)
{
$goodsSku = GoodsSku::query()->find($skuId);
$business = BusinessGoodsSku::query()->where('shop_id', $this->shop->id)->where('self_sku_id', $skuId)->first(['goods_id', 'sku_id']);
[$type, $appendParams] = Goods::incrQuantity($business->goodsId, $goodsSku->stock, $business->sku_id);
$this->formDataPostRequest($type, $appendParams);
$log = new Log();
$log->module = 'plat';
$log->action = 'POST';
$log->target_type = 'kuaituantuan';
$log->target_id = 0;
$log->target_field = $type;
$log->user_id = 1;
$log->message = 'success';
$log->save();
}
public function downloadOrders($beginTime, $endTime, $activityNo, $downloadType = 'default')
{
if ('increment' === $downloadType) {
[$type, $appendParams] = Order::downloadIncrementOrders($beginTime, $endTime, $activityNo);
} else {
[$type, $appendParams] = Order::downloadOrders($beginTime, $endTime, $activityNo);
}
$res = $this->formDataPostRequest($type, $appendParams);
return $res['ktt_order_list_response']['order_list'];
}
public function saveOrders($orders)
{
Order::saveOrders($orders, $this->shop->id);
}
protected function getAccessTokenWithCode()
{
$type = 'pdd.pop.auth.token.create';
$res = $this->doRequest($type, ['code' => $this->code]);
return $res['pop_auth_token_create_response'];
}
protected function getSign($params)
{
$params = ksort($params);
$str = '';
foreach ($params as $key => $val) {
$str .= $key . $val;
}
$str = $this->clientSecret . $str . $this->clientSecret;
return strtoupper(md5($str));
}
public function doRequest($type, $appendParams = [], $url = 'http://gw-api.pinduoduo.com/api/router')
{
$publicParams = [
'type' => $type,
'client_id' => $this->clientId,
'timestamp' => time()
];
if ('pdd.pop.auth.token.create' !== $type) {
$publicParams['access_token'] = $this->getAccessToken();
}
$publicParams = array_merge($publicParams, $appendParams);
$publicParams['sign'] = $this->getSign($publicParams);
return $this->formDataPostRequest($url, $publicParams);
}
public function getAuthUrl($shopId, $platId)
{
$state = $shopId . '_' . $platId;
return "https://oauth.pinduoduo.com/authorize/ktt?client_id={$this->clientId}&redirect_uri={$this->redirectUri}&state={$state}";
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Services\Business\KuaiTuanTuan;
use App\Models\BusinessOrder;
class Order
{
/**
* 根据成交时间拉取订单列表
*/
public static function downloadOrders($beginTime, $endTime, $activityNo)
{
$type = 'pdd.ktt.order.list';
$appendParams = [
'confirm_at_begin' => $beginTime, // 成交启始时间, 必填,毫秒时间戳
'confirm_at_end' => $endTime, // 成交结束时间,必填, 毫秒时间戳,成交结束时间 - 成交启始时间 <= 24h
'page_number' => 1, // 页码, 必填
'page_size' => 100, // 数量, 必填, 1100
// 非必填
'activity_no' => $activityNo, // 团号
'after_sales_status' => 0, // 售后状态, 可选 0-未发起售后 1-退款中 2-退款成功 3-待处理 4-拒绝退款 6-待(顾客)退货 7-待(团长)确认退货 8-(顾客)撤销 9-(系统)关闭
'cancel_status' => 0, // 取消状态, 可选 0-未取消 1-已取消
// 'shipping_status' => '', // 发货状态 0-未发货 1-已发货 2-部分发货 3-已收货
// 'verification_status' => '', // 核销状态, 可选 0-未核销 1-已核销 2-部分核销
];
return [$type, $appendParams];
}
/**
* 快团团增量查订单
*/
public static function downloadIncrementOrders($beginTime, $endTime, $activityNo)
{
$type = 'pdd.ktt.increment.order.query';
$appendParams = [
'start_updated_at' => $beginTime, // 更新起始时间
'end_updated_at' => $endTime, // 更新结束时间
'page_number' => 1, // 页码
'page_size' => 100, // 数量
// 非必填
'activity_no' => $activityNo, // 团号
'after_sales_status' => 0, // 售后状态, 可选 0-未发起售后 1-退款中 2-退款成功 3-待处理 4-拒绝退款 6-待(顾客)退货 7-待(团长)确认退货 8-(顾客)撤销 9-(系统)关闭
'cancel_status' => 0, // 取消状态, 可选 0-未取消 1-已取消
// 'shipping_status' => '', // 发货状态 0-未发货 1-已发货 2-部分发货 3-已收货
// 'verification_status' => '', // 核销状态, 可选 0-未核销 1-已核销 2-部分核销
];
return [$type, $appendParams];
}
// 下载订单事件之后去统计 然后更新库存
public static function saveOrders(array $orders, $shopId)
{
foreach ($orders as $order) {
$orderRecord = BusinessOrder::updateOrCreate(
['shop_id' => $shopId, 'order_sn' => $order['order_sn']],
$order
);
foreach ($order['sub_order_list'] as $item) {
$orderRecord = BusinessOrder::updateOrCreate(
['shop_id' => $shopId, 'business_order_id' => $orderRecord->id, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']],
$item
);
}
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Services\Business\MiaoXuan;
use App\Models\BusinessGoodsSku;
class Goods
{
public static function bindGoods(array $goods, $shopId)
{
foreach ($goods as $item) {
BusinessGoodsSku::updateOrCreate(
['shop_id' => $shopId, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']],
$item
);
}
}
public static function incrQuantity($shopId, $quantity, $businessGoods)
{
return [
'stock' => $quantity,
'business_sku_id' => $businessGoods['sku_id'],
'business_goods_id' => $businessGoods['goods_id'],
'erp_shop_id' => $shopId,
'erp_sku_id' => $businessGoods['external_sku_id']
];
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Services\Business\MiaoXuan;
use App\Models\BusinessGoodsSku;
use App\Models\GoodsSku;
use App\Models\Log;
use App\Services\Business\BusinessClient;
class MiaoXuan extends BusinessClient
{
public function auth()
{
// TODO: Implement auth() method.
}
public function downloadGoods()
{
}
public function bindGoods($goods)
{
Goods::bindGoods($goods, $this->shop->id);
}
public function incrQuantity($skuId)
{
$goodsSku = GoodsSku::query()->find($skuId);
$business = BusinessGoodsSku::query()->where('shop_id', $this->shop->id)->where('self_sku_id', $skuId)->first(['goods_id', 'sku_id', 'external_sku_id'])->toArray();
$appendParams = Goods::incrQuantity($this->shop->id, $goodsSku->stock, $business);
$url = ''; // http://shop.chutang66.com/miaoxuan/stock
$this->formDataPostRequest($url, $appendParams);
$log = new Log();
$log->module = 'plat';
$log->action = 'POST';
$log->target_type = 'miaoxuan';
$log->target_id = 0;
$log->target_field = '更新库存';
$log->user_id = 1;
$log->message = 'success';
$log->save();
}
public function downloadOrders($beginTime, $endTime, $activityNo, $downloadType = 'default')
{
}
public function saveOrders($orders)
{
Order::saveOrders($orders, $this->shop->id);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Services\Business\MiaoXuan;
use App\Models\BusinessOrder;
class Order
{
// 下载订单事件之后去统计 然后更新库存
public static function saveOrders(array $orders, $shopId)
{
foreach ($orders as $order) {
$orderRecord = BusinessOrder::updateOrCreate(
['shop_id' => $shopId, 'order_sn' => $order['order_sn']],
$order
);
foreach ($order['sub_order_list'] as $item) {
$orderRecord = BusinessOrder::updateOrCreate(
['shop_id' => $shopId, 'business_order_id' => $orderRecord->id, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']],
$item
);
}
}
}
}

23
app/Utils/ArrayUtils.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Utils;
class ArrayUtils
{
public static function index(array $array, $name)
{
$indexedArray = [];
if (empty($array)) {
return $indexedArray;
}
foreach ($array as $item) {
if (isset($item[$name])) {
$indexedArray[$item[$name]] = $item;
continue;
}
}
return $indexedArray;
}
}

50
app/Utils/FormatUtils.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace App\Utils;
class FormatUtils
{
/**
* 格式化为树形结构
* @param $menus
* @param $parentValue
* @param string $juniorTitle //下级标题
* @param string $parentKey
* @param string $subKey
* @param string $useKey
* @return array
*/
public static function formatTreeData($menus, $parentValue, $juniorTitle = 'children', $parentKey = 'parent_id', $subKey = 'id', $useKey = '')
{
$data = [];
foreach ($menus as $menu) {
if ($menu[$parentKey] == $parentValue) {
$res = self::formatTreeData($menus, $menu[$subKey], $juniorTitle, $parentKey, $subKey, $useKey);
if (!empty($res)) {
$menu[$juniorTitle] = $res;
}
if ($useKey) {
$data[$menu[$useKey]] = $menu;
} else {
$data[] = $menu;
}
}
}
return $data;
}
/**
* 今天7点到明天7点算作今天
*/
public static function date()
{
$time = time();
$inventoryTime = strtotime(date('Y-m-d 07:00:00'));
if ($time < $inventoryTime) {
$time = strtotime('-1 day');
}
return date('Y-m-d', $time);
}
}

123
app/Utils/UploadUtils.php Normal file
View File

@ -0,0 +1,123 @@
<?php
namespace App\Utils;
use Illuminate\Http\UploadedFile;
use Intervention\Image\Facades\Image;
use OSS\Core\OssException;
use OSS\OssClient;
use Illuminate\Support\Str;
class UploadUtils
{
private static $ossClient;
private static $bucket = 'ct-upimg';
private static $prefix = 'ju8hn6/erp/shop/';
public static function init()
{
if (static::$ossClient === null) {
static::$ossClient = new OssClient(config('filesystems.disks.aliyun.access_id'), config('filesystems.disks.aliyun.access_key'), config('filesystems.disks.aliyun.endpoint'));
}
if (static::$bucket === null) {
static::$bucket = config('filesystems.disks.aliyun.bucket');
}
}
public static function putForUploadedResizeImage($bucketPath, UploadedFile $upload, $width = 800, $extension = null)
{
if ($upload->extension() != 'gif') {
// 压缩图片
$originalName = static::randomFileName($extension ?: $upload->getClientOriginalExtension());
$storagePath = storage_path('app/public/file/') . $originalName;
$img = Image::make($upload->getRealPath());
// 未传宽度 大于800压缩至800 否则按照原宽度
$width = $img->width() >= 800 ? 800 : $img->width();
// 高度自适应
$img->resize($width, null, function ($constraint) {
$constraint->aspectRatio();
});
$img->save($storagePath);
$upload = new UploadedFile($storagePath, $originalName);
}
// 上传并删除压缩图片
try {
$filePath = static::put($bucketPath, $upload->getRealPath(), $upload->getClientOriginalExtension());
} catch (OssException $e) {
throw $e;
} finally {
if (isset($storagePath)) {
unlink($storagePath);
}
}
return $filePath;
}
//上传图片
public static function putForUploadedFile($bucketPath, UploadedFile $upload)
{
return static::put($bucketPath, $upload->getRealPath(), $upload->getClientOriginalExtension());
}
public static function put($bucketPath, $filepath, $ext)
{
static::init();
try {
$filePath = static::$prefix . $bucketPath . '/' . static::randomFileName($ext);
static::$ossClient->uploadFile(static::$bucket, $filePath, $filepath);
} catch (OssException $e) {
throw $e;
}
return $filePath;
}
public static function buildFilePath()
{
return date('Y') . '/' . date('m') . '/' . date('d');
}
public static function randomFileName($ext, $len = 40)
{
return Str::random($len) . '.' . $ext;
}
//删除修改前,上传修改后图片
public static function delAndUploadImage($file, $filePath, UploadedFile $upload)
{
static::delFile($file);
return static::putForUploadedFile($filePath, $upload);
}
//删除修改前,上传修改后视频
public static function delAndUploadVideo($file, $filePath, UploadedFile $upload)
{
static::delFile($file);
return static::putForUploadedFile($filePath, $upload);
}
public static function delFile($file)
{
static::init();
if (static::exist($file)) {
return static::$ossClient->deleteObject(static::$bucket, $file);
}
return false;
}
public static function exist($file)
{
static::init();
return static::$ossClient->doesObjectExist(static::$bucket, $file);
}
public static function getFullImgUrl($filePath)
{
return config('filesystems.disks.aliyun.url') . $filePath;
}
}

View File

@ -9,9 +9,13 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.2.5|^8.0", "php": "^7.2.5|^8.0",
"aliyuncs/oss-sdk-php": "^2.5",
"fideloper/proxy": "^4.4", "fideloper/proxy": "^4.4",
"intervention/image": "^2.7",
"laravel/framework": "^6.20.26", "laravel/framework": "^6.20.26",
"laravel/tinker": "^2.5" "laravel/tinker": "^2.5",
"maatwebsite/excel": "^3.1",
"spatie/laravel-permission": "*"
}, },
"require-dev": { "require-dev": {
"facade/ignition": "^1.16.15", "facade/ignition": "^1.16.15",

1003
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ return [
| |
*/ */
'name' => env('APP_NAME', 'Laravel'), 'name' => env('APP_NAME', 'CFen Erp'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -52,7 +52,7 @@ return [
| |
*/ */
'url' => env('APP_URL', 'http://localhost'), 'url' => env('APP_URL', 'http://erp.ii090.com'),
'asset_url' => env('ASSET_URL', null), 'asset_url' => env('ASSET_URL', null),

View File

@ -68,7 +68,7 @@ return [
'providers' => [ 'providers' => [
'users' => [ 'users' => [
'driver' => 'eloquent', 'driver' => 'eloquent',
'model' => App\User::class, 'model' => \App\Models\User::class,
], ],
// 'users' => [ // 'users' => [

View File

@ -48,12 +48,12 @@ return [
'url' => env('DATABASE_URL'), 'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'), 'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'), 'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'), 'database' => env('DB_DATABASE', 'erp'),
'username' => env('DB_USERNAME', 'forge'), 'username' => env('DB_USERNAME', ''),
'password' => env('DB_PASSWORD', ''), 'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''), 'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4', 'charset' => 'utf8',
'collation' => 'utf8mb4_unicode_ci', 'collation' => 'utf8_general_ci',
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => true, 'strict' => true,

View File

@ -64,7 +64,22 @@ return [
'url' => env('AWS_URL'), 'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'), 'endpoint' => env('AWS_ENDPOINT'),
], ],
'aliyun' => [
'driver' => 'oss',
'access_id' => 'LTAI6do39W9jINpe',
'access_key' => 'XLhp5w4WnRcqji204GYOm7hZsoXSB6',
'bucket' => 'ct-upimg',
'endpoint' => 'oss-cn-hangzhou.aliyuncs.com', // OSS 外网节点或自定义外部域名
// 'cdnDomain' => 'cdn-xiangma.yx090.com', // 如果isCName为true, getUrl会判断cdnDomain是否设定来决定返回的url如果cdnDomain未设置则使用endpoint来生成url否则使用cdn
'url' => 'https://ct-upimg.yx090.com/',
'ssl' => true,
'isCName' => false, // 是否使用自定义域名,true: 则Storage.url()会使用自定义的cdn或域名生成文件url false: 则使用外部节点生成url
'debug' => true
],
'root' => [
'driver' => 'local',
'root' => '/'
]
], ],
]; ];

161
config/permission.php Normal file
View File

@ -0,0 +1,161 @@
<?php
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related pivots other than defaults
*/
'role_pivot_key' => null, //default 'role_id',
'permission_pivot_key' => null, //default 'permission_id',
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
/*
* Change this if you want to use the teams feature and your related model's
* foreign key is other than `team_id`.
*/
'team_foreign_key' => 'team_id',
],
/*
* When set to true, the method for checking permissions will be registered on the gate.
* Set this to false, if you want to implement custom logic for checking permissions.
*/
'register_permission_check_method' => true,
/*
* When set to true the package implements teams using the 'team_foreign_key'. If you want
* the migrations to register the 'team_foreign_key', you must set this to true
* before doing the migration. If you already did the migration then you must make a new
* migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and
* 'model_has_permissions'(view the latest version of package's migration file)
*/
'teams' => false,
/*
* When set to true, the required permission names are added to the exception
* message. This could be considered an information leak in some contexts, so
* the default setting is false here for optimum safety.
*/
'display_permission_in_exception' => false,
/*
* When set to true, the required role names are added to the exception
* message. This could be considered an information leak in some contexts, so
* the default setting is false here for optimum safety.
*/
'display_role_in_exception' => false,
/*
* By default wildcard permission lookups are disabled.
*/
'enable_wildcard_permission' => false,
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];

View File

@ -2,7 +2,7 @@
/** @var \Illuminate\Database\Eloquent\Factory $factory */ /** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User; use App\Models\User;
use Faker\Generator as Faker; use Faker\Generator as Faker;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -19,10 +19,11 @@ use Illuminate\Support\Str;
$factory->define(User::class, function (Faker $faker) { $factory->define(User::class, function (Faker $faker) {
return [ return [
'name' => $faker->name, 'name' => 'erpAdmin',
'email' => $faker->unique()->safeEmail, 'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(), 'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'password' => 'password', // password
'api_token' => Str::random(60),
'remember_token' => Str::random(10), 'remember_token' => Str::random(10),
]; ];
}); });

View File

@ -19,6 +19,8 @@ class CreateUsersTable extends Migration
$table->string('email')->unique(); $table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable(); $table->timestamp('email_verified_at')->nullable();
$table->string('password'); $table->string('password');
$table->string('api_token', 80)->unique()->nullable(false);
$table->softDeletes();
$table->rememberToken(); $table->rememberToken();
$table->timestamps(); $table->timestamps();
}); });

View File

@ -0,0 +1,141 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Spatie\Permission\PermissionRegistrar;
class CreatePermissionTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$teams = config('permission.teams');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
Schema::create($tableNames['permissions'], function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name'); // For MySQL 8.0 use string('name', 125);
$table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125);
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) {
$table->bigIncrements('id');
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name'); // For MySQL 8.0 use string('name', 125);
$table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125);
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) {
$table->unsignedBigInteger(PermissionRegistrar::$pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign(PermissionRegistrar::$pivotPermission)
->references('id')
->on($tableNames['permissions'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
$table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) {
$table->unsignedBigInteger(PermissionRegistrar::$pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign(PermissionRegistrar::$pivotRole)
->references('id')
->on($tableNames['roles'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
$table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) {
$table->unsignedBigInteger(PermissionRegistrar::$pivotPermission);
$table->unsignedBigInteger(PermissionRegistrar::$pivotRole);
$table->foreign(PermissionRegistrar::$pivotPermission)
->references('id')
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign(PermissionRegistrar::$pivotRole)
->references('id')
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateGoodsTypesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('goods_types', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name')->nullable(false);
$table->softDeletes();
$table->timestamps();
// 索引
$table->unique('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('goods_types');
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateGoodsBrandsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('goods_brands', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name')->nullable(false);
$table->softDeletes();
$table->timestamps();
// 索引
$table->unique('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('goods_brands');
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateGoodsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('goods', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title')->nullable(false);
$table->string('img_url')->nullable()->comment('商品图片');
$table->unsignedBigInteger('type_id')->nullable(false)->comment('商品种类id');
$table->unsignedBigInteger('brand_id')->nullable()->comment('商品品牌id');
$table->string('goods_code', 32)->nullable(false)->comment('商品编码');
$table->timestamps();
// 索引
$table->unique('goods_code');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('goods');
}
}

View File

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateGoodsSkusTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('goods_skus', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('goods_id')->nullable(false)->comment('商品id');
$table->string('title')->nullable(false)->comment('商品规格');
$table->string('sku_code', 32)->nullable(false)->comment('规格编码');
$table->unsignedTinyInteger('status')->default(0)->comment('规格状态(0-下架,1在售,2预警)');
$table->unsignedInteger('num')->default(0)->comment('总量');
$table->unsignedInteger('stock')->default(0)->comment('库存');
$table->unsignedDecimal('cost')->default(0)->comment('成本');
$table->unsignedInteger('two_days_ago_num')->default(0)->comment('2天前库存');
$table->unsignedInteger('yesterday_num')->default(0)->comment('1天前库存');
$table->unsignedDecimal('reference_price')->default(0)->comment('参考售价');
$table->unsignedInteger('reserve')->default(0)->comment('预留量');
$table->timestamps();
// 索引
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('goods_skus');
}
}

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDailyStockRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('daily_stock_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('sku_id')->nullable(false);
$table->date('day')->nullable(false);
$table->unsignedInteger('arrived_today_num')->default(0)->comment('今日到货');
$table->unsignedInteger('loss_num')->default(0)->comment('损耗');
$table->unsignedInteger('inventory')->default(0)->comment('库存盘点');
$table->unique('sku_id', 'day');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('daily_stock_records');
}
}

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('logs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('module')->comment('模块');
$table->string('action')->comment('操作');
$table->string('target_type')->nullable()->comment('目标类型');
$table->bigInteger('target_id')->nullable()->default(0)->comment('目标id');
$table->string('target_field')->nullable()->comment('目标字段');
$table->text('before_update')->nullable()->comment('更新前数据');
$table->text('after_update')->nullable()->comment('更新后数据');
$table->text('message')->nullable()->comment('备注信息');
$table->bigInteger('user_id')->comment('操作人id');
$table->index(['target_type', 'target_id', 'target_field']);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('logs');
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMenusTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('menus', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('code', 32)->nullable(false)->comment('菜单编码');
$table->string('name', 32)->nullable(false)->comment('菜单名称');
$table->unsignedBigInteger('parent_id')->default(0);
$table->unsignedInteger('seq')->default(0)->comment('排序序号');
$table->softDeletes();
$table->timestamps();
// 索引
$table->unique('code');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('menus');
}
}

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateShopsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('shops', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name')->unique();
$table->unsignedTinyInteger('plat_id')->comment('平台id');
$table->string('access_token')->nullable();
$table->unsignedMediumInteger('expires_at')->nullable()->comment('access_token过期时间点');
$table->unsignedInteger('expires_in')->nullable()->comment('access_token过期时间段10表示10秒后过期');
$table->string('owner_id')->nullable()->comment('商家店铺id');
$table->string('owner_name')->nullable()->comment('商家账号名称');
$table->string('refresh_token')->nullable()->comment('refresh token可用来刷新access_token');
$table->unsignedMediumInteger('refresh_token_expires_at')->nullable()->comment('Refresh token过期时间点');
$table->unsignedInteger('refresh_token_expires_in')->nullable()->comment('refresh_token过期时间段10表示10秒后过期');
$table->text('scope')->nullable()->comment('接口列表');
$table->text('pop_auth_token_create_response')->nullable()->comment('授权认证信息');
$table->string('status')->default(0)->comment('状态');
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('shops');
}
}

View File

@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBusinessGoodsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('business_goods_skus', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('shop_id');
$table->bigInteger('self_sku_id')->nullable();
$table->string('activity_no')->nullable();
$table->string('category_name')->nullable();
$table->mediumInteger('create_time')->nullable();
$table->text('goods_desc')->nullable();
$table->string('goods_id')->nullable();
$table->text('goods_image_list')->nullable();
$table->string('goods_name')->nullable();
$table->integer('is_activity_delete')->nullable();
$table->integer('limit_buy')->nullable();
$table->mediumInteger('market_price')->nullable();
$table->mediumInteger('update_time')->nullable();
$table->string('external_sku_id')->nullable();
$table->mediumInteger('goods_purchase_price')->nullable();
$table->mediumInteger('price_in_fen')->nullable();
$table->mediumInteger('quantity')->nullable();
$table->integer('quantity_type')->nullable();
$table->mediumInteger('reserve_quantity')->nullable();
$table->mediumInteger('sku_id')->nullable();
$table->mediumInteger('sold_quantity')->nullable();
$table->text('spec_list')->nullable();
$table->string('spec_name')->nullable();
$table->string('thumb_url')->nullable();
$table->mediumInteger('total_quantity')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('business_goods');
}
}

View File

@ -0,0 +1,71 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBusinessOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('business_orders', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('shop_id');
$table->bigInteger('activity_no')->nullable();
$table->string('activity_title')->nullable();
$table->mediumInteger('after_sales_status')->nullable();
$table->string('business_note')->nullable();
$table->string('buyer_memo')->nullable();
$table->integer('cancel_status')->nullable();
$table->mediumInteger('confirm_at')->nullable();
$table->mediumInteger('discount_amount')->nullable();
$table->string('help_sell_nickname')->nullable();
$table->string('inner_transaction_id')->nullable();
$table->boolean('is_supplier')->nullable();
$table->integer('logistics_type')->nullable();
$table->integer('mall_activity_type')->nullable();
$table->string('nick_name')->nullable();
$table->mediumInteger('order_amount')->nullable();
$table->string('order_sn')->nullable();
$table->integer('participate_no')->nullable();
$table->mediumInteger('platform_discount_amount')->nullable();
$table->string('receiver_address_city')->nullable();
$table->string('receiver_address_detail')->nullable();
$table->string('receiver_address_district')->nullable();
$table->string('receiver_address_province')->nullable();
$table->string('receiver_mobile')->nullable();
$table->string('receiver_name')->nullable();
$table->string('secret_remark')->nullable();
$table->string('self_pick_site_no')->nullable();
$table->string('self_pick_up_address')->nullable();
$table->string('self_pick_up_contact_mobile')->nullable();
$table->string('self_pick_up_contact_name')->nullable();
$table->string('self_pick_up_site_name')->nullable();
$table->mediumInteger('service_amount')->nullable();
$table->mediumInteger('shipping_amount')->nullable();
$table->integer('shipping_status')->nullable();
$table->string('supply_activity_no')->nullable();
$table->integer('supply_participate_no')->nullable();
$table->mediumInteger('theoretical_refund_amount')->nullable();
$table->string('transaction_id')->nullable();
$table->mediumInteger('updated_at')->nullable();
$table->integer('verification_status')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('business_orders');
}
}

View File

@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBusinessOrderItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('business_order_items', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('shop_id');
$table->bigInteger('business_order_id');
$table->integer('already_cancel_number')->nullable();
$table->integer('cancel_status')->nullable();
$table->string('category_name')->nullable();
$table->string('external_sku_id')->nullable();
$table->mediumInteger('goods_amount')->nullable();
$table->mediumInteger('goods_cost_price')->nullable();
$table->mediumInteger('goods_id')->nullable();
$table->string('goods_name')->nullable();
$table->integer('goods_number')->nullable();
$table->mediumInteger('goods_price')->nullable();
$table->mediumInteger('goods_purchase_price')->nullable();
$table->string('goods_specification')->nullable();
$table->mediumInteger('help_sell_amount')->nullable();
$table->boolean('is_supplier')->nullable();
$table->integer('need_verification_number')->nullable();
$table->integer('shipping_status')->nullable();
$table->mediumInteger('sku_id')->nullable();
$table->string('sub_order_sn')->nullable();
$table->mediumInteger('theoretically_refund_amount')->nullable();
$table->string('thumb_url')->nullable();
$table->integer('verification_number')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('business_order_items');
}
}

View File

@ -12,5 +12,11 @@ class DatabaseSeeder extends Seeder
public function run() public function run()
{ {
// $this->call(UsersTableSeeder::class); // $this->call(UsersTableSeeder::class);
factory(\App\Models\User::class)->create();
$this->call([
MenusTableSeeder::class,
PermissionsTableSeeder::class,
RolesTableSeeder::class,
]);
} }
} }

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class MenusTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// 商品管理-(商品列表,商品种类,商品品牌)
$id = DB::table('menus')->insertGetId(['parent_id' => 0,'code' => 'GOODS_MANAGE', 'name' => '商品管理', 'seq' => 0]);
DB::table('menus')->insert([
['parent_id' => $id,'code' => 'GOODS_LIST', 'name' => '商品列表', 'seq' => 0],
['parent_id' => $id,'code' => 'GOODS_TYPE', 'name' => '商品种类', 'seq' => 1],
['parent_id' => $id,'code' => 'GOODS_BRAND', 'name' => '商品品牌', 'seq' => 2],
]);
// 店铺管理
DB::table('menus')->insertGetId(['parent_id' => 0,'code' => 'SHOP_MANAGE', 'name' => '店铺管理', 'seq' => 1]);
// 用户管理
DB::table('menus')->insertGetId(['parent_id' => 0,'code' => 'USER_MANAGE', 'name' => '用户管理', 'seq' => 2]);
// 系统管理-(角色管理,权限管理)
$id = DB::table('menus')->insertGetId(['parent_id' => 0,'code' => 'SYSTEM_MANAGE', 'name' => '系统管理', 'seq' => 3]);
DB::table('menus')->insert([
['parent_id' => $id,'code' => 'ROLE_MANAGE', 'name' => '角色管理', 'seq' => 0],
['parent_id' => $id,'code' => 'PERMISSION_MANAGE', 'name' => '权限管理', 'seq' => 1],
]);
// 系统日志
DB::table('menus')->insertGetId(['parent_id' => 0,'code' => 'SYSTEM_LOG', 'name' => '系统日志', 'seq' => 4]);
}
}

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class PermissionsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$routes = include(resource_path('lang/zh-CN/permission.php'));
$data = [];
foreach ($routes as $key => $route) {
$data[] = ['name' => $key, 'guard_name' => 'api'];
}
DB::table('permissions')->insert($data);
}
}

View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class RolesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('roles')->insert([
['name' => '超级管理员', 'guard_name' => 'api'],
]);
}
}

View File

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

17
resources/frontend/.eslintrc.js vendored Normal file
View File

@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

23
resources/frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,24 @@
# hello-world
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
resources/frontend/babel.config.js vendored Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

13478
resources/frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

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