Project

General

Profile

Task #35 » FacilityCSVDocumentation.md

Nghia Nguyen, 07/07/2025 10:54 AM

 

Facility CSV Import/Export Documentation

Tổng quan

Tài liệu này mô tả chi tiết về chức năng import/export CSV cho module Facility

THAY ĐỔI QUAN TRỌNG

  • Trước đây: CSV có 58 fields
  • Hiện tại: CSV đã thay đổi, còn 18 fields
  • Database: Bảng facilities đã được thay đổi
  • Field mới: Thêm field 状態 (status) để quản lý trạng thái active/inactive

Database Structure

Tables được sử dụng trong Facility CSV Import/Export

Import Process

  • facilities - Bảng chính lưu thông tin Facility
  • facility_category_relations - Bảng quan hệ giữa Facility và Category

Export Process

  • facilities - Bảng chính lấy thông tin Facility
  • facility_category_relations - Bảng quan hệ để lấy Category names
  • facility_categories - Bảng master để lấy Category names
  • owner_accounts - Bảng tham chiếu để lấy Manager name

1. facilities (Bảng chính) - CẬP NHẬT MỚI

Column Name Data Type Length Nullable Default Comment
id BIGINT - NO - Primary Key
name VARCHAR 64 NO - 施設名
name_kana VARCHAR 128 NO - 施設名カナ
area VARCHAR 32 NO - エリア
postal_code VARCHAR 8 NO - 郵便番号
prefecture VARCHAR 8 NO - 都道府県
city VARCHAR 128 NO - 市区郡
ward VARCHAR 128 YES - 行政区
address VARCHAR 128 NO - 町名番地
building VARCHAR 64 YES - 建物
phone VARCHAR 16 NO - 施設電話番号
parking TINYINT - NO - 駐車場利用(0:-、1:利用可、2:不可)
parking_notes VARCHAR 1024 YES - 駐車場の備考
manager VARCHAR 60 YES - 管理担当者
status TINYINT - NO 1 状態(1:有効、2:無効)
created_at TIMESTAMP - NO - Created timestamp
updated_at TIMESTAMP - NO - Updated timestamp

Indexes:

  • id (PRIMARY KEY)

LƯU Ý: Cấu trúc cũ (58 fields) đã được đơn giản hóa thành 15 fields

2. facility_category_relations (Bảng quan hệ category)

Column Name Data Type Length Nullable Default Comment
id BIGINT - NO - Primary Key
facility_id INT - NO - 施設ID
facility_category_id INT - NO - 施設カテゴリID
created_at TIMESTAMP - NO - Created timestamp
updated_at TIMESTAMP - NO - Updated timestamp

Indexes:

  • facility_id (INDEX)

3. facility_categories (Bảng master category - được tạo trong seeder)

Column Name Data Type Length Nullable Default Comment
id INT - NO - Primary Key (Auto Increment)
name VARCHAR 60 NO - 施設カテゴリ名
status TINYINT - NO 1 状態(1:有効、2:無効)
created_at TIMESTAMP - NO - Created timestamp
updated_at TIMESTAMP - NO - Updated timestamp

4. owner_accounts (Bảng tham chiếu manager)

Column Name Data Type Length Nullable Default Comment
id BIGINT - NO - Primary Key
user_id BIGINT - NO - 認証ユーザID (UNIQUE)
owner_owner_account_id BIGINT - NO - オーナー単位のオーナアカウントID
owner_organization_id INT - NO - オーナー企業ID
main_flag TINYINT - NO 2 メインフラグ(1:メイン, 2:サブ)
branch_name VARCHAR 60 YES - 支店名
shop_name VARCHAR 60 YES - 店舗名
name VARCHAR 60 NO - オーナーアカウント名
department VARCHAR 60 YES - 所属
position VARCHAR 60 YES - 役職
tel VARCHAR 15 NO - 電話番号
is_test TINYINT - NO 0 テストフラグ(0:本番, 1:テスト)
created_at TIMESTAMP - NO - Created timestamp
updated_at TIMESTAMP - NO - Updated timestamp

