6
0
Fork 0

门店商品库存批量

base
panliang 2023-02-20 14:56:06 +08:00
parent bd7ad825f8
commit 653bd1cc13
13 changed files with 370 additions and 27 deletions

View File

@ -0,0 +1,64 @@
<?php
namespace App\Admin\Actions\Store;
use Dcat\Admin\Grid\RowAction;
use App\Models\Admin\Administrator;
use App\Models\Store\{ProductSku, StockBatch};
class RowAddStock extends RowAction
{
protected $title = '确认';
public function handle()
{
// 添加库存记录
$list = [];
$model = StockBatch::findOrFail($this->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 ['是否确定?', '不可恢复'];
}
}

View File

@ -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();

View File

@ -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 <<<HTML
<table class="table table-bordered">
<tbody>
@ -157,8 +157,7 @@ class OrderController extends AdminController
<td>统计</td>
<td>订单数: $count</td>
<td>订单总额: $total_amount</td>
<td>市场价: $market_price</td>
<td>成本价: $cost_price</td>
<td>总毛利: $profit</td>
<td>成长值: $sales_value</td>
<tr>
</tbody>
@ -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;

View File

@ -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 <<<HTML
<table class="table table-bordered">
<tbody>
<tr>
<td>统计</td>
<td>总数: $count</td>
<td></td>
<td></td>
<td>成本价: $cost_price</td>
<td>销售价: $market_price</td>
<td>销售价: $sell_price</td>
<td>库存: $stock</td>
<td>总成本: $cost</td>
<td>总销售: $sell</td>
<tr>
</tbody>
</table>
@ -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');

View File

@ -0,0 +1,121 @@
<?php
namespace App\Admin\Controllers\Store;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\{Form, Grid, Admin, Show};
use App\Models\Store\{Store, StockBatch, StockLog};
use App\Models\{ProductSku};
use Dcat\Admin\Widgets\Table;
use App\Models\Admin\Administrator;
class StockBatchController extends AdminController
{
protected $translation = 'store-stock-bacth';
protected function grid()
{
return Grid::make(StockBatch::with(['store', 'adminUser', 'tag', 'productSkus']), function (Grid $grid) {
$grid->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);
});
}
}

View File

@ -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');

View File

@ -77,7 +77,7 @@ class ProductSku extends Model
StoreProductSku::where('product_sku_id', $model->id)->delete();
});
}
/**
* 仅查询已上架的商品
*

View File

@ -0,0 +1,46 @@
<?php
namespace App\Models\Store;
use Illuminate\Database\Eloquent\Model;
use Dcat\Admin\Traits\HasDateTimeFormatter;
use App\Models\Tag;
use App\Models\Admin\Administrator;
class StockBatch extends Model
{
use HasDateTimeFormatter;
protected $table = 'store_stock_batches';
protected $fillable = ['admin_user_id', 'store_id', 'remarks', 'sn', 'tag_id', 'status'];
protected $attributes = [
'status' => 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');
}
}

View File

@ -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();

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStoreStockBatchesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('store_stock_batches', function (Blueprint $table) {
$table->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');
}
}

View File

@ -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' => ''],
]
],
[

View File

@ -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' => [

View File

@ -0,0 +1,22 @@
<?php
return [
'labels' => [
'store' => '门店管理',
'StockBatch' => '库存批次',
'batch' => '库存批次',
],
'fields' => [
'sn' => '批次号',
'tag_id' => '类目',
'remarks' => '备注',
'productSkus' => '商品',
'store_id' => '门店',
'product_sku_id' => '商品',
'amount' => '数量',
'admin_user_id' => '操作人',
'status' => '状态',
],
'options' => [
],
];