time
This commit is contained in:
@@ -3,6 +3,8 @@ FROM node:20-bookworm-slim
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
ENV TZ=Asia/Ho_Chi_Minh
|
||||||
|
ENV APP_TIME_ZONE=Asia/Ho_Chi_Minh
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci --omit=dev && npm cache clean --force
|
RUN npm ci --omit=dev && npm cache clean --force
|
||||||
@@ -12,4 +14,4 @@ COPY public ./public
|
|||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["node", "backend/server.js"]
|
CMD ["node", "backend/server.js"]
|
||||||
|
|||||||
@@ -10,10 +10,14 @@ const nodemailer = require('nodemailer');
|
|||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const XLSX = require('xlsx');
|
const XLSX = require('xlsx');
|
||||||
const dotenv = require('dotenv');
|
const dotenv = require('dotenv');
|
||||||
const app = express();
|
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
const APP_TIME_ZONE = process.env.APP_TIME_ZONE || process.env.TZ || 'Asia/Ho_Chi_Minh';
|
||||||
|
process.env.TZ = APP_TIME_ZONE;
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
function envBool(name, defaultValue) {
|
function envBool(name, defaultValue) {
|
||||||
const value = process.env[name];
|
const value = process.env[name];
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
@@ -48,6 +52,52 @@ const PASSWORD_RESET_TOKEN_TTL_MINUTES = Number(process.env.PASSWORD_RESET_TOKEN
|
|||||||
|
|
||||||
let mailTransporter;
|
let mailTransporter;
|
||||||
|
|
||||||
|
const appTimePartsFormatter = new Intl.DateTimeFormat('en-CA', {
|
||||||
|
timeZone: APP_TIME_ZONE,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hourCycle: 'h23'
|
||||||
|
});
|
||||||
|
|
||||||
|
function getAppTimeParts(value = new Date()) {
|
||||||
|
const date = value instanceof Date ? value : new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return appTimePartsFormatter.formatToParts(date).reduce((parts, part) => {
|
||||||
|
if (part.type !== 'literal') {
|
||||||
|
parts[part.type] = part.value;
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAppTimestampForCode(value = new Date(), includeMilliseconds = false) {
|
||||||
|
const date = value instanceof Date ? value : new Date(value);
|
||||||
|
const parts = getAppTimeParts(date);
|
||||||
|
if (!parts) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = [
|
||||||
|
parts.year,
|
||||||
|
parts.month,
|
||||||
|
parts.day,
|
||||||
|
parts.hour,
|
||||||
|
parts.minute,
|
||||||
|
parts.second
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
return includeMilliseconds
|
||||||
|
? `${timestamp}${String(date.getMilliseconds()).padStart(3, '0')}`
|
||||||
|
: timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
function isBcryptHash(value) {
|
function isBcryptHash(value) {
|
||||||
return typeof value === 'string' && /^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/.test(value);
|
return typeof value === 'string' && /^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/.test(value);
|
||||||
}
|
}
|
||||||
@@ -1113,16 +1163,7 @@ function generateManualAssetCodeFromPayload(payload = {}) {
|
|||||||
const fromSerial = sanitizeAssetCodeToken(payload.serialNumber);
|
const fromSerial = sanitizeAssetCodeToken(payload.serialNumber);
|
||||||
const fromName = sanitizeAssetCodeToken(payload.assetName);
|
const fromName = sanitizeAssetCodeToken(payload.assetName);
|
||||||
const base = (fromModel || fromSerial || fromName || 'ASSET').slice(0, 32);
|
const base = (fromModel || fromSerial || fromName || 'ASSET').slice(0, 32);
|
||||||
const now = new Date();
|
const timestamp = formatAppTimestampForCode(new Date(), true);
|
||||||
const timestamp = [
|
|
||||||
String(now.getFullYear()),
|
|
||||||
String(now.getMonth() + 1).padStart(2, '0'),
|
|
||||||
String(now.getDate()).padStart(2, '0'),
|
|
||||||
String(now.getHours()).padStart(2, '0'),
|
|
||||||
String(now.getMinutes()).padStart(2, '0'),
|
|
||||||
String(now.getSeconds()).padStart(2, '0'),
|
|
||||||
String(now.getMilliseconds()).padStart(3, '0')
|
|
||||||
].join('');
|
|
||||||
const randomSuffix = String(Math.floor(Math.random() * 100)).padStart(2, '0');
|
const randomSuffix = String(Math.floor(Math.random() * 100)).padStart(2, '0');
|
||||||
return `AST-${base}-${timestamp}${randomSuffix}`;
|
return `AST-${base}-${timestamp}${randomSuffix}`;
|
||||||
}
|
}
|
||||||
@@ -1598,7 +1639,8 @@ const sqlConfig = {
|
|||||||
trustServerCertificate: DB_TRUST_CERTIFICATE,
|
trustServerCertificate: DB_TRUST_CERTIFICATE,
|
||||||
enableKeepAlive: true,
|
enableKeepAlive: true,
|
||||||
connectTimeout: DB_CONNECT_TIMEOUT,
|
connectTimeout: DB_CONNECT_TIMEOUT,
|
||||||
encrypt: DB_ENCRYPT
|
encrypt: DB_ENCRYPT,
|
||||||
|
useUTC: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1619,7 +1661,8 @@ async function initializeDatabase() {
|
|||||||
connectTimeout: DB_CONNECT_TIMEOUT,
|
connectTimeout: DB_CONNECT_TIMEOUT,
|
||||||
database: 'master',
|
database: 'master',
|
||||||
trustServerCertificate: DB_TRUST_CERTIFICATE,
|
trustServerCertificate: DB_TRUST_CERTIFICATE,
|
||||||
encrypt: DB_ENCRYPT
|
encrypt: DB_ENCRYPT,
|
||||||
|
useUTC: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1683,6 +1726,97 @@ async function migrateLegacyPasswords() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureAppTimeDefaultConstraints() {
|
||||||
|
await pool.request().query(`
|
||||||
|
DECLARE @schemaName SYSNAME = N'dbo';
|
||||||
|
DECLARE @defaults TABLE (
|
||||||
|
TableName SYSNAME NOT NULL,
|
||||||
|
ColumnName SYSNAME NOT NULL,
|
||||||
|
ConstraintName SYSNAME NOT NULL,
|
||||||
|
Definition NVARCHAR(MAX) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO @defaults (TableName, ColumnName, ConstraintName, Definition)
|
||||||
|
VALUES
|
||||||
|
(N'Users', N'CreatedDate', N'DF_Users_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'Applications', N'CreatedDate', N'DF_Applications_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'Applications', N'UpdatedDate', N'DF_Applications_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'Accounts', N'CreatedDate', N'DF_Accounts_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'Accounts', N'UpdatedDate', N'DF_Accounts_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetInventory', N'CreatedDate', N'DF_AssetInventory_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetInventory', N'UpdatedDate', N'DF_AssetInventory_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetDepartments', N'CreatedDate', N'DF_AssetDepartments_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetDepartments', N'UpdatedDate', N'DF_AssetDepartments_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetProjects', N'CreatedDate', N'DF_AssetProjects_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetProjects', N'UpdatedDate', N'DF_AssetProjects_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetBorrowRequests', N'BorrowDate', N'DF_AssetBorrowRequests_BorrowDate', N'(CAST(DATEADD(HOUR, 7, SYSUTCDATETIME()) AS DATE))'),
|
||||||
|
(N'AssetBorrowRequests', N'CreatedDate', N'DF_AssetBorrowRequests_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetBorrowRequests', N'UpdatedDate', N'DF_AssetBorrowRequests_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetBorrowRequestLinks', N'CreatedDate', N'DF_AssetBorrowRequestLinks_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetExportHistory', N'ExportedDate', N'DF_AssetExportHistory_ExportedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetExportHistory', N'CreatedDate', N'DF_AssetExportHistory_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetExportHistory', N'UpdatedDate', N'DF_AssetExportHistory_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetDamageDisposalHistory', N'ActionDate', N'DF_AssetDamageDisposalHistory_ActionDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetDamageDisposalHistory', N'CreatedDate', N'DF_AssetDamageDisposalHistory_CreatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AssetDamageDisposalHistory', N'UpdatedDate', N'DF_AssetDamageDisposalHistory_UpdatedDate', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))'),
|
||||||
|
(N'AuditLog', N'Timestamp', N'DF_AuditLog_Timestamp', N'(DATEADD(HOUR, 7, SYSUTCDATETIME()))');
|
||||||
|
|
||||||
|
DECLARE @tableName SYSNAME;
|
||||||
|
DECLARE @columnName SYSNAME;
|
||||||
|
DECLARE @constraintName SYSNAME;
|
||||||
|
DECLARE @definition NVARCHAR(MAX);
|
||||||
|
DECLARE @existingName SYSNAME;
|
||||||
|
DECLARE @objectId INT;
|
||||||
|
DECLARE @sql NVARCHAR(MAX);
|
||||||
|
|
||||||
|
DECLARE default_cursor CURSOR LOCAL FAST_FORWARD FOR
|
||||||
|
SELECT TableName, ColumnName, ConstraintName, Definition
|
||||||
|
FROM @defaults;
|
||||||
|
|
||||||
|
OPEN default_cursor;
|
||||||
|
FETCH NEXT FROM default_cursor INTO @tableName, @columnName, @constraintName, @definition;
|
||||||
|
|
||||||
|
WHILE @@FETCH_STATUS = 0
|
||||||
|
BEGIN
|
||||||
|
SET @objectId = OBJECT_ID(QUOTENAME(@schemaName) + N'.' + QUOTENAME(@tableName), N'U');
|
||||||
|
|
||||||
|
IF @objectId IS NOT NULL
|
||||||
|
AND EXISTS (SELECT 1 FROM sys.columns WHERE object_id = @objectId AND name = @columnName)
|
||||||
|
BEGIN
|
||||||
|
SET @existingName = NULL;
|
||||||
|
|
||||||
|
SELECT @existingName = dc.name
|
||||||
|
FROM sys.default_constraints dc
|
||||||
|
INNER JOIN sys.columns c
|
||||||
|
ON c.object_id = dc.parent_object_id
|
||||||
|
AND c.column_id = dc.parent_column_id
|
||||||
|
WHERE dc.parent_object_id = @objectId
|
||||||
|
AND c.name = @columnName;
|
||||||
|
|
||||||
|
IF @existingName IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
SET @sql = N'ALTER TABLE '
|
||||||
|
+ QUOTENAME(@schemaName) + N'.' + QUOTENAME(@tableName)
|
||||||
|
+ N' DROP CONSTRAINT ' + QUOTENAME(@existingName);
|
||||||
|
EXEC sp_executesql @sql;
|
||||||
|
END
|
||||||
|
|
||||||
|
SET @sql = N'ALTER TABLE '
|
||||||
|
+ QUOTENAME(@schemaName) + N'.' + QUOTENAME(@tableName)
|
||||||
|
+ N' ADD CONSTRAINT ' + QUOTENAME(@constraintName)
|
||||||
|
+ N' DEFAULT ' + @definition
|
||||||
|
+ N' FOR ' + QUOTENAME(@columnName);
|
||||||
|
EXEC sp_executesql @sql;
|
||||||
|
END
|
||||||
|
|
||||||
|
FETCH NEXT FROM default_cursor INTO @tableName, @columnName, @constraintName, @definition;
|
||||||
|
END
|
||||||
|
|
||||||
|
CLOSE default_cursor;
|
||||||
|
DEALLOCATE default_cursor;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
async function createTables() {
|
async function createTables() {
|
||||||
const queries = [
|
const queries = [
|
||||||
// Users Table
|
// Users Table
|
||||||
@@ -1696,7 +1830,7 @@ async function createTables() {
|
|||||||
FullName NVARCHAR(100),
|
FullName NVARCHAR(100),
|
||||||
Role NVARCHAR(50) NOT NULL,
|
Role NVARCHAR(50) NOT NULL,
|
||||||
Status NVARCHAR(20) DEFAULT 'Active',
|
Status NVARCHAR(20) DEFAULT 'Active',
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
LastLogin DATETIME,
|
LastLogin DATETIME,
|
||||||
IsActive BIT DEFAULT 1
|
IsActive BIT DEFAULT 1
|
||||||
)
|
)
|
||||||
@@ -1713,8 +1847,8 @@ async function createTables() {
|
|||||||
Icon NVARCHAR(50),
|
Icon NVARCHAR(50),
|
||||||
Description NVARCHAR(500),
|
Description NVARCHAR(500),
|
||||||
Url NVARCHAR(255),
|
Url NVARCHAR(255),
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE()
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
)
|
)
|
||||||
END`,
|
END`,
|
||||||
|
|
||||||
@@ -1731,8 +1865,8 @@ async function createTables() {
|
|||||||
AccessLevel NVARCHAR(50),
|
AccessLevel NVARCHAR(50),
|
||||||
Status NVARCHAR(20) DEFAULT 'Active',
|
Status NVARCHAR(20) DEFAULT 'Active',
|
||||||
Notes NVARCHAR(MAX),
|
Notes NVARCHAR(MAX),
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE(),
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
|
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE
|
FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE
|
||||||
)
|
)
|
||||||
@@ -1765,8 +1899,8 @@ async function createTables() {
|
|||||||
Status NVARCHAR(30) NOT NULL DEFAULT 'in_use',
|
Status NVARCHAR(30) NOT NULL DEFAULT 'in_use',
|
||||||
Notes NVARCHAR(MAX),
|
Notes NVARCHAR(MAX),
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE(),
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
||||||
)
|
)
|
||||||
END`,
|
END`,
|
||||||
@@ -1777,8 +1911,8 @@ async function createTables() {
|
|||||||
CREATE TABLE AssetDepartments (
|
CREATE TABLE AssetDepartments (
|
||||||
DepartmentId INT PRIMARY KEY IDENTITY(1,1),
|
DepartmentId INT PRIMARY KEY IDENTITY(1,1),
|
||||||
DepartmentName NVARCHAR(100) NOT NULL,
|
DepartmentName NVARCHAR(100) NOT NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE()
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
)
|
)
|
||||||
END`,
|
END`,
|
||||||
|
|
||||||
@@ -1788,8 +1922,8 @@ async function createTables() {
|
|||||||
CREATE TABLE AssetProjects (
|
CREATE TABLE AssetProjects (
|
||||||
ProjectId INT PRIMARY KEY IDENTITY(1,1),
|
ProjectId INT PRIMARY KEY IDENTITY(1,1),
|
||||||
ProjectName NVARCHAR(150) NOT NULL,
|
ProjectName NVARCHAR(150) NOT NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE()
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
)
|
)
|
||||||
END`,
|
END`,
|
||||||
|
|
||||||
@@ -1805,15 +1939,15 @@ async function createTables() {
|
|||||||
BorrowQuantity INT NOT NULL DEFAULT 1,
|
BorrowQuantity INT NOT NULL DEFAULT 1,
|
||||||
ReturnedQuantity INT NOT NULL DEFAULT 0,
|
ReturnedQuantity INT NOT NULL DEFAULT 0,
|
||||||
Unit NVARCHAR(50),
|
Unit NVARCHAR(50),
|
||||||
BorrowDate DATE NOT NULL DEFAULT CAST(GETDATE() AS DATE),
|
BorrowDate DATE NOT NULL DEFAULT (CAST(DATEADD(HOUR, 7, SYSUTCDATETIME()) AS DATE)),
|
||||||
RequestNote NVARCHAR(500) NULL,
|
RequestNote NVARCHAR(500) NULL,
|
||||||
RejectReason NVARCHAR(1000) NULL,
|
RejectReason NVARCHAR(1000) NULL,
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
ProcessedBy INT NULL,
|
ProcessedBy INT NULL,
|
||||||
ProcessedByName NVARCHAR(100) NULL,
|
ProcessedByName NVARCHAR(100) NULL,
|
||||||
ProcessedDate DATETIME NULL,
|
ProcessedDate DATETIME NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE(),
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL,
|
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL,
|
||||||
FOREIGN KEY (ProcessedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
FOREIGN KEY (ProcessedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
||||||
@@ -1828,7 +1962,7 @@ async function createTables() {
|
|||||||
BorrowId INT NOT NULL,
|
BorrowId INT NOT NULL,
|
||||||
ReturnId INT NOT NULL,
|
ReturnId INT NOT NULL,
|
||||||
Quantity INT NOT NULL DEFAULT 1,
|
Quantity INT NOT NULL DEFAULT 1,
|
||||||
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
CreatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (BorrowId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION,
|
FOREIGN KEY (BorrowId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION,
|
||||||
FOREIGN KEY (ReturnId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION
|
FOREIGN KEY (ReturnId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION
|
||||||
)
|
)
|
||||||
@@ -1848,9 +1982,9 @@ async function createTables() {
|
|||||||
ExportedByName NVARCHAR(100) NOT NULL,
|
ExportedByName NVARCHAR(100) NOT NULL,
|
||||||
ExportNote NVARCHAR(1000) NULL,
|
ExportNote NVARCHAR(1000) NULL,
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
ExportedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
ExportedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
CreatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
UpdatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE
|
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE
|
||||||
)
|
)
|
||||||
END`,
|
END`,
|
||||||
@@ -1882,9 +2016,9 @@ async function createTables() {
|
|||||||
ActionNote NVARCHAR(1000) NULL,
|
ActionNote NVARCHAR(1000) NULL,
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
CreatedByName NVARCHAR(100) NULL,
|
CreatedByName NVARCHAR(100) NULL,
|
||||||
ActionDate DATETIME NOT NULL DEFAULT GETDATE(),
|
ActionDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
CreatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
UpdatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
||||||
)
|
)
|
||||||
@@ -1901,7 +2035,7 @@ async function createTables() {
|
|||||||
RecordId INT,
|
RecordId INT,
|
||||||
OldValue NVARCHAR(MAX),
|
OldValue NVARCHAR(MAX),
|
||||||
NewValue NVARCHAR(MAX),
|
NewValue NVARCHAR(MAX),
|
||||||
Timestamp DATETIME DEFAULT GETDATE(),
|
Timestamp DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (UserId) REFERENCES Users(UserId)
|
FOREIGN KEY (UserId) REFERENCES Users(UserId)
|
||||||
)
|
)
|
||||||
END`
|
END`
|
||||||
@@ -1988,7 +2122,7 @@ async function createTables() {
|
|||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetInventory','Borrower') IS NULL ALTER TABLE AssetInventory ADD Borrower NVARCHAR(255) NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetInventory','Borrower') IS NULL ALTER TABLE AssetInventory ADD Borrower NVARCHAR(255) NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetInventory','ExportedBy') IS NULL ALTER TABLE AssetInventory ADD ExportedBy NVARCHAR(100) NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetInventory','ExportedBy') IS NULL ALTER TABLE AssetInventory ADD ExportedBy NVARCHAR(100) NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','Unit') IS NULL ALTER TABLE AssetBorrowRequests ADD Unit NVARCHAR(50) NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','Unit') IS NULL ALTER TABLE AssetBorrowRequests ADD Unit NVARCHAR(50) NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','BorrowDate') IS NULL ALTER TABLE AssetBorrowRequests ADD BorrowDate DATE NOT NULL CONSTRAINT DF_AssetBorrowRequests_BorrowDate DEFAULT(CAST(GETDATE() AS DATE));`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','BorrowDate') IS NULL ALTER TABLE AssetBorrowRequests ADD BorrowDate DATE NOT NULL CONSTRAINT DF_AssetBorrowRequests_BorrowDate DEFAULT(CAST(DATEADD(HOUR, 7, SYSUTCDATETIME()) AS DATE));`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','RequestType') IS NULL ALTER TABLE AssetBorrowRequests ADD RequestType NVARCHAR(20) NOT NULL CONSTRAINT DF_AssetBorrowRequests_RequestType DEFAULT('borrow');`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','RequestType') IS NULL ALTER TABLE AssetBorrowRequests ADD RequestType NVARCHAR(20) NOT NULL CONSTRAINT DF_AssetBorrowRequests_RequestType DEFAULT('borrow');`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','RequestStatus') IS NULL ALTER TABLE AssetBorrowRequests ADD RequestStatus NVARCHAR(20) NOT NULL CONSTRAINT DF_AssetBorrowRequests_RequestStatus DEFAULT('approved');`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','RequestStatus') IS NULL ALTER TABLE AssetBorrowRequests ADD RequestStatus NVARCHAR(20) NOT NULL CONSTRAINT DF_AssetBorrowRequests_RequestStatus DEFAULT('approved');`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','ReturnedQuantity') IS NULL ALTER TABLE AssetBorrowRequests ADD ReturnedQuantity INT NOT NULL CONSTRAINT DF_AssetBorrowRequests_ReturnedQuantity DEFAULT(0);`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','ReturnedQuantity') IS NULL ALTER TABLE AssetBorrowRequests ADD ReturnedQuantity INT NOT NULL CONSTRAINT DF_AssetBorrowRequests_ReturnedQuantity DEFAULT(0);`);
|
||||||
@@ -2007,7 +2141,7 @@ async function createTables() {
|
|||||||
ADD CONSTRAINT FK_AssetBorrowRequests_ProcessedBy
|
ADD CONSTRAINT FK_AssetBorrowRequests_ProcessedBy
|
||||||
FOREIGN KEY (ProcessedBy) REFERENCES Users(UserId) ON DELETE SET NULL;
|
FOREIGN KEY (ProcessedBy) REFERENCES Users(UserId) ON DELETE SET NULL;
|
||||||
`);
|
`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','UpdatedDate') IS NULL ALTER TABLE AssetBorrowRequests ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequests_UpdatedDate DEFAULT(GETDATE());`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetBorrowRequests','UpdatedDate') IS NULL ALTER TABLE AssetBorrowRequests ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequests_UpdatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));`);
|
||||||
await pool.request().query(`
|
await pool.request().query(`
|
||||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AssetBorrowRequestLinks')
|
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AssetBorrowRequestLinks')
|
||||||
BEGIN
|
BEGIN
|
||||||
@@ -2016,7 +2150,7 @@ async function createTables() {
|
|||||||
BorrowId INT NOT NULL,
|
BorrowId INT NOT NULL,
|
||||||
ReturnId INT NOT NULL,
|
ReturnId INT NOT NULL,
|
||||||
Quantity INT NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_Quantity DEFAULT(1),
|
Quantity INT NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_Quantity DEFAULT(1),
|
||||||
CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_CreatedDate DEFAULT(GETDATE()),
|
CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_CreatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (BorrowId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION,
|
FOREIGN KEY (BorrowId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION,
|
||||||
FOREIGN KEY (ReturnId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION
|
FOREIGN KEY (ReturnId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION
|
||||||
);
|
);
|
||||||
@@ -2030,9 +2164,9 @@ async function createTables() {
|
|||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','ExportedByName') IS NULL ALTER TABLE AssetExportHistory ADD ExportedByName NVARCHAR(100) NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','ExportedByName') IS NULL ALTER TABLE AssetExportHistory ADD ExportedByName NVARCHAR(100) NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','ExportNote') IS NULL ALTER TABLE AssetExportHistory ADD ExportNote NVARCHAR(1000) NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','ExportNote') IS NULL ALTER TABLE AssetExportHistory ADD ExportNote NVARCHAR(1000) NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','CreatedBy') IS NULL ALTER TABLE AssetExportHistory ADD CreatedBy INT NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','CreatedBy') IS NULL ALTER TABLE AssetExportHistory ADD CreatedBy INT NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','ExportedDate') IS NULL ALTER TABLE AssetExportHistory ADD ExportedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_ExportedDate DEFAULT(GETDATE());`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','ExportedDate') IS NULL ALTER TABLE AssetExportHistory ADD ExportedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_ExportedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','CreatedDate') IS NULL ALTER TABLE AssetExportHistory ADD CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_CreatedDate DEFAULT(GETDATE());`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','CreatedDate') IS NULL ALTER TABLE AssetExportHistory ADD CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_CreatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','UpdatedDate') IS NULL ALTER TABLE AssetExportHistory ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_UpdatedDate DEFAULT(GETDATE());`);
|
await pool.request().query(`IF COL_LENGTH('dbo.AssetExportHistory','UpdatedDate') IS NULL ALTER TABLE AssetExportHistory ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_UpdatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));`);
|
||||||
await pool.request().query(`
|
await pool.request().query(`
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@@ -2102,7 +2236,7 @@ async function createTables() {
|
|||||||
THEN 'returned'
|
THEN 'returned'
|
||||||
ELSE borrowRows.RequestStatus
|
ELSE borrowRows.RequestStatus
|
||||||
END,
|
END,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
FROM AssetBorrowRequests borrowRows
|
FROM AssetBorrowRequests borrowRows
|
||||||
INNER JOIN (
|
INNER JOIN (
|
||||||
SELECT links.BorrowId, SUM(ISNULL(links.Quantity, 0)) AS ReturnedQuantity
|
SELECT links.BorrowId, SUM(ISNULL(links.Quantity, 0)) AS ReturnedQuantity
|
||||||
@@ -2164,13 +2298,19 @@ async function createTables() {
|
|||||||
await pool.request().query(`IF COL_LENGTH('dbo.Users','EmailVerifyTokenExpires') IS NULL ALTER TABLE Users ADD EmailVerifyTokenExpires DATETIME NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.Users','EmailVerifyTokenExpires') IS NULL ALTER TABLE Users ADD EmailVerifyTokenExpires DATETIME NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.Users','PasswordResetToken') IS NULL ALTER TABLE Users ADD PasswordResetToken NVARCHAR(255) NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.Users','PasswordResetToken') IS NULL ALTER TABLE Users ADD PasswordResetToken NVARCHAR(255) NULL;`);
|
||||||
await pool.request().query(`IF COL_LENGTH('dbo.Users','PasswordResetTokenExpires') IS NULL ALTER TABLE Users ADD PasswordResetTokenExpires DATETIME NULL;`);
|
await pool.request().query(`IF COL_LENGTH('dbo.Users','PasswordResetTokenExpires') IS NULL ALTER TABLE Users ADD PasswordResetTokenExpires DATETIME NULL;`);
|
||||||
await pool.request().query(`UPDATE Users SET EmailVerified = 1, EmailVerifiedAt = ISNULL(EmailVerifiedAt, GETDATE()) WHERE LOWER(ISNULL(Role, '')) = 'admin';`);
|
await pool.request().query(`UPDATE Users SET EmailVerified = 1, EmailVerifiedAt = ISNULL(EmailVerifiedAt, DATEADD(HOUR, 7, SYSUTCDATETIME())) WHERE LOWER(ISNULL(Role, '')) = 'admin';`);
|
||||||
// Backfill Url to empty string to avoid undefined in responses
|
// Backfill Url to empty string to avoid undefined in responses
|
||||||
await pool.request().query(`UPDATE Applications SET Url = '' WHERE Url IS NULL;`);
|
await pool.request().query(`UPDATE Applications SET Url = '' WHERE Url IS NULL;`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Column addition error (Applications):', err.message);
|
console.error('Column addition error (Applications):', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ensureAppTimeDefaultConstraints();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Timezone default constraint error:', err.message);
|
||||||
|
}
|
||||||
|
|
||||||
// Sync legacy departments from AssetInventory to AssetDepartments
|
// Sync legacy departments from AssetInventory to AssetDepartments
|
||||||
try {
|
try {
|
||||||
await syncAssetDepartmentsFromInventory();
|
await syncAssetDepartmentsFromInventory();
|
||||||
@@ -2198,11 +2338,11 @@ async function createTables() {
|
|||||||
.input('role', sql.NVarChar, 'admin')
|
.input('role', sql.NVarChar, 'admin')
|
||||||
.query(`IF NOT EXISTS (SELECT * FROM Users WHERE Username = @username)
|
.query(`IF NOT EXISTS (SELECT * FROM Users WHERE Username = @username)
|
||||||
INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, Role, IsActive, EmailVerified, EmailVerifiedAt)
|
INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, Role, IsActive, EmailVerified, EmailVerifiedAt)
|
||||||
VALUES (@username, @password, @viewPassword, @email, @fullname, @role, 1, 1, GETDATE())
|
VALUES (@username, @password, @viewPassword, @email, @fullname, @role, 1, 1, DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
ELSE
|
ELSE
|
||||||
UPDATE Users
|
UPDATE Users
|
||||||
SET EmailVerified = 1,
|
SET EmailVerified = 1,
|
||||||
EmailVerifiedAt = ISNULL(EmailVerifiedAt, GETDATE())
|
EmailVerifiedAt = ISNULL(EmailVerifiedAt, DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
WHERE Username = @username`);
|
WHERE Username = @username`);
|
||||||
console.log('[OK] Admin user created: admin / admin');
|
console.log('[OK] Admin user created: admin / admin');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -2287,7 +2427,7 @@ app.post('/api/auth/login', async (req, res) => {
|
|||||||
// Update last login
|
// Update last login
|
||||||
await pool.request()
|
await pool.request()
|
||||||
.input('userId', sql.Int, user.UserId)
|
.input('userId', sql.Int, user.UserId)
|
||||||
.query('UPDATE Users SET LastLogin = GETDATE() WHERE UserId = @userId');
|
.query('UPDATE Users SET LastLogin = DATEADD(HOUR, 7, SYSUTCDATETIME()) WHERE UserId = @userId');
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -2387,7 +2527,7 @@ app.post('/api/auth/register', async (req, res) => {
|
|||||||
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
|
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
|
||||||
.query(`INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, RoleId, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
|
.query(`INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, RoleId, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
|
||||||
OUTPUT INSERTED.UserId, INSERTED.Username, INSERTED.Email, INSERTED.FullName, INSERTED.Role, INSERTED.RoleId
|
OUTPUT INSERTED.UserId, INSERTED.Username, INSERTED.Email, INSERTED.FullName, INSERTED.Role, INSERTED.RoleId
|
||||||
VALUES (@username, @password, @viewPassword, @email, @fullname, @roleId, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, GETDATE()))`);
|
VALUES (@username, @password, @viewPassword, @email, @fullname, @roleId, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME())))`);
|
||||||
} else {
|
} else {
|
||||||
result = await pool.request()
|
result = await pool.request()
|
||||||
.input('username', sql.NVarChar, safeUsername)
|
.input('username', sql.NVarChar, safeUsername)
|
||||||
@@ -2400,7 +2540,7 @@ app.post('/api/auth/register', async (req, res) => {
|
|||||||
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
|
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
|
||||||
.query(`INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
|
.query(`INSERT INTO Users (Username, Password, ViewPassword, Email, FullName, Role, Status, IsActive, EmailVerified, EmailVerifyToken, EmailVerifyTokenExpires)
|
||||||
OUTPUT INSERTED.UserId, INSERTED.Username, INSERTED.Email, INSERTED.FullName, INSERTED.Role
|
OUTPUT INSERTED.UserId, INSERTED.Username, INSERTED.Email, INSERTED.FullName, INSERTED.Role
|
||||||
VALUES (@username, @password, @viewPassword, @email, @fullname, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, GETDATE()))`);
|
VALUES (@username, @password, @viewPassword, @email, @fullname, @role, 'Active', 1, 0, @emailVerifyToken, DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME())))`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inserted = result.recordset[0];
|
const inserted = result.recordset[0];
|
||||||
@@ -2475,7 +2615,7 @@ app.get('/api/auth/verify-email', async (req, res) => {
|
|||||||
.input('userId', sql.Int, verifiedUser.UserId)
|
.input('userId', sql.Int, verifiedUser.UserId)
|
||||||
.query(`UPDATE Users
|
.query(`UPDATE Users
|
||||||
SET EmailVerified = 1,
|
SET EmailVerified = 1,
|
||||||
EmailVerifiedAt = GETDATE(),
|
EmailVerifiedAt = DATEADD(HOUR, 7, SYSUTCDATETIME()),
|
||||||
EmailVerifyToken = NULL,
|
EmailVerifyToken = NULL,
|
||||||
EmailVerifyTokenExpires = NULL
|
EmailVerifyTokenExpires = NULL
|
||||||
WHERE UserId = @userId`);
|
WHERE UserId = @userId`);
|
||||||
@@ -2493,7 +2633,7 @@ app.get('/api/auth/verify-email', async (req, res) => {
|
|||||||
|
|
||||||
await pool.request()
|
await pool.request()
|
||||||
.input('userId', sql.Int, verifiedUser.UserId)
|
.input('userId', sql.Int, verifiedUser.UserId)
|
||||||
.query('UPDATE Users SET LastLogin = GETDATE() WHERE UserId = @userId');
|
.query('UPDATE Users SET LastLogin = DATEADD(HOUR, 7, SYSUTCDATETIME()) WHERE UserId = @userId');
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -2540,7 +2680,7 @@ app.post('/api/auth/resend-verification', async (req, res) => {
|
|||||||
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
|
.input('tokenTtlMinutes', sql.Int, EMAIL_VERIFY_TOKEN_TTL_MINUTES)
|
||||||
.query(`UPDATE Users
|
.query(`UPDATE Users
|
||||||
SET EmailVerifyToken = @tokenHash,
|
SET EmailVerifyToken = @tokenHash,
|
||||||
EmailVerifyTokenExpires = DATEADD(MINUTE, @tokenTtlMinutes, GETDATE())
|
EmailVerifyTokenExpires = DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
WHERE UserId = @userId`);
|
WHERE UserId = @userId`);
|
||||||
|
|
||||||
const emailResult = await sendVerificationEmail({
|
const emailResult = await sendVerificationEmail({
|
||||||
@@ -2607,7 +2747,7 @@ app.post('/api/auth/forgot-password', async (req, res) => {
|
|||||||
.input('tokenTtlMinutes', sql.Int, PASSWORD_RESET_TOKEN_TTL_MINUTES)
|
.input('tokenTtlMinutes', sql.Int, PASSWORD_RESET_TOKEN_TTL_MINUTES)
|
||||||
.query(`UPDATE Users
|
.query(`UPDATE Users
|
||||||
SET PasswordResetToken = @tokenHash,
|
SET PasswordResetToken = @tokenHash,
|
||||||
PasswordResetTokenExpires = DATEADD(MINUTE, @tokenTtlMinutes, GETDATE())
|
PasswordResetTokenExpires = DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
WHERE UserId = @userId`);
|
WHERE UserId = @userId`);
|
||||||
|
|
||||||
const emailResult = await sendPasswordResetEmail({
|
const emailResult = await sendPasswordResetEmail({
|
||||||
@@ -2946,7 +3086,7 @@ app.put('/api/users/me', async (req, res) => {
|
|||||||
SET FullName = @fullname,
|
SET FullName = @fullname,
|
||||||
Email = @email
|
Email = @email
|
||||||
${shouldChangePassword ? ', Password = @password, ViewPassword = @viewPassword' : ''}
|
${shouldChangePassword ? ', Password = @password, ViewPassword = @viewPassword' : ''}
|
||||||
${emailChanged ? ', EmailVerified = 0, EmailVerifiedAt = NULL, EmailVerifyToken = @emailVerifyToken, EmailVerifyTokenExpires = DATEADD(MINUTE, @tokenTtlMinutes, GETDATE())' : ''}
|
${emailChanged ? ', EmailVerified = 0, EmailVerifiedAt = NULL, EmailVerifyToken = @emailVerifyToken, EmailVerifyTokenExpires = DATEADD(MINUTE, @tokenTtlMinutes, DATEADD(HOUR, 7, SYSUTCDATETIME()))' : ''}
|
||||||
WHERE UserId = @userId`);
|
WHERE UserId = @userId`);
|
||||||
|
|
||||||
let emailResult = { sent: true };
|
let emailResult = { sent: true };
|
||||||
@@ -3201,7 +3341,7 @@ app.put('/api/applications/:id', async (req, res) => {
|
|||||||
Icon = @icon,
|
Icon = @icon,
|
||||||
Description = @description,
|
Description = @description,
|
||||||
Url = @url,
|
Url = @url,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE AppId = @appId`);
|
WHERE AppId = @appId`);
|
||||||
|
|
||||||
res.json({ success: true, message: 'Application updated' });
|
res.json({ success: true, message: 'Application updated' });
|
||||||
@@ -3304,7 +3444,7 @@ app.put('/api/accounts/:id', async (req, res) => {
|
|||||||
Email = @email,
|
Email = @email,
|
||||||
AccessLevel = @accessLevel,
|
AccessLevel = @accessLevel,
|
||||||
Notes = @notes,
|
Notes = @notes,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE AccountId = @accountId`);
|
WHERE AccountId = @accountId`);
|
||||||
|
|
||||||
res.json({ success: true, message: 'Account updated' });
|
res.json({ success: true, message: 'Account updated' });
|
||||||
@@ -3454,7 +3594,7 @@ app.put('/api/asset-departments/:id', requireAssetOrAdmin, async (req, res) => {
|
|||||||
.query(`
|
.query(`
|
||||||
UPDATE AssetDepartments
|
UPDATE AssetDepartments
|
||||||
SET DepartmentName = @departmentName,
|
SET DepartmentName = @departmentName,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE DepartmentId = @departmentId
|
WHERE DepartmentId = @departmentId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -3464,7 +3604,7 @@ app.put('/api/asset-departments/:id', requireAssetOrAdmin, async (req, res) => {
|
|||||||
.query(`
|
.query(`
|
||||||
UPDATE AssetInventory
|
UPDATE AssetInventory
|
||||||
SET Department = @newDepartmentName,
|
SET Department = @newDepartmentName,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE LOWER(LTRIM(RTRIM(Department))) = LOWER(@oldDepartmentName)
|
WHERE LOWER(LTRIM(RTRIM(Department))) = LOWER(@oldDepartmentName)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -3518,7 +3658,7 @@ app.delete('/api/asset-departments/:id', requireAssetOrAdmin, async (req, res) =
|
|||||||
.query(`
|
.query(`
|
||||||
UPDATE AssetInventory
|
UPDATE AssetInventory
|
||||||
SET Department = NULL,
|
SET Department = NULL,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE LOWER(LTRIM(RTRIM(Department))) = LOWER(@departmentName)
|
WHERE LOWER(LTRIM(RTRIM(Department))) = LOWER(@departmentName)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -3672,7 +3812,7 @@ app.put('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
|
|||||||
.query(`
|
.query(`
|
||||||
UPDATE AssetProjects
|
UPDATE AssetProjects
|
||||||
SET ProjectName = @projectName,
|
SET ProjectName = @projectName,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE ProjectId = @projectId
|
WHERE ProjectId = @projectId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -3682,7 +3822,7 @@ app.put('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
|
|||||||
.query(`
|
.query(`
|
||||||
UPDATE AssetInventory
|
UPDATE AssetInventory
|
||||||
SET Project = @newProjectName,
|
SET Project = @newProjectName,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE LOWER(LTRIM(RTRIM(Project))) = LOWER(@oldProjectName)
|
WHERE LOWER(LTRIM(RTRIM(Project))) = LOWER(@oldProjectName)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -3736,7 +3876,7 @@ app.delete('/api/asset-projects/:id', requireAssetOrAdmin, async (req, res) => {
|
|||||||
.query(`
|
.query(`
|
||||||
UPDATE AssetInventory
|
UPDATE AssetInventory
|
||||||
SET Project = NULL,
|
SET Project = NULL,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE LOWER(LTRIM(RTRIM(Project))) = LOWER(@projectName)
|
WHERE LOWER(LTRIM(RTRIM(Project))) = LOWER(@projectName)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -4560,7 +4700,7 @@ app.post('/api/asset-borrows/:id/process', requireAssetOrAdmin, async (req, res)
|
|||||||
UsedQuantity = @usedQuantity,
|
UsedQuantity = @usedQuantity,
|
||||||
Status = @status,
|
Status = @status,
|
||||||
ExportedBy = @exportedBy,
|
ExportedBy = @exportedBy,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE AssetId = @assetId
|
WHERE AssetId = @assetId
|
||||||
`);
|
`);
|
||||||
} else {
|
} else {
|
||||||
@@ -4619,7 +4759,7 @@ app.post('/api/asset-borrows/:id/process', requireAssetOrAdmin, async (req, res)
|
|||||||
UsedQuantity = @usedQuantity,
|
UsedQuantity = @usedQuantity,
|
||||||
Status = @status,
|
Status = @status,
|
||||||
ExportedBy = CASE WHEN @borrower IS NULL THEN NULL ELSE @exportedBy END,
|
ExportedBy = CASE WHEN @borrower IS NULL THEN NULL ELSE @exportedBy END,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE AssetId = @assetId
|
WHERE AssetId = @assetId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -4665,7 +4805,7 @@ app.post('/api/asset-borrows/:id/process', requireAssetOrAdmin, async (req, res)
|
|||||||
THEN 'returned'
|
THEN 'returned'
|
||||||
ELSE RequestStatus
|
ELSE RequestStatus
|
||||||
END,
|
END,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE BorrowId = @borrowId
|
WHERE BorrowId = @borrowId
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
@@ -4742,7 +4882,7 @@ app.post('/api/asset-borrows/:id/process', requireAssetOrAdmin, async (req, res)
|
|||||||
THEN 'returned'
|
THEN 'returned'
|
||||||
ELSE RequestStatus
|
ELSE RequestStatus
|
||||||
END,
|
END,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE BorrowId = @borrowId
|
WHERE BorrowId = @borrowId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -4764,8 +4904,8 @@ app.post('/api/asset-borrows/:id/process', requireAssetOrAdmin, async (req, res)
|
|||||||
RejectReason = @rejectReason,
|
RejectReason = @rejectReason,
|
||||||
ProcessedBy = @processedBy,
|
ProcessedBy = @processedBy,
|
||||||
ProcessedByName = @processedByName,
|
ProcessedByName = @processedByName,
|
||||||
ProcessedDate = GETDATE(),
|
ProcessedDate = DATEADD(HOUR, 7, SYSUTCDATETIME()),
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE BorrowId = @borrowId
|
WHERE BorrowId = @borrowId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -5194,7 +5334,7 @@ app.post('/api/assets/:id/damage-disposal', requireAssetOrAdmin, async (req, res
|
|||||||
NewQuantity = @newQuantity,
|
NewQuantity = @newQuantity,
|
||||||
UsedQuantity = @usedQuantity,
|
UsedQuantity = @usedQuantity,
|
||||||
Status = @status,
|
Status = @status,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE AssetId = @assetId
|
WHERE AssetId = @assetId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -5449,7 +5589,7 @@ app.post('/api/assets/:id/export', requireAssetOrAdmin, async (req, res) => {
|
|||||||
Status = @status,
|
Status = @status,
|
||||||
ExportedBy = @exportedBy,
|
ExportedBy = @exportedBy,
|
||||||
Notes = @notes,
|
Notes = @notes,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE AssetId = @assetId
|
WHERE AssetId = @assetId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -5651,7 +5791,7 @@ app.put('/api/assets/:id', requireAssetOrAdmin, async (req, res) => {
|
|||||||
PurchasePrice = @purchasePrice,
|
PurchasePrice = @purchasePrice,
|
||||||
Status = @status,
|
Status = @status,
|
||||||
Notes = @notes,
|
Notes = @notes,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHERE AssetId = @assetId
|
WHERE AssetId = @assetId
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -5868,7 +6008,7 @@ app.post('/api/assets/import', requireAssetOrAdmin, upload.single('file'), async
|
|||||||
PurchasePrice = @purchasePrice,
|
PurchasePrice = @purchasePrice,
|
||||||
Status = @status,
|
Status = @status,
|
||||||
Notes = @notes,
|
Notes = @notes,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
WHEN NOT MATCHED THEN
|
WHEN NOT MATCHED THEN
|
||||||
INSERT (
|
INSERT (
|
||||||
AssetCode, AssetName, Model, SerialNumber,
|
AssetCode, AssetName, Model, SerialNumber,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ BEGIN
|
|||||||
FullName NVARCHAR(100),
|
FullName NVARCHAR(100),
|
||||||
Role NVARCHAR(50) NOT NULL,
|
Role NVARCHAR(50) NOT NULL,
|
||||||
Status NVARCHAR(20) DEFAULT 'Active',
|
Status NVARCHAR(20) DEFAULT 'Active',
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
LastLogin DATETIME,
|
LastLogin DATETIME,
|
||||||
IsActive BIT DEFAULT 1
|
IsActive BIT DEFAULT 1
|
||||||
);
|
);
|
||||||
@@ -51,8 +51,8 @@ BEGIN
|
|||||||
Status NVARCHAR(20) DEFAULT 'online',
|
Status NVARCHAR(20) DEFAULT 'online',
|
||||||
Icon NVARCHAR(50),
|
Icon NVARCHAR(50),
|
||||||
Description NVARCHAR(500),
|
Description NVARCHAR(500),
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE()
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
);
|
);
|
||||||
PRINT 'Table Applications created successfully.';
|
PRINT 'Table Applications created successfully.';
|
||||||
END
|
END
|
||||||
@@ -72,8 +72,8 @@ BEGIN
|
|||||||
AccessLevel NVARCHAR(50),
|
AccessLevel NVARCHAR(50),
|
||||||
Status NVARCHAR(20) DEFAULT 'Active',
|
Status NVARCHAR(20) DEFAULT 'Active',
|
||||||
Notes NVARCHAR(MAX),
|
Notes NVARCHAR(MAX),
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE(),
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
|
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE
|
FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
@@ -109,8 +109,8 @@ BEGIN
|
|||||||
Status NVARCHAR(30) NOT NULL DEFAULT 'in_use',
|
Status NVARCHAR(30) NOT NULL DEFAULT 'in_use',
|
||||||
Notes NVARCHAR(MAX),
|
Notes NVARCHAR(MAX),
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE(),
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
PRINT 'Table AssetInventory created successfully.';
|
PRINT 'Table AssetInventory created successfully.';
|
||||||
@@ -175,8 +175,8 @@ BEGIN
|
|||||||
CREATE TABLE AssetDepartments (
|
CREATE TABLE AssetDepartments (
|
||||||
DepartmentId INT PRIMARY KEY IDENTITY(1,1),
|
DepartmentId INT PRIMARY KEY IDENTITY(1,1),
|
||||||
DepartmentName NVARCHAR(100) NOT NULL,
|
DepartmentName NVARCHAR(100) NOT NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE()
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
);
|
);
|
||||||
PRINT 'Table AssetDepartments created successfully.';
|
PRINT 'Table AssetDepartments created successfully.';
|
||||||
END
|
END
|
||||||
@@ -204,8 +204,8 @@ BEGIN
|
|||||||
CREATE TABLE AssetProjects (
|
CREATE TABLE AssetProjects (
|
||||||
ProjectId INT PRIMARY KEY IDENTITY(1,1),
|
ProjectId INT PRIMARY KEY IDENTITY(1,1),
|
||||||
ProjectName NVARCHAR(150) NOT NULL,
|
ProjectName NVARCHAR(150) NOT NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE()
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME()))
|
||||||
);
|
);
|
||||||
PRINT 'Table AssetProjects created successfully.';
|
PRINT 'Table AssetProjects created successfully.';
|
||||||
END
|
END
|
||||||
@@ -224,15 +224,15 @@ BEGIN
|
|||||||
BorrowQuantity INT NOT NULL DEFAULT 1,
|
BorrowQuantity INT NOT NULL DEFAULT 1,
|
||||||
ReturnedQuantity INT NOT NULL DEFAULT 0,
|
ReturnedQuantity INT NOT NULL DEFAULT 0,
|
||||||
Unit NVARCHAR(50),
|
Unit NVARCHAR(50),
|
||||||
BorrowDate DATE NOT NULL DEFAULT CAST(GETDATE() AS DATE),
|
BorrowDate DATE NOT NULL DEFAULT (CAST(DATEADD(HOUR, 7, SYSUTCDATETIME()) AS DATE)),
|
||||||
RequestNote NVARCHAR(500) NULL,
|
RequestNote NVARCHAR(500) NULL,
|
||||||
RejectReason NVARCHAR(1000) NULL,
|
RejectReason NVARCHAR(1000) NULL,
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
ProcessedBy INT NULL,
|
ProcessedBy INT NULL,
|
||||||
ProcessedByName NVARCHAR(100) NULL,
|
ProcessedByName NVARCHAR(100) NULL,
|
||||||
ProcessedDate DATETIME NULL,
|
ProcessedDate DATETIME NULL,
|
||||||
CreatedDate DATETIME DEFAULT GETDATE(),
|
CreatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME DEFAULT GETDATE(),
|
UpdatedDate DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL,
|
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL,
|
||||||
FOREIGN KEY (ProcessedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
FOREIGN KEY (ProcessedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
||||||
@@ -247,12 +247,12 @@ END
|
|||||||
|
|
||||||
IF COL_LENGTH('dbo.AssetBorrowRequests', 'BorrowDate') IS NULL
|
IF COL_LENGTH('dbo.AssetBorrowRequests', 'BorrowDate') IS NULL
|
||||||
BEGIN
|
BEGIN
|
||||||
ALTER TABLE AssetBorrowRequests ADD BorrowDate DATE NOT NULL CONSTRAINT DF_AssetBorrowRequests_BorrowDate DEFAULT(CAST(GETDATE() AS DATE));
|
ALTER TABLE AssetBorrowRequests ADD BorrowDate DATE NOT NULL CONSTRAINT DF_AssetBorrowRequests_BorrowDate DEFAULT(CAST(DATEADD(HOUR, 7, SYSUTCDATETIME()) AS DATE));
|
||||||
END
|
END
|
||||||
|
|
||||||
IF COL_LENGTH('dbo.AssetBorrowRequests', 'UpdatedDate') IS NULL
|
IF COL_LENGTH('dbo.AssetBorrowRequests', 'UpdatedDate') IS NULL
|
||||||
BEGIN
|
BEGIN
|
||||||
ALTER TABLE AssetBorrowRequests ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequests_UpdatedDate DEFAULT(GETDATE());
|
ALTER TABLE AssetBorrowRequests ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequests_UpdatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));
|
||||||
END
|
END
|
||||||
|
|
||||||
IF COL_LENGTH('dbo.AssetBorrowRequests', 'RequestType') IS NULL
|
IF COL_LENGTH('dbo.AssetBorrowRequests', 'RequestType') IS NULL
|
||||||
@@ -319,7 +319,7 @@ BEGIN
|
|||||||
BorrowId INT NOT NULL,
|
BorrowId INT NOT NULL,
|
||||||
ReturnId INT NOT NULL,
|
ReturnId INT NOT NULL,
|
||||||
Quantity INT NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_Quantity DEFAULT(1),
|
Quantity INT NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_Quantity DEFAULT(1),
|
||||||
CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_CreatedDate DEFAULT(GETDATE()),
|
CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetBorrowRequestLinks_CreatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (BorrowId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION,
|
FOREIGN KEY (BorrowId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION,
|
||||||
FOREIGN KEY (ReturnId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION
|
FOREIGN KEY (ReturnId) REFERENCES AssetBorrowRequests(BorrowId) ON DELETE NO ACTION
|
||||||
);
|
);
|
||||||
@@ -373,7 +373,7 @@ BEGIN
|
|||||||
THEN 'returned'
|
THEN 'returned'
|
||||||
ELSE borrowRows.RequestStatus
|
ELSE borrowRows.RequestStatus
|
||||||
END,
|
END,
|
||||||
UpdatedDate = GETDATE()
|
UpdatedDate = DATEADD(HOUR, 7, SYSUTCDATETIME())
|
||||||
FROM AssetBorrowRequests borrowRows
|
FROM AssetBorrowRequests borrowRows
|
||||||
INNER JOIN (
|
INNER JOIN (
|
||||||
SELECT links.BorrowId, SUM(ISNULL(links.Quantity, 0)) AS ReturnedQuantity
|
SELECT links.BorrowId, SUM(ISNULL(links.Quantity, 0)) AS ReturnedQuantity
|
||||||
@@ -401,9 +401,9 @@ BEGIN
|
|||||||
ExportedByName NVARCHAR(100) NOT NULL,
|
ExportedByName NVARCHAR(100) NOT NULL,
|
||||||
ExportNote NVARCHAR(1000) NULL,
|
ExportNote NVARCHAR(1000) NULL,
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
ExportedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
ExportedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
CreatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
UpdatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE
|
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
PRINT 'Table AssetExportHistory created successfully.';
|
PRINT 'Table AssetExportHistory created successfully.';
|
||||||
@@ -451,17 +451,17 @@ END
|
|||||||
|
|
||||||
IF COL_LENGTH('dbo.AssetExportHistory', 'ExportedDate') IS NULL
|
IF COL_LENGTH('dbo.AssetExportHistory', 'ExportedDate') IS NULL
|
||||||
BEGIN
|
BEGIN
|
||||||
ALTER TABLE AssetExportHistory ADD ExportedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_ExportedDate DEFAULT(GETDATE());
|
ALTER TABLE AssetExportHistory ADD ExportedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_ExportedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));
|
||||||
END
|
END
|
||||||
|
|
||||||
IF COL_LENGTH('dbo.AssetExportHistory', 'CreatedDate') IS NULL
|
IF COL_LENGTH('dbo.AssetExportHistory', 'CreatedDate') IS NULL
|
||||||
BEGIN
|
BEGIN
|
||||||
ALTER TABLE AssetExportHistory ADD CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_CreatedDate DEFAULT(GETDATE());
|
ALTER TABLE AssetExportHistory ADD CreatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_CreatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));
|
||||||
END
|
END
|
||||||
|
|
||||||
IF COL_LENGTH('dbo.AssetExportHistory', 'UpdatedDate') IS NULL
|
IF COL_LENGTH('dbo.AssetExportHistory', 'UpdatedDate') IS NULL
|
||||||
BEGIN
|
BEGIN
|
||||||
ALTER TABLE AssetExportHistory ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_UpdatedDate DEFAULT(GETDATE());
|
ALTER TABLE AssetExportHistory ADD UpdatedDate DATETIME NOT NULL CONSTRAINT DF_AssetExportHistory_UpdatedDate DEFAULT(DATEADD(HOUR, 7, SYSUTCDATETIME()));
|
||||||
END
|
END
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_AssetExportHistory_CreatedBy')
|
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_AssetExportHistory_CreatedBy')
|
||||||
@@ -512,9 +512,9 @@ BEGIN
|
|||||||
ActionNote NVARCHAR(1000) NULL,
|
ActionNote NVARCHAR(1000) NULL,
|
||||||
CreatedBy INT NULL,
|
CreatedBy INT NULL,
|
||||||
CreatedByName NVARCHAR(100) NULL,
|
CreatedByName NVARCHAR(100) NULL,
|
||||||
ActionDate DATETIME NOT NULL DEFAULT GETDATE(),
|
ActionDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
CreatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
UpdatedDate DATETIME NOT NULL DEFAULT GETDATE(),
|
UpdatedDate DATETIME NOT NULL DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
FOREIGN KEY (AssetId) REFERENCES AssetInventory(AssetId) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
FOREIGN KEY (CreatedBy) REFERENCES Users(UserId) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
@@ -534,7 +534,7 @@ BEGIN
|
|||||||
RecordId INT,
|
RecordId INT,
|
||||||
OldValue NVARCHAR(MAX),
|
OldValue NVARCHAR(MAX),
|
||||||
NewValue NVARCHAR(MAX),
|
NewValue NVARCHAR(MAX),
|
||||||
Timestamp DATETIME DEFAULT GETDATE(),
|
Timestamp DATETIME DEFAULT (DATEADD(HOUR, 7, SYSUTCDATETIME())),
|
||||||
FOREIGN KEY (UserId) REFERENCES Users(UserId)
|
FOREIGN KEY (UserId) REFERENCES Users(UserId)
|
||||||
);
|
);
|
||||||
PRINT 'Table AuditLog created successfully.';
|
PRINT 'Table AuditLog created successfully.';
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
NODE_ENV: ${NODE_ENV:-production}
|
NODE_ENV: ${NODE_ENV:-production}
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
|
TZ: ${TZ:-Asia/Ho_Chi_Minh}
|
||||||
|
APP_TIME_ZONE: ${APP_TIME_ZONE:-Asia/Ho_Chi_Minh}
|
||||||
DB_SERVER: ${DB_SERVER:-172.20.235.176}
|
DB_SERVER: ${DB_SERVER:-172.20.235.176}
|
||||||
DB_USER: ${DB_USER:-sa}
|
DB_USER: ${DB_USER:-sa}
|
||||||
DB_PASSWORD: ${DB_PASSWORD:-changeme}
|
DB_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||||
@@ -23,4 +25,4 @@ services:
|
|||||||
SMTP_USER: ${SMTP_USER:-}
|
SMTP_USER: ${SMTP_USER:-}
|
||||||
SMTP_PASS: ${SMTP_PASS:-}
|
SMTP_PASS: ${SMTP_PASS:-}
|
||||||
SMTP_FROM: ${SMTP_FROM:-}
|
SMTP_FROM: ${SMTP_FROM:-}
|
||||||
EMAIL_VERIFY_TOKEN_TTL_MINUTES: ${EMAIL_VERIFY_TOKEN_TTL_MINUTES:-30}
|
EMAIL_VERIFY_TOKEN_TTL_MINUTES: ${EMAIL_VERIFY_TOKEN_TTL_MINUTES:-30}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
|
TZ: ${TZ:-Asia/Ho_Chi_Minh}
|
||||||
|
APP_TIME_ZONE: ${APP_TIME_ZONE:-Asia/Ho_Chi_Minh}
|
||||||
DB_SERVER: ${DB_SERVER:-172.20.235.176}
|
DB_SERVER: ${DB_SERVER:-172.20.235.176}
|
||||||
DB_USER: ${DB_USER:-sa}
|
DB_USER: ${DB_USER:-sa}
|
||||||
DB_PASSWORD: ${DB_PASSWORD:-changeme}
|
DB_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||||
@@ -25,4 +27,4 @@ services:
|
|||||||
SMTP_USER: ${SMTP_USER:-}
|
SMTP_USER: ${SMTP_USER:-}
|
||||||
SMTP_PASS: ${SMTP_PASS:-}
|
SMTP_PASS: ${SMTP_PASS:-}
|
||||||
SMTP_FROM: ${SMTP_FROM:-}
|
SMTP_FROM: ${SMTP_FROM:-}
|
||||||
EMAIL_VERIFY_TOKEN_TTL_MINUTES: ${EMAIL_VERIFY_TOKEN_TTL_MINUTES:-30}
|
EMAIL_VERIFY_TOKEN_TTL_MINUTES: ${EMAIL_VERIFY_TOKEN_TTL_MINUTES:-30}
|
||||||
|
|||||||
@@ -1,6 +1,34 @@
|
|||||||
// VaultSentinel - Account Management Application
|
// VaultSentinel - Account Management Application
|
||||||
// Main JavaScript functionality
|
// Main JavaScript functionality
|
||||||
|
|
||||||
|
const APP_TIME_ZONE = 'Asia/Ho_Chi_Minh';
|
||||||
|
const APP_DATE_FORMATTER = new Intl.DateTimeFormat('vi-VN', {
|
||||||
|
timeZone: APP_TIME_ZONE,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit'
|
||||||
|
});
|
||||||
|
const APP_DATE_TIME_FORMATTER = new Intl.DateTimeFormat('vi-VN', {
|
||||||
|
timeZone: APP_TIME_ZONE,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hourCycle: 'h23'
|
||||||
|
});
|
||||||
|
const APP_TIME_PARTS_FORMATTER = new Intl.DateTimeFormat('en-CA', {
|
||||||
|
timeZone: APP_TIME_ZONE,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hourCycle: 'h23'
|
||||||
|
});
|
||||||
|
|
||||||
class AccountManager {
|
class AccountManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
// Check if user is logged in
|
// Check if user is logged in
|
||||||
@@ -1795,7 +1823,7 @@ class AccountManager {
|
|||||||
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
|
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
|
||||||
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Last Updated</span>
|
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Last Updated</span>
|
||||||
<div class="flex items-baseline justify-between">
|
<div class="flex items-baseline justify-between">
|
||||||
<span class="text-sm font-black text-on-surface">${new Date().toLocaleDateString()}</span>
|
<span class="text-sm font-black text-on-surface">${APP_DATE_FORMATTER.format(new Date())}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-primary-container/10 p-4 rounded-xl border border-primary/20 flex flex-col">
|
<div class="bg-primary-container/10 p-4 rounded-xl border border-primary/20 flex flex-col">
|
||||||
@@ -2055,21 +2083,49 @@ class AccountManager {
|
|||||||
return { label: 'Đang sử dụng', className: 'bg-blue-100 text-blue-700' };
|
return { label: 'Đang sử dụng', className: 'bg-blue-100 text-blue-700' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAppTimeParts(value = new Date()) {
|
||||||
|
const date = value instanceof Date ? value : new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return null;
|
||||||
|
|
||||||
|
return APP_TIME_PARTS_FORMATTER.formatToParts(date).reduce((parts, part) => {
|
||||||
|
if (part.type !== 'literal') {
|
||||||
|
parts[part.type] = part.value;
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
formatTimestampForCode(value = new Date(), includeMilliseconds = false) {
|
||||||
|
const date = value instanceof Date ? value : new Date(value);
|
||||||
|
const parts = this.getAppTimeParts(date);
|
||||||
|
if (!parts) return '';
|
||||||
|
|
||||||
|
const timestamp = [
|
||||||
|
parts.year,
|
||||||
|
parts.month,
|
||||||
|
parts.day,
|
||||||
|
parts.hour,
|
||||||
|
parts.minute,
|
||||||
|
parts.second
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
return includeMilliseconds
|
||||||
|
? `${timestamp}${String(date.getMilliseconds()).padStart(3, '0')}`
|
||||||
|
: timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
formatDateOnly(value) {
|
formatDateOnly(value) {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
if (Number.isNaN(date.getTime())) return String(value);
|
if (Number.isNaN(date.getTime())) return String(value);
|
||||||
return date.toLocaleDateString();
|
return APP_DATE_FORMATTER.format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
toDateInputValue(value) {
|
toDateInputValue(value) {
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
const date = new Date(value);
|
const parts = this.getAppTimeParts(value);
|
||||||
if (Number.isNaN(date.getTime())) return '';
|
if (!parts) return '';
|
||||||
const year = date.getFullYear();
|
return `${parts.year}-${parts.month}-${parts.day}`;
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
|
||||||
return `${year}-${month}-${day}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formatBorrowerDisplay(name, quantity = 1) {
|
formatBorrowerDisplay(name, quantity = 1) {
|
||||||
@@ -5019,16 +5075,7 @@ class AccountManager {
|
|||||||
.slice(0, 32);
|
.slice(0, 32);
|
||||||
|
|
||||||
const base = toToken(payload.model) || toToken(payload.serialNumber) || toToken(payload.assetName) || 'ASSET';
|
const base = toToken(payload.model) || toToken(payload.serialNumber) || toToken(payload.assetName) || 'ASSET';
|
||||||
const now = new Date();
|
const timestamp = this.formatTimestampForCode(new Date(), true);
|
||||||
const timestamp = [
|
|
||||||
String(now.getFullYear()),
|
|
||||||
String(now.getMonth() + 1).padStart(2, '0'),
|
|
||||||
String(now.getDate()).padStart(2, '0'),
|
|
||||||
String(now.getHours()).padStart(2, '0'),
|
|
||||||
String(now.getMinutes()).padStart(2, '0'),
|
|
||||||
String(now.getSeconds()).padStart(2, '0'),
|
|
||||||
String(now.getMilliseconds()).padStart(3, '0')
|
|
||||||
].join('');
|
|
||||||
const randomSuffix = String(Math.floor(Math.random() * 100)).padStart(2, '0');
|
const randomSuffix = String(Math.floor(Math.random() * 100)).padStart(2, '0');
|
||||||
return `AST-${base}-${timestamp}${randomSuffix}`;
|
return `AST-${base}-${timestamp}${randomSuffix}`;
|
||||||
}
|
}
|
||||||
@@ -6481,8 +6528,7 @@ class AccountManager {
|
|||||||
const workbook = window.XLSX.utils.book_new();
|
const workbook = window.XLSX.utils.book_new();
|
||||||
window.XLSX.utils.book_append_sheet(workbook, worksheet, 'TaiSan');
|
window.XLSX.utils.book_append_sheet(workbook, worksheet, 'TaiSan');
|
||||||
|
|
||||||
const now = new Date();
|
const timestamp = this.formatTimestampForCode(new Date()).slice(0, 8);
|
||||||
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
|
||||||
window.XLSX.writeFile(workbook, `danh-sach-tai-san-${timestamp}.xlsx`);
|
window.XLSX.writeFile(workbook, `danh-sach-tai-san-${timestamp}.xlsx`);
|
||||||
this.notifySuccess('Xuất Excel thành công');
|
this.notifySuccess('Xuất Excel thành công');
|
||||||
}
|
}
|
||||||
@@ -7418,7 +7464,7 @@ class AccountManager {
|
|||||||
if (Number.isNaN(date.getTime())) {
|
if (Number.isNaN(date.getTime())) {
|
||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
return date.toLocaleString();
|
return APP_DATE_TIME_FORMATTER.format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Users Management ==========
|
// ========== Users Management ==========
|
||||||
|
|||||||
Reference in New Issue
Block a user