Indexes:

  • user_id (UNIQUE INDEX)
  • owner_organization_id (INDEX)

CSV Field Structure

THAY ĐỔI QUAN TRỌNG: Từ 58 fields xuống 15 fields

Format cũ (58 fields) - ĐÃ NGỪNG SỬ DỤNG

施設ID,施設名,施設名カナ,実施場所,独自ID,エリア,郵便番号,都道府県,市区郡,行政区,町名番地,建物,施設電話番号,実施場所の広さ,催事開始時間,催事終了時間,屋内外(0:要確認、1:屋内、2:屋外(屋根あり)、3:屋外(屋根なし)),屋内外コメント,平日料金,土曜料金,日祝料金,電源の有無(0:-、1:有、2:無),電源のコメント,デスクの有無(0:-、1:有、2:無),デスクのコメント,椅子の有無(0:-、1:有、2:無),椅子のコメント,Wi-Fiの有無(0:-、1:有、2:無),Wi-Fiのコメント,飲食の有無(0:-、1:有、2:無),飲食のコメント,NG商材の有無(0:未設定,1:有, 2:無),NG商材のコメント,コメント,注意コメント,開催不可曜日,申請期日,回答目安,回答予定日数,申請時の注意事項,開催時の注意事項,事前打ち合わせ,備品貸出,備品に関する備考,台車貸出,駐車場利用(0:-、1:利用可、2:不可),駐車場の備考,当日搬入開始可能時間,完全撤退時間,キャンセルポリシー,社内メモ,空き状況通知(1:通知する,2:通知しない),空き状況通知対象週(週間後~),空き状況通知対象週(~週間後),管理担当者,カテゴリー1,カテゴリー2,カテゴリー3

Format mới (15 fields) - HIỆN TẠI

STT Field Name (Japanese) Field Name (English) Data Type Required Max Length Validation Rules Database Column Description
1 施設ID Facility ID Integer No - 1-999999 id ID của facility (để trống nếu tạo mới)
2 施設名 Facility Name Text Yes 64 2-64 chars name Tên Facility
3 施設名カナ Facility Name Kana Text Yes 128 2-128 chars name_kana Tên Facility bằng katakana
4 エリア Area Text Yes 32 Must exist in Area list area Khu vực
5 郵便番号 Postal Code Text Yes 8 Format: xxx-xxxx postal_code Mã bưu điện
6 都道府県 Prefecture Text Yes 8 Must match Area prefecture Tỉnh/thành phố
7 市区郡 City Text Yes 128 - city Quận/huyện
8 行政区 Ward Text No 128 - ward Phường/xã
9 町名番地 Address Text Yes 128 - address Địa chỉ chi tiết
10 建物 Building Text No 64 - building Tòa nhà
11 施設電話番号 Facility Phone Text Yes 16 Phone format phone Số điện thoại Facility
12 駐車場利用 Parking Usage Integer Yes - 0-2 parking 0: -、1: 利用可、2: 不可
13 駐車場の備考 Parking Notes Text No 1024 - parking_notes Ghi chú về bãi đỗ xe
14 管理担当者 Manager Text Yes 60 Must exist in Owner Account manager Người quản lý
15 カテゴリー1 Category 1 Text Yes 60 Must exist in Facility Category facility_category_relations Danh mục 1
16 カテゴリー2 Category 2 Text No 60 Must exist in Facility Category facility_category_relations Danh mục 2
17 カテゴリー3 Category 3 Text No 60 Must exist in Facility Category facility_category_relations Danh mục 3
18 状態 Status Integer Yes - 1-2 status 1: 有効、2: 無効 (Field mới)

CSV Format Examples

CSV Header Structure

Format cũ (58 fields) - ĐÃ NGỪNG SỬ DỤNG

