168 lines
6.0 KiB
PHP
168 lines
6.0 KiB
PHP
|
|
<?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]],
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|