add dự án
This commit is contained in:
@@ -263,6 +263,10 @@ function normalizeDepartmentName(value) {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function normalizeProjectName(value) {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
async function syncAssetDepartmentsFromInventory() {
|
||||
if (!pool) {
|
||||
return;
|
||||
@@ -1547,6 +1551,17 @@ async function createTables() {
|
||||
)
|
||||
END`,
|
||||
|
||||
// Asset Projects Table
|
||||
`IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AssetProjects')
|
||||
BEGIN
|
||||
CREATE TABLE AssetProjects (
|
||||
ProjectId INT PRIMARY KEY IDENTITY(1,1),
|
||||
ProjectName NVARCHAR(150) NOT NULL,
|
||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
||||
UpdatedDate DATETIME DEFAULT GETDATE()
|
||||
)
|
||||
END`,
|
||||
|
||||
// Asset Borrow Requests Table
|
||||
`IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AssetBorrowRequests')
|
||||
BEGIN
|
||||
@@ -1614,6 +1629,13 @@ async function createTables() {
|
||||
console.error('AssetDepartments index creation error:', err.message);
|
||||
}
|
||||
|
||||
// Ensure AssetProjects indexes exist
|
||||
try {
|
||||
await pool.request().query(`IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'UX_AssetProjects_ProjectName') CREATE UNIQUE INDEX UX_AssetProjects_ProjectName ON AssetProjects(ProjectName);`);
|
||||
} catch (err) {
|
||||
console.error('AssetProjects index creation error:', err.message);
|
||||
}
|
||||
|
||||
// Ensure AssetBorrowRequests indexes exist
|
||||
try {
|
||||
await pool.request().query(`IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_AssetBorrowRequests_AssetId') CREATE INDEX IX_AssetBorrowRequests_AssetId ON AssetBorrowRequests(AssetId);`);
|
||||
@@ -3058,6 +3080,216 @@ app.delete('/api/asset-departments/:id', requireAssetOrAdmin, async (req, res) =
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// API ROUTES - Asset Projects
|
||||
// ==========================================
|
||||
|
||||
app.get('/api/asset-projects', async (req, res) => {
|
||||
try {
|
||||
const result = await pool.request().query(`
|
||||
SELECT
|
||||
p.ProjectId,
|
||||
p.ProjectName,
|
||||
p.CreatedDate,
|
||||
p.UpdatedDate,
|
||||
COUNT(ai.AssetId) AS AssetCount
|
||||
FROM AssetProjects p
|
||||
LEFT JOIN AssetInventory ai
|
||||
ON LOWER(LTRIM(RTRIM(ai.Project))) = LOWER(LTRIM(RTRIM(p.ProjectName)))
|
||||
GROUP BY p.ProjectId, p.ProjectName, p.CreatedDate, p.UpdatedDate
|
||||
ORDER BY p.ProjectName ASC
|
||||
`);
|
||||
|
||||
res.json({ success: true, data: result.recordset });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/asset-projects', requireAssetOrAdmin, async (req, res) => {
|
||||
try {
|
||||
const projectName = normalizeProjectName(req.body?.projectName);
|
||||
if (!projectName) {
|
||||
return res.status(400).json({ success: false, message: 'Ten du an la bat buoc' });
|
||||
}
|
||||
|
||||
const existed = await pool.request()
|
||||
.input('projectName', sql.NVarChar, projectName)
|
||||
.query(`
|
||||
SELECT TOP 1 ProjectId
|
||||
FROM AssetProjects
|
||||
WHERE LOWER(LTRIM(RTRIM(ProjectName))) = LOWER(@projectName)
|
||||
`);
|
||||
|
||||
if (existed.recordset.length > 0) {
|
||||
return res.status(409).json({ success: false, message: 'Du an da ton tai' });
|
||||
}
|
||||
|
||||
const inserted = await pool.request()
|
||||
.input('projectName', sql.NVarChar, projectName)
|
||||
.query(`
|
||||
INSERT INTO AssetProjects (ProjectName)
|
||||
VALUES (@projectName);
|
||||
SELECT SCOPE_IDENTITY() AS ProjectId;
|
||||
`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Da them du an',
|
||||
projectId: inserted.recordset[0]?.ProjectId
|
||||
});
|
||||
} catch (err) {
|
||||
if (String(err.message || '').includes('UX_AssetProjects_ProjectName')) {
|
||||
return res.status(409).json({ success: false, message: 'Du an da ton tai' });
|
||||
}
|
||||
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
|
||||
try {
|
||||
const projectId = Number(req.params.id);
|
||||
if (!Number.isInteger(projectId) || projectId <= 0) {
|
||||
return res.status(400).json({ success: false, message: 'Ma du an khong hop le' });
|
||||
}
|
||||
|
||||
const projectName = normalizeProjectName(req.body?.projectName);
|
||||
if (!projectName) {
|
||||
return res.status(400).json({ success: false, message: 'Ten du an la bat buoc' });
|
||||
}
|
||||
|
||||
const currentResult = await pool.request()
|
||||
.input('projectId', sql.Int, projectId)
|
||||
.query(`
|
||||
SELECT ProjectId, ProjectName
|
||||
FROM AssetProjects
|
||||
WHERE ProjectId = @projectId
|
||||
`);
|
||||
|
||||
if (currentResult.recordset.length === 0) {
|
||||
return res.status(404).json({ success: false, message: 'Khong tim thay du an' });
|
||||
}
|
||||
|
||||
const currentProject = currentResult.recordset[0];
|
||||
const currentName = String(currentProject.ProjectName || '').trim();
|
||||
|
||||
if (currentName.toLowerCase() === projectName.toLowerCase()) {
|
||||
return res.json({ success: true, message: 'Da cap nhat du an' });
|
||||
}
|
||||
|
||||
const duplicated = await pool.request()
|
||||
.input('projectName', sql.NVarChar, projectName)
|
||||
.input('projectId', sql.Int, projectId)
|
||||
.query(`
|
||||
SELECT TOP 1 ProjectId
|
||||
FROM AssetProjects
|
||||
WHERE ProjectId <> @projectId
|
||||
AND LOWER(LTRIM(RTRIM(ProjectName))) = LOWER(@projectName)
|
||||
`);
|
||||
|
||||
if (duplicated.recordset.length > 0) {
|
||||
return res.status(409).json({ success: false, message: 'Du an da ton tai' });
|
||||
}
|
||||
|
||||
const transaction = new sql.Transaction(pool);
|
||||
await transaction.begin();
|
||||
|
||||
try {
|
||||
await new sql.Request(transaction)
|
||||
.input('projectId', sql.Int, projectId)
|
||||
.input('projectName', sql.NVarChar, projectName)
|
||||
.query(`
|
||||
UPDATE AssetProjects
|
||||
SET ProjectName = @projectName,
|
||||
UpdatedDate = GETDATE()
|
||||
WHERE ProjectId = @projectId
|
||||
`);
|
||||
|
||||
await new sql.Request(transaction)
|
||||
.input('oldProjectName', sql.NVarChar, currentName)
|
||||
.input('newProjectName', sql.NVarChar, projectName)
|
||||
.query(`
|
||||
UPDATE AssetInventory
|
||||
SET Project = @newProjectName,
|
||||
UpdatedDate = GETDATE()
|
||||
WHERE LOWER(LTRIM(RTRIM(Project))) = LOWER(@oldProjectName)
|
||||
`);
|
||||
|
||||
await transaction.commit();
|
||||
res.json({ success: true, message: 'Da cap nhat du an' });
|
||||
} catch (transactionErr) {
|
||||
try {
|
||||
await transaction.rollback();
|
||||
} catch (rollbackErr) {
|
||||
// Ignore rollback errors if transaction already ended.
|
||||
}
|
||||
throw transactionErr;
|
||||
}
|
||||
} catch (err) {
|
||||
if (String(err.message || '').includes('UX_AssetProjects_ProjectName')) {
|
||||
return res.status(409).json({ success: false, message: 'Du an da ton tai' });
|
||||
}
|
||||
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
|
||||
try {
|
||||
const projectId = Number(req.params.id);
|
||||
if (!Number.isInteger(projectId) || projectId <= 0) {
|
||||
return res.status(400).json({ success: false, message: 'Ma du an khong hop le' });
|
||||
}
|
||||
|
||||
const currentResult = await pool.request()
|
||||
.input('projectId', sql.Int, projectId)
|
||||
.query(`
|
||||
SELECT ProjectId, ProjectName
|
||||
FROM AssetProjects
|
||||
WHERE ProjectId = @projectId
|
||||
`);
|
||||
|
||||
if (currentResult.recordset.length === 0) {
|
||||
return res.status(404).json({ success: false, message: 'Khong tim thay du an' });
|
||||
}
|
||||
|
||||
const projectName = String(currentResult.recordset[0].ProjectName || '').trim();
|
||||
const transaction = new sql.Transaction(pool);
|
||||
await transaction.begin();
|
||||
|
||||
try {
|
||||
await new sql.Request(transaction)
|
||||
.input('projectName', sql.NVarChar, projectName)
|
||||
.query(`
|
||||
UPDATE AssetInventory
|
||||
SET Project = NULL,
|
||||
UpdatedDate = GETDATE()
|
||||
WHERE LOWER(LTRIM(RTRIM(Project))) = LOWER(@projectName)
|
||||
`);
|
||||
|
||||
await new sql.Request(transaction)
|
||||
.input('projectId', sql.Int, projectId)
|
||||
.query(`
|
||||
DELETE FROM AssetProjects
|
||||
WHERE ProjectId = @projectId
|
||||
`);
|
||||
|
||||
await transaction.commit();
|
||||
res.json({ success: true, message: 'Da xoa du an' });
|
||||
} catch (transactionErr) {
|
||||
try {
|
||||
await transaction.rollback();
|
||||
} catch (rollbackErr) {
|
||||
// Ignore rollback errors if transaction already ended.
|
||||
}
|
||||
throw transactionErr;
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// API ROUTES - Asset Inventory
|
||||
// ==========================================
|
||||
|
||||
Reference in New Issue
Block a user