施設ID,施設名,施設名カナ,実施場所,独自ID,エリア,郵便番号,都道府県,市区郡,行政区,町名番地,建物,施設電話番号,実施場所の広さ,催事開始時間,催事終了時間,屋内外(0:要確認、1:屋内、2:屋外(屋根あり)、3:屋外(屋根なし)),屋内外コメント,平日料金,土曜料金,日祝料金,電源の有無(0:-、1:有、2:無),電源のコメント,デスクの有無(0:-、1:有、2:無),デスクのコメント,椅子の有無(0:-、1:有、2:無),椅子のコメント,Wi-Fiの有無(0:-、1:有、2:無),Wi-Fiのコメント,飲食の有無(0:-、1:有、2:無),飲食のコメント,NG商材の有無(0:未設定,1:有, 2:無),NG商材のコメント,コメント,注意コメント,開催不可曜日,申請期日,回答目安,回答予定日数,申請時の注意事項,開催時の注意事項,事前打ち合わせ,備品貸出,備品に関する備考,台車貸出,駐車場利用(0:-、1:利用可、2:不可),駐車場の備考,当日搬入開始可能時間,完全撤退時間,キャンセルポリシー,社内メモ,空き状況通知(1:通知する,2:通知しない),空き状況通知対象週(週間後~),空き状況通知対象週(~週間後),管理担当者,カテゴリー1,カテゴリー2,カテゴリー3

Format mới (18 fields) - HIỆN TẠI

施設ID,施設名,施設名カナ,エリア,郵便番号,都道府県,市区郡,行政区,町名番地,建物,施設電話番号,駐車場利用(0:-、1:利用可、2:不可),駐車場の備考,管理担当者,カテゴリー1,カテゴリー2,カテゴリー3,状態(1:有効、2:無効)

Import CSV Sample Data (Format mới)

,東京コンベンションセンター,トウキョウコンベンションセンター,関東,123-4567,東京都,渋谷区,神南,1-1-1,ビルA,03-1234-5678,1,駐車場利用可,田中太郎,会議室,大型会議室,,1

File Processing

Import File Processing

1. File Upload & Storage

// File được upload và lưu tạm thời
$filePath = storage_path('app/tmp/' . $fileName);
$file->move($filePath);

// File được xử lý theo chunk (100 records/chunk)
$chunkSize = 100;

2. File Validation

// Kiểm tra file type (.csv)
if ($file->getClientOriginalExtension() !== 'csv') {
    throw new RuntimeException('CSVファイルのみアップロード可能です。');
}

// Kiểm tra số lượng records
if (!ImportCsvUtility::checkRecordSize($file)) {
    throw new RuntimeException('データ数が3000件を超えています。');
}

// Kiểm tra header
ImportCsvUtility::checkCsvHeader($file, $this->createHeader(), '施設');

3. Encoding Processing

// Chuyển đổi encoding
public static function convertStringCode($value)
{
    $strType = mb_detect_encoding($value, ['UTF-8', 'SJIS-win']);
    if ($strType == 'SJIS-win') {
        $value = mb_convert_encoding($value, 'UTF-8', 'SJIS-win');
    }
    return $value;
}

// Xử lý BOM
$wk = preg_replace("/^\xEF\xBB\xBF/", '', $wk);

4. Error File Generation

// Tạo error file với BOM
$fileName = 'Facility_import_error_' . date('Ymd') . '.csv';
$filePath = $dir . '/' . $fileName;
$file = fopen($filePath, 'w');

// Thêm BOM
fwrite($file, "\xEF\xBB\xBF");

// Write header
fputcsv($file, $this->createHeader());

Export File Processing

1. Data Retrieval

// Lấy dữ liệu từ database
$facilities = $this->facilitySelectRepository->select($filterDto);

// Chunk processing để tránh memory overflow
$chunks = $facilities->chunk(config('web.db.chunkSize'));

2. File Generation

// Tạo CSV file
$file = CsvUtility::createCsvFile($filePath);

