diff --git a/app/Admin/Controllers/Store/ProductController.php b/app/Admin/Controllers/Store/ProductController.php index 3eccdf38..d3d75c40 100644 --- a/app/Admin/Controllers/Store/ProductController.php +++ b/app/Admin/Controllers/Store/ProductController.php @@ -56,20 +56,20 @@ class ProductController extends AdminController $grid->column('productSku.specs', '规格')->label(); $grid->column('productSku.sell_price', '销售价')->display(fn ($value) => bcdiv($value, 100, 2)); $grid->column('productSku.cost_price', '成本价')->display(fn ($value) => bcdiv($value, 100, 2)); - $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('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('amount', '库存'); - $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('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('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('status', '状态')->switch(); $grid->paginate(10); @@ -92,33 +92,30 @@ class ProductController extends AdminController $tools->append(new \App\Admin\Actions\Store\DownloadProductTemplate()); }); - $grid->footer(function ($collection) use ($grid) { - $query = StoreProductSku::join('product_skus', 'product_skus.id', '=', 'store_product_skus.product_sku_id'); - $grid->model()->getQueries()->unique()->each(function ($value) use (&$query) { - if (in_array($value['method'], ['paginate', 'get', 'orderBy', 'orderByDesc'], true)) { - return; - } + // $grid->footer(function ($collection) use ($grid) { + // $query = StoreProductSku::join('product_skus', 'product_skus.id', '=', 'store_product_skus.product_sku_id'); + // $grid->model()->getQueries()->unique()->each(function ($value) use (&$query) { + // if (in_array($value['method'], ['paginate', 'get', 'orderBy', 'orderByDesc'], true)) { + // return; + // } - $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []); - }); - $count = $query->count(); - $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 - 库存: $stock - 总销售: $sell - 总成本: $cost - - - - HTML; - }); + // $query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []); + // }); + // $count = $query->count(); + // return << + // + // + // 统计 + // 总数: $count + // + // + // + // + // + // + // HTML; + // }); return $grid; } diff --git a/app/Admin/Controllers/Store/StockController.php b/app/Admin/Controllers/Store/StockController.php index ccef755d..8970ba15 100644 --- a/app/Admin/Controllers/Store/StockController.php +++ b/app/Admin/Controllers/Store/StockController.php @@ -8,7 +8,6 @@ use App\Models\{User, ProductCategory}; use Carbon\Carbon; use Dcat\Admin\{Form, Grid, Admin, Show}; use Dcat\Admin\Http\Controllers\AdminController; -use Illuminate\Database\Eloquent\Relations\Relation; class StockController extends AdminController { diff --git a/app/Admin/Controllers/Store/StockTotalController.php b/app/Admin/Controllers/Store/StockTotalController.php new file mode 100644 index 00000000..513c41d8 --- /dev/null +++ b/app/Admin/Controllers/Store/StockTotalController.php @@ -0,0 +1,62 @@ +get(); + $grid->column('name', '产品'); + $grid->column('category_name', '分类'); + $grid->column('cost_price', '成本单价'); + $grid->column('sell_price', '售价'); + + $grid->column('tag_in', '入库数'); + $grid->column('tag_sell', '销售数'); + $grid->column('tag_online', '线上销售数'); + $grid->column('tag_loss', '报损数'); + $grid->column('tag_self', '自用数'); + $grid->column('tag_out', '调货数'); + + $grid->column('stock', '现有库存')->sortable(); + $grid->column('total_profit', '商品总毛利'); + + $grid->disableActions(); + + $grid->filter(function (Grid\Filter $filter) use ($tags) { + $filter->panel(); + $filter->equal('store_id', '门店')->select(Store::pluck('title', 'id'))->width(3); + $filter->equal('category_id', '分类')->select(ProductCategory::selectOptions())->width(3); + $filter->equal('product_name', '商品')->width(3); + $filter->between('date', '时间')->date()->width(6); + }); + + $grid->footer(function ($collection) use ($grid) { + $total = $grid->model()->repository()->getTotal($grid->model()); + $html = ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
入库总成本: '.data_get($total, 'total_in').'销售总金额: '.data_get($total, 'total_sell').'线上销售总金额: '.data_get($total, 'total_online').'报损总成本: '.data_get($total, 'total_loss').'自用总成本: '.data_get($total, 'total_self').'调货总金额: '.data_get($total, 'total_out').'现有总成本: '.data_get($total, 'total_cost_price').'现有总价值: '.data_get($total, 'total_sell_price').'产品毛利汇总: '.data_get($total, 'total_profit').'
'; + return $html; + }); + }); + } +} diff --git a/app/Admin/Forms/OrderPackage.php b/app/Admin/Forms/OrderPackage.php index d12b9b6f..e4348fdf 100644 --- a/app/Admin/Forms/OrderPackage.php +++ b/app/Admin/Forms/OrderPackage.php @@ -11,6 +11,8 @@ use Dcat\Admin\Widgets\Form; use Illuminate\Support\Facades\DB; use Throwable; use Dcat\Admin\Admin; +use App\Models\Store\ProductSku as StoreProductSku; +use App\Models\Store\Store; class OrderPackage extends Form implements LazyRenderable { @@ -45,10 +47,11 @@ class OrderPackage extends Form implements LazyRenderable $orderPackageService = new OrderPackageService(); $orderPackageService->createPackage($order, $input); // 店铺发货, 添加出库记录 - if ($order->store) { + $store_id = data_get($input, 'store_id', $order->store_id); + if ($store_id) { $packageProducts = $input['packages']; $operator = Admin::user(); - $store = $order->store; + $store = Store::findOrFail($store_id); $tag = Tag::firstOrCreate([ 'type' => Tag::TYPE_STORE_STOCK, 'name' => '发货' @@ -114,6 +117,10 @@ class OrderPackage extends Form implements LazyRenderable '提货' => '提货', ])->default('韵达快递')->required(); $this->text('shipping_number')->required(); + + $list = StoreProductSku::with(['store'])->whereIn('product_sku_id', $order->products->pluck('sku_id'))->get(); + $stores = $list->pluck('store'); + $this->select('store_id', '发货门店')->options($stores->pluck('title', 'id'))->value($order->store_id); $products = $order->products->filter(function ($items) { if ($items->after_sale_state != 1) { return true; diff --git a/app/Admin/Repositories/StoreTockRepository.php b/app/Admin/Repositories/StoreTockRepository.php new file mode 100644 index 00000000..dda8fa2a --- /dev/null +++ b/app/Admin/Repositories/StoreTockRepository.php @@ -0,0 +1,144 @@ +getCurrentPage(); + // 每页显示行数 + $perPage = $model->getPerPage(); + + $query = $this->getQuery($model); + + $list = $query->paginate($perPage, ['*'], 'page', $currentPage); + $total = $query->count(); + $tags = StockLog::tags()->get(); + $data = []; + foreach($list as $item) { + $stock = $item->stockLogs; + $in = $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_IN)->sum('amount'); + $loss = $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_LOSS)->sum('amount'); + $self = $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_SELF)->sum('amount'); + $sell_price = data_get($item->productSku, 'sell_price') / 100; + $cost_price = data_get($item->productSku, 'cost_price') / 100; + $subData = [ + 'id' => $item->product_sku_id, + 'name' => data_get($item->productSku, 'name'), + 'category_id' => data_get($item->productSku, 'category.id'), + 'category_name' => data_get($item->productSku, 'category.name'), + 'cost_price' => $cost_price, + 'sell_price' => $sell_price, + 'stock' => data_get($item, 'amount'), + // 入库数 + 'tag_in' => $in, + // 销售数 = 提货数 + 发货数 + 'tag_sell' => $stock->where('product_sku_id', $item->product_sku_id)->whereIn('tag_id', [StockLog::TAG_CARRY, StockLog::TAG_SEND])->sum('amount'), + // 线上销售数 = 发货数 + 'tag_online' => $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_SEND)->sum('amount'), + // 报损数 + 'tag_loss' => $loss, + // 自用数 + 'tag_self' => $self, + // 调货数 = 出库数 + 'tag_out' => $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_OUT)->sum('amount'), + // 毛利 = (入库数-报损数-自用数) x (售价-成本) + 'total_profit' => floor(($in - $loss - $self) * ($sell_price - $cost_price)), + ]; + array_push($data, $subData); + } + + return $model->makePaginator($total, $data); + } + + public function getQuery(Grid\Model $model) + { + $query = ProductSku::with([ + 'productSku.category', + 'stockLogs' => function ($q) use ($model) { + $q->select('id', 'product_sku_id', DB::raw('abs(`amount`) as `amount`'), 'tag_id'); + if ($start = $model->filter()->input('date.start')) { + $start = Carbon::createFromFormat('Y-m-d', $start); + $q->where('created_at', '>=', $start); + } + if ($end = $model->filter()->input('date.end')) { + $end = Carbon::createFromFormat('Y-m-d', $end); + $q->where('created_at', '<=', $end); + } + } + ]); + + // 查询条件 + $filters = $model->filter()->input(); + foreach($filters as $key => $value) { + if ($key == 'category_id') { + $query->whereHas('productSku', fn($q1) => $q1->filter(['category' => $value], \App\Endpoint\Api\Filters\ProductSkuFilter::class)); + } else if ($key == 'store_id') { + $query->where('store_id', $value); + } else if ($key == 'product_name') { + $query->whereHas('productSku', fn($q1) => $q1->filter(['keyword' => $value], \App\Endpoint\Api\Filters\ProductSkuFilter::class)); + } + } + // 排序条件 + [$orderColumn, $orderType] = $model->getSort(); + if ($orderColumn == 'stock') { + $query->orderBy('amount', $orderType); + } + + return $query; + } + + public function getTotal(Grid\Model $model) + { + $query = $this->getQuery($model); + $list = $query->get(); + $data = [ + // 入库总成本 = 总数量 * 成本价 + 'total_in' => 0, + // 销售数 = (提货数 + 发货数) * 售价 + 'total_sell' => 0, + // 线上销售总金额 = 发货数 * 售价 + 'total_online' => 0, + // 报损总成本 = 报损数 * 成本价 + 'total_loss' => 0, + // 自用总成本 = 自用数 * 成本价 + 'total_self' => 0, + // 调货总成本 = 调货数(出库) * 成本价 + 'total_out' => 0, + // 现有总成本 = 库存 * 成本价 + 'total_cost_price' => 0, + // 现有总价值 = 库存 * 售价 + 'total_sell_price' => 0, + // 毛利 = (入库数-报损数-自用数) x (售价-成本) + 'total_profit' => 0, + ]; + foreach ($list as $item) { + $stock = $item->stockLogs; + $cost_price = data_get($item, 'productSku.cost_price', 0) / 100; + $sell_price = data_get($item, 'productSku.sell_price', 0) / 100; + $current_stock = $item->amount; + $in_amount = $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_IN)->sum('amount'); + $loss_amount = $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_LOSS)->sum('amount'); + $self_amount = $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_SELF)->sum('amount'); + + $data['total_in'] += $in_amount * $cost_price; + $data['total_sell'] += $stock->where('product_sku_id', $item->product_sku_id)->whereIn('tag_id', [StockLog::TAG_CARRY, StockLog::TAG_SEND])->sum('amount') * $sell_price; + $data['total_online'] += $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_SEND)->sum('amount') * $sell_price; + $data['total_loss'] += $loss_amount * $cost_price; + $data['total_self'] += $self_amount * $cost_price; + $data['total_out'] += $stock->where('product_sku_id', $item->product_sku_id)->where('tag_id', StockLog::TAG_OUT)->sum('amount') * $cost_price; + $data['total_cost_price'] += $current_stock * $cost_price; + $data['total_sell_price'] += $current_stock * $sell_price; + $data['total_profit'] += ($in_amount - $loss_amount - $self_amount) * ($sell_price - $cost_price); + } + return $data; + } +} diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 434adaf0..f0579727 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -194,6 +194,7 @@ Route::group([ $router->resource('store/batch', 'Store\StockBatchController')->names('store.batch'); $router->resource('store/device', 'Store\DeviceController')->names('store.device'); $router->resource('store/desk', 'Store\DeskController')->names('store.desk'); + $router->get('store/stock-total', 'Store\StockTotalController@index')->name('store.stock_total.index'); $router->resource('profit', 'OrderProfitController'); diff --git a/app/Models/Store/ProductSku.php b/app/Models/Store/ProductSku.php index 73fa8759..5f01257d 100644 --- a/app/Models/Store/ProductSku.php +++ b/app/Models/Store/ProductSku.php @@ -31,4 +31,9 @@ class ProductSku extends Model { return $this->belongsTo(\App\Models\ProductSpu::class, 'product_spu_id'); } + + public function stockLogs() + { + return $this->hasMany(StockLog::class, 'product_sku_id', 'product_sku_id'); + } } diff --git a/app/Models/Store/StockLog.php b/app/Models/Store/StockLog.php index 51149609..10026467 100644 --- a/app/Models/Store/StockLog.php +++ b/app/Models/Store/StockLog.php @@ -11,6 +11,14 @@ class StockLog extends Model { use HasFactory, HasDateTimeFormatter; + const TAG_OUT = 1;// 出库, 调出 + const TAG_IN = 2;// 入库, 进货 + const TAG_CARRY = 3;// 提货, 线下订单 + const TAG_LOSS = 4;// 报损 + const TAG_SELF = 5;// 自用 + const TAG_SEND = 9;// 发货, 线上订单发货(绑定门店) + const TAG_JOIN = 13;// 调入 + protected $table = 'store_stock_logs'; protected $fillable = ['operator_type', 'operator_id', 'operator_name', 'amount', 'product_sku_id', 'remarks', 'balance', 'source_id', 'source_type', 'store_id', 'tag_id']; diff --git a/app/Traits/SkuInfo.php b/app/Traits/SkuInfo.php index bb521364..f717d52c 100644 --- a/app/Traits/SkuInfo.php +++ b/app/Traits/SkuInfo.php @@ -104,7 +104,7 @@ trait SkuInfo return [ 'spu_id' => $spu->id, // 'name' => $spu->name, - 'name' => static::createName($spu->name, $skuSpec['specs']), + 'name' => static::createName('', $skuSpec['specs']), 'subtitle' => $spu->subtitle, 'category_id' => $spu->category_id, 'cover' => $spu->cover, diff --git a/database/sql/tags.sql b/database/sql/tags.sql new file mode 100644 index 00000000..53ef03d0 --- /dev/null +++ b/database/sql/tags.sql @@ -0,0 +1,9 @@ +delete from `tags` where `type` = 4; +INSERT INTO `tags` (`id`, `type`, `name`, `created_at`, `updated_at`) VALUES +(1, 4, '调出', '2022-06-12 19:49:55', '2023-08-10 04:24:02'), +(2, 4, '进货', '2022-06-12 19:49:55', '2023-08-10 04:23:31'), +(3, 4, '提货', '2022-07-27 01:05:41', '2022-07-27 01:05:41'), +(4, 4, '报损', '2022-08-14 11:22:07', '2022-08-14 11:22:07'), +(5, 4, '自用', '2022-08-14 11:24:40', '2022-08-14 11:24:55'), +(9, 4, '发货', '2022-08-26 01:54:33', '2022-08-26 01:54:33'), +(13, 4, '调入', '2023-08-10 04:24:14', '2023-08-10 04:24:14'); \ No newline at end of file diff --git a/resources/lang/zh_CN/store-stock-total.php b/resources/lang/zh_CN/store-stock-total.php new file mode 100644 index 00000000..736d2e4c --- /dev/null +++ b/resources/lang/zh_CN/store-stock-total.php @@ -0,0 +1,9 @@ + [ + 'store' => '门店管理', + 'StockTotal' => '库存盘点', + 'stock-total' => '库存盘点', + ] +];