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