// Write header
fputcsv($file, $this->createHeader());

// Write data theo chunk
foreach ($chunks as $chunk) {
    foreach ($chunk as $facility) {
        fputcsv($file, $this->formatData($facility));
    }
}

3. File Storage & Download

// File được lưu trong storage/app/tmp/
$fileName = 'Facility_' . date('YmdHis') . '.csv';
$filePath = storage_path('app/tmp/' . $fileName);

// Download link
return response()->download($filePath, $fileName);

Database Operations

Import Database Operations

1. Facility Table Operations

// Insert new facility
if (empty($wk->ownerFacilityId)) {
    $facility = $this->facilityUpsertRepository->insert($wk);
} else {
    // Update existing facility
    $facility = $this->facilityUpsertRepository->update($wk);
}

2. Category Relations Operations

// Delete existing categories
$this->facilityCategoryRelationUpsertRepository->deleteByFacilityId($facility->facilityId);

// Insert new categories
$this->saveCategory($facility->facilityId, $wk->category1);
$this->saveCategory($facility->facilityId, $wk->category2);
$this->saveCategory($facility->facilityId, $wk->category3);

3. Transaction Handling

DB::transaction(function () use ($insertRecords) {
    foreach ($insertRecords as $wk) {
        // Facility operations
        $facility = $this->facilityUpsertRepository->insert($wk);
        
        // Category operations
        $this->facilityCategoryRelationUpsertRepository->deleteByFacilityId($facility->facilityId);
        $this->saveCategory($facility->facilityId, $wk->category1);
        $this->saveCategory($facility->facilityId, $wk->category2);
        $this->saveCategory($facility->facilityId, $wk->category3);
    }
});

Export Database Operations

1. Data Retrieval

// Query facilities với filter
$facilities = $this->facilitySelectRepository->select($filterDto);

// Join với categories
$facilities = $facilities->map(function ($facility) {
    $facility->facilityCategories = $this->getCategoryNames($facility->facilityId);
    return $facility;
});

2. Data Formatting

protected function formatData($dbRecord)
{
    $categoryNames = explode(',', $dbRecord->facilityCategories);
    
    return [
        $dbRecord->ownerFacilityId,
        $dbRecord->name,
        $dbRecord->nameKana,
        $dbRecord->place,
        // ... 58 fields total
        $categoryNames[0] ?? '',
        $categoryNames[1] ?? '',
        $categoryNames[2] ?? ''
    ];
}

Validation Rules

Business Logic Validation

1. Duplicate Check

  • Facility Name: Không được trùng lặp trong cùng organization
  • Error: "施設名は既に登録されています。"

2. Area & Prefecture Validation

  • Area: Phải tồn tại trong danh sách Area
  • Prefecture: Phải khớp với Area đã chọn
  • Error: "存在しないエリア名です。" / "エリアに対応しない都道府県名です。"

3. Category Validation

  • Category 1: Bắt buộc, phải tồn tại trong Facility Category
  • Category 2, 3: Tùy chọn, nếu có thì phải tồn tại
  • Error: "カテゴリー1は存在しないカテゴリ名です。"

4. Manager Validation

  • Manager: Phải tồn tại trong Owner Account
  • Error: "存在しないオーナーアカウントです。"

5. Status Validation

  • Status: Phải là 1 (有効) hoặc 2 (無効)
  • Error: "状態は1または2で入力してください。"

Import Process

Step 1: File Validation

// Kiểm tra số lượng records
if (!ImportCsvUtility::checkRecordSize($file)) {
    throw new RuntimeException('データ数が3000件を超えています。');
}

// Kiểm tra header
ImportCsvUtility::checkCsvHeader($file, $this->createHeader(), '施設');

Step 2: Data Processing

// Chuyển đổi encoding
$csvRecord[] = self::convertStringCode($wk);

// Xử lý BOM
$wk = preg_replace("/^\xEF\xBB\xBF/", '', $wk);

Step 3: Record Validation

