chore: [misc] 거래처 통계 수정 + 문서 템플릿 file_id 추가 + 라우트 정리

- ClientService stats() active/inactive 카운트 추가
- DocumentTemplateSection file_id 컬럼 마이그레이션
- 논리 관계 문서 업데이트 (BendingItemMapping 추가)
This commit is contained in:
2026-03-17 13:56:08 +09:00
parent 0863afc8d0
commit 9358c4112e
6 changed files with 86 additions and 12 deletions

View File

@@ -1,6 +1,6 @@
# 논리적 데이터베이스 관계 문서
> **자동 생성**: 2026-03-12 13:58:25
> **자동 생성**: 2026-03-17 13:34:26
> **소스**: Eloquent 모델 관계 분석
## 📊 모델별 관계 현황
@@ -374,6 +374,8 @@ ### esign_signers
### equipments
**모델**: `App\Models\Equipment\Equipment`
- **manager()**: belongsTo → `users`
- **subManager()**: belongsTo → `users`
- **inspectionTemplates()**: hasMany → `equipment_inspection_templates`
- **inspections()**: hasMany → `equipment_inspections`
- **repairs()**: hasMany → `equipment_repairs`
@@ -384,6 +386,7 @@ ### equipment_inspections
**모델**: `App\Models\Equipment\EquipmentInspection`
- **equipment()**: belongsTo → `equipments`
- **inspector()**: belongsTo → `users`
- **details()**: hasMany → `equipment_inspection_details`
### equipment_inspection_details
@@ -407,6 +410,7 @@ ### equipment_repairs
**모델**: `App\Models\Equipment\EquipmentRepair`
- **equipment()**: belongsTo → `equipments`
- **repairer()**: belongsTo → `users`
### estimates
**모델**: `App\Models\Estimate\Estimate`
@@ -429,6 +433,12 @@ ### file_share_links
- **file()**: belongsTo → `files`
- **tenant()**: belongsTo → `tenants`
### corporate_vehicles
**모델**: `App\Models\Tenants\CorporateVehicle`
- **logs()**: hasMany → `vehicle_logs`
- **maintenances()**: hasMany → `vehicle_maintenances`
### folders
**모델**: `App\Models\Folder`
@@ -713,6 +723,11 @@ ### process_steps
- **process()**: belongsTo → `processes`
### bending_item_mappings
**모델**: `App\Models\Production\BendingItemMapping`
- **item()**: belongsTo → `items`
### work_orders
**모델**: `App\Models\Production\WorkOrder`
@@ -898,6 +913,7 @@ ### quality_documents
- **documentOrders()**: hasMany → `quality_document_orders`
- **locations()**: hasMany → `quality_document_locations`
- **performanceReport()**: hasOne → `performance_reports`
- **file()**: hasOne → `files`
### quality_document_locations
**모델**: `App\Models\Qualitys\QualityDocumentLocation`
@@ -1232,6 +1248,7 @@ ### shipments
- **order()**: belongsTo → `orders`
- **workOrder()**: belongsTo → `work_orders`
- **client()**: belongsTo → `clients`
- **creator()**: belongsTo → `users`
- **updater()**: belongsTo → `users`
- **items()**: hasMany → `shipment_items`
@@ -1242,6 +1259,7 @@ ### shipment_items
- **shipment()**: belongsTo → `shipments`
- **stockLot()**: belongsTo → `stock_lots`
- **orderItem()**: belongsTo → `order_items`
### shipment_vehicle_dispatchs
**모델**: `App\Models\Tenants\ShipmentVehicleDispatch`
@@ -1353,6 +1371,16 @@ ### today_issues
- **reader()**: belongsTo → `users`
- **targetUser()**: belongsTo → `users`
### vehicle_logs
**모델**: `App\Models\Tenants\VehicleLog`
- **vehicle()**: belongsTo → `corporate_vehicles`
### vehicle_maintenances
**모델**: `App\Models\Tenants\VehicleMaintenance`
- **vehicle()**: belongsTo → `corporate_vehicles`
### withdrawals
**모델**: `App\Models\Tenants\Withdrawal`

View File

