feat: [shipment] 배차정보 다중 행 시스템 — shipment_vehicle_dispatches 테이블 추가
- 신규 마이그레이션: shipment_vehicle_dispatches 테이블 (seq, logistics_company, arrival_datetime, tonnage, vehicle_no, driver_contact, remarks) - 신규 모델: ShipmentVehicleDispatch (ShipmentItem 패턴 복제) - Shipment 모델: vehicleDispatches() HasMany 관계 추가 - ShipmentService: syncDispatches() 추가, store/update/delete/show/index에서 연동 - FormRequest: Store/Update에 vehicle_dispatches 배열 검증 규칙 추가 - delivery_method 검증에 확장 옵션 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ public function rules(): array
|
||||
'scheduled_date' => 'required|date',
|
||||
'status' => 'nullable|in:scheduled,ready,shipping,completed',
|
||||
'priority' => 'nullable|in:urgent,normal,low',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics,direct_dispatch,loading,kyungdong_delivery,daesin_delivery,kyungdong_freight,daesin_freight,self_pickup',
|
||||
|
||||
// 발주처/배송 정보
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
@@ -55,6 +55,16 @@ public function rules(): array
|
||||
// 기타
|
||||
'remarks' => 'nullable|string',
|
||||
|
||||
// 배차정보
|
||||
'vehicle_dispatches' => 'nullable|array',
|
||||
'vehicle_dispatches.*.seq' => 'nullable|integer|min:1',
|
||||
'vehicle_dispatches.*.logistics_company' => 'nullable|string|max:100',
|
||||
'vehicle_dispatches.*.arrival_datetime' => 'nullable|date',
|
||||
'vehicle_dispatches.*.tonnage' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.vehicle_no' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.driver_contact' => 'nullable|string|max:50',
|
||||
'vehicle_dispatches.*.remarks' => 'nullable|string',
|
||||
|
||||
// 출하 품목
|
||||
'items' => 'nullable|array',
|
||||
'items.*.seq' => 'nullable|integer|min:1',
|
||||
|
||||
@@ -19,7 +19,7 @@ public function rules(): array
|
||||
'order_id' => 'nullable|integer|exists:orders,id',
|
||||
'scheduled_date' => 'nullable|date',
|
||||
'priority' => 'nullable|in:urgent,normal,low',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics',
|
||||
'delivery_method' => 'nullable|in:pickup,direct,logistics,direct_dispatch,loading,kyungdong_delivery,daesin_delivery,kyungdong_freight,daesin_freight,self_pickup',
|
||||
|
||||
// 발주처/배송 정보
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
@@ -53,6 +53,16 @@ public function rules(): array
|
||||
// 기타
|
||||
'remarks' => 'nullable|string',
|
||||
|
||||
// 배차정보
|
||||
'vehicle_dispatches' => 'nullable|array',
|
||||
'vehicle_dispatches.*.seq' => 'nullable|integer|min:1',
|
||||
'vehicle_dispatches.*.logistics_company' => 'nullable|string|max:100',
|
||||
'vehicle_dispatches.*.arrival_datetime' => 'nullable|date',
|
||||
'vehicle_dispatches.*.tonnage' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.vehicle_no' => 'nullable|string|max:20',
|
||||
'vehicle_dispatches.*.driver_contact' => 'nullable|string|max:50',
|
||||
'vehicle_dispatches.*.remarks' => 'nullable|string',
|
||||
|
||||
// 출하 품목
|
||||
'items' => 'nullable|array',
|
||||
'items.*.seq' => 'nullable|integer|min:1',
|
||||
|
||||
@@ -134,6 +134,14 @@ public function items(): HasMany
|
||||
return $this->hasMany(ShipmentItem::class)->orderBy('seq');
|
||||
}
|
||||
|
||||
/**
|
||||
* 배차정보 관계
|
||||
*/
|
||||
public function vehicleDispatches(): HasMany
|
||||
{
|
||||
return $this->hasMany(ShipmentVehicleDispatch::class)->orderBy('seq');
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래처 관계
|
||||
*/
|
||||
|
||||
50
app/Models/Tenants/ShipmentVehicleDispatch.php
Normal file
50
app/Models/Tenants/ShipmentVehicleDispatch.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Traits\Auditable;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class ShipmentVehicleDispatch extends Model
|
||||
{
|
||||
use Auditable, BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'shipment_id',
|
||||
'seq',
|
||||
'logistics_company',
|
||||
'arrival_datetime',
|
||||
'tonnage',
|
||||
'vehicle_no',
|
||||
'driver_contact',
|
||||
'remarks',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'seq' => 'integer',
|
||||
'shipment_id' => 'integer',
|
||||
'arrival_datetime' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 출하 관계
|
||||
*/
|
||||
public function shipment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Shipment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 다음 순번 가져오기
|
||||
*/
|
||||
public static function getNextSeq(int $shipmentId): int
|
||||
{
|
||||
$maxSeq = static::where('shipment_id', $shipmentId)->max('seq');
|
||||
|
||||
return ($maxSeq ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
use App\Models\Orders\Order;
|
||||
use App\Models\Tenants\Shipment;
|
||||
use App\Models\Tenants\ShipmentItem;
|
||||
use App\Models\Tenants\ShipmentVehicleDispatch;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@@ -19,7 +20,7 @@ public function index(array $params): LengthAwarePaginator
|
||||
|
||||
$query = Shipment::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['items', 'order.client', 'order.writer', 'workOrder']);
|
||||
->with(['items', 'vehicleDispatches', 'order.client', 'order.writer', 'workOrder']);
|
||||
|
||||
// 검색어 필터
|
||||
if (! empty($params['search'])) {
|
||||
@@ -164,6 +165,7 @@ public function show(int $id): Shipment
|
||||
'items' => function ($query) {
|
||||
$query->orderBy('seq');
|
||||
},
|
||||
'vehicleDispatches',
|
||||
'order.client',
|
||||
'order.writer',
|
||||
'workOrder',
|
||||
@@ -228,7 +230,12 @@ public function store(array $data): Shipment
|
||||
$this->syncItems($shipment, $data['items'], $tenantId);
|
||||
}
|
||||
|
||||
return $shipment->load('items');
|
||||
// 배차정보 추가
|
||||
if (! empty($data['vehicle_dispatches'])) {
|
||||
$this->syncDispatches($shipment, $data['vehicle_dispatches'], $tenantId);
|
||||
}
|
||||
|
||||
return $shipment->load(['items', 'vehicleDispatches']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -283,7 +290,12 @@ public function update(int $id, array $data): Shipment
|
||||
$this->syncItems($shipment, $data['items'], $tenantId);
|
||||
}
|
||||
|
||||
return $shipment->load('items');
|
||||
// 배차정보 동기화
|
||||
if (isset($data['vehicle_dispatches'])) {
|
||||
$this->syncDispatches($shipment, $data['vehicle_dispatches'], $tenantId);
|
||||
}
|
||||
|
||||
return $shipment->load(['items', 'vehicleDispatches']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -340,7 +352,7 @@ public function updateStatus(int $id, string $status, ?array $additionalData = n
|
||||
// 연결된 수주(Order) 상태 동기화
|
||||
$this->syncOrderStatus($shipment, $tenantId);
|
||||
|
||||
return $shipment->load('items');
|
||||
return $shipment->load(['items', 'vehicleDispatches']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,6 +451,9 @@ public function delete(int $id): bool
|
||||
// 품목 삭제
|
||||
$shipment->items()->delete();
|
||||
|
||||
// 배차정보 삭제
|
||||
$shipment->vehicleDispatches()->delete();
|
||||
|
||||
// 출하 삭제
|
||||
$shipment->update(['deleted_by' => $userId]);
|
||||
$shipment->delete();
|
||||
@@ -477,6 +492,32 @@ protected function syncItems(Shipment $shipment, array $items, int $tenantId): v
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배차정보 동기화
|
||||
*/
|
||||
protected function syncDispatches(Shipment $shipment, array $dispatches, int $tenantId): void
|
||||
{
|
||||
// 기존 배차정보 삭제
|
||||
$shipment->vehicleDispatches()->forceDelete();
|
||||
|
||||
// 새 배차정보 생성
|
||||
$seq = 1;
|
||||
foreach ($dispatches as $dispatch) {
|
||||
ShipmentVehicleDispatch::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'shipment_id' => $shipment->id,
|
||||
'seq' => $dispatch['seq'] ?? $seq,
|
||||
'logistics_company' => $dispatch['logistics_company'] ?? null,
|
||||
'arrival_datetime' => $dispatch['arrival_datetime'] ?? null,
|
||||
'tonnage' => $dispatch['tonnage'] ?? null,
|
||||
'vehicle_no' => $dispatch['vehicle_no'] ?? null,
|
||||
'driver_contact' => $dispatch['driver_contact'] ?? null,
|
||||
'remarks' => $dispatch['remarks'] ?? null,
|
||||
]);
|
||||
$seq++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LOT 옵션 조회 (출고 가능한 LOT 목록)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('shipment_vehicle_dispatches', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->comment('테넌트 ID');
|
||||
$table->foreignId('shipment_id')->comment('출하 ID');
|
||||
$table->integer('seq')->default(1)->comment('순번');
|
||||
$table->string('logistics_company', 100)->nullable()->comment('물류사');
|
||||
$table->datetime('arrival_datetime')->nullable()->comment('도착일시');
|
||||
$table->string('tonnage', 20)->nullable()->comment('톤수');
|
||||
$table->string('vehicle_no', 20)->nullable()->comment('차량번호');
|
||||
$table->string('driver_contact', 50)->nullable()->comment('운전자 연락처');
|
||||
$table->text('remarks')->nullable()->comment('비고');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 인덱스
|
||||
$table->index(['shipment_id', 'seq']);
|
||||
|
||||
// 외래키
|
||||
$table->foreign('shipment_id')->references('id')->on('shipments')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('shipment_vehicle_dispatches');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user