diff --git a/backend/src/opsce/assets/entities/asset.entity.ts b/backend/src/opsce/assets/entities/asset.entity.ts index 46ef9ed1..8f0640e0 100644 --- a/backend/src/opsce/assets/entities/asset.entity.ts +++ b/backend/src/opsce/assets/entities/asset.entity.ts @@ -6,9 +6,6 @@ import { UpdateDateColumn, DeleteDateColumn, ManyToOne, - - OneToMany, - JoinColumn, Index, Check, @@ -110,9 +107,9 @@ export enum AssetCondition { } export enum DepreciationMethod { - STRAIGHT_LINE = 'straight_line', - DECLINING_BALANCE = 'declining_balance', - NONE = 'none', + STRAIGHT_LINE = 'straight_line', + DECLINING_BALANCE = 'declining_balance', + NONE = 'none', } export enum MaintenanceFrequency { @@ -183,27 +180,13 @@ export interface AssetCheckout { // ─── Entity ─────────────────────────────────────────────────────────────────── @Entity('assets') - @Index('IDX_ASSET_STATUS_CATEGORY', ['status', 'category']) -@Index('IDX_ASSET_DEPT_STATUS', ['departmentId', 'status']) +@Index('IDX_ASSET_DEPT_STATUS', ['departmentId', 'status']) @Index('IDX_ASSET_LOCATION_STATUS', ['locationId', 'status']) -@Index('IDX_ASSET_ASSIGNED_USER', ['assignedToUserId']) -@Index('IDX_ASSET_DELETED_AT', ['deletedAt']) - -@Index('IDX_ASSET_STATUS_CATEGORY', ['status', 'category']) -@Index('IDX_ASSET_DEPT_STATUS', ['departmentId', 'status']) -@Index('IDX_ASSET_LOCATION_STATUS', ['locationId', 'status']) -@Index('IDX_ASSET_ASSIGNED_USER', ['assignedToUserId']) -@Index('IDX_ASSET_DELETED_AT', ['deletedAt']) - +@Index('IDX_ASSET_ASSIGNED_USER', ['assignedToUserId']) +@Index('IDX_ASSET_DELETED_AT', ['deletedAt']) @Check(`"name" <> ''`) -@Check(`"purchaseValue" IS NULL OR "purchaseValue" >= 0`) -@Check(`"currentValue" IS NULL OR "currentValue" >= 0`) -@Check(`"residualValue" IS NULL OR "residualValue" >= 0`) -@Check(`"usefulLifeYears" IS NULL OR "usefulLifeYears" > 0`) -@Check( - `"warrantyExpiryDate" IS NULL OR "purchaseDate" IS NULL OR "warrantyExpiryDate" >= "purchaseDate"`, -) +@Check(`"warrantyExpiryDate" IS NULL OR "purchaseDate" IS NULL OR "warrantyExpiryDate" >= "purchaseDate"`) export class Asset { // ─── Identity ─────────────────────────────────────────────────────────────── @@ -441,14 +424,6 @@ export class Asset { @Column({ type: 'date', nullable: true }) expectedReturnDate?: Date; - /** - * Rolling checkout history (last 50 entries). - * Full history should be stored in a dedicated `asset_checkouts` table - * for high-volume assets. - */ - @Column({ type: 'jsonb', nullable: true, default: [] }) - checkoutHistory?: AssetCheckout[]; - // ─── Placement ─────────────────────────────────────────────────────────────── @Column({ type: 'uuid', nullable: true }) @@ -577,44 +552,6 @@ export class Asset { return Math.max(0, parseFloat(total.toFixed(2))); } - /** - * Estimated current book value based on straight-line depreciation. - * Returns currentValue when explicitly set, falls back to computed value. - */ - get estimatedBookValue(): number | null { - if (this.currentValue != null) return Number(this.currentValue); - - if (this.accruedDepreciation == null || this.purchaseValue == null) - return null; - return Math.max( - this.residualValue ?? 0, - parseFloat( - (Number(this.purchaseValue) - this.accruedDepreciation).toFixed(2), - ), - - if (this.accruedDepreciation == null || this.purchaseValue == null) return null; - return Math.max( - this.residualValue ?? 0, - parseFloat((Number(this.purchaseValue) - this.accruedDepreciation).toFixed(2)), - - ); - } - - get isWarrantyExpired(): boolean { - if (!this.warrantyExpiryDate) return false; - return new Date(this.warrantyExpiryDate) < new Date(); - } - - get isInsuranceExpired(): boolean { - if (!this.insuranceExpiryDate) return false; - return new Date(this.insuranceExpiryDate) < new Date(); - } - - get isMaintenanceOverdue(): boolean { - if (!this.nextMaintenanceDue) return false; - return new Date(this.nextMaintenanceDue) < new Date(); - } - // ─── Lifecycle hooks ───────────────────────────────────────────────────────── @BeforeInsert() diff --git a/backend/src/opsce/users/entities/user.entity.ts b/backend/src/opsce/users/entities/user.entity.ts index 660d70fd..b93f9cc1 100644 --- a/backend/src/opsce/users/entities/user.entity.ts +++ b/backend/src/opsce/users/entities/user.entity.ts @@ -9,7 +9,6 @@ import { BeforeInsert, BeforeUpdate, Check, - OneToMany, } from 'typeorm'; import { Exclude } from 'class-transformer'; import * as bcrypt from 'bcrypt'; @@ -41,6 +40,7 @@ const MAX_FAILED_ATTEMPTS = 5; @Check(`"failedLoginAttempts" >= 0`) @Check(`"failedLoginAttempts" <= ${MAX_FAILED_ATTEMPTS}`) export class User { + // ─── Identity ────────────────────────────────────────────────────────── @PrimaryGeneratedColumn('uuid') @@ -135,13 +135,9 @@ export class User { @Column({ nullable: true, length: 64 }) timezone?: string; - // ─── Preferences (JSON) ──────────────────────────────────────────────── + // ─── Preferences ─────────────────────────────────────────────────────── - /** - * Arbitrary user preferences stored as a JSON column. - * Using `simple-json` avoids a separate table for simple key-value pairs. - */ - @Column({ type: 'simple-json', nullable: true }) + @Column({ type: 'jsonb', nullable: true }) preferences?: Record; // ─── Timestamps ──────────────────────────────────────────────────────── @@ -155,6 +151,15 @@ export class User { @DeleteDateColumn({ type: 'timestamptz' }) deletedAt?: Date; + // ─── Transient / virtual fields ──────────────────────────────────────── + + /** + * Transient plain-text password. Set via `setPassword()` — never persisted. + * Excluded from serialisation by class-transformer. + */ + @Exclude() + private _plainPassword?: string; + // ─── Lifecycle hooks ─────────────────────────────────────────────────── /** @@ -179,15 +184,6 @@ export class User { } } - // ─── Transient / virtual fields ──────────────────────────────────────── - - /** - * Transient plain-text password. Set via `setPassword()` — never persisted. - * Excluded from serialisation by class-transformer. - */ - @Exclude() - private _plainPassword?: string; - // ─── Domain methods ──────────────────────────────────────────────────── /** @@ -209,9 +205,7 @@ export class User { return bcrypt.compare(candidate, this.passwordHash); } - /** - * Record a successful login, resetting the failure counter and lock. - */ + /** Record a successful login, resetting the failure counter and lock. */ recordLoginSuccess(ip?: string): void { this.failedLoginAttempts = 0; this.lockedUntil = undefined; @@ -267,24 +261,4 @@ export class User { this.passwordResetExpiresAt > new Date() ); } - - /** - * Resolve a user's display name in priority order: - * username → first word of fullName → email local-part. - */ - get displayName(): string { - if (this.username) return this.username; - if (this.fullName) return this.fullName.split(' ')[0]; - return this.email.split('@')[0]; - } - - /** Guard: only ADMIN users may perform privileged operations. */ - isAdmin(): boolean { - return this.role === UserRole.ADMIN; - } - - /** Guard: ADMIN or MANAGER may perform management operations. */ - canManage(): boolean { - return this.role === UserRole.ADMIN || this.role === UserRole.MANAGER; - } } \ No newline at end of file