// Validate từng field
$validator->validateText('施設名', $obj->name, true, 2, 60);
$validator->validateZip('郵便番号', $obj->zipCode, true);
$validator->validateDigit('平日料金', $obj->weekdaySellPrice, false, 0, 99999999);

// Business logic validation
if (!$this->validateNameAndPlace($obj->ownerFacilityId, $obj->name, $obj->place, $ownerOrganizationId)) {
    $validator->addErrorMessage('施設名・実施場所は既に登録されています。');
}

Step 4: Database Operations

// Insert/Update facility
if (empty($wk->ownerFacilityId)) {
    $facility = $this->facilityUpsertRepository->insert($wk);
} else {
    $facility = $this->facilityUpsertRepository->update($wk);
}

// Update categories
$this->facilityCategoryRelationUpsertRepository->deleteByFacilityId($facility->facilityId);
$this->saveCategory($facility->facilityId, $wk->category1);
$this->saveCategory($facility->facilityId, $wk->category2);
$this->saveCategory($facility->facilityId, $wk->category3);

Export Process

Step 1: Data Retrieval

// Lấy danh sách facilities
$facilities = $this->facilitySelectRepository->select($filterDto);

// Chunk processing
$chunks = $facilities->chunk(config('web.db.chunkSize'));

Step 2: Data Formatting

// Format data cho CSV
return [
    $dbRecord->ownerFacilityId,
    $dbRecord->name,
    $dbRecord->nameKana,
    $dbRecord->place,
    // ... các field khác
];

Step 3: File Generation

// Tạo CSV file
$file = CsvUtility::createCsvFile($filePath);
fputcsv($file, $this->createHeader());

// Write data
foreach ($chunks as $chunk) {
    foreach ($chunk as $facility) {
        fputcsv($file, $this->formatData($facility));
    }
}

Error Handling

Error File Structure

施設ID,施設名,施設名カナ,実施場所,...,Error
,東京コンベンションセンター,トウキョウコンベンションセンター,メインホールA,...,施設名・実施場所は既に登録されています。

Common Error Messages

File Validation

項目数が正しくありません。最新の施設CSVをダウンロードして項目数を確認してください。
項目名が正しくありません。最新の施設CSVをダウンロードして項目名を確認してください。
データ数が3000件を超えています。

Field Validation

施設名は入力必須です。
施設名は2文字以上で入力してください。
施設名は64文字以内で入力してください。
郵便番号はxxx-xxxxの形式で入力してください。
駐車場利用は0、1、2のいずれかで入力してください。
状態は1または2で入力してください。

Business Logic

施設名は既に登録されています。
存在しないエリア名です。
エリアに対応しない都道府県名です。
存在しないオーナーアカウントです。
カテゴリー1は存在しないカテゴリ名です。

Configuration

CSV Import/Export Config

Environment Variables

# CSV Import Limit
CSV_IMPORT_LIMIT=3000

# Database Chunk Size
DB_CHUNK_SIZE=10000

# Storage Path
SYSTEM_STORAGE_TEMPORARY_PATH=tmp/

# File Expiration (hours)
CSV_DOWNLOAD_FILE_EXPIRED_AT=72
CSV_ERROR_FILE_EXPIRED_AT=336

Config Structure (config/web.php)

'csv' => [
    'importLimit' => env('CSV_IMPORT_LIMIT', '3000'),        // Max records per import
    'downloadFileExpiredAt' => env('CSV_DOWNLOAD_FILE_EXPIRED_AT', 72),  // Download file expiration (hours)
    'errorFileExpiredAt' => env('CSV_ERROR_FILE_EXPIRED_AT', 336)       // Error file expiration (hours)
],
'db' => [
    'chunkSize' => env('DB_CHUNK_SIZE', '10000')             // Database chunk size for processing
],

Area Mapping

// Supported Areas
$areas = [
    '北海道',
    '東北',
    '関東',
    '中部',
    '関西',
    '中国',
    '四国',
    '九州',
    '沖縄'
];

