feat: add inventory CSV import with warehouse/bin locations
- Add warehouse and bin_location columns to Inventory model - Create inventory_import_service for bulk TSV/CSV import - Add POST /api/v1/admin/inventory/import endpoint - Add Import button and modal to inventory admin page - Support both single-unit rows and explicit QUANTITY column File format: BIN, EAN, PRODUCT (optional), QUANTITY (optional) Products matched by GTIN/EAN, unmatched items reported. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,7 @@ function adminInventory() {
|
||||
showAdjustModal: false,
|
||||
showSetModal: false,
|
||||
showDeleteModal: false,
|
||||
showImportModal: false,
|
||||
selectedItem: null,
|
||||
|
||||
// Form data
|
||||
@@ -76,6 +77,17 @@ function adminInventory() {
|
||||
quantity: 0
|
||||
},
|
||||
|
||||
// Import form
|
||||
importForm: {
|
||||
vendor_id: '',
|
||||
warehouse: 'strassen',
|
||||
file: null,
|
||||
clear_existing: false
|
||||
},
|
||||
importing: false,
|
||||
importResult: null,
|
||||
vendorsList: [],
|
||||
|
||||
// Debounce timer
|
||||
searchTimeout: null,
|
||||
|
||||
@@ -144,6 +156,9 @@ function adminInventory() {
|
||||
this.initVendorSelector();
|
||||
});
|
||||
|
||||
// Load vendors list for import modal
|
||||
await this.loadVendorsList();
|
||||
|
||||
// Check localStorage for saved vendor
|
||||
const savedVendorId = localStorage.getItem('inventory_selected_vendor_id');
|
||||
if (savedVendorId) {
|
||||
@@ -501,6 +516,93 @@ function adminInventory() {
|
||||
this.pagination.page = pageNum;
|
||||
this.loadInventory();
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// Import Methods
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Load vendors list for import modal
|
||||
*/
|
||||
async loadVendorsList() {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/vendors', { limit: 100 });
|
||||
this.vendorsList = response.vendors || [];
|
||||
} catch (error) {
|
||||
adminInventoryLog.error('Failed to load vendors:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute inventory import
|
||||
*/
|
||||
async executeImport() {
|
||||
if (!this.importForm.vendor_id || !this.importForm.file) {
|
||||
Utils.showToast('Please select a vendor and file', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.importing = true;
|
||||
this.importResult = null;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.importForm.file);
|
||||
formData.append('vendor_id', this.importForm.vendor_id);
|
||||
formData.append('warehouse', this.importForm.warehouse || 'strassen');
|
||||
formData.append('clear_existing', this.importForm.clear_existing);
|
||||
|
||||
const response = await fetch('/api/v1/admin/inventory/import', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token') || ''}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || 'Import failed');
|
||||
}
|
||||
|
||||
this.importResult = await response.json();
|
||||
|
||||
if (this.importResult.success) {
|
||||
adminInventoryLog.info('Import successful:', this.importResult);
|
||||
Utils.showToast(
|
||||
`Imported ${this.importResult.quantity_imported} units (${this.importResult.entries_created} new, ${this.importResult.entries_updated} updated)`,
|
||||
'success'
|
||||
);
|
||||
// Refresh inventory list
|
||||
await this.refresh();
|
||||
} else {
|
||||
Utils.showToast('Import completed with errors', 'warning');
|
||||
}
|
||||
} catch (error) {
|
||||
adminInventoryLog.error('Import failed:', error);
|
||||
this.importResult = {
|
||||
success: false,
|
||||
errors: [error.message || 'Import failed']
|
||||
};
|
||||
Utils.showToast(error.message || 'Import failed', 'error');
|
||||
} finally {
|
||||
this.importing = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Close import modal and reset form
|
||||
*/
|
||||
closeImportModal() {
|
||||
this.showImportModal = false;
|
||||
this.importResult = null;
|
||||
this.importForm = {
|
||||
vendor_id: '',
|
||||
warehouse: 'strassen',
|
||||
file: null,
|
||||
clear_existing: false
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user