@@ -12,7 +12,8 @@
* @property int $id
* @property int $template_id
* @property string $title 섹션 제목
* @property string|null $image_path 검사 기준 이미지 경로
* @property string|null $image_path 검사 기준 이미지 경로 (R2 key)
* @property int|null $file_id 도해 이미지 파일 ID (files 테이블 참조)
* @property int $sort_order 정렬 순서
*/
class DocumentTemplateSection extends Model
@@ -24,6 +25,7 @@ class DocumentTemplateSection extends Model
'title',
'description',
'image_path',
'file_id',
'sort_order',
];

View File

@@ -53,7 +53,7 @@ public function index(array $params)
$query->whereDate('created_at', '<=', $endDate);
}
$query->orderBy('client_code')->orderBy('id');
$query->orderBy('id', 'desc');
$paginator = $query->paginate($size, ['*'], 'page', $page);
@@ -304,10 +304,14 @@ public function stats(): array
{
$tenantId = $this->tenantId();
$total = Client::where('tenant_id', $tenantId)->count();
$base = Client::where('tenant_id', $tenantId);
$total = (clone $base)->count();
$active = (clone $base)->where('is_active', true)->count();
$inactive = $total - $active;
// 거래처 유형별 통계
$typeCounts = Client::where('tenant_id', $tenantId)
$typeCounts = (clone $base)
->selectRaw('client_type, COUNT(*) as count')
->groupBy('client_type')
->pluck('count', 'client_type')
@@ -321,12 +325,14 @@ public function stats(): array
->distinct('client_id')
->pluck('client_id');
$badDebtCount = Client::where('tenant_id', $tenantId)
$badDebtCount = (clone $base)
->whereIn('id', $badDebtClientIds)
->count();
return [
'total' => $total,
'active' => $active,
'inactive' => $inactive,
'sales' => $typeCounts['SALES'] ?? 0,
'purchase' => $typeCounts['PURCHASE'] ?? 0,
'both' => $typeCounts['BOTH'] ?? 0,

View File

@@ -982,6 +982,7 @@ public function formatTemplateForReact(DocumentTemplate $template): array
'name' => $section->title,
'title' => $section->title,
'image_path' => $section->image_path,
'file_id' => $section->file_id,
'sort_order' => $section->sort_order,
'items' => $section->items->map(function ($item) use ($methodCodes) {
// method 코드를 한글 이름으로 변환

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('document_template_sections', function (Blueprint $table) {
$table->unsignedBigInteger('file_id')->nullable()->after('image_path')
->comment('도해 이미지 파일 ID (files 테이블 참조)');
});
// 기존 image_path → file_id 백필 (files.file_path와 매칭)
DB::statement('
UPDATE document_template_sections dts
INNER JOIN files f ON f.file_path = dts.image_path AND f.deleted_at IS NULL
SET dts.file_id = f.id
WHERE dts.image_path IS NOT NULL AND dts.file_id IS NULL
');
}
public function down(): void
{
Schema::table('document_template_sections', function (Blueprint $table) {
$table->dropColumn('file_id');
});
}
};

View File

@@ -38,14 +38,19 @@
]);
})->where('path', '.*');
// R2 테넌트 파일 프록시 (문서 템플릿 이미지 등, 인증 불필요, 캐시 1일)
Route::get('/storage/tenants/{path}', function (string $path) {
if (! Storage::disk('r2')->exists($path)) {
// R2 파일 프록시 (file_id 기반, 인증 불필요, 캐시 1일)
Route::get('/files/{id}/view', function (int $id) {
$file = \App\Models\File::find($id);
if (! $file || ! $file->file_path) {
abort(404);
}
$mime = Storage::disk('r2')->mimeType($path);
$stream = Storage::disk('r2')->readStream($path);
if (! Storage::disk('r2')->exists($file->file_path)) {
abort(404);
}
$mime = Storage::disk('r2')->mimeType($file->file_path);
$stream = Storage::disk('r2')->readStream($file->file_path);
return response()->stream(function () use ($stream) {
fpassthru($stream);
@@ -56,7 +61,7 @@
'Content-Type' => $mime,
'Cache-Control' => 'public, max-age=86400',
]);
})->where('path', '.*');
});
// Swagger 설정
Route::get('/docs/api-docs.json', function () {