Category Mapping

// Facility Categories
$categories = [
    '会議室',
    '展示場',
    'ホール',
    'スタジオ',
    'オフィス',
    // ... more categories
];

Performance Considerations

Chunk Processing

  • Chunk size: 100 records per chunk
  • Memory usage: Giảm memory usage bằng cách xử lý từng chunk
  • Error handling: Mỗi chunk được xử lý độc lập

Database Operations

  • Batch insert: Sử dụng chunk để insert/update
  • Transaction: Mỗi chunk được wrap trong transaction
  • Error rollback: Rollback toàn bộ chunk nếu có lỗi

Security Considerations

File Upload Security

  • File type validation: Chỉ chấp nhận .csv
  • File size limit: Giới hạn số lượng records
  • Path traversal protection: Validate file path

Data Validation

  • Input sanitization: Tự động chuyển đổi encoding
  • SQL injection protection: Sử dụng prepared statements
  • XSS protection: Validate và escape output

Access Control

  • Authentication: Yêu cầu đăng nhập
  • Authorization: Kiểm tra quyền truy cập
  • Organization isolation: Chỉ xử lý dữ liệu của organization hiện tại

Troubleshooting

Common Issues

1. Encoding Issues

Problem: Japanese characters display incorrectly
Solution:

  • Đảm bảo file CSV có BOM (Byte Order Mark)
  • Sử dụng UTF-8 encoding
  • Kiểm tra convertStringCode() function

2. Header Mismatch

Problem: "項目名が正しくありません" error
Solution:

  • Download template CSV mới nhất
  • Đảm bảo header khớp chính xác
  • Kiểm tra thứ tự cột

3. Duplicate Facility

Problem: "施設名・実施場所は既に登録されています" error
Solution:

  • Kiểm tra tên Facility và place
  • Sử dụng Facility ID để update thay vì tạo mới
  • Xóa Facility cũ trước khi tạo mới

4. Category Not Found

Problem: "カテゴリー1は存在しないカテゴリ名です" error
Solution:

  • Kiểm tra danh sách category hợp lệ
  • Đảm bảo category name khớp chính xác
  • Sử dụng category từ template

Debug Information

// Enable debug logging
Log::debug('Facility CSV Import Debug', [
    'file_size' => $file->getSize(),
    'record_count' => $recordCount,
    'validation_errors' => $errors,
    'organization_id' => $ownerOrganizationId
]);

Current System Implementation

File Processing

  • Chunk size: 100 records per chunk (configurable via config('web.db.chunkSize'))
  • Memory management: Xử lý từng chunk để tránh memory overflow
  • File storage: Temporary files lưu trong storage/app/tmp/
  • File cleanup: Tự động xóa file tạm sau khi xử lý

Error Handling

  • Error file generation: Tạo file CSV với BOM chứa records có lỗi
  • Error file format: Bao gồm header + data + error message column
  • Error file storage: Lưu trong storage/app/tmp/ với prefix Facility_import_error_
  • Error file expiration: Tự động xóa sau 336 giờ (14 ngày)

Validation

  • File validation: Kiểm tra extension .csv, số lượng records (max 3000)
  • Header validation: Kiểm tra số lượng và tên cột chính xác
  • Encoding validation: Tự động chuyển đổi từ SJIS-win sang UTF-8
  • Business validation: Kiểm tra duplicate, reference data, business rules

Sequence Diagram

Facility CSV Import Process

