feat: 實作即時庫存查詢功能、儀表板庫存導盤,及優化手動入庫批號與儲位連動與選單顯示
This commit is contained in:
167
app/Modules/Inventory/Exports/StockQueryExport.php
Normal file
167
app/Modules/Inventory/Exports/StockQueryExport.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Inventory\Exports;
|
||||
|
||||
use App\Modules\Inventory\Models\Inventory;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class StockQueryExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize, WithStyles
|
||||
{
|
||||
protected array $filters;
|
||||
|
||||
public function __construct(array $filters = [])
|
||||
{
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
public function collection()
|
||||
{
|
||||
$today = now()->toDateString();
|
||||
$expiryThreshold = now()->addDays(30)->toDateString();
|
||||
|
||||
$query = Inventory::query()
|
||||
->join('products', 'inventories.product_id', '=', 'products.id')
|
||||
->join('warehouses', 'inventories.warehouse_id', '=', 'warehouses.id')
|
||||
->leftJoin('categories', 'products.category_id', '=', 'categories.id')
|
||||
->leftJoin('warehouse_product_safety_stocks as ss', function ($join) {
|
||||
$join->on('inventories.warehouse_id', '=', 'ss.warehouse_id')
|
||||
->on('inventories.product_id', '=', 'ss.product_id');
|
||||
})
|
||||
->whereNull('inventories.deleted_at')
|
||||
->select([
|
||||
'inventories.id',
|
||||
'inventories.quantity',
|
||||
'inventories.batch_number',
|
||||
'inventories.expiry_date',
|
||||
'inventories.quality_status',
|
||||
'products.code as product_code',
|
||||
'products.name as product_name',
|
||||
'categories.name as category_name',
|
||||
'warehouses.name as warehouse_name',
|
||||
'ss.safety_stock',
|
||||
]);
|
||||
|
||||
// 篩選
|
||||
if (!empty($this->filters['warehouse_id'])) {
|
||||
$query->where('inventories.warehouse_id', $this->filters['warehouse_id']);
|
||||
}
|
||||
if (!empty($this->filters['category_id'])) {
|
||||
$query->where('products.category_id', $this->filters['category_id']);
|
||||
}
|
||||
if (!empty($this->filters['search'])) {
|
||||
$search = $this->filters['search'];
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('products.code', 'like', "%{$search}%")
|
||||
->orWhere('products.name', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
if (!empty($this->filters['status'])) {
|
||||
switch ($this->filters['status']) {
|
||||
case 'low_stock':
|
||||
$query->whereNotNull('ss.safety_stock')
|
||||
->whereRaw('inventories.quantity <= ss.safety_stock')
|
||||
->where('inventories.quantity', '>=', 0);
|
||||
break;
|
||||
case 'negative':
|
||||
$query->where('inventories.quantity', '<', 0);
|
||||
break;
|
||||
case 'expiring':
|
||||
$query->whereNotNull('inventories.expiry_date')
|
||||
->where('inventories.expiry_date', '>', $today)
|
||||
->where('inventories.expiry_date', '<=', $expiryThreshold);
|
||||
break;
|
||||
case 'expired':
|
||||
$query->whereNotNull('inventories.expiry_date')
|
||||
->where('inventories.expiry_date', '<=', $today);
|
||||
break;
|
||||
case 'abnormal':
|
||||
$query->where(function ($q) use ($today, $expiryThreshold) {
|
||||
$q->where('inventories.quantity', '<', 0)
|
||||
->orWhere(function ($q2) {
|
||||
$q2->whereNotNull('ss.safety_stock')
|
||||
->whereRaw('inventories.quantity <= ss.safety_stock');
|
||||
})
|
||||
->orWhere(function ($q2) use ($expiryThreshold) {
|
||||
$q2->whereNotNull('inventories.expiry_date')
|
||||
->where('inventories.expiry_date', '<=', $expiryThreshold);
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $query->orderBy('products.code', 'asc')->get();
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'商品代碼',
|
||||
'商品名稱',
|
||||
'分類',
|
||||
'倉庫',
|
||||
'批號',
|
||||
'數量',
|
||||
'安全庫存',
|
||||
'到期日',
|
||||
'品質狀態',
|
||||
'狀態',
|
||||
];
|
||||
}
|
||||
|
||||
public function map($row): array
|
||||
{
|
||||
$today = now()->toDateString();
|
||||
$expiryThreshold = now()->addDays(30)->toDateString();
|
||||
|
||||
$statuses = [];
|
||||
if ($row->quantity < 0) {
|
||||
$statuses[] = '負庫存';
|
||||
}
|
||||
if ($row->safety_stock !== null && $row->quantity <= $row->safety_stock && $row->quantity >= 0) {
|
||||
$statuses[] = '低庫存';
|
||||
}
|
||||
if ($row->expiry_date) {
|
||||
if ($row->expiry_date <= $today) {
|
||||
$statuses[] = '已過期';
|
||||
} elseif ($row->expiry_date <= $expiryThreshold) {
|
||||
$statuses[] = '即將過期';
|
||||
}
|
||||
}
|
||||
if (empty($statuses)) {
|
||||
$statuses[] = '正常';
|
||||
}
|
||||
|
||||
$qualityLabels = [
|
||||
'normal' => '正常',
|
||||
'inspecting' => '檢驗中',
|
||||
'rejected' => '不合格',
|
||||
];
|
||||
|
||||
return [
|
||||
$row->product_code,
|
||||
$row->product_name,
|
||||
$row->category_name ?? '-',
|
||||
$row->warehouse_name,
|
||||
$row->batch_number ?? '-',
|
||||
$row->quantity,
|
||||
$row->safety_stock ?? '-',
|
||||
$row->expiry_date ?? '-',
|
||||
$qualityLabels[$row->quality_status] ?? $row->quality_status ?? '-',
|
||||
implode('、', $statuses),
|
||||
];
|
||||
}
|
||||
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
return [
|
||||
1 => ['font' => ['bold' => true]],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user