Support import images via URL or ZIP file reference

This commit is contained in:
Arya Dwi Putra
2025-12-03 08:39:02 +07:00
parent 403364a6ad
commit 4ab1eaeb8c
3 changed files with 58 additions and 23 deletions

View File

@@ -198,6 +198,7 @@ class AssetController extends Controller
{ {
$request->validate([ $request->validate([
'file' => ['required','file','mimes:csv,txt'], 'file' => ['required','file','mimes:csv,txt'],
'images_zip' => ['nullable','file','mimes:zip'],
'action' => ['nullable','in:preview,import'], 'action' => ['nullable','in:preview,import'],
]); ]);
@@ -208,6 +209,24 @@ class AssetController extends Controller
return back()->withErrors(['file' => 'File tidak bisa dibaca']); return back()->withErrors(['file' => 'File tidak bisa dibaca']);
} }
// Jika ada ZIP gambar, siapkan mapping filename => binary
$images = [];
if ($request->hasFile('images_zip')) {
$zipPath = $request->file('images_zip')->getRealPath();
$zip = new \ZipArchive();
if ($zip->open($zipPath) === true) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
// abaikan folder
if (str_ends_with($name, '/')) {
continue;
}
$images[basename($name)] = $zip->getFromIndex($i);
}
$zip->close();
}
}
$header = fgetcsv($handle); $header = fgetcsv($handle);
$rows = []; $rows = [];
$line = 1; $line = 1;
@@ -257,6 +276,7 @@ class AssetController extends Controller
'available_quantity' => (int) ($row['available_quantity'] ?? $row['quantity'] ?? 1), 'available_quantity' => (int) ($row['available_quantity'] ?? $row['quantity'] ?? 1),
'is_pool' => !empty($row['is_pool']), 'is_pool' => !empty($row['is_pool']),
'image_url' => $row['image_url'] ?? null, 'image_url' => $row['image_url'] ?? null,
'image_file' => $row['image_file'] ?? null,
], ],
'target_id' => $targetId, 'target_id' => $targetId,
]; ];
@@ -281,7 +301,8 @@ class AssetController extends Controller
if ($row['status'] === 'invalid') { $invalid++; continue; } if ($row['status'] === 'invalid') { $invalid++; continue; }
$payload = $row['payload']; $payload = $row['payload'];
$imageUrl = $payload['image_url'] ?? null; $imageUrl = $payload['image_url'] ?? null;
unset($payload['image_url']); $imageFile = $payload['image_file'] ?? null;
unset($payload['image_url'], $payload['image_file']);
$assetModel = null; $assetModel = null;
if ($row['target_id']) { if ($row['target_id']) {
@@ -296,21 +317,30 @@ class AssetController extends Controller
$created++; $created++;
} }
// Download image jika disediakan // Tambahkan foto jika disediakan via URL atau file name di ZIP
if ($assetModel && $imageUrl) { if ($assetModel) {
try { $binary = null;
$resp = \Illuminate\Support\Facades\Http::get($imageUrl); if ($imageUrl) {
if ($resp->ok()) { try {
$path = "assets/{$assetModel->id}/import-".Str::random(6).".jpg"; $resp = \Illuminate\Support\Facades\Http::get($imageUrl);
\Illuminate\Support\Facades\Storage::disk('public')->put($path, $resp->body()); if ($resp->ok()) {
\App\Models\AssetPhoto::create([ $binary = $resp->body();
'asset_id' => $assetModel->id, }
'path' => $path, } catch (\Throwable $e) {
'is_primary' => $assetModel->photos()->count() === 0, // abaikan error download agar import tetap lanjut
]);
} }
} catch (\Throwable $e) { } elseif ($imageFile && isset($images[$imageFile])) {
// abaikan error download agar import tetap lanjut $binary = $images[$imageFile];
}
if ($binary) {
$path = "assets/{$assetModel->id}/import-".Str::random(6).".jpg";
\Illuminate\Support\Facades\Storage::disk('public')->put($path, $binary);
\App\Models\AssetPhoto::create([
'asset_id' => $assetModel->id,
'path' => $path,
'is_primary' => $assetModel->photos()->count() === 0,
]);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
code,name,serial_number,status,category,location,rfid_tag,nfc_tag,is_consumable,quantity,available_quantity,is_pool,image_url code,name,serial_number,status,category,location,rfid_tag,nfc_tag,is_consumable,quantity,available_quantity,is_pool,image_url,image_file
AST-90001,Laptop Dell XPS 13,SN-XPS-90001,Active,Laptop,Warehouse,RFID-10001,NFC-10001,0,1,1,0,https://images.unsplash.com/photo-1517336714731-489689fd1ca8?auto=format&fit=crop&w=800&q=80 AST-90001,Laptop Dell XPS 13,SN-XPS-90001,Active,Laptop,Warehouse,RFID-10001,NFC-10001,0,1,1,0,https://images.unsplash.com/photo-1517336714731-489689fd1ca8?auto=format&fit=crop&w=800&q=80,
AST-90002,Printer HP LaserJet,SN-HP-90002,Active,Printer,Warehouse,RFID-10002,NFC-10002,0,1,1,0,https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?auto=format&fit=crop&w=800&q=80 AST-90002,Printer HP LaserJet,SN-HP-90002,Active,Printer,Warehouse,RFID-10002,NFC-10002,0,1,1,0,,printer.jpg
AST-90003,Kursi Ergonomis,SN-CHAIR-90003,Active,Furniture,Office A,RFID-10003,NFC-10003,0,10,10,1,https://images.unsplash.com/photo-1433838552652-f9a46b332c40?auto=format&fit=crop&w=800&q=80 AST-90003,Kursi Ergonomis,SN-CHAIR-90003,Active,Furniture,Office A,RFID-10003,NFC-10003,0,10,10,1,https://images.unsplash.com/photo-1433838552652-f9a46b332c40?auto=format&fit=crop&w=800&q=80,
AST-90004,Tinta Printer Hitam,,Active,Consumable,Warehouse,,,1,50,50,0,https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&w=800&q=80 AST-90004,Tinta Printer Hitam,,Active,Consumable,Warehouse,,,1,50,50,0,,tinta.jpg
AST-90005,Router Mikrotik CCR,SN-RT-90005,Active,Network,Data Center,RFID-10005,NFC-10005,0,1,1,0,https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=800&q=80 AST-90005,Router Mikrotik CCR,SN-RT-90005,Active,Network,Data Center,RFID-10005,NFC-10005,0,1,1,0,https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=800&q=80,
1 code name serial_number status category location rfid_tag nfc_tag is_consumable quantity available_quantity is_pool image_url image_file
2 AST-90001 Laptop Dell XPS 13 SN-XPS-90001 Active Laptop Warehouse RFID-10001 NFC-10001 0 1 1 0 https://images.unsplash.com/photo-1517336714731-489689fd1ca8?auto=format&fit=crop&w=800&q=80
3 AST-90002 Printer HP LaserJet SN-HP-90002 Active Printer Warehouse RFID-10002 NFC-10002 0 1 1 0 https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?auto=format&fit=crop&w=800&q=80 printer.jpg
4 AST-90003 Kursi Ergonomis SN-CHAIR-90003 Active Furniture Office A RFID-10003 NFC-10003 0 10 10 1 https://images.unsplash.com/photo-1433838552652-f9a46b332c40?auto=format&fit=crop&w=800&q=80
5 AST-90004 Tinta Printer Hitam Active Consumable Warehouse 1 50 50 0 https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&w=800&q=80 tinta.jpg
6 AST-90005 Router Mikrotik CCR SN-RT-90005 Active Network Data Center RFID-10005 NFC-10005 0 1 1 0 https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=800&q=80

View File

@@ -327,14 +327,19 @@
<div class="modal-body"> <div class="modal-body">
<p class="text-muted"> <p class="text-muted">
Unggah file CSV (Excel bisa ekspor ke CSV). Header yang didukung: Unggah file CSV (Excel bisa ekspor ke CSV). Header yang didukung:
<code>code,name,serial_number,status,category,location,rfid_tag,nfc_tag,is_consumable,quantity,available_quantity,is_pool,image_url</code>. <code>code,name,serial_number,status,category,location,rfid_tag,nfc_tag,is_consumable,quantity,available_quantity,is_pool,image_url,image_file</code>.
Baris tanpa nama akan ditandai invalid. Contoh file: Kolom <code>image_url</code> akan di-download; kolom <code>image_file</code> akan dicari di ZIP gambar (opsional). Baris tanpa nama akan ditandai invalid. Contoh file:
<a href="{{ asset('samples/assets_import_sample.csv') }}" target="_blank">assets_import_sample.csv</a> <a href="{{ asset('samples/assets_import_sample.csv') }}" target="_blank">assets_import_sample.csv</a>
</p> </p>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">File</label> <label class="form-label">File</label>
<input type="file" name="file" class="form-control" accept=".csv,.txt" required> <input type="file" name="file" class="form-control" accept=".csv,.txt" required>
</div> </div>
<div class="mb-3">
<label class="form-label">ZIP Gambar (opsional)</label>
<input type="file" name="images_zip" class="form-control" accept=".zip">
<small class="text-muted">Isi dengan gambar yang namanya sesuai kolom <code>image_file</code> (misal printer.jpg, tinta.jpg).</small>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" name="action" id="importPreview" value="preview" checked> <input class="form-check-input" type="radio" name="action" id="importPreview" value="preview" checked>
<label class="form-check-label" for="importPreview">Preview (tidak menyimpan)</label> <label class="form-check-label" for="importPreview">Preview (tidak menyimpan)</label>