sequenceDiagram
    participant User as User
    participant Frontend as Frontend
    participant Controller as Facility Controller
    participant Service as Facility CSV Service
    participant Utility as ImportCsvUtility
    participant Validator as CsvValidate
    participant Repository as Facility Repository
    participant DB as Database
    participant Storage as Storage
    participant Mail as Mail Service

    User->>Frontend: Upload CSV file
    Frontend->>Controller: POST /api/v1/facility/import
    
    Note over Controller: Validate request parameters
    
    Controller->>Service: importCsvFile()
    
    Note over Service: Step 1: File Validation
    Service->>Utility: checkRecordSize()
    Utility-->>Service: Record count OK/Error
    
    alt Record count exceeds limit
        Service-->>Controller: RuntimeException
        Controller-->>Frontend: 400 Error
        Frontend-->>User: Error message
    end
    
    Note over Service: Step 2: Header Validation
    Service->>Utility: checkCsvHeader()
    Utility->>Utility: readHeader()
    Utility-->>Service: Header validation result
    
    alt Header validation fails
        Service-->>Controller: RuntimeException
        Controller-->>Frontend: 400 Error
        Frontend-->>User: Error message
    end
    
    Note over Service: Step 3: Create Error File
    Service->>Utility: createErrorFile('Facility')
    Utility-->>Service: Error file handle
    
    Note over Service: Step 4: Process Records
    loop For each record chunk (100 records)
        Service->>Utility: readRecords()
        Utility-->>Service: CSV records
        
        loop For each record
            Service->>Service: createRecordObject()
            Service->>Validator: validateRecord()
            
            Note over Validator: Validate each field
            Validator->>Validator: validateText('施設名')
            Validator->>Validator: validateZip('郵便番号')
            Validator->>Validator: validateDigit('平日料金')
            Validator->>Validator: validateTime('催事開始時間')
            
            Note over Validator: Business logic validation
            Validator->>Service: validateNameAndPlace()
            Validator->>Service: validateArea()
            Validator->>Service: validatePrefecture()
            Validator->>Service: validateFacilityCategory()
            Validator->>Service: validateOwnerAccount()
            
            alt Validation fails
                Service->>Utility: Write error record
                Utility-->>Service: Error file updated
            else Validation passes
                Service->>Repository: upsertCsvData()
                Repository->>DB: Insert/Update facilities
                Repository->>DB: Delete facility_category_relations
                Repository->>DB: Insert facility_category_relations
                DB-->>Repository: Success
                Repository-->>Service: Success
            end
        end
    end
    
    Note over Service: Step 5: Final Processing
    alt All records valid
        Service->>Mail: sendEmail()
        Service->>Storage: deleteCsvFile()
        Service-->>Controller: Success
        Controller-->>Frontend: 200 Success
        Frontend-->>User: Success message
    else Some records invalid
        Service->>Storage: Keep error file
        Service-->>Controller: Success with error file
        Controller-->>Frontend: 200 Success + error file
        Frontend-->>User: Success + error file download
    end

Facility CSV Export Process

sequenceDiagram
    participant User as User
    participant Frontend as Frontend
    participant Controller as Facility Controller
    participant Service as Facility CSV Service
    participant Repository as Facility Repository
    participant DB as Database
    participant Storage as Storage

    User->>Frontend: Click Export CSV
    Frontend->>Controller: GET /api/v1/facility/export
    
    Note over Controller: Validate request parameters
    
    Controller->>Service: createCsv()
    
    Note over Service: Step 1: Data Retrieval
    Service->>Repository: select(filterDto)
    Repository->>DB: SELECT facilities
    DB-->>Repository: Facilities data
    Repository-->>Service: Facilities collection
    
    Note over Service: Step 2: Create CSV File
    Service->>Storage: createCsvFile(filePath)
    Storage-->>Service: File handle
    
    Note over Service: Step 3: Write Header
    Service->>Service: createHeader()
    Service->>Storage: fputcsv(header)
    
    Note over Service: Step 4: Write Data
    loop For each facility chunk
        Service->>Service: formatData(facility)
        Service->>Storage: fputcsv(data)
    end
    
    Service->>Storage: fclose(file)
    
    Note over Service: Step 5: Return Download
    Service-->>Controller: File path
    Controller-->>Frontend: Download response
    Frontend-->>User: File download

Cách xem Sequence Diagram:

    (1-1/1)