This commit is contained in:
2026-05-06 16:56:58 +07:00
parent 9f14491562
commit 395b1f6e85
3 changed files with 101 additions and 3 deletions

View File

@@ -4254,7 +4254,8 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
NewQuantity,
UsedQuantity,
Custodian,
Borrower
Borrower,
Notes
FROM AssetInventory WITH (UPDLOCK, ROWLOCK)
WHERE AssetId = @assetId
`);
@@ -4311,6 +4312,11 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
const nextNewQuantity = Math.max(stockBuckets.newQuantity - borrowFromNew, 0);
const nextUsedQuantity = Math.max(stockBuckets.usedQuantity - borrowFromUsed, 0);
const nextStatus = resolveAssetStatusFromStock(nextEndingBalance, nextExportInPeriod);
const existingAssetNotes = String(asset.Notes || '').trim();
const cleanExportNote = String(exportNote || '').trim();
const nextAssetNotes = cleanExportNote
? (existingAssetNotes ? `${existingAssetNotes}\n${cleanExportNote}` : cleanExportNote)
: (existingAssetNotes || null);
await new sql.Request(transaction)
.input('assetId', sql.Int, assetId)
@@ -4322,6 +4328,7 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
.input('usedQuantity', sql.Int, nextUsedQuantity)
.input('status', sql.NVarChar, nextStatus)
.input('exportedBy', sql.NVarChar, exportedByName)
.input('notes', sql.NVarChar, nextAssetNotes)
.query(`
UPDATE AssetInventory
SET Project = @project,
@@ -4332,6 +4339,7 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
UsedQuantity = @usedQuantity,
Status = @status,
ExportedBy = @exportedBy,
Notes = @notes,
UpdatedDate = GETDATE()
WHERE AssetId = @assetId
`);

View File

@@ -66,6 +66,7 @@ class AccountManager {
this.pendingAssetRequestRejectId = undefined;
this.assetBorrowAutoRefreshTimer = undefined;
this.pendingAssetRequestDeleteConfirmResolver = undefined;
this.pendingBulkAssetDeleteConfirmResolver = undefined;
}
configureNotifications() {
@@ -1228,6 +1229,21 @@ class AccountManager {
btn.dataset.boundClick = 'true';
});
const confirmBulkAssetDeleteBtn = document.getElementById('confirmBulkAssetDeleteBtn');
if (confirmBulkAssetDeleteBtn && confirmBulkAssetDeleteBtn.dataset.boundClick !== 'true') {
confirmBulkAssetDeleteBtn.addEventListener('click', () => this.resolveBulkAssetDeleteConfirm(true));
confirmBulkAssetDeleteBtn.dataset.boundClick = 'true';
}
document.querySelectorAll('.cancel-bulk-asset-delete-confirm').forEach(btn => {
if (btn.dataset.boundClick === 'true') {
return;
}
btn.addEventListener('click', () => this.resolveBulkAssetDeleteConfirm(false));
btn.dataset.boundClick = 'true';
});
this.setupAssetBorrowRequestModalListeners();
const assetDepartmentForm = document.getElementById('assetDepartmentForm');
@@ -3358,6 +3374,42 @@ class AccountManager {
}
}
resolveBulkAssetDeleteConfirm(confirmed) {
const modal = document.getElementById('bulkDeleteAssetsConfirmModal');
if (modal) {
modal.classList.remove('open');
}
const resolver = this.pendingBulkAssetDeleteConfirmResolver;
this.pendingBulkAssetDeleteConfirmResolver = undefined;
if (typeof resolver === 'function') {
resolver(Boolean(confirmed));
}
}
async confirmBulkAssetDelete(selectedCount) {
const modal = document.getElementById('bulkDeleteAssetsConfirmModal');
const messageNode = document.getElementById('bulkDeleteAssetsConfirmMessage');
const countNode = document.getElementById('bulkDeleteAssetsConfirmCount');
if (!modal || !messageNode || !countNode) {
return window.confirm(`Bạn có chắc muốn xóa ${selectedCount} tài sản đã chọn?`);
}
countNode.textContent = String(selectedCount);
messageNode.textContent = `Bạn có chắc muốn xóa ${selectedCount} tài sản đã chọn?`;
if (this.pendingBulkAssetDeleteConfirmResolver) {
this.resolveBulkAssetDeleteConfirm(false);
}
modal.classList.add('open');
return new Promise(resolve => {
this.pendingBulkAssetDeleteConfirmResolver = resolve;
});
}
async confirmAssetRequestDelete(message, confirmButtonText = 'Xóa đơn') {
const modal = document.getElementById('assetRequestDeleteConfirmModal');
const messageNode = document.getElementById('assetRequestDeleteConfirmMessage');
@@ -3580,7 +3632,7 @@ class AccountManager {
</div>
<button id="bulkDeleteAssetsBtn" class="border border-red-200 text-red-600 px-3 py-1.5 rounded-md text-[11px] font-bold flex items-center gap-1.5 transition-colors ${(selectedCount === 0 || !canManageAssets) ? 'opacity-50 cursor-not-allowed' : 'hover:bg-red-50'}" ${(selectedCount === 0 || !canManageAssets) ? 'disabled' : ''}>
<span class="material-symbols-outlined text-base">delete_sweep</span>
Xóa dã chọn (<span id="selectedAssetCount">${selectedCount}</span>)
Xóa đã chọn (<span id="selectedAssetCount">${selectedCount}</span>)
</button>
</div>
@@ -3897,7 +3949,7 @@ class AccountManager {
return;
}
const confirmed = window.confirm(`Bạn có chắc mudn xóa ${selectedIds.length} tài sản dã chọn?`);
const confirmed = await this.confirmBulkAssetDelete(selectedIds.length);
if (!confirmed) {
return;
}
@@ -6148,6 +6200,9 @@ class AccountManager {
if (this.pendingAssetRequestDeleteConfirmResolver) {
this.resolveAssetRequestDeleteConfirm(false);
}
if (this.pendingBulkAssetDeleteConfirmResolver) {
this.resolveBulkAssetDeleteConfirm(false);
}
document.querySelectorAll('.modal-backdrop').forEach(modal => {
modal.classList.remove('open');
});
@@ -7058,6 +7113,9 @@ function closeAllModals() {
if (app?.pendingAssetRequestDeleteConfirmResolver) {
app.resolveAssetRequestDeleteConfirm(false);
}
if (app?.pendingBulkAssetDeleteConfirmResolver) {
app.resolveBulkAssetDeleteConfirm(false);
}
document.querySelectorAll('.modal-backdrop').forEach(modal => {
modal.classList.remove('open');
});
@@ -7099,6 +7157,18 @@ function closeDeleteAssetModal() {
document.getElementById('deleteAssetModal').classList.remove('open');
}
function closeBulkDeleteAssetsConfirmModal() {
if (app?.pendingBulkAssetDeleteConfirmResolver) {
app.resolveBulkAssetDeleteConfirm(false);
return;
}
const modal = document.getElementById('bulkDeleteAssetsConfirmModal');
if (modal) {
modal.classList.remove('open');
}
}
function closeBorrowAssetModal() {
const modal = document.getElementById('borrowAssetModal');
if (modal) {

View File

@@ -571,6 +571,26 @@
</div>
</div>
<!-- Bulk Delete Assets Confirm Modal -->
<div class="modal-backdrop fixed inset-0 z-[110] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="bulkDeleteAssetsConfirmModal" style="z-index: 130;">
<div class="modal-content w-full max-w-md bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 bg-red-50 flex items-center gap-3">
<span class="material-symbols-outlined text-red-600 text-2xl">warning</span>
<h3 class="text-base font-extrabold text-red-700">Xóa tài sản đã chọn</h3>
</div>
<div class="p-6">
<p id="bulkDeleteAssetsConfirmMessage" class="text-sm text-slate-600 mb-6">Bạn có chắc muốn xóa các tài sản đã chọn?</p>
<div class="mb-4 rounded-lg border border-red-100 bg-red-50 px-3 py-2 text-xs text-red-700">
Số lượng: <strong id="bulkDeleteAssetsConfirmCount">0</strong> tài sản
</div>
<div class="flex gap-3">
<button type="button" class="cancel-bulk-asset-delete-confirm flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeBulkDeleteAssetsConfirmModal()">Hủy</button>
<button type="button" id="confirmBulkAssetDeleteBtn" class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-xs font-bold">Xóa</button>
</div>
</div>
</div>
</div>
<!-- Add/Edit Asset Department Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="assetDepartmentModal">
<div class="modal-content w-full max-w-md bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">