From 653bd1cc13d38fa0ae8039a03d069e564c3e8d27 Mon Sep 17 00:00:00 2001 From: panliang <1163816051@qq.com> Date: Mon, 20 Feb 2023 14:56:06 +0800 Subject: [PATCH] =?UTF-8?q?=E9=97=A8=E5=BA=97=E5=95=86=E5=93=81=E5=BA=93?= =?UTF-8?q?=E5=AD=98=E6=89=B9=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Admin/Actions/Store/RowAddStock.php | 64 +++++++++ .../Controllers/AdministratorController.php | 21 +++ .../Controllers/Store/OrderController.php | 31 +++-- .../Controllers/Store/ProductController.php | 40 ++++-- .../Store/StockBatchController.php | 121 ++++++++++++++++++ app/Admin/routes.php | 2 + app/Models/ProductSku.php | 2 +- app/Models/Store/StockBatch.php | 46 +++++++ app/Providers/AppServiceProvider.php | 1 + ...60810_create_store_stock_batches_table.php | 45 +++++++ database/seeders/AdminMenuSeeder.php | 1 + database/seeders/AdminPermissionSeeder.php | 1 + resources/lang/zh_CN/store-stock-bacth.php | 22 ++++ 13 files changed, 370 insertions(+), 27 deletions(-) create mode 100644 app/Admin/Actions/Store/RowAddStock.php create mode 100644 app/Admin/Controllers/Store/StockBatchController.php create mode 100644 app/Models/Store/StockBatch.php create mode 100644 database/migrations/2023_02_19_160810_create_store_stock_batches_table.php create mode 100644 resources/lang/zh_CN/store-stock-bacth.php diff --git a/app/Admin/Actions/Store/RowAddStock.php b/app/Admin/Actions/Store/RowAddStock.php new file mode 100644 index 00000000..5ad42efa --- /dev/null +++ b/app/Admin/Actions/Store/RowAddStock.php @@ -0,0 +1,64 @@ +getKey()); + if ($model->status) { + return $this->response()->error('已经确认过了'); + } + $type = (new Administrator())->getMorphClass(); + foreach($model->productSkus()->get() as $item) { + $amount = $item->pivot?->amount; + if ($amount) { + $product = ProductSku::firstOrCreate([ + 'store_id' => $model->store_id, + 'product_sku_id' => $item->id + ], [ + 'amount' => 0, + 'status' => 1, + 'product_spu_id' => $item->spu_id, + ]); + $balance = $product->amount + $amount; + if ($balance < 0) { + continue; + } + array_push($list, [ + 'operator_type' => $type, + 'operator_id' => $model->admin_user_id, + 'amount' => $amount, + 'product_sku_id' => $item->id, + 'balance' => $balance, + 'store_id' => $model->store_id, + 'tag_id' => $model->tag_id, + 'remarks' => '批次号: ' . $model->sn, + ]); + + $product->update(['amount' => $balance]); + } + } + + $model->logs()->createMany($list); + + $model->update(['status' => 1]); + + return $this->response()->success('操作成功')->refresh(); + } + + public function confirm() + { + return ['是否确定?', '不可恢复']; + } +} + diff --git a/app/Admin/Controllers/AdministratorController.php b/app/Admin/Controllers/AdministratorController.php index fe80250c..1efd5bd1 100644 --- a/app/Admin/Controllers/AdministratorController.php +++ b/app/Admin/Controllers/AdministratorController.php @@ -4,9 +4,30 @@ namespace App\Admin\Controllers; use Dcat\Admin\Admin; use Dcat\Admin\Http\Controllers\UserController; +use App\Models\Admin\Administrator; +use Illuminate\Http\Request; class AdministratorController extends UserController { + public function list(Request $request) + { + $query = Administrator::select('id', 'name as text'); + + if ($request->filled('q')) { + $phone = $request->input('q'); + $query->where(fn($q) => $q->where('username', 'like', "%$phone%")->orWhere('name', 'like', "%$phone%")); + } + + if ($request->filled('_paginate')) { + $list = $query->paginate(); + } else { + $list = $query->get(); + } + + + return $list; + } + public function grid() { $grid = parent::grid(); diff --git a/app/Admin/Controllers/Store/OrderController.php b/app/Admin/Controllers/Store/OrderController.php index b991d78d..fafcf227 100644 --- a/app/Admin/Controllers/Store/OrderController.php +++ b/app/Admin/Controllers/Store/OrderController.php @@ -76,11 +76,9 @@ class OrderController extends AdminController })->prepend('¥'); $grid->column('sales_value'); $grid->column('order_status')->using($this->statusMap)->dot($this->statusColor); - $grid->column('pay_way')->display(function ($v) { - return $v?->mallText(); - })->circleDot(PayWay::colors()); - $grid->column('pay_at'); $grid->column('created_at'); + $grid->column('pay_at'); + $grid->column('completed_at'); $grid->showViewButton($user->can('dcat.admin.store.order.show')); @@ -100,10 +98,10 @@ class OrderController extends AdminController ->orWhereHas('inviterInfo', fn($q) => $q->where('nickname', 'like', '%'.$this->input.'%')); }); })->width(3)->placeholder('昵称/手机号'); - $filter->equal('pay_way')->select([ - PayWay::WxpayMiniProgram->value => PayWay::WxpayMiniProgram->text(), - PayWay::Offline->value => PayWay::Offline->text(), - ])->width(3); + // $filter->equal('pay_way')->select([ + // PayWay::WxpayMiniProgram->value => PayWay::WxpayMiniProgram->text(), + // PayWay::Offline->value => PayWay::Offline->text(), + // ])->width(3); $filter->where('order_status', function ($q) { switch ($this->input) { case OrderStatus::PENDING: @@ -134,6 +132,7 @@ class OrderController extends AdminController OrderStatus::CANCELLED => '已取消' ])->width(3); $filter->between('created_at')->dateTime()->width(6); + $filter->between('completed_at')->dateTime()->width(6); }); $grid->footer(function ($collection) use ($grid) { @@ -146,10 +145,11 @@ class OrderController extends AdminController $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []); }); $count = $query->count(); - $total_amount = number_format($query->sum('total_amount') / 100); - $market_price = number_format($query->sum('market_price') / 100); - $cost_price = number_format($query->sum('cost_price') / 100); - $sales_value = number_format($query->sum('sales_value')); + $total_amount = round($query->sum('total_amount') / 100, 2, PHP_ROUND_HALF_DOWN); + $market_price = round($query->sum('market_price') / 100, 2, PHP_ROUND_HALF_DOWN); + $cost_price = round($query->sum('cost_price') / 100, 2, PHP_ROUND_HALF_DOWN); + $sales_value = round($query->sum('sales_value'), 2, PHP_ROUND_HALF_DOWN); + $profit = round($total_amount - $cost_price, 2, PHP_ROUND_HALF_DOWN); return << @@ -157,8 +157,7 @@ class OrderController extends AdminController 统计 订单数: $count 订单总额: $total_amount - 市场价: $market_price - 成本价: $cost_price + 总毛利: $profit 成长值: $sales_value @@ -184,6 +183,10 @@ class OrderController extends AdminController $show->field('cost_price')->as(function ($value) { return bcdiv($value, 100, 2); }); + $show->field('products_total_amount')->as(fn($value) => bcdiv($value, 100, 2)); + $show->field('coupon_discount_amount')->as(fn($value) => bcdiv($value, 100, 2)); + // $show->field('vip_discount_amount')->as(fn($value) => bcdiv($value, 100, 2)); + $show->field('total_amount', '支付金额')->as(fn($value) => bcdiv($value, 100, 2)); $show->field('sales_value'); $show->field('order_status')->as(function ($v) { return $this->order_status; diff --git a/app/Admin/Controllers/Store/ProductController.php b/app/Admin/Controllers/Store/ProductController.php index f6686119..e38c3352 100644 --- a/app/Admin/Controllers/Store/ProductController.php +++ b/app/Admin/Controllers/Store/ProductController.php @@ -7,6 +7,7 @@ use Dcat\Admin\Http\Controllers\AdminController; use Dcat\Admin\Models\Administrator; use App\Models\{ProductCategory, ProductSku}; use App\Models\Store\{Store, ProductSku as StoreProductSku}; +use Illuminate\Support\Facades\DB; class ProductController extends AdminController { @@ -57,9 +58,22 @@ class ProductController extends AdminController return bcdiv($value, 100, 2); }); $grid->column('productSku.sell_price', '销售价')->display(function ($value) { - return bcdiv($value, 100, 2);; + return bcdiv($value, 100, 2); + }); + $grid->column('amount', '库存'); + $grid->column('profit', '毛利')->display(function () { + $sell_price = data_get($this->productSku, 'sell_price'); + $cost_price = data_get($this->productSku, 'cost_price'); + return round(($sell_price - $cost_price) / 100, 2, PHP_ROUND_HALF_DOWN); + }); + $grid->column('cost', '成本')->display(function () { + $cost_price = data_get($this->productSku, 'cost_price'); + return round($cost_price / 100 * $this->amount, 2, PHP_ROUND_HALF_DOWN); + }); + $grid->column('sell', '销售')->display(function () { + $sell_price = data_get($this->productSku, 'sell_price'); + return round($sell_price / 100 * $this->amount, 2, PHP_ROUND_HALF_DOWN); }); - $grid->column('amount', '库存')->editable(); $grid->column('status', '状态')->switch(); $grid->paginate(10); @@ -71,7 +85,10 @@ class ProductController extends AdminController $grid->filter(function (Grid\Filter $filter) { $filter->panel(); $filter->equal('store_id')->select('api/store')->width(3); - $filter->equal('productSku.category_id', '分类')->select(admin_route('api.product_categories', ['level' => 2]))->width(3); + $filter->where('category_id', function ($q) { + $id = $this->input; + $q->whereHas('productSku', fn($q1) => $q1->filter(['category' => $id], \App\Endpoint\Api\Filters\ProductSkuFilter::class)); + }, '分类')->select(ProductCategory::selectOptions())->width(3); $filter->like('productSku.name', '名称')->width(3); }); $grid->tools(function (Grid\Tools $tools) { @@ -89,22 +106,22 @@ class ProductController extends AdminController $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []); }); $count = $query->count(); - $market_price = 0; - $cost_price = 0; - $market_price = number_format($query->sum('product_skus.market_price') / 100); - $cost_price = number_format($query->sum('product_skus.cost_price') / 100); - $stock = number_format($query->sum('store_product_skus.amount')); + $sell_price = round($query->sum('product_skus.sell_price') / 100, 2, PHP_ROUND_HALF_DOWN); + $cost_price = round($query->sum('product_skus.cost_price') / 100, 2, PHP_ROUND_HALF_DOWN); + $stock = round($query->sum('store_product_skus.amount'), 2, PHP_ROUND_HALF_DOWN); + $cost = round($query->sum(DB::raw('product_skus.cost_price * amount')) / 100, 2, PHP_ROUND_HALF_DOWN); + $sell = round($query->sum(DB::raw('product_skus.sell_price * amount')) / 100, 2, PHP_ROUND_HALF_DOWN); return << 统计 总数: $count - - 成本价: $cost_price - 销售价: $market_price + 销售价: $sell_price 库存: $stock + 总成本: $cost + 总销售: $sell @@ -120,7 +137,6 @@ class ProductController extends AdminController $form->select('product_sku_id')->options(ProductSku::class)->ajax('api/product-skus'); $form->hidden('product_spu_id'); $form->switch('status', '状态')->default(1); - $form->number('amount'); $form->saving(function (Form $form) { if ($form->isCreating()) { $store_id = $form->input('store_id'); diff --git a/app/Admin/Controllers/Store/StockBatchController.php b/app/Admin/Controllers/Store/StockBatchController.php new file mode 100644 index 00000000..818a0192 --- /dev/null +++ b/app/Admin/Controllers/Store/StockBatchController.php @@ -0,0 +1,121 @@ +model()->orderBy('created_at', 'desc'); + + $grid->column('sn'); + $grid->column('store_id')->display(fn() => data_get($this->store, 'title')); + $grid->column('tag_id')->display(fn() => data_get($this->tag, 'name')); + $grid->column('admin_user_id')->display(fn() => data_get($this->adminUser, 'name')); + $grid->column('status')->using([0 => '未确认', 1 => '已确认'])->label([0 => 'danger', 1 => 'success']); + $grid->column('product_sku_id')->display(fn() => '共'.$this->productSkus->count().'件')->modal(function ($modal) { + $modal->title('商品记录'); + $data = $this->productSkus->map(fn($item) => [$item->name, $item->pivot?->amount])->all(); + return Table::make(['名称', '数量'], $data); + }); + $grid->column('remarks')->editable(); + $grid->column('created_at'); + + $grid->filter(function (Grid\Filter $filter) { + $filter->panel(); + $filter->equal('store_id')->select('api/store')->width(3); + $filter->equal('tag_id', '类目')->select(StockLog::tags()->pluck('name', 'id'))->width(3); + $filter->equal('admin_user_id')->select()->model(Administrator::class)->ajax('api/administrators?_paginate=1')->width(3); + $filter->equal('status')->select([0 => '未确认', 1 => '已确认'])->width(3); + }); + + $user = Admin::user(); + $grid->showCreateButton($user->can('dcat.admin.store.batch.create')); + $grid->showViewButton($user->can('dcat.admin.store.batch.show')); + + $grid->actions(function (Grid\Displayers\Actions $actions) use ($user) { + $row = $actions->row; + if (!$row->status) { + $actions->append(new \App\Admin\Actions\Store\RowAddStock()); + } + + $actions->delete($user->can('dcat.admin.store.batch.destroy') && !$row->status); + $actions->edit($user->can('dcat.admin.store.batch.edit') && !$row->status); + }); + }); + } + + protected function form() + { + return Form::make(StockBatch::with(['productSkus']), function (Form $form) { + $fullForm = $form->isCreating() || ($form->isEditing() && $form->model()->status == 0); + if ($fullForm) { + $form->text('sn')->required(); + $form->select('store_id')->options(Store::pluck('title', 'id'))->required(); + $form->select('tag_id')->options(StockLog::tags()->pluck('name', 'id'))->required(); + $form->text('remarks'); + $form->hidden('admin_user_id')->value(Admin::user()->id); + + $form->table('productSkus', function (Form\NestedForm $form) { + $form->select('product_sku_id')->options(ProductSku::class)->ajax('api/product-skus'); + $form->text('amount')->help('正数为增加, 负数为减少'); + })->saving(function ($value) { + $result = []; + if ($value) { + $products = ProductSku::whereIn('id', array_column($value, 'product_sku_id'))->get(); + foreach($value as $item) { + $product = $products->firstWhere('id', $item['product_sku_id']); + if ($product) { + array_push($result, [ + 'product_sku_id' => $product->id, + 'product_spu_id' => $product->spu_id, + 'amount' => $item['amount'] + ]); + } + } + } + + return $result; + }); + } else { + $form->text('sn')->required(); + $form->text('remarks'); + } + + $form->disableCreatingCheck(); + $form->disableEditingCheck(); + $form->disableResetButton(); + }); + } + + protected function detail($id) + { + return Show::make($id, StockBatch::with(['store', 'adminUser', 'tag', 'productSkus']), function (Show $show) { + $show->field('sn'); + $show->field('store_id')->as(fn() => $this->store?->title); + $show->field('tag_id')->as(fn() => $this->tag?->name); + $show->field('admin_user_id')->as(fn() => $this->adminUser?->name); + $show->field('remarks'); + $show->field('product_sku_id')->as(function () { + $data = $this->productSkus->map(fn($item) => [$item->name, $item->pivot?->amount])->all(); + return Table::make(['名称', '数量'], $data); + })->unescape(); + + $user = Admin::user(); + $model = $show->model(); + + $show->disableEditButton(!$user->can('dcat.admin.store.batch.edit') || $model->status); + $show->disableDeleteButton(!$user->can('dcat.admin.store.batch.destroy') || $model->status); + }); + } +} diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 472b2969..0743a27e 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -169,6 +169,7 @@ Route::group([ $router->get('api/product-by-store', 'Store\ProductController@listByStore')->name('api.store_product'); $router->get('api/store', 'Store\StoreController@list')->name('api.store'); $router->post('api/wang-editor/upload', 'WangEditoController@upload')->name('api.wang-editor.upload'); + $router->get('api/administrators', 'AdministratorController@list')->name('api.administrators'); // 抽奖管理 $router->resource('draw-prizes', 'Draw\DrawPrizeController')->names('draw_prizes'); @@ -192,6 +193,7 @@ Route::group([ $router->resource('store/user', 'Store\UserController')->only(['index'])->names('store.user'); $router->resource('store/order', 'Store\OrderController')->only(['index', 'show'])->names('store.order'); $router->resource('store/stock', 'Store\StockController')->only(['index', 'create', 'store', 'show'])->names('store.stock'); + $router->resource('store/batch', 'Store\StockBatchController')->names('store.batch'); $router->resource('profit', 'OrderProfitController'); diff --git a/app/Models/ProductSku.php b/app/Models/ProductSku.php index 5a405324..ca446ee7 100644 --- a/app/Models/ProductSku.php +++ b/app/Models/ProductSku.php @@ -77,7 +77,7 @@ class ProductSku extends Model StoreProductSku::where('product_sku_id', $model->id)->delete(); }); } - + /** * 仅查询已上架的商品 * diff --git a/app/Models/Store/StockBatch.php b/app/Models/Store/StockBatch.php new file mode 100644 index 00000000..1ed1cf88 --- /dev/null +++ b/app/Models/Store/StockBatch.php @@ -0,0 +1,46 @@ + 0 + ]; + + public function store() + { + return $this->belongsTo(Store::class, 'store_id'); + } + + public function adminUser() + { + return $this->belongsTo(Administrator::class, 'admin_user_id'); + } + + public function tag() + { + return $this->belongsTo(Tag::class, 'tag_id'); + } + + public function productSkus() + { + return $this->belongsToMany(\App\Models\ProductSku::class, 'store_stock_batch_goods', 'batch_id', 'product_sku_id')->withPivot('amount'); + } + + public function logs() + { + return $this->morphMany(StockLog::class, 'source'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9e81c38b..28798ed3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -57,6 +57,7 @@ class AppServiceProvider extends ServiceProvider 'admin_users' => \App\Models\Admin\Administrator::class, 'user_vip' => \App\Models\UserVip::class, 'draw_log' => \App\Models\DrawLog::class, + 'store_stock_batch' => \App\Models\Store\StockBatch::class, ]); JsonResource::withoutWrapping(); diff --git a/database/migrations/2023_02_19_160810_create_store_stock_batches_table.php b/database/migrations/2023_02_19_160810_create_store_stock_batches_table.php new file mode 100644 index 00000000..249b3ffe --- /dev/null +++ b/database/migrations/2023_02_19_160810_create_store_stock_batches_table.php @@ -0,0 +1,45 @@ +id(); + $table->unsignedBigInteger('store_id')->comment('门店'); + $table->unsignedBigInteger('admin_user_id')->comment('操作人'); + $table->string('sn')->comment('批次号'); + $table->unsignedBigInteger('tag_id')->comment('变更类目(tags.id)'); + $table->string('remarks')->nullable()->comment('备注'); + $table->unsignedInteger('status')->default(0)->comment('0: 未确认, 1: 已确认'); + $table->timestamps(); + }); + + Schema::create('store_stock_batch_goods', function (Blueprint $table) { + $table->unsignedBigInteger('batch_id'); + $table->unsignedBigInteger('product_sku_id'); + $table->unsignedBigInteger('product_spu_id'); + $table->integer('amount')->comment('数量'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('store_stock_batches'); + Schema::dropIfExists('store_stock_batch_goods'); + } +} diff --git a/database/seeders/AdminMenuSeeder.php b/database/seeders/AdminMenuSeeder.php index e6dff5a2..d6b43ace 100644 --- a/database/seeders/AdminMenuSeeder.php +++ b/database/seeders/AdminMenuSeeder.php @@ -94,6 +94,7 @@ class AdminMenuSeeder extends Seeder ['title' => '员工业绩', 'uri' => 'store/user', 'icon' => ''], ['title' => '订单管理', 'uri' => 'store/order', 'icon' => ''], ['title' => '库存管理', 'uri' => 'store/stock', 'icon' => ''], + ['title' => '库存批次', 'uri' => 'store/batch', 'icon' => ''], ] ], [ diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index bf5bb7a2..c719d631 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -333,6 +333,7 @@ class AdminPermissionSeeder extends Seeder 'user' => ['name' => '员工业绩', 'curd' => ['index']], 'order' => ['name' => '订单管理', 'curd' => ['index', 'show']], 'stock' => ['name' => '库存管理', 'curd' => ['index', 'create', 'store', 'show']], + 'batch' => ['name' => '批次管理', 'curd' => true], ], ], 'profit' => [ diff --git a/resources/lang/zh_CN/store-stock-bacth.php b/resources/lang/zh_CN/store-stock-bacth.php new file mode 100644 index 00000000..74a2756c --- /dev/null +++ b/resources/lang/zh_CN/store-stock-bacth.php @@ -0,0 +1,22 @@ + [ + 'store' => '门店管理', + 'StockBatch' => '库存批次', + 'batch' => '库存批次', + ], + 'fields' => [ + 'sn' => '批次号', + 'tag_id' => '类目', + 'remarks' => '备注', + 'productSkus' => '商品', + 'store_id' => '门店', + 'product_sku_id' => '商品', + 'amount' => '数量', + 'admin_user_id' => '操作人', + 'status' => '状态', + ], + 'options' => [ + ], +]; \ No newline at end of file