fixx 01
This commit is contained in:
@@ -4254,7 +4254,8 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
|
|||||||
NewQuantity,
|
NewQuantity,
|
||||||
UsedQuantity,
|
UsedQuantity,
|
||||||
Custodian,
|
Custodian,
|
||||||
Borrower
|
Borrower,
|
||||||
|
Notes
|
||||||
FROM AssetInventory WITH (UPDLOCK, ROWLOCK)
|
FROM AssetInventory WITH (UPDLOCK, ROWLOCK)
|
||||||
WHERE AssetId = @assetId
|
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 nextNewQuantity = Math.max(stockBuckets.newQuantity - borrowFromNew, 0);
|
||||||
const nextUsedQuantity = Math.max(stockBuckets.usedQuantity - borrowFromUsed, 0);
|
const nextUsedQuantity = Math.max(stockBuckets.usedQuantity - borrowFromUsed, 0);
|
||||||
const nextStatus = resolveAssetStatusFromStock(nextEndingBalance, nextExportInPeriod);
|
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)
|
await new sql.Request(transaction)
|
||||||
.input('assetId', sql.Int, assetId)
|
.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('usedQuantity', sql.Int, nextUsedQuantity)
|
||||||
.input('status', sql.NVarChar, nextStatus)
|
.input('status', sql.NVarChar, nextStatus)
|
||||||
.input('exportedBy', sql.NVarChar, exportedByName)
|
.input('exportedBy', sql.NVarChar, exportedByName)
|
||||||
|
.input('notes', sql.NVarChar, nextAssetNotes)
|
||||||
.query(`
|
.query(`
|
||||||
UPDATE AssetInventory
|
UPDATE AssetInventory
|
||||||
SET Project = @project,
|
SET Project = @project,
|
||||||
@@ -4332,6 +4339,7 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
|
|||||||
UsedQuantity = @usedQuantity,
|
UsedQuantity = @usedQuantity,
|
||||||
Status = @status,
|
Status = @status,
|
||||||
ExportedBy = @exportedBy,
|
ExportedBy = @exportedBy,
|
||||||
|
Notes = @notes,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = GETDATE()
|
||||||
WHERE AssetId = @assetId
|
WHERE AssetId = @assetId
|
||||||
`);
|
`);
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class AccountManager {
|
|||||||
this.pendingAssetRequestRejectId = undefined;
|
this.pendingAssetRequestRejectId = undefined;
|
||||||
this.assetBorrowAutoRefreshTimer = undefined;
|
this.assetBorrowAutoRefreshTimer = undefined;
|
||||||
this.pendingAssetRequestDeleteConfirmResolver = undefined;
|
this.pendingAssetRequestDeleteConfirmResolver = undefined;
|
||||||
|
this.pendingBulkAssetDeleteConfirmResolver = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
configureNotifications() {
|
configureNotifications() {
|
||||||
@@ -1228,6 +1229,21 @@ class AccountManager {
|
|||||||
btn.dataset.boundClick = 'true';
|
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();
|
this.setupAssetBorrowRequestModalListeners();
|
||||||
|
|
||||||
const assetDepartmentForm = document.getElementById('assetDepartmentForm');
|
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') {
|
async confirmAssetRequestDelete(message, confirmButtonText = 'Xóa đơn') {
|
||||||
const modal = document.getElementById('assetRequestDeleteConfirmModal');
|
const modal = document.getElementById('assetRequestDeleteConfirmModal');
|
||||||
const messageNode = document.getElementById('assetRequestDeleteConfirmMessage');
|
const messageNode = document.getElementById('assetRequestDeleteConfirmMessage');
|
||||||
@@ -3580,7 +3632,7 @@ class AccountManager {
|
|||||||
</div>
|
</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' : ''}>
|
<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>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -3897,7 +3949,7 @@ class AccountManager {
|
|||||||
return;
|
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) {
|
if (!confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -6148,6 +6200,9 @@ class AccountManager {
|
|||||||
if (this.pendingAssetRequestDeleteConfirmResolver) {
|
if (this.pendingAssetRequestDeleteConfirmResolver) {
|
||||||
this.resolveAssetRequestDeleteConfirm(false);
|
this.resolveAssetRequestDeleteConfirm(false);
|
||||||
}
|
}
|
||||||
|
if (this.pendingBulkAssetDeleteConfirmResolver) {
|
||||||
|
this.resolveBulkAssetDeleteConfirm(false);
|
||||||
|
}
|
||||||
document.querySelectorAll('.modal-backdrop').forEach(modal => {
|
document.querySelectorAll('.modal-backdrop').forEach(modal => {
|
||||||
modal.classList.remove('open');
|
modal.classList.remove('open');
|
||||||
});
|
});
|
||||||
@@ -7058,6 +7113,9 @@ function closeAllModals() {
|
|||||||
if (app?.pendingAssetRequestDeleteConfirmResolver) {
|
if (app?.pendingAssetRequestDeleteConfirmResolver) {
|
||||||
app.resolveAssetRequestDeleteConfirm(false);
|
app.resolveAssetRequestDeleteConfirm(false);
|
||||||
}
|
}
|
||||||
|
if (app?.pendingBulkAssetDeleteConfirmResolver) {
|
||||||
|
app.resolveBulkAssetDeleteConfirm(false);
|
||||||
|
}
|
||||||
document.querySelectorAll('.modal-backdrop').forEach(modal => {
|
document.querySelectorAll('.modal-backdrop').forEach(modal => {
|
||||||
modal.classList.remove('open');
|
modal.classList.remove('open');
|
||||||
});
|
});
|
||||||
@@ -7099,6 +7157,18 @@ function closeDeleteAssetModal() {
|
|||||||
document.getElementById('deleteAssetModal').classList.remove('open');
|
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() {
|
function closeBorrowAssetModal() {
|
||||||
const modal = document.getElementById('borrowAssetModal');
|
const modal = document.getElementById('borrowAssetModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
|
|||||||
@@ -571,6 +571,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- 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-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">
|
<div class="modal-content w-full max-w-md bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user