feat: Propagate core entities from erp-construccion
Modules added: - audit (7 entities): audit logs, config changes, entity changes - billing-usage (14 entities): subscriptions, invoices, coupons - core (13 entities): countries, currencies, UoMs, sequences - invoices (4 entities): invoices, payments, allocations - notifications (6 entities): notifications, templates, channels Total: 44 new entity files Build: Clean (0 TypeScript errors) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a0ca2bd77a
commit
8c010221fb
116
src/modules/audit/entities/audit-log.entity.ts
Normal file
116
src/modules/audit/entities/audit-log.entity.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* AuditLog Entity
|
||||||
|
* General activity tracking with full request context
|
||||||
|
* Compatible with erp-core audit-log.entity
|
||||||
|
*
|
||||||
|
* @module Audit
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type AuditAction = 'create' | 'read' | 'update' | 'delete' | 'login' | 'logout' | 'export';
|
||||||
|
export type AuditCategory = 'data' | 'auth' | 'system' | 'config' | 'billing';
|
||||||
|
export type AuditStatus = 'success' | 'failure' | 'partial';
|
||||||
|
|
||||||
|
@Entity({ name: 'audit_logs', schema: 'audit' })
|
||||||
|
export class AuditLog {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'user_id', type: 'uuid', nullable: true })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'user_email', type: 'varchar', length: 255, nullable: true })
|
||||||
|
userEmail: string;
|
||||||
|
|
||||||
|
@Column({ name: 'user_name', type: 'varchar', length: 200, nullable: true })
|
||||||
|
userName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'session_id', type: 'uuid', nullable: true })
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'impersonator_id', type: 'uuid', nullable: true })
|
||||||
|
impersonatorId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'action', type: 'varchar', length: 50 })
|
||||||
|
action: AuditAction;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'action_category', type: 'varchar', length: 50, nullable: true })
|
||||||
|
actionCategory: AuditCategory;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'resource_type', type: 'varchar', length: 100 })
|
||||||
|
resourceType: string;
|
||||||
|
|
||||||
|
@Column({ name: 'resource_id', type: 'uuid', nullable: true })
|
||||||
|
resourceId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'resource_name', type: 'varchar', length: 255, nullable: true })
|
||||||
|
resourceName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'old_values', type: 'jsonb', nullable: true })
|
||||||
|
oldValues: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'new_values', type: 'jsonb', nullable: true })
|
||||||
|
newValues: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'changed_fields', type: 'text', array: true, nullable: true })
|
||||||
|
changedFields: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'ip_address', type: 'inet', nullable: true })
|
||||||
|
ipAddress: string;
|
||||||
|
|
||||||
|
@Column({ name: 'user_agent', type: 'text', nullable: true })
|
||||||
|
userAgent: string;
|
||||||
|
|
||||||
|
@Column({ name: 'device_info', type: 'jsonb', default: {} })
|
||||||
|
deviceInfo: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'location', type: 'jsonb', default: {} })
|
||||||
|
location: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'request_id', type: 'varchar', length: 100, nullable: true })
|
||||||
|
requestId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'request_method', type: 'varchar', length: 10, nullable: true })
|
||||||
|
requestMethod: string;
|
||||||
|
|
||||||
|
@Column({ name: 'request_path', type: 'text', nullable: true })
|
||||||
|
requestPath: string;
|
||||||
|
|
||||||
|
@Column({ name: 'request_params', type: 'jsonb', default: {} })
|
||||||
|
requestParams: Record<string, any>;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'status', type: 'varchar', length: 20, default: 'success' })
|
||||||
|
status: AuditStatus;
|
||||||
|
|
||||||
|
@Column({ name: 'error_message', type: 'text', nullable: true })
|
||||||
|
errorMessage: string;
|
||||||
|
|
||||||
|
@Column({ name: 'duration_ms', type: 'int', nullable: true })
|
||||||
|
durationMs: number;
|
||||||
|
|
||||||
|
@Column({ name: 'metadata', type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'tags', type: 'text', array: true, default: [] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
55
src/modules/audit/entities/config-change.entity.ts
Normal file
55
src/modules/audit/entities/config-change.entity.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* ConfigChange Entity
|
||||||
|
* System configuration change auditing
|
||||||
|
* Compatible with erp-core config-change.entity
|
||||||
|
*
|
||||||
|
* @module Audit
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type ConfigType = 'tenant_settings' | 'user_settings' | 'system_settings' | 'feature_flags';
|
||||||
|
|
||||||
|
@Entity({ name: 'config_changes', schema: 'audit' })
|
||||||
|
export class ConfigChange {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid', nullable: true })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'changed_by', type: 'uuid' })
|
||||||
|
changedBy: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'config_type', type: 'varchar', length: 50 })
|
||||||
|
configType: ConfigType;
|
||||||
|
|
||||||
|
@Column({ name: 'config_key', type: 'varchar', length: 100 })
|
||||||
|
configKey: string;
|
||||||
|
|
||||||
|
@Column({ name: 'config_path', type: 'text', nullable: true })
|
||||||
|
configPath: string;
|
||||||
|
|
||||||
|
@Column({ name: 'old_value', type: 'jsonb', nullable: true })
|
||||||
|
oldValue: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'new_value', type: 'jsonb', nullable: true })
|
||||||
|
newValue: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'reason', type: 'text', nullable: true })
|
||||||
|
reason: string;
|
||||||
|
|
||||||
|
@Column({ name: 'ticket_id', type: 'varchar', length: 50, nullable: true })
|
||||||
|
ticketId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'changed_at', type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
changedAt: Date;
|
||||||
|
}
|
||||||
88
src/modules/audit/entities/data-export.entity.ts
Normal file
88
src/modules/audit/entities/data-export.entity.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* DataExport Entity
|
||||||
|
* GDPR/reporting data export request management
|
||||||
|
* Compatible with erp-core data-export.entity
|
||||||
|
*
|
||||||
|
* @module Audit
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type ExportType = 'report' | 'backup' | 'gdpr_request' | 'bulk_export';
|
||||||
|
export type ExportFormat = 'csv' | 'xlsx' | 'pdf' | 'json';
|
||||||
|
export type ExportStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'expired';
|
||||||
|
|
||||||
|
@Entity({ name: 'data_exports', schema: 'audit' })
|
||||||
|
export class DataExport {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'user_id', type: 'uuid' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'export_type', type: 'varchar', length: 50 })
|
||||||
|
exportType: ExportType;
|
||||||
|
|
||||||
|
@Column({ name: 'export_format', type: 'varchar', length: 20, nullable: true })
|
||||||
|
exportFormat: ExportFormat;
|
||||||
|
|
||||||
|
@Column({ name: 'entity_types', type: 'text', array: true })
|
||||||
|
entityTypes: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'filters', type: 'jsonb', default: {} })
|
||||||
|
filters: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'date_range_start', type: 'timestamptz', nullable: true })
|
||||||
|
dateRangeStart: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'date_range_end', type: 'timestamptz', nullable: true })
|
||||||
|
dateRangeEnd: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'record_count', type: 'int', nullable: true })
|
||||||
|
recordCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'file_size_bytes', type: 'bigint', nullable: true })
|
||||||
|
fileSizeBytes: number;
|
||||||
|
|
||||||
|
@Column({ name: 'file_hash', type: 'varchar', length: 64, nullable: true })
|
||||||
|
fileHash: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'status', type: 'varchar', length: 20, default: 'pending' })
|
||||||
|
status: ExportStatus;
|
||||||
|
|
||||||
|
@Column({ name: 'download_url', type: 'text', nullable: true })
|
||||||
|
downloadUrl: string;
|
||||||
|
|
||||||
|
@Column({ name: 'download_expires_at', type: 'timestamptz', nullable: true })
|
||||||
|
downloadExpiresAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'download_count', type: 'int', default: 0 })
|
||||||
|
downloadCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'ip_address', type: 'inet', nullable: true })
|
||||||
|
ipAddress: string;
|
||||||
|
|
||||||
|
@Column({ name: 'user_agent', type: 'text', nullable: true })
|
||||||
|
userAgent: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'requested_at', type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
requestedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'completed_at', type: 'timestamptz', nullable: true })
|
||||||
|
completedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
|
||||||
|
expiresAt: Date;
|
||||||
|
}
|
||||||
63
src/modules/audit/entities/entity-change.entity.ts
Normal file
63
src/modules/audit/entities/entity-change.entity.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* EntityChange Entity
|
||||||
|
* Data modification versioning and change history
|
||||||
|
* Compatible with erp-core entity-change.entity
|
||||||
|
*
|
||||||
|
* @module Audit
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type ChangeType = 'create' | 'update' | 'delete' | 'restore';
|
||||||
|
|
||||||
|
@Entity({ name: 'entity_changes', schema: 'audit' })
|
||||||
|
export class EntityChange {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'entity_type', type: 'varchar', length: 100 })
|
||||||
|
entityType: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'entity_id', type: 'uuid' })
|
||||||
|
entityId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'entity_name', type: 'varchar', length: 255, nullable: true })
|
||||||
|
entityName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'version', type: 'int', default: 1 })
|
||||||
|
version: number;
|
||||||
|
|
||||||
|
@Column({ name: 'previous_version', type: 'int', nullable: true })
|
||||||
|
previousVersion: number;
|
||||||
|
|
||||||
|
@Column({ name: 'data_snapshot', type: 'jsonb' })
|
||||||
|
dataSnapshot: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'changes', type: 'jsonb', default: [] })
|
||||||
|
changes: Record<string, any>[];
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'changed_by', type: 'uuid', nullable: true })
|
||||||
|
changedBy: string;
|
||||||
|
|
||||||
|
@Column({ name: 'change_reason', type: 'text', nullable: true })
|
||||||
|
changeReason: string;
|
||||||
|
|
||||||
|
@Column({ name: 'change_type', type: 'varchar', length: 20 })
|
||||||
|
changeType: ChangeType;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'changed_at', type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
changedAt: Date;
|
||||||
|
}
|
||||||
11
src/modules/audit/entities/index.ts
Normal file
11
src/modules/audit/entities/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Audit Entities - Export
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { AuditLog, AuditAction, AuditCategory, AuditStatus } from './audit-log.entity';
|
||||||
|
export { EntityChange, ChangeType } from './entity-change.entity';
|
||||||
|
export { LoginHistory, LoginStatus, AuthMethod, MfaMethod } from './login-history.entity';
|
||||||
|
export { SensitiveDataAccess, DataType, AccessType } from './sensitive-data-access.entity';
|
||||||
|
export { DataExport, ExportType, ExportFormat, ExportStatus } from './data-export.entity';
|
||||||
|
export { PermissionChange, PermissionChangeType, PermissionScope } from './permission-change.entity';
|
||||||
|
export { ConfigChange, ConfigType } from './config-change.entity';
|
||||||
114
src/modules/audit/entities/login-history.entity.ts
Normal file
114
src/modules/audit/entities/login-history.entity.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* LoginHistory Entity
|
||||||
|
* Authentication event tracking with device, location and risk scoring
|
||||||
|
* Compatible with erp-core login-history.entity
|
||||||
|
*
|
||||||
|
* @module Audit
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type LoginStatus = 'success' | 'failed' | 'blocked' | 'mfa_required' | 'mfa_failed';
|
||||||
|
export type AuthMethod = 'password' | 'sso' | 'oauth' | 'mfa' | 'magic_link' | 'biometric';
|
||||||
|
export type MfaMethod = 'totp' | 'sms' | 'email' | 'push';
|
||||||
|
|
||||||
|
@Entity({ name: 'login_history', schema: 'audit' })
|
||||||
|
export class LoginHistory {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid', nullable: true })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'user_id', type: 'uuid', nullable: true })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'email', type: 'varchar', length: 255, nullable: true })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column({ name: 'username', type: 'varchar', length: 100, nullable: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'status', type: 'varchar', length: 20 })
|
||||||
|
status: LoginStatus;
|
||||||
|
|
||||||
|
@Column({ name: 'auth_method', type: 'varchar', length: 30, nullable: true })
|
||||||
|
authMethod: AuthMethod;
|
||||||
|
|
||||||
|
@Column({ name: 'oauth_provider', type: 'varchar', length: 30, nullable: true })
|
||||||
|
oauthProvider: string;
|
||||||
|
|
||||||
|
@Column({ name: 'mfa_method', type: 'varchar', length: 20, nullable: true })
|
||||||
|
mfaMethod: MfaMethod;
|
||||||
|
|
||||||
|
@Column({ name: 'mfa_verified', type: 'boolean', nullable: true })
|
||||||
|
mfaVerified: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'device_id', type: 'uuid', nullable: true })
|
||||||
|
deviceId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'device_fingerprint', type: 'varchar', length: 255, nullable: true })
|
||||||
|
deviceFingerprint: string;
|
||||||
|
|
||||||
|
@Column({ name: 'device_type', type: 'varchar', length: 30, nullable: true })
|
||||||
|
deviceType: string;
|
||||||
|
|
||||||
|
@Column({ name: 'device_os', type: 'varchar', length: 50, nullable: true })
|
||||||
|
deviceOs: string;
|
||||||
|
|
||||||
|
@Column({ name: 'device_browser', type: 'varchar', length: 50, nullable: true })
|
||||||
|
deviceBrowser: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'ip_address', type: 'inet', nullable: true })
|
||||||
|
ipAddress: string;
|
||||||
|
|
||||||
|
@Column({ name: 'user_agent', type: 'text', nullable: true })
|
||||||
|
userAgent: string;
|
||||||
|
|
||||||
|
@Column({ name: 'country_code', type: 'varchar', length: 2, nullable: true })
|
||||||
|
countryCode: string;
|
||||||
|
|
||||||
|
@Column({ name: 'city', type: 'varchar', length: 100, nullable: true })
|
||||||
|
city: string;
|
||||||
|
|
||||||
|
@Column({ name: 'latitude', type: 'decimal', precision: 10, scale: 8, nullable: true })
|
||||||
|
latitude: number;
|
||||||
|
|
||||||
|
@Column({ name: 'longitude', type: 'decimal', precision: 11, scale: 8, nullable: true })
|
||||||
|
longitude: number;
|
||||||
|
|
||||||
|
@Column({ name: 'risk_score', type: 'int', nullable: true })
|
||||||
|
riskScore: number;
|
||||||
|
|
||||||
|
@Column({ name: 'risk_factors', type: 'jsonb', default: [] })
|
||||||
|
riskFactors: string[];
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'is_suspicious', type: 'boolean', default: false })
|
||||||
|
isSuspicious: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_new_device', type: 'boolean', default: false })
|
||||||
|
isNewDevice: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_new_location', type: 'boolean', default: false })
|
||||||
|
isNewLocation: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'failure_reason', type: 'varchar', length: 100, nullable: true })
|
||||||
|
failureReason: string;
|
||||||
|
|
||||||
|
@Column({ name: 'failure_count', type: 'int', nullable: true })
|
||||||
|
failureCount: number;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'attempted_at', type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
attemptedAt: Date;
|
||||||
|
}
|
||||||
71
src/modules/audit/entities/permission-change.entity.ts
Normal file
71
src/modules/audit/entities/permission-change.entity.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* PermissionChange Entity
|
||||||
|
* Access control change auditing
|
||||||
|
* Compatible with erp-core permission-change.entity
|
||||||
|
*
|
||||||
|
* @module Audit
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type PermissionChangeType = 'role_assigned' | 'role_revoked' | 'permission_granted' | 'permission_revoked';
|
||||||
|
export type PermissionScope = 'global' | 'tenant' | 'branch';
|
||||||
|
|
||||||
|
@Entity({ name: 'permission_changes', schema: 'audit' })
|
||||||
|
export class PermissionChange {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'changed_by', type: 'uuid' })
|
||||||
|
changedBy: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'target_user_id', type: 'uuid' })
|
||||||
|
targetUserId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'target_user_email', type: 'varchar', length: 255, nullable: true })
|
||||||
|
targetUserEmail: string;
|
||||||
|
|
||||||
|
@Column({ name: 'change_type', type: 'varchar', length: 30 })
|
||||||
|
changeType: PermissionChangeType;
|
||||||
|
|
||||||
|
@Column({ name: 'role_id', type: 'uuid', nullable: true })
|
||||||
|
roleId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'role_code', type: 'varchar', length: 50, nullable: true })
|
||||||
|
roleCode: string;
|
||||||
|
|
||||||
|
@Column({ name: 'permission_id', type: 'uuid', nullable: true })
|
||||||
|
permissionId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'permission_code', type: 'varchar', length: 100, nullable: true })
|
||||||
|
permissionCode: string;
|
||||||
|
|
||||||
|
@Column({ name: 'branch_id', type: 'uuid', nullable: true })
|
||||||
|
branchId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'scope', type: 'varchar', length: 30, nullable: true })
|
||||||
|
scope: PermissionScope;
|
||||||
|
|
||||||
|
@Column({ name: 'previous_roles', type: 'text', array: true, nullable: true })
|
||||||
|
previousRoles: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'previous_permissions', type: 'text', array: true, nullable: true })
|
||||||
|
previousPermissions: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'reason', type: 'text', nullable: true })
|
||||||
|
reason: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'changed_at', type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
changedAt: Date;
|
||||||
|
}
|
||||||
70
src/modules/audit/entities/sensitive-data-access.entity.ts
Normal file
70
src/modules/audit/entities/sensitive-data-access.entity.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* SensitiveDataAccess Entity
|
||||||
|
* Security/compliance logging for PII, financial, medical and credential access
|
||||||
|
* Compatible with erp-core sensitive-data-access.entity
|
||||||
|
*
|
||||||
|
* @module Audit
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type DataType = 'pii' | 'financial' | 'medical' | 'credentials';
|
||||||
|
export type AccessType = 'view' | 'export' | 'modify' | 'decrypt';
|
||||||
|
|
||||||
|
@Entity({ name: 'sensitive_data_access', schema: 'audit' })
|
||||||
|
export class SensitiveDataAccess {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'user_id', type: 'uuid' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'session_id', type: 'uuid', nullable: true })
|
||||||
|
sessionId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'data_type', type: 'varchar', length: 100 })
|
||||||
|
dataType: DataType;
|
||||||
|
|
||||||
|
@Column({ name: 'data_category', type: 'varchar', length: 100, nullable: true })
|
||||||
|
dataCategory: string;
|
||||||
|
|
||||||
|
@Column({ name: 'entity_type', type: 'varchar', length: 100, nullable: true })
|
||||||
|
entityType: string;
|
||||||
|
|
||||||
|
@Column({ name: 'entity_id', type: 'uuid', nullable: true })
|
||||||
|
entityId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'access_type', type: 'varchar', length: 30 })
|
||||||
|
accessType: AccessType;
|
||||||
|
|
||||||
|
@Column({ name: 'access_reason', type: 'text', nullable: true })
|
||||||
|
accessReason: string;
|
||||||
|
|
||||||
|
@Column({ name: 'ip_address', type: 'inet', nullable: true })
|
||||||
|
ipAddress: string;
|
||||||
|
|
||||||
|
@Column({ name: 'user_agent', type: 'text', nullable: true })
|
||||||
|
userAgent: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'was_authorized', type: 'boolean', default: true })
|
||||||
|
wasAuthorized: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'denial_reason', type: 'text', nullable: true })
|
||||||
|
denialReason: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'accessed_at', type: 'timestamptz', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
accessedAt: Date;
|
||||||
|
}
|
||||||
72
src/modules/billing-usage/entities/billing-alert.entity.ts
Normal file
72
src/modules/billing-usage/entities/billing-alert.entity.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type BillingAlertType =
|
||||||
|
| 'usage_limit'
|
||||||
|
| 'payment_due'
|
||||||
|
| 'payment_failed'
|
||||||
|
| 'trial_ending'
|
||||||
|
| 'subscription_ending';
|
||||||
|
|
||||||
|
export type AlertSeverity = 'info' | 'warning' | 'critical';
|
||||||
|
export type AlertStatus = 'active' | 'acknowledged' | 'resolved';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entidad para alertas de facturacion y limites de uso.
|
||||||
|
* Mapea a billing.billing_alerts (DDL: 05-billing-usage.sql)
|
||||||
|
*/
|
||||||
|
@Entity({ name: 'billing_alerts', schema: 'billing' })
|
||||||
|
export class BillingAlert {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
// Tipo de alerta
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'alert_type', type: 'varchar', length: 30 })
|
||||||
|
alertType: BillingAlertType;
|
||||||
|
|
||||||
|
// Detalles
|
||||||
|
@Column({ type: 'varchar', length: 200 })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'info' })
|
||||||
|
severity: AlertSeverity;
|
||||||
|
|
||||||
|
// Estado
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'active' })
|
||||||
|
status: AlertStatus;
|
||||||
|
|
||||||
|
// Notificacion
|
||||||
|
@Column({ name: 'notified_at', type: 'timestamptz', nullable: true })
|
||||||
|
notifiedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'acknowledged_at', type: 'timestamptz', nullable: true })
|
||||||
|
acknowledgedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'acknowledged_by', type: 'uuid', nullable: true })
|
||||||
|
acknowledgedBy: string;
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
CreateDateColumn,
|
||||||
|
Unique,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Coupon } from './coupon.entity';
|
||||||
|
import { TenantSubscription } from './tenant-subscription.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'coupon_redemptions', schema: 'billing' })
|
||||||
|
@Unique(['couponId', 'tenantId'])
|
||||||
|
export class CouponRedemption {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'coupon_id', type: 'uuid' })
|
||||||
|
couponId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Coupon, (coupon) => coupon.redemptions)
|
||||||
|
@JoinColumn({ name: 'coupon_id' })
|
||||||
|
coupon!: Coupon;
|
||||||
|
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'subscription_id', type: 'uuid', nullable: true })
|
||||||
|
subscriptionId?: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => TenantSubscription, { nullable: true })
|
||||||
|
@JoinColumn({ name: 'subscription_id' })
|
||||||
|
subscription?: TenantSubscription;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_amount', type: 'decimal', precision: 10, scale: 2 })
|
||||||
|
discountAmount!: number;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'redeemed_at', type: 'timestamptz' })
|
||||||
|
redeemedAt!: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
|
||||||
|
expiresAt?: Date;
|
||||||
|
}
|
||||||
72
src/modules/billing-usage/entities/coupon.entity.ts
Normal file
72
src/modules/billing-usage/entities/coupon.entity.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
OneToMany,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { CouponRedemption } from './coupon-redemption.entity';
|
||||||
|
|
||||||
|
export type DiscountType = 'percentage' | 'fixed';
|
||||||
|
export type DurationPeriod = 'once' | 'forever' | 'months';
|
||||||
|
|
||||||
|
@Entity({ name: 'coupons', schema: 'billing' })
|
||||||
|
export class Coupon {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, unique: true })
|
||||||
|
code!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255 })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_type', type: 'varchar', length: 20 })
|
||||||
|
discountType!: DiscountType;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_value', type: 'decimal', precision: 10, scale: 2 })
|
||||||
|
discountValue!: number;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 3, default: 'MXN' })
|
||||||
|
currency!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'applicable_plans', type: 'uuid', array: true, default: [] })
|
||||||
|
applicablePlans!: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'min_amount', type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
|
minAmount!: number;
|
||||||
|
|
||||||
|
@Column({ name: 'duration_period', type: 'varchar', length: 20, default: 'once' })
|
||||||
|
durationPeriod!: DurationPeriod;
|
||||||
|
|
||||||
|
@Column({ name: 'duration_months', type: 'integer', nullable: true })
|
||||||
|
durationMonths?: number;
|
||||||
|
|
||||||
|
@Column({ name: 'max_redemptions', type: 'integer', nullable: true })
|
||||||
|
maxRedemptions?: number;
|
||||||
|
|
||||||
|
@Column({ name: 'current_redemptions', type: 'integer', default: 0 })
|
||||||
|
currentRedemptions!: number;
|
||||||
|
|
||||||
|
@Column({ name: 'valid_from', type: 'timestamptz', nullable: true })
|
||||||
|
validFrom?: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'valid_until', type: 'timestamptz', nullable: true })
|
||||||
|
validUntil?: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive!: boolean;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt!: Date;
|
||||||
|
|
||||||
|
@OneToMany(() => CouponRedemption, (redemption) => redemption.coupon)
|
||||||
|
redemptions!: CouponRedemption[];
|
||||||
|
}
|
||||||
13
src/modules/billing-usage/entities/index.ts
Normal file
13
src/modules/billing-usage/entities/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export { SubscriptionPlan, PlanType } from './subscription-plan.entity';
|
||||||
|
export { TenantSubscription, BillingCycle, SubscriptionStatus } from './tenant-subscription.entity';
|
||||||
|
export { UsageTracking } from './usage-tracking.entity';
|
||||||
|
export { UsageEvent, EventCategory } from './usage-event.entity';
|
||||||
|
export { Invoice, InvoiceStatus, InvoiceContext, InvoiceType, InvoiceItem } from './invoice.entity';
|
||||||
|
export { InvoiceItemType } from './invoice-item.entity';
|
||||||
|
export { BillingPaymentMethod, PaymentProvider, PaymentMethodType } from './payment-method.entity';
|
||||||
|
export { BillingAlert, BillingAlertType, AlertSeverity, AlertStatus } from './billing-alert.entity';
|
||||||
|
export { PlanFeature } from './plan-feature.entity';
|
||||||
|
export { PlanLimit, LimitType } from './plan-limit.entity';
|
||||||
|
export { Coupon, DiscountType, DurationPeriod } from './coupon.entity';
|
||||||
|
export { CouponRedemption } from './coupon-redemption.entity';
|
||||||
|
export { StripeEvent } from './stripe-event.entity';
|
||||||
65
src/modules/billing-usage/entities/invoice-item.entity.ts
Normal file
65
src/modules/billing-usage/entities/invoice-item.entity.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Invoice } from '../../invoices/entities/invoice.entity';
|
||||||
|
|
||||||
|
export type InvoiceItemType = 'subscription' | 'user' | 'profile' | 'overage' | 'addon';
|
||||||
|
|
||||||
|
@Entity({ name: 'invoice_items', schema: 'billing' })
|
||||||
|
export class InvoiceItem {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'invoice_id', type: 'uuid' })
|
||||||
|
invoiceId: string;
|
||||||
|
|
||||||
|
// Descripcion
|
||||||
|
@Column({ type: 'varchar', length: 500 })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'item_type', type: 'varchar', length: 30 })
|
||||||
|
itemType: InvoiceItemType;
|
||||||
|
|
||||||
|
// Cantidades
|
||||||
|
@Column({ type: 'integer', default: 1 })
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
@Column({ name: 'unit_price', type: 'decimal', precision: 12, scale: 2 })
|
||||||
|
unitPrice: number;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 12, scale: 2 })
|
||||||
|
subtotal: number;
|
||||||
|
|
||||||
|
// Detalles adicionales
|
||||||
|
@Column({ name: 'profile_code', type: 'varchar', length: 10, nullable: true })
|
||||||
|
profileCode: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, nullable: true })
|
||||||
|
platform: string;
|
||||||
|
|
||||||
|
@Column({ name: 'period_start', type: 'date', nullable: true })
|
||||||
|
periodStart: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'period_end', type: 'date', nullable: true })
|
||||||
|
periodEnd: Date;
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
|
@ManyToOne(() => Invoice, (invoice) => invoice.items, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'invoice_id' })
|
||||||
|
invoice: Invoice;
|
||||||
|
}
|
||||||
17
src/modules/billing-usage/entities/invoice.entity.ts
Normal file
17
src/modules/billing-usage/entities/invoice.entity.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @deprecated Use Invoice from 'modules/invoices/entities' instead.
|
||||||
|
*
|
||||||
|
* This entity has been unified with the commercial Invoice entity.
|
||||||
|
* Both SaaS billing and commercial invoices now use the same table.
|
||||||
|
*
|
||||||
|
* Migration guide:
|
||||||
|
* - Import from: import { Invoice, InvoiceStatus, InvoiceContext } from '../../invoices/entities/invoice.entity';
|
||||||
|
* - Set invoiceContext: 'saas' for SaaS billing invoices
|
||||||
|
* - Use subscriptionId, periodStart, periodEnd for SaaS-specific fields
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Re-export from unified invoice entity
|
||||||
|
export { Invoice, InvoiceStatus, InvoiceContext, InvoiceType } from '../../invoices/entities/invoice.entity';
|
||||||
|
|
||||||
|
// Re-export InvoiceItem as well since it's used together
|
||||||
|
export { InvoiceItem } from './invoice-item.entity';
|
||||||
85
src/modules/billing-usage/entities/payment-method.entity.ts
Normal file
85
src/modules/billing-usage/entities/payment-method.entity.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type PaymentProvider = 'stripe' | 'mercadopago' | 'bank_transfer';
|
||||||
|
export type PaymentMethodType = 'card' | 'bank_account' | 'wallet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entidad para metodos de pago guardados por tenant.
|
||||||
|
* Almacena informacion tokenizada/encriptada de metodos de pago.
|
||||||
|
* Mapea a billing.payment_methods (DDL: 05-billing-usage.sql)
|
||||||
|
*/
|
||||||
|
@Entity({ name: 'payment_methods', schema: 'billing' })
|
||||||
|
export class BillingPaymentMethod {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
// Proveedor
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 30 })
|
||||||
|
provider: PaymentProvider;
|
||||||
|
|
||||||
|
// Tipo
|
||||||
|
@Column({ name: 'method_type', type: 'varchar', length: 20 })
|
||||||
|
methodType: PaymentMethodType;
|
||||||
|
|
||||||
|
// Datos tokenizados del proveedor
|
||||||
|
@Column({ name: 'provider_customer_id', type: 'varchar', length: 255, nullable: true })
|
||||||
|
providerCustomerId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'provider_method_id', type: 'varchar', length: 255, nullable: true })
|
||||||
|
providerMethodId: string;
|
||||||
|
|
||||||
|
// Display info (no sensible)
|
||||||
|
@Column({ name: 'display_name', type: 'varchar', length: 100, nullable: true })
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'card_brand', type: 'varchar', length: 20, nullable: true })
|
||||||
|
cardBrand: string;
|
||||||
|
|
||||||
|
@Column({ name: 'card_last_four', type: 'varchar', length: 4, nullable: true })
|
||||||
|
cardLastFour: string;
|
||||||
|
|
||||||
|
@Column({ name: 'card_exp_month', type: 'integer', nullable: true })
|
||||||
|
cardExpMonth: number;
|
||||||
|
|
||||||
|
@Column({ name: 'card_exp_year', type: 'integer', nullable: true })
|
||||||
|
cardExpYear: number;
|
||||||
|
|
||||||
|
@Column({ name: 'bank_name', type: 'varchar', length: 100, nullable: true })
|
||||||
|
bankName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'bank_last_four', type: 'varchar', length: 4, nullable: true })
|
||||||
|
bankLastFour: string;
|
||||||
|
|
||||||
|
// Estado
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'is_default', type: 'boolean', default: false })
|
||||||
|
isDefault: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_verified', type: 'boolean', default: false })
|
||||||
|
isVerified: boolean;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date;
|
||||||
|
}
|
||||||
61
src/modules/billing-usage/entities/plan-feature.entity.ts
Normal file
61
src/modules/billing-usage/entities/plan-feature.entity.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { SubscriptionPlan } from './subscription-plan.entity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlanFeature Entity
|
||||||
|
* Maps to billing.plan_features DDL table
|
||||||
|
* Features disponibles por plan de suscripcion
|
||||||
|
* Propagated from template-saas HU-REFACT-005
|
||||||
|
*/
|
||||||
|
@Entity({ schema: 'billing', name: 'plan_features' })
|
||||||
|
@Index('idx_plan_features_plan', ['planId'])
|
||||||
|
@Index('idx_plan_features_key', ['featureKey'])
|
||||||
|
export class PlanFeature {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'plan_id' })
|
||||||
|
planId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: false, name: 'feature_key' })
|
||||||
|
featureKey: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: false, name: 'feature_name' })
|
||||||
|
featureName: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||||
|
category: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', default: true })
|
||||||
|
enabled: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
configuration: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
|
@ManyToOne(() => SubscriptionPlan, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'plan_id' })
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
52
src/modules/billing-usage/entities/plan-limit.entity.ts
Normal file
52
src/modules/billing-usage/entities/plan-limit.entity.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { SubscriptionPlan } from './subscription-plan.entity';
|
||||||
|
|
||||||
|
export type LimitType = 'monthly' | 'daily' | 'total' | 'per_user';
|
||||||
|
|
||||||
|
@Entity({ name: 'plan_limits', schema: 'billing' })
|
||||||
|
export class PlanLimit {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'plan_id', type: 'uuid' })
|
||||||
|
planId!: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => SubscriptionPlan, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'plan_id' })
|
||||||
|
plan!: SubscriptionPlan;
|
||||||
|
|
||||||
|
@Column({ name: 'limit_key', type: 'varchar', length: 100 })
|
||||||
|
limitKey!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'limit_name', type: 'varchar', length: 255 })
|
||||||
|
limitName!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'limit_value', type: 'integer' })
|
||||||
|
limitValue!: number;
|
||||||
|
|
||||||
|
@Column({ name: 'limit_type', type: 'varchar', length: 50, default: 'monthly' })
|
||||||
|
limitType!: LimitType;
|
||||||
|
|
||||||
|
@Column({ name: 'allow_overage', type: 'boolean', default: false })
|
||||||
|
allowOverage!: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'overage_unit_price', type: 'decimal', precision: 10, scale: 4, default: 0 })
|
||||||
|
overageUnitPrice!: number;
|
||||||
|
|
||||||
|
@Column({ name: 'overage_currency', type: 'varchar', length: 3, default: 'MXN' })
|
||||||
|
overageCurrency!: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt!: Date;
|
||||||
|
}
|
||||||
43
src/modules/billing-usage/entities/stripe-event.entity.ts
Normal file
43
src/modules/billing-usage/entities/stripe-event.entity.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity({ name: 'stripe_events', schema: 'billing' })
|
||||||
|
export class StripeEvent {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'stripe_event_id', type: 'varchar', length: 255, unique: true })
|
||||||
|
@Index()
|
||||||
|
stripeEventId!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'event_type', type: 'varchar', length: 100 })
|
||||||
|
@Index()
|
||||||
|
eventType!: string;
|
||||||
|
|
||||||
|
@Column({ name: 'api_version', type: 'varchar', length: 20, nullable: true })
|
||||||
|
apiVersion?: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb' })
|
||||||
|
data!: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', default: false })
|
||||||
|
@Index()
|
||||||
|
processed!: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'processed_at', type: 'timestamptz', nullable: true })
|
||||||
|
processedAt?: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'error_message', type: 'text', nullable: true })
|
||||||
|
errorMessage?: string;
|
||||||
|
|
||||||
|
@Column({ name: 'retry_count', type: 'integer', default: 0 })
|
||||||
|
retryCount!: number;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt!: Date;
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type PlanType = 'saas' | 'on_premise' | 'hybrid';
|
||||||
|
|
||||||
|
@Entity({ name: 'subscription_plans', schema: 'billing' })
|
||||||
|
export class SubscriptionPlan {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
// Identificacion
|
||||||
|
@Index({ unique: true })
|
||||||
|
@Column({ type: 'varchar', length: 30 })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
// Tipo
|
||||||
|
@Column({ name: 'plan_type', type: 'varchar', length: 20, default: 'saas' })
|
||||||
|
planType: PlanType;
|
||||||
|
|
||||||
|
// Precios base
|
||||||
|
@Column({ name: 'base_monthly_price', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||||
|
baseMonthlyPrice: number;
|
||||||
|
|
||||||
|
@Column({ name: 'base_annual_price', type: 'decimal', precision: 12, scale: 2, nullable: true })
|
||||||
|
baseAnnualPrice: number;
|
||||||
|
|
||||||
|
@Column({ name: 'setup_fee', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||||
|
setupFee: number;
|
||||||
|
|
||||||
|
// Limites base
|
||||||
|
@Column({ name: 'max_users', type: 'integer', default: 5 })
|
||||||
|
maxUsers: number;
|
||||||
|
|
||||||
|
@Column({ name: 'max_branches', type: 'integer', default: 1 })
|
||||||
|
maxBranches: number;
|
||||||
|
|
||||||
|
@Column({ name: 'storage_gb', type: 'integer', default: 10 })
|
||||||
|
storageGb: number;
|
||||||
|
|
||||||
|
@Column({ name: 'api_calls_monthly', type: 'integer', default: 10000 })
|
||||||
|
apiCallsMonthly: number;
|
||||||
|
|
||||||
|
// Modulos incluidos
|
||||||
|
@Column({ name: 'included_modules', type: 'text', array: true, default: [] })
|
||||||
|
includedModules: string[];
|
||||||
|
|
||||||
|
// Plataformas incluidas
|
||||||
|
@Column({ name: 'included_platforms', type: 'text', array: true, default: ['web'] })
|
||||||
|
includedPlatforms: string[];
|
||||||
|
|
||||||
|
// Features
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
features: Record<string, boolean>;
|
||||||
|
|
||||||
|
// Estado
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_public', type: 'boolean', default: true })
|
||||||
|
isPublic: boolean;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date;
|
||||||
|
}
|
||||||
132
src/modules/billing-usage/entities/tenant-subscription.entity.ts
Normal file
132
src/modules/billing-usage/entities/tenant-subscription.entity.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
Unique,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { SubscriptionPlan } from './subscription-plan.entity';
|
||||||
|
|
||||||
|
export type BillingCycle = 'monthly' | 'annual';
|
||||||
|
export type SubscriptionStatus = 'trial' | 'active' | 'past_due' | 'cancelled' | 'suspended';
|
||||||
|
|
||||||
|
@Entity({ name: 'tenant_subscriptions', schema: 'billing' })
|
||||||
|
@Unique(['tenantId'])
|
||||||
|
export class TenantSubscription {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'plan_id', type: 'uuid' })
|
||||||
|
planId: string;
|
||||||
|
|
||||||
|
// Periodo
|
||||||
|
@Column({ name: 'billing_cycle', type: 'varchar', length: 20, default: 'monthly' })
|
||||||
|
billingCycle: BillingCycle;
|
||||||
|
|
||||||
|
@Column({ name: 'current_period_start', type: 'timestamptz' })
|
||||||
|
currentPeriodStart: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'current_period_end', type: 'timestamptz' })
|
||||||
|
currentPeriodEnd: Date;
|
||||||
|
|
||||||
|
// Estado
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'active' })
|
||||||
|
status: SubscriptionStatus;
|
||||||
|
|
||||||
|
// Trial
|
||||||
|
@Column({ name: 'trial_start', type: 'timestamptz', nullable: true })
|
||||||
|
trialStart: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'trial_end', type: 'timestamptz', nullable: true })
|
||||||
|
trialEnd: Date;
|
||||||
|
|
||||||
|
// Configuracion de facturacion
|
||||||
|
@Column({ name: 'billing_email', type: 'varchar', length: 255, nullable: true })
|
||||||
|
billingEmail: string;
|
||||||
|
|
||||||
|
@Column({ name: 'billing_name', type: 'varchar', length: 200, nullable: true })
|
||||||
|
billingName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'billing_address', type: 'jsonb', default: {} })
|
||||||
|
billingAddress: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'tax_id', type: 'varchar', length: 20, nullable: true })
|
||||||
|
taxId: string; // RFC para Mexico
|
||||||
|
|
||||||
|
// Metodo de pago
|
||||||
|
@Column({ name: 'payment_method_id', type: 'uuid', nullable: true })
|
||||||
|
paymentMethodId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'payment_provider', type: 'varchar', length: 30, nullable: true })
|
||||||
|
paymentProvider: string; // stripe, mercadopago, bank_transfer
|
||||||
|
|
||||||
|
// Stripe integration
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'stripe_customer_id', type: 'varchar', length: 255, nullable: true })
|
||||||
|
stripeCustomerId?: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'stripe_subscription_id', type: 'varchar', length: 255, nullable: true })
|
||||||
|
stripeSubscriptionId?: string;
|
||||||
|
|
||||||
|
@Column({ name: 'last_payment_at', type: 'timestamptz', nullable: true })
|
||||||
|
lastPaymentAt?: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'last_payment_amount', type: 'decimal', precision: 12, scale: 2, nullable: true })
|
||||||
|
lastPaymentAmount?: number;
|
||||||
|
|
||||||
|
// Precios actuales
|
||||||
|
@Column({ name: 'current_price', type: 'decimal', precision: 12, scale: 2 })
|
||||||
|
currentPrice: number;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_percent', type: 'decimal', precision: 5, scale: 2, default: 0 })
|
||||||
|
discountPercent: number;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_reason', type: 'varchar', length: 100, nullable: true })
|
||||||
|
discountReason: string;
|
||||||
|
|
||||||
|
// Uso contratado
|
||||||
|
@Column({ name: 'contracted_users', type: 'integer', nullable: true })
|
||||||
|
contractedUsers: number;
|
||||||
|
|
||||||
|
@Column({ name: 'contracted_branches', type: 'integer', nullable: true })
|
||||||
|
contractedBranches: number;
|
||||||
|
|
||||||
|
// Facturacion automatica
|
||||||
|
@Column({ name: 'auto_renew', type: 'boolean', default: true })
|
||||||
|
autoRenew: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'next_invoice_date', type: 'date', nullable: true })
|
||||||
|
nextInvoiceDate: Date;
|
||||||
|
|
||||||
|
// Cancelacion
|
||||||
|
@Column({ name: 'cancel_at_period_end', type: 'boolean', default: false })
|
||||||
|
cancelAtPeriodEnd: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'cancelled_at', type: 'timestamptz', nullable: true })
|
||||||
|
cancelledAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'cancellation_reason', type: 'text', nullable: true })
|
||||||
|
cancellationReason: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
// Relaciones
|
||||||
|
@ManyToOne(() => SubscriptionPlan)
|
||||||
|
@JoinColumn({ name: 'plan_id' })
|
||||||
|
plan: SubscriptionPlan;
|
||||||
|
}
|
||||||
73
src/modules/billing-usage/entities/usage-event.entity.ts
Normal file
73
src/modules/billing-usage/entities/usage-event.entity.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type EventCategory = 'user' | 'api' | 'storage' | 'transaction' | 'mobile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entidad para eventos de uso en tiempo real.
|
||||||
|
* Utilizada para calculo de billing y tracking granular.
|
||||||
|
* Mapea a billing.usage_events (DDL: 05-billing-usage.sql)
|
||||||
|
*/
|
||||||
|
@Entity({ name: 'usage_events', schema: 'billing' })
|
||||||
|
export class UsageEvent {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', type: 'uuid', nullable: true })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'device_id', type: 'uuid', nullable: true })
|
||||||
|
deviceId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'branch_id', type: 'uuid', nullable: true })
|
||||||
|
branchId: string;
|
||||||
|
|
||||||
|
// Evento
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'event_type', type: 'varchar', length: 50 })
|
||||||
|
eventType: string; // login, api_call, document_upload, sale, invoice, sync
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'event_category', type: 'varchar', length: 30 })
|
||||||
|
eventCategory: EventCategory;
|
||||||
|
|
||||||
|
// Detalles
|
||||||
|
@Column({ name: 'profile_code', type: 'varchar', length: 10, nullable: true })
|
||||||
|
profileCode: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, nullable: true })
|
||||||
|
platform: string;
|
||||||
|
|
||||||
|
@Column({ name: 'resource_id', type: 'uuid', nullable: true })
|
||||||
|
resourceId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'resource_type', type: 'varchar', length: 50, nullable: true })
|
||||||
|
resourceType: string;
|
||||||
|
|
||||||
|
// Metricas
|
||||||
|
@Column({ type: 'integer', default: 1 })
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
@Column({ name: 'bytes_used', type: 'bigint', default: 0 })
|
||||||
|
bytesUsed: number;
|
||||||
|
|
||||||
|
@Column({ name: 'duration_ms', type: 'integer', nullable: true })
|
||||||
|
durationMs: number;
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
91
src/modules/billing-usage/entities/usage-tracking.entity.ts
Normal file
91
src/modules/billing-usage/entities/usage-tracking.entity.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
Unique,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity({ name: 'usage_tracking', schema: 'billing' })
|
||||||
|
@Unique(['tenantId', 'periodStart'])
|
||||||
|
export class UsageTracking {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
// Periodo
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'period_start', type: 'date' })
|
||||||
|
periodStart: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'period_end', type: 'date' })
|
||||||
|
periodEnd: Date;
|
||||||
|
|
||||||
|
// Usuarios
|
||||||
|
@Column({ name: 'active_users', type: 'integer', default: 0 })
|
||||||
|
activeUsers: number;
|
||||||
|
|
||||||
|
@Column({ name: 'peak_concurrent_users', type: 'integer', default: 0 })
|
||||||
|
peakConcurrentUsers: number;
|
||||||
|
|
||||||
|
// Por perfil
|
||||||
|
@Column({ name: 'users_by_profile', type: 'jsonb', default: {} })
|
||||||
|
usersByProfile: Record<string, number>; // {"ADM": 2, "VNT": 5, "ALM": 3}
|
||||||
|
|
||||||
|
// Por plataforma
|
||||||
|
@Column({ name: 'users_by_platform', type: 'jsonb', default: {} })
|
||||||
|
usersByPlatform: Record<string, number>; // {"web": 8, "mobile": 5, "desktop": 0}
|
||||||
|
|
||||||
|
// Sucursales
|
||||||
|
@Column({ name: 'active_branches', type: 'integer', default: 0 })
|
||||||
|
activeBranches: number;
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
@Column({ name: 'storage_used_gb', type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
|
storageUsedGb: number;
|
||||||
|
|
||||||
|
@Column({ name: 'documents_count', type: 'integer', default: 0 })
|
||||||
|
documentsCount: number;
|
||||||
|
|
||||||
|
// API
|
||||||
|
@Column({ name: 'api_calls', type: 'integer', default: 0 })
|
||||||
|
apiCalls: number;
|
||||||
|
|
||||||
|
@Column({ name: 'api_errors', type: 'integer', default: 0 })
|
||||||
|
apiErrors: number;
|
||||||
|
|
||||||
|
// Transacciones
|
||||||
|
@Column({ name: 'sales_count', type: 'integer', default: 0 })
|
||||||
|
salesCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'sales_amount', type: 'decimal', precision: 14, scale: 2, default: 0 })
|
||||||
|
salesAmount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'invoices_generated', type: 'integer', default: 0 })
|
||||||
|
invoicesGenerated: number;
|
||||||
|
|
||||||
|
// Mobile
|
||||||
|
@Column({ name: 'mobile_sessions', type: 'integer', default: 0 })
|
||||||
|
mobileSessions: number;
|
||||||
|
|
||||||
|
@Column({ name: 'offline_syncs', type: 'integer', default: 0 })
|
||||||
|
offlineSyncs: number;
|
||||||
|
|
||||||
|
@Column({ name: 'payment_transactions', type: 'integer', default: 0 })
|
||||||
|
paymentTransactions: number;
|
||||||
|
|
||||||
|
// Calculado
|
||||||
|
@Column({ name: 'total_billable_amount', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||||
|
totalBillableAmount: number;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
35
src/modules/core/entities/country.entity.ts
Normal file
35
src/modules/core/entities/country.entity.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'countries' })
|
||||||
|
@Index('idx_countries_code', ['code'], { unique: true })
|
||||||
|
export class Country {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 2, nullable: false, unique: true })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 10, nullable: true, name: 'phone_code' })
|
||||||
|
phoneCode: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'varchar',
|
||||||
|
length: 3,
|
||||||
|
nullable: true,
|
||||||
|
name: 'currency_code',
|
||||||
|
})
|
||||||
|
currencyCode: string | null;
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
55
src/modules/core/entities/currency-rate.entity.ts
Normal file
55
src/modules/core/entities/currency-rate.entity.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Currency } from './currency.entity';
|
||||||
|
|
||||||
|
export type RateSource = 'manual' | 'banxico' | 'xe' | 'openexchange';
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'currency_rates' })
|
||||||
|
@Index('idx_currency_rates_tenant', ['tenantId'])
|
||||||
|
@Index('idx_currency_rates_from', ['fromCurrencyId'])
|
||||||
|
@Index('idx_currency_rates_to', ['toCurrencyId'])
|
||||||
|
@Index('idx_currency_rates_date', ['rateDate'])
|
||||||
|
@Index('idx_currency_rates_lookup', ['fromCurrencyId', 'toCurrencyId', 'rateDate'])
|
||||||
|
export class CurrencyRate {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', name: 'tenant_id', nullable: true })
|
||||||
|
tenantId: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', name: 'from_currency_id', nullable: false })
|
||||||
|
fromCurrencyId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Currency)
|
||||||
|
@JoinColumn({ name: 'from_currency_id' })
|
||||||
|
fromCurrency: Currency;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', name: 'to_currency_id', nullable: false })
|
||||||
|
toCurrencyId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Currency)
|
||||||
|
@JoinColumn({ name: 'to_currency_id' })
|
||||||
|
toCurrency: Currency;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 18, scale: 8, nullable: false })
|
||||||
|
rate: number;
|
||||||
|
|
||||||
|
@Column({ type: 'date', name: 'rate_date', nullable: false })
|
||||||
|
rateDate: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, default: 'manual' })
|
||||||
|
source: RateSource;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', name: 'created_by', nullable: true })
|
||||||
|
createdBy: string | null;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
43
src/modules/core/entities/currency.entity.ts
Normal file
43
src/modules/core/entities/currency.entity.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'currencies' })
|
||||||
|
@Index('idx_currencies_code', ['code'], { unique: true })
|
||||||
|
@Index('idx_currencies_active', ['active'])
|
||||||
|
export class Currency {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 3, nullable: false, unique: true })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 10, nullable: false })
|
||||||
|
symbol: string;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 2, name: 'decimals' })
|
||||||
|
decimals: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 12,
|
||||||
|
scale: 6,
|
||||||
|
nullable: true,
|
||||||
|
default: 0.01,
|
||||||
|
})
|
||||||
|
rounding: number;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: true })
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
163
src/modules/core/entities/discount-rule.entity.ts
Normal file
163
src/modules/core/entities/discount-rule.entity.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tipo de descuento
|
||||||
|
*/
|
||||||
|
export enum DiscountType {
|
||||||
|
PERCENTAGE = 'percentage', // Porcentaje del total
|
||||||
|
FIXED = 'fixed', // Monto fijo
|
||||||
|
PRICE_OVERRIDE = 'price_override', // Precio especial
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aplicacion del descuento
|
||||||
|
*/
|
||||||
|
export enum DiscountAppliesTo {
|
||||||
|
ALL = 'all', // Todos los productos
|
||||||
|
CATEGORY = 'category', // Categoria especifica
|
||||||
|
PRODUCT = 'product', // Producto especifico
|
||||||
|
CUSTOMER = 'customer', // Cliente especifico
|
||||||
|
CUSTOMER_GROUP = 'customer_group', // Grupo de clientes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Condicion de activacion
|
||||||
|
*/
|
||||||
|
export enum DiscountCondition {
|
||||||
|
NONE = 'none', // Sin condicion
|
||||||
|
MIN_QUANTITY = 'min_quantity', // Cantidad minima
|
||||||
|
MIN_AMOUNT = 'min_amount', // Monto minimo
|
||||||
|
DATE_RANGE = 'date_range', // Rango de fechas
|
||||||
|
FIRST_PURCHASE = 'first_purchase', // Primera compra
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regla de descuento
|
||||||
|
*/
|
||||||
|
@Entity({ schema: 'core', name: 'discount_rules' })
|
||||||
|
@Index('idx_discount_rules_tenant_id', ['tenantId'])
|
||||||
|
@Index('idx_discount_rules_code_tenant', ['tenantId', 'code'], { unique: true })
|
||||||
|
@Index('idx_discount_rules_active', ['tenantId', 'isActive'])
|
||||||
|
@Index('idx_discount_rules_dates', ['tenantId', 'startDate', 'endDate'])
|
||||||
|
@Index('idx_discount_rules_priority', ['tenantId', 'priority'])
|
||||||
|
export class DiscountRule {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'company_id' })
|
||||||
|
companyId: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: false })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: DiscountType,
|
||||||
|
default: DiscountType.PERCENTAGE,
|
||||||
|
name: 'discount_type',
|
||||||
|
})
|
||||||
|
discountType: DiscountType;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 15,
|
||||||
|
scale: 4,
|
||||||
|
nullable: false,
|
||||||
|
name: 'discount_value',
|
||||||
|
})
|
||||||
|
discountValue: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 15,
|
||||||
|
scale: 2,
|
||||||
|
nullable: true,
|
||||||
|
name: 'max_discount_amount',
|
||||||
|
})
|
||||||
|
maxDiscountAmount: number | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: DiscountAppliesTo,
|
||||||
|
default: DiscountAppliesTo.ALL,
|
||||||
|
name: 'applies_to',
|
||||||
|
})
|
||||||
|
appliesTo: DiscountAppliesTo;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'applies_to_id' })
|
||||||
|
appliesToId: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: DiscountCondition,
|
||||||
|
default: DiscountCondition.NONE,
|
||||||
|
name: 'condition_type',
|
||||||
|
})
|
||||||
|
conditionType: DiscountCondition;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 15,
|
||||||
|
scale: 4,
|
||||||
|
nullable: true,
|
||||||
|
name: 'condition_value',
|
||||||
|
})
|
||||||
|
conditionValue: number | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', nullable: true, name: 'start_date' })
|
||||||
|
startDate: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', nullable: true, name: 'end_date' })
|
||||||
|
endDate: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 10 })
|
||||||
|
priority: number;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: true, name: 'combinable' })
|
||||||
|
combinable: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: true, name: 'usage_limit' })
|
||||||
|
usageLimit: number | null;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 0, name: 'usage_count' })
|
||||||
|
usageCount: number;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: true, name: 'is_active' })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'created_by' })
|
||||||
|
createdBy: string | null;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', nullable: true })
|
||||||
|
updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'updated_by' })
|
||||||
|
updatedBy: string | null;
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'deleted_by' })
|
||||||
|
deletedBy: string | null;
|
||||||
|
}
|
||||||
19
src/modules/core/entities/index.ts
Normal file
19
src/modules/core/entities/index.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Core Entities Index
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Existing entities
|
||||||
|
export { Tenant } from './tenant.entity';
|
||||||
|
export { User } from './user.entity';
|
||||||
|
|
||||||
|
// Catalog entities (propagated from erp-core)
|
||||||
|
export { Country } from './country.entity';
|
||||||
|
export { Currency } from './currency.entity';
|
||||||
|
export { CurrencyRate, RateSource } from './currency-rate.entity';
|
||||||
|
export { DiscountRule, DiscountType, DiscountAppliesTo, DiscountCondition } from './discount-rule.entity';
|
||||||
|
export { PaymentTerm, PaymentTermLine, PaymentTermLineType } from './payment-term.entity';
|
||||||
|
export { ProductCategory } from './product-category.entity';
|
||||||
|
export { Sequence, ResetPeriod } from './sequence.entity';
|
||||||
|
export { State } from './state.entity';
|
||||||
|
export { Uom, UomType } from './uom.entity';
|
||||||
|
export { UomCategory } from './uom-category.entity';
|
||||||
144
src/modules/core/entities/payment-term.entity.ts
Normal file
144
src/modules/core/entities/payment-term.entity.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
Index,
|
||||||
|
OneToMany,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tipo de calculo para la linea del termino de pago
|
||||||
|
*/
|
||||||
|
export enum PaymentTermLineType {
|
||||||
|
BALANCE = 'balance', // Saldo restante
|
||||||
|
PERCENT = 'percent', // Porcentaje del total
|
||||||
|
FIXED = 'fixed', // Monto fijo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linea de termino de pago (para terminos con multiples vencimientos)
|
||||||
|
*/
|
||||||
|
@Entity({ schema: 'core', name: 'payment_term_lines' })
|
||||||
|
@Index('idx_payment_term_lines_term', ['paymentTermId'])
|
||||||
|
export class PaymentTermLine {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'payment_term_id' })
|
||||||
|
paymentTermId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 1 })
|
||||||
|
sequence: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: PaymentTermLineType,
|
||||||
|
default: PaymentTermLineType.BALANCE,
|
||||||
|
name: 'line_type',
|
||||||
|
})
|
||||||
|
lineType: PaymentTermLineType;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 5,
|
||||||
|
scale: 2,
|
||||||
|
nullable: true,
|
||||||
|
name: 'value_percent',
|
||||||
|
})
|
||||||
|
valuePercent: number | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 15,
|
||||||
|
scale: 2,
|
||||||
|
nullable: true,
|
||||||
|
name: 'value_amount',
|
||||||
|
})
|
||||||
|
valueAmount: number | null;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 0 })
|
||||||
|
days: number;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: true, name: 'day_of_month' })
|
||||||
|
dayOfMonth: number | null;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: false, name: 'end_of_month' })
|
||||||
|
endOfMonth: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Termino de pago (Net 30, 50% advance + 50% on delivery, etc.)
|
||||||
|
*/
|
||||||
|
@Entity({ schema: 'core', name: 'payment_terms' })
|
||||||
|
@Index('idx_payment_terms_tenant_id', ['tenantId'])
|
||||||
|
@Index('idx_payment_terms_code_tenant', ['tenantId', 'code'], { unique: true })
|
||||||
|
@Index('idx_payment_terms_active', ['tenantId', 'isActive'])
|
||||||
|
export class PaymentTerm {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'company_id' })
|
||||||
|
companyId: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: false })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 0, name: 'due_days' })
|
||||||
|
dueDays: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 5,
|
||||||
|
scale: 2,
|
||||||
|
nullable: true,
|
||||||
|
default: 0,
|
||||||
|
name: 'discount_percent',
|
||||||
|
})
|
||||||
|
discountPercent: number | null;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: true, default: 0, name: 'discount_days' })
|
||||||
|
discountDays: number | null;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: false, name: 'is_immediate' })
|
||||||
|
isImmediate: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: true, name: 'is_active' })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 0 })
|
||||||
|
sequence: number;
|
||||||
|
|
||||||
|
@OneToMany(() => PaymentTermLine, (line) => line.paymentTermId, { eager: true })
|
||||||
|
lines: PaymentTermLine[];
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'created_by' })
|
||||||
|
createdBy: string | null;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', nullable: true })
|
||||||
|
updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'updated_by' })
|
||||||
|
updatedBy: string | null;
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'deleted_by' })
|
||||||
|
deletedBy: string | null;
|
||||||
|
}
|
||||||
79
src/modules/core/entities/product-category.entity.ts
Normal file
79
src/modules/core/entities/product-category.entity.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
OneToMany,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'product_categories' })
|
||||||
|
@Index('idx_product_categories_tenant_id', ['tenantId'])
|
||||||
|
@Index('idx_product_categories_parent_id', ['parentId'])
|
||||||
|
@Index('idx_product_categories_code_tenant', ['tenantId', 'code'], {
|
||||||
|
unique: true,
|
||||||
|
})
|
||||||
|
@Index('idx_product_categories_active', ['tenantId', 'active'], {
|
||||||
|
where: 'deleted_at IS NULL',
|
||||||
|
})
|
||||||
|
export class ProductCategory {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||||
|
code: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'parent_id' })
|
||||||
|
parentId: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true, name: 'full_path' })
|
||||||
|
fullPath: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
notes: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: true })
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
@ManyToOne(() => ProductCategory, (category) => category.children, {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'parent_id' })
|
||||||
|
parent: ProductCategory | null;
|
||||||
|
|
||||||
|
@OneToMany(() => ProductCategory, (category) => category.parent)
|
||||||
|
children: ProductCategory[];
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'created_by' })
|
||||||
|
createdBy: string | null;
|
||||||
|
|
||||||
|
@UpdateDateColumn({
|
||||||
|
name: 'updated_at',
|
||||||
|
type: 'timestamp',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'updated_by' })
|
||||||
|
updatedBy: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', nullable: true, name: 'deleted_at' })
|
||||||
|
deletedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'deleted_by' })
|
||||||
|
deletedBy: string | null;
|
||||||
|
}
|
||||||
83
src/modules/core/entities/sequence.entity.ts
Normal file
83
src/modules/core/entities/sequence.entity.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export enum ResetPeriod {
|
||||||
|
NONE = 'none',
|
||||||
|
YEAR = 'year',
|
||||||
|
MONTH = 'month',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'sequences' })
|
||||||
|
@Index('idx_sequences_tenant_id', ['tenantId'])
|
||||||
|
@Index('idx_sequences_code_tenant', ['tenantId', 'code'], { unique: true })
|
||||||
|
@Index('idx_sequences_active', ['tenantId', 'isActive'])
|
||||||
|
export class Sequence {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'company_id' })
|
||||||
|
companyId: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: false })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||||
|
prefix: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||||
|
suffix: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 1, name: 'next_number' })
|
||||||
|
nextNumber: number;
|
||||||
|
|
||||||
|
@Column({ type: 'integer', nullable: false, default: 4 })
|
||||||
|
padding: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: ResetPeriod,
|
||||||
|
nullable: true,
|
||||||
|
default: ResetPeriod.NONE,
|
||||||
|
name: 'reset_period',
|
||||||
|
})
|
||||||
|
resetPeriod: ResetPeriod | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'timestamp',
|
||||||
|
nullable: true,
|
||||||
|
name: 'last_reset_date',
|
||||||
|
})
|
||||||
|
lastResetDate: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: true, name: 'is_active' })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'created_by' })
|
||||||
|
createdBy: string | null;
|
||||||
|
|
||||||
|
@UpdateDateColumn({
|
||||||
|
name: 'updated_at',
|
||||||
|
type: 'timestamp',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'updated_by' })
|
||||||
|
updatedBy: string | null;
|
||||||
|
}
|
||||||
45
src/modules/core/entities/state.entity.ts
Normal file
45
src/modules/core/entities/state.entity.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Country } from './country.entity';
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'states' })
|
||||||
|
@Index('idx_states_country', ['countryId'])
|
||||||
|
@Index('idx_states_code', ['code'])
|
||||||
|
@Index('idx_states_country_code', ['countryId', 'code'], { unique: true })
|
||||||
|
export class State {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', name: 'country_id', nullable: false })
|
||||||
|
countryId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Country, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'country_id' })
|
||||||
|
country: Country;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 10, nullable: false })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||||
|
timezone: string | null;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', name: 'is_active', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
50
src/modules/core/entities/tenant.entity.ts
Normal file
50
src/modules/core/entities/tenant.entity.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Tenant Entity
|
||||||
|
* Entidad para multi-tenancy
|
||||||
|
*
|
||||||
|
* @module Core
|
||||||
|
* @table core.tenants
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
OneToMany,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
|
||||||
|
@Entity({ schema: 'auth', name: 'tenants' })
|
||||||
|
export class Tenant {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, unique: true })
|
||||||
|
@Index()
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 200 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
settings: Record<string, unknown>;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date;
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
@OneToMany(() => User, (user) => user.tenant)
|
||||||
|
users: User[];
|
||||||
|
}
|
||||||
45
src/modules/core/entities/uom-category.entity.ts
Normal file
45
src/modules/core/entities/uom-category.entity.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
OneToMany,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Uom } from './uom.entity';
|
||||||
|
import { Tenant } from './tenant.entity';
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'uom_categories' })
|
||||||
|
@Index('idx_uom_categories_tenant', ['tenantId'])
|
||||||
|
@Index('idx_uom_categories_tenant_name', ['tenantId', 'name'], { unique: true })
|
||||||
|
export class UomCategory {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string | null;
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
@ManyToOne(() => Tenant)
|
||||||
|
@JoinColumn({ name: 'tenant_id' })
|
||||||
|
tenant: Tenant;
|
||||||
|
|
||||||
|
@OneToMany(() => Uom, (uom) => uom.category)
|
||||||
|
uoms: Uom[];
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
89
src/modules/core/entities/uom.entity.ts
Normal file
89
src/modules/core/entities/uom.entity.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { UomCategory } from './uom-category.entity';
|
||||||
|
import { Tenant } from './tenant.entity';
|
||||||
|
|
||||||
|
export enum UomType {
|
||||||
|
REFERENCE = 'reference',
|
||||||
|
BIGGER = 'bigger',
|
||||||
|
SMALLER = 'smaller',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity({ schema: 'core', name: 'uom' })
|
||||||
|
@Index('idx_uom_tenant', ['tenantId'])
|
||||||
|
@Index('idx_uom_category_id', ['categoryId'])
|
||||||
|
@Index('idx_uom_code', ['code'])
|
||||||
|
@Index('idx_uom_active', ['active'])
|
||||||
|
@Index('idx_uom_tenant_category_name', ['tenantId', 'categoryId', 'name'], { unique: true })
|
||||||
|
export class Uom {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: false, name: 'category_id' })
|
||||||
|
categoryId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, nullable: true })
|
||||||
|
code: string | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: UomType,
|
||||||
|
nullable: false,
|
||||||
|
default: UomType.REFERENCE,
|
||||||
|
name: 'uom_type',
|
||||||
|
})
|
||||||
|
uomType: UomType;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 12,
|
||||||
|
scale: 6,
|
||||||
|
nullable: false,
|
||||||
|
default: 1.0,
|
||||||
|
})
|
||||||
|
factor: number;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'decimal',
|
||||||
|
precision: 12,
|
||||||
|
scale: 6,
|
||||||
|
nullable: true,
|
||||||
|
default: 0.01,
|
||||||
|
})
|
||||||
|
rounding: number;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', nullable: false, default: true })
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
@ManyToOne(() => Tenant)
|
||||||
|
@JoinColumn({ name: 'tenant_id' })
|
||||||
|
tenant: Tenant;
|
||||||
|
|
||||||
|
@ManyToOne(() => UomCategory, (category) => category.uoms, {
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'category_id' })
|
||||||
|
category: UomCategory;
|
||||||
|
|
||||||
|
// Audit fields
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
78
src/modules/core/entities/user.entity.ts
Normal file
78
src/modules/core/entities/user.entity.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* User Entity
|
||||||
|
* Entidad de usuarios del sistema
|
||||||
|
*
|
||||||
|
* @module Core
|
||||||
|
* @table core.users
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Tenant } from './tenant.entity';
|
||||||
|
|
||||||
|
@Entity({ schema: 'auth', name: 'users' })
|
||||||
|
@Index(['tenantId', 'email'], { unique: true })
|
||||||
|
export class User {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid', nullable: true })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 255 })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@Column({ name: 'password_hash', type: 'varchar', length: 255, select: false })
|
||||||
|
passwordHash: string;
|
||||||
|
|
||||||
|
@Column({ name: 'first_name', type: 'varchar', length: 100, nullable: true })
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@Column({ name: 'last_name', type: 'varchar', length: 100, nullable: true })
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', array: true, default: ['viewer'] })
|
||||||
|
roles: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'last_login_at', type: 'timestamptz', nullable: true })
|
||||||
|
lastLoginAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'default_tenant_id', type: 'uuid', nullable: true })
|
||||||
|
defaultTenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date;
|
||||||
|
|
||||||
|
// Placeholder para relación de roles (se implementará en ST-004)
|
||||||
|
userRoles?: { role: { code: string } }[];
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
@ManyToOne(() => Tenant, (tenant) => tenant.users)
|
||||||
|
@JoinColumn({ name: 'tenant_id' })
|
||||||
|
tenant: Tenant;
|
||||||
|
|
||||||
|
// Computed property
|
||||||
|
get fullName(): string {
|
||||||
|
return [this.firstName, this.lastName].filter(Boolean).join(' ') || this.email;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/modules/invoices/entities/index.ts
Normal file
8
src/modules/invoices/entities/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Invoices Entities - Export
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { Invoice, InvoiceType, InvoiceStatus, InvoiceContext } from './invoice.entity';
|
||||||
|
export { InvoiceItem } from './invoice-item.entity';
|
||||||
|
export { Payment } from './payment.entity';
|
||||||
|
export { PaymentAllocation } from './payment-allocation.entity';
|
||||||
95
src/modules/invoices/entities/invoice-item.entity.ts
Normal file
95
src/modules/invoices/entities/invoice-item.entity.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* InvoiceItem Entity
|
||||||
|
* Line items for unified invoices
|
||||||
|
* Compatible with erp-core invoice-item.entity
|
||||||
|
*
|
||||||
|
* @module Invoices
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Invoice } from './invoice.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'invoice_items', schema: 'billing' })
|
||||||
|
export class InvoiceItem {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'invoice_id', type: 'uuid' })
|
||||||
|
invoiceId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Invoice, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'invoice_id' })
|
||||||
|
invoice: Invoice;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'product_id', type: 'uuid', nullable: true })
|
||||||
|
productId?: string;
|
||||||
|
|
||||||
|
@Column({ name: 'line_number', type: 'int', default: 1 })
|
||||||
|
lineNumber: number;
|
||||||
|
|
||||||
|
@Column({ name: 'product_sku', type: 'varchar', length: 50, nullable: true })
|
||||||
|
productSku?: string;
|
||||||
|
|
||||||
|
@Column({ name: 'product_name', type: 'varchar', length: 200 })
|
||||||
|
productName: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
// SAT (Mexico)
|
||||||
|
@Column({ name: 'sat_product_code', type: 'varchar', length: 20, nullable: true })
|
||||||
|
satProductCode?: string;
|
||||||
|
|
||||||
|
@Column({ name: 'sat_unit_code', type: 'varchar', length: 10, nullable: true })
|
||||||
|
satUnitCode?: string;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 15, scale: 4, default: 1 })
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'PZA' })
|
||||||
|
uom: string;
|
||||||
|
|
||||||
|
@Column({ name: 'unit_price', type: 'decimal', precision: 15, scale: 4, default: 0 })
|
||||||
|
unitPrice: number;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_percent', type: 'decimal', precision: 5, scale: 2, default: 0 })
|
||||||
|
discountPercent: number;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_amount', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
discountAmount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'tax_rate', type: 'decimal', precision: 5, scale: 2, default: 16.00 })
|
||||||
|
taxRate: number;
|
||||||
|
|
||||||
|
@Column({ name: 'tax_amount', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
taxAmount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'withholding_rate', type: 'decimal', precision: 5, scale: 2, default: 0 })
|
||||||
|
withholdingRate: number;
|
||||||
|
|
||||||
|
@Column({ name: 'withholding_amount', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
withholdingAmount: number;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
subtotal: number;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
187
src/modules/invoices/entities/invoice.entity.ts
Normal file
187
src/modules/invoices/entities/invoice.entity.ts
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* Unified Invoice Entity
|
||||||
|
* Combines commercial and SaaS billing invoices
|
||||||
|
* Compatible with erp-core invoice.entity
|
||||||
|
*
|
||||||
|
* @module Invoices
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
Index,
|
||||||
|
OneToMany,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { InvoiceItem } from './invoice-item.entity';
|
||||||
|
|
||||||
|
export type InvoiceType = 'sale' | 'purchase' | 'credit_note' | 'debit_note';
|
||||||
|
export type InvoiceStatus = 'draft' | 'validated' | 'sent' | 'partial' | 'paid' | 'overdue' | 'void' | 'refunded' | 'cancelled' | 'voided';
|
||||||
|
export type InvoiceContext = 'commercial' | 'saas';
|
||||||
|
|
||||||
|
@Entity({ name: 'invoices', schema: 'billing' })
|
||||||
|
export class Invoice {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index({ unique: true })
|
||||||
|
@Column({ name: 'invoice_number', type: 'varchar', length: 30 })
|
||||||
|
invoiceNumber: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'invoice_type', type: 'varchar', length: 20, default: 'sale' })
|
||||||
|
invoiceType: InvoiceType;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'invoice_context', type: 'varchar', length: 20, default: 'commercial' })
|
||||||
|
invoiceContext: InvoiceContext;
|
||||||
|
|
||||||
|
// Commercial fields
|
||||||
|
@Column({ name: 'sales_order_id', type: 'uuid', nullable: true })
|
||||||
|
salesOrderId: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'purchase_order_id', type: 'uuid', nullable: true })
|
||||||
|
purchaseOrderId: string | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'partner_id', type: 'uuid', nullable: true })
|
||||||
|
partnerId: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'partner_name', type: 'varchar', length: 200, nullable: true })
|
||||||
|
partnerName: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'partner_tax_id', type: 'varchar', length: 50, nullable: true })
|
||||||
|
partnerTaxId: string | null;
|
||||||
|
|
||||||
|
// SaaS billing fields
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'subscription_id', type: 'uuid', nullable: true })
|
||||||
|
subscriptionId: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'period_start', type: 'date', nullable: true })
|
||||||
|
periodStart: Date | null;
|
||||||
|
|
||||||
|
@Column({ name: 'period_end', type: 'date', nullable: true })
|
||||||
|
periodEnd: Date | null;
|
||||||
|
|
||||||
|
// Billing information
|
||||||
|
@Column({ name: 'billing_name', type: 'varchar', length: 200, nullable: true })
|
||||||
|
billingName: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'billing_email', type: 'varchar', length: 255, nullable: true })
|
||||||
|
billingEmail: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'billing_address', type: 'jsonb', nullable: true })
|
||||||
|
billingAddress: Record<string, any> | null;
|
||||||
|
|
||||||
|
@Column({ name: 'tax_id', type: 'varchar', length: 50, nullable: true })
|
||||||
|
taxId: string | null;
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'invoice_date', type: 'date', default: () => 'CURRENT_DATE' })
|
||||||
|
invoiceDate: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'due_date', type: 'date', nullable: true })
|
||||||
|
dueDate: Date | null;
|
||||||
|
|
||||||
|
@Column({ name: 'payment_date', type: 'date', nullable: true })
|
||||||
|
paymentDate: Date | null;
|
||||||
|
|
||||||
|
@Column({ name: 'paid_at', type: 'timestamptz', nullable: true })
|
||||||
|
paidAt: Date | null;
|
||||||
|
|
||||||
|
// Amounts
|
||||||
|
@Column({ type: 'varchar', length: 3, default: 'MXN' })
|
||||||
|
currency: string;
|
||||||
|
|
||||||
|
@Column({ name: 'exchange_rate', type: 'decimal', precision: 10, scale: 6, default: 1 })
|
||||||
|
exchangeRate: number;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
subtotal: number;
|
||||||
|
|
||||||
|
@Column({ name: 'tax_amount', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
taxAmount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'withholding_tax', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
withholdingTax: number;
|
||||||
|
|
||||||
|
@Column({ name: 'discount_amount', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
discountAmount: number;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
@Column({ name: 'amount_paid', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
amountPaid: number;
|
||||||
|
|
||||||
|
@Column({ name: 'amount_due', type: 'decimal', precision: 15, scale: 2, insert: false, update: false, nullable: true })
|
||||||
|
amountDue: number | null;
|
||||||
|
|
||||||
|
@Column({ name: 'paid_amount', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||||
|
paidAmount: number;
|
||||||
|
|
||||||
|
// Payment details
|
||||||
|
@Column({ name: 'payment_term_days', type: 'int', default: 0 })
|
||||||
|
paymentTermDays: number;
|
||||||
|
|
||||||
|
@Column({ name: 'payment_method', type: 'varchar', length: 50, nullable: true })
|
||||||
|
paymentMethod: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'payment_reference', type: 'varchar', length: 100, nullable: true })
|
||||||
|
paymentReference: string | null;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'draft' })
|
||||||
|
status: InvoiceStatus;
|
||||||
|
|
||||||
|
// CFDI (Mexico)
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'cfdi_uuid', type: 'varchar', length: 40, nullable: true })
|
||||||
|
cfdiUuid: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'cfdi_status', type: 'varchar', length: 20, nullable: true })
|
||||||
|
cfdiStatus: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'cfdi_xml', type: 'text', nullable: true })
|
||||||
|
cfdiXml: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'cfdi_pdf_url', type: 'varchar', length: 500, nullable: true })
|
||||||
|
cfdiPdfUrl: string | null;
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
notes: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'internal_notes', type: 'text', nullable: true })
|
||||||
|
internalNotes: string | null;
|
||||||
|
|
||||||
|
// Audit
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||||
|
createdBy: string | null;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'updated_by', type: 'uuid', nullable: true })
|
||||||
|
updatedBy: string | null;
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date | null;
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
@OneToMany(() => InvoiceItem, (item) => item.invoice, { cascade: true })
|
||||||
|
items: InvoiceItem[];
|
||||||
|
}
|
||||||
53
src/modules/invoices/entities/payment-allocation.entity.ts
Normal file
53
src/modules/invoices/entities/payment-allocation.entity.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* PaymentAllocation Entity
|
||||||
|
* Links payments to invoices
|
||||||
|
* Compatible with erp-core payment-allocation.entity
|
||||||
|
*
|
||||||
|
* @module Invoices
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Payment } from './payment.entity';
|
||||||
|
import { Invoice } from './invoice.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'payment_allocations', schema: 'billing' })
|
||||||
|
export class PaymentAllocation {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'payment_id', type: 'uuid' })
|
||||||
|
paymentId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Payment, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'payment_id' })
|
||||||
|
payment: Payment;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'invoice_id', type: 'uuid' })
|
||||||
|
invoiceId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Invoice, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'invoice_id' })
|
||||||
|
invoice: Invoice;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 15, scale: 2 })
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'allocation_date', type: 'date', default: () => 'CURRENT_DATE' })
|
||||||
|
allocationDate: Date;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||||
|
createdBy?: string;
|
||||||
|
}
|
||||||
89
src/modules/invoices/entities/payment.entity.ts
Normal file
89
src/modules/invoices/entities/payment.entity.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* Payment Entity
|
||||||
|
* Payment received or made
|
||||||
|
* Compatible with erp-core payment.entity
|
||||||
|
*
|
||||||
|
* @module Invoices
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@Entity({ name: 'payments', schema: 'billing' })
|
||||||
|
export class Payment {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'payment_number', type: 'varchar', length: 30 })
|
||||||
|
paymentNumber: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'payment_type', type: 'varchar', length: 20, default: 'received' })
|
||||||
|
paymentType: 'received' | 'made';
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'partner_id', type: 'uuid' })
|
||||||
|
partnerId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'partner_name', type: 'varchar', length: 200, nullable: true })
|
||||||
|
partnerName: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 3, default: 'MXN' })
|
||||||
|
currency: string;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 15, scale: 2 })
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'exchange_rate', type: 'decimal', precision: 10, scale: 6, default: 1 })
|
||||||
|
exchangeRate: number;
|
||||||
|
|
||||||
|
@Column({ name: 'payment_date', type: 'date', default: () => 'CURRENT_DATE' })
|
||||||
|
paymentDate: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'payment_method', type: 'varchar', length: 50 })
|
||||||
|
paymentMethod: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||||
|
reference: string;
|
||||||
|
|
||||||
|
@Column({ name: 'bank_account_id', type: 'uuid', nullable: true })
|
||||||
|
bankAccountId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'draft' })
|
||||||
|
status: 'draft' | 'confirmed' | 'reconciled' | 'cancelled';
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
notes: string;
|
||||||
|
|
||||||
|
@Column({ name: 'cfdi_uuid', type: 'varchar', length: 40, nullable: true })
|
||||||
|
cfdiUuid: string;
|
||||||
|
|
||||||
|
@Column({ name: 'cfdi_status', type: 'varchar', length: 20, nullable: true })
|
||||||
|
cfdiStatus: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||||
|
createdBy?: string;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', type: 'timestamptz', nullable: true })
|
||||||
|
deletedAt: Date;
|
||||||
|
}
|
||||||
65
src/modules/notifications/entities/channel.entity.ts
Normal file
65
src/modules/notifications/entities/channel.entity.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Channel Entity
|
||||||
|
* Notification delivery channel configuration
|
||||||
|
* Compatible with erp-core channel.entity
|
||||||
|
*
|
||||||
|
* @module Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type ChannelType = 'email' | 'sms' | 'push' | 'whatsapp' | 'in_app' | 'webhook';
|
||||||
|
|
||||||
|
@Entity({ name: 'channels', schema: 'notifications' })
|
||||||
|
export class Channel {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 30, unique: true })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 100 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column({ name: 'channel_type', type: 'varchar', length: 30 })
|
||||||
|
channelType: ChannelType;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||||
|
provider: string;
|
||||||
|
|
||||||
|
@Column({ name: 'provider_config', type: 'jsonb', default: {} })
|
||||||
|
providerConfig: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'rate_limit_per_minute', type: 'int', nullable: true })
|
||||||
|
rateLimitPerMinute: number;
|
||||||
|
|
||||||
|
@Column({ name: 'rate_limit_per_hour', type: 'int', nullable: true })
|
||||||
|
rateLimitPerHour: number;
|
||||||
|
|
||||||
|
@Column({ name: 'rate_limit_per_day', type: 'int', nullable: true })
|
||||||
|
rateLimitPerDay: number;
|
||||||
|
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_default', type: 'boolean', default: false })
|
||||||
|
isDefault: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* InAppNotification Entity
|
||||||
|
* In-app notification with read/archive tracking
|
||||||
|
* Compatible with erp-core in-app-notification.entity
|
||||||
|
*
|
||||||
|
* @module Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Index,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type InAppCategory = 'info' | 'success' | 'warning' | 'error' | 'task';
|
||||||
|
export type InAppPriority = 'low' | 'normal' | 'high' | 'urgent';
|
||||||
|
export type InAppActionType = 'link' | 'modal' | 'function';
|
||||||
|
|
||||||
|
@Entity({ name: 'in_app_notifications', schema: 'notifications' })
|
||||||
|
export class InAppNotification {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'user_id', type: 'uuid' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 200 })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text' })
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||||
|
icon: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, nullable: true })
|
||||||
|
color: string;
|
||||||
|
|
||||||
|
@Column({ name: 'action_type', type: 'varchar', length: 20, nullable: true })
|
||||||
|
actionType: InAppActionType;
|
||||||
|
|
||||||
|
@Column({ name: 'action_url', type: 'text', nullable: true })
|
||||||
|
actionUrl: string;
|
||||||
|
|
||||||
|
@Column({ name: 'action_data', type: 'jsonb', nullable: true })
|
||||||
|
actionData: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 30, default: 'info' })
|
||||||
|
category: InAppCategory;
|
||||||
|
|
||||||
|
@Column({ name: 'context_type', type: 'varchar', length: 100, nullable: true })
|
||||||
|
contextType: string;
|
||||||
|
|
||||||
|
@Column({ name: 'context_id', type: 'uuid', nullable: true })
|
||||||
|
contextId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'is_read', type: 'boolean', default: false })
|
||||||
|
isRead: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'read_at', type: 'timestamptz', nullable: true })
|
||||||
|
readAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'is_archived', type: 'boolean', default: false })
|
||||||
|
isArchived: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'archived_at', type: 'timestamptz', nullable: true })
|
||||||
|
archivedAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'normal' })
|
||||||
|
priority: InAppPriority;
|
||||||
|
|
||||||
|
@Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
|
||||||
|
expiresAt: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
10
src/modules/notifications/entities/index.ts
Normal file
10
src/modules/notifications/entities/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Notifications Entities - Export
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { Channel, ChannelType } from './channel.entity';
|
||||||
|
export { NotificationTemplate, TemplateTranslation, TemplateCategory } from './template.entity';
|
||||||
|
export { NotificationPreference, DigestFrequency } from './preference.entity';
|
||||||
|
export { Notification, NotificationStatus, NotificationPriority } from './notification.entity';
|
||||||
|
export { NotificationBatch, BatchStatus, AudienceType } from './notification-batch.entity';
|
||||||
|
export { InAppNotification, InAppCategory, InAppPriority, InAppActionType } from './in-app-notification.entity';
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* NotificationBatch Entity
|
||||||
|
* Batch notification campaigns with audience targeting
|
||||||
|
* Compatible with erp-core notification-batch.entity
|
||||||
|
*
|
||||||
|
* @module Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { NotificationTemplate } from './template.entity';
|
||||||
|
import { ChannelType } from './channel.entity';
|
||||||
|
|
||||||
|
export type BatchStatus = 'draft' | 'scheduled' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
||||||
|
export type AudienceType = 'all_users' | 'segment' | 'custom';
|
||||||
|
|
||||||
|
@Entity({ name: 'notification_batches', schema: 'notifications' })
|
||||||
|
export class NotificationBatch {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 200 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column({ name: 'template_id', type: 'uuid', nullable: true })
|
||||||
|
templateId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'channel_type', type: 'varchar', length: 30 })
|
||||||
|
channelType: ChannelType;
|
||||||
|
|
||||||
|
@Column({ name: 'audience_type', type: 'varchar', length: 30 })
|
||||||
|
audienceType: AudienceType;
|
||||||
|
|
||||||
|
@Column({ name: 'audience_filter', type: 'jsonb', default: {} })
|
||||||
|
audienceFilter: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
variables: Record<string, any>;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'scheduled_at', type: 'timestamptz', nullable: true })
|
||||||
|
scheduledAt: Date;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'draft' })
|
||||||
|
status: BatchStatus;
|
||||||
|
|
||||||
|
@Column({ name: 'total_recipients', type: 'int', default: 0 })
|
||||||
|
totalRecipients: number;
|
||||||
|
|
||||||
|
@Column({ name: 'sent_count', type: 'int', default: 0 })
|
||||||
|
sentCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'delivered_count', type: 'int', default: 0 })
|
||||||
|
deliveredCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'failed_count', type: 'int', default: 0 })
|
||||||
|
failedCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'read_count', type: 'int', default: 0 })
|
||||||
|
readCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'started_at', type: 'timestamptz', nullable: true })
|
||||||
|
startedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'completed_at', type: 'timestamptz', nullable: true })
|
||||||
|
completedAt: Date;
|
||||||
|
|
||||||
|
@ManyToOne(() => NotificationTemplate, { onDelete: 'SET NULL', nullable: true })
|
||||||
|
@JoinColumn({ name: 'template_id' })
|
||||||
|
template: NotificationTemplate | null;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||||
|
createdBy: string | null;
|
||||||
|
}
|
||||||
138
src/modules/notifications/entities/notification.entity.ts
Normal file
138
src/modules/notifications/entities/notification.entity.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Notification Entity
|
||||||
|
* Individual notification with delivery tracking
|
||||||
|
* Compatible with erp-core notification.entity
|
||||||
|
*
|
||||||
|
* @module Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { NotificationTemplate } from './template.entity';
|
||||||
|
import { Channel, ChannelType } from './channel.entity';
|
||||||
|
|
||||||
|
export type NotificationStatus = 'pending' | 'queued' | 'sending' | 'sent' | 'delivered' | 'read' | 'failed' | 'cancelled';
|
||||||
|
export type NotificationPriority = 'low' | 'normal' | 'high' | 'urgent';
|
||||||
|
|
||||||
|
@Entity({ name: 'notifications', schema: 'notifications' })
|
||||||
|
export class Notification {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'user_id', type: 'uuid', nullable: true })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'recipient_email', type: 'varchar', length: 255, nullable: true })
|
||||||
|
recipientEmail: string;
|
||||||
|
|
||||||
|
@Column({ name: 'recipient_phone', type: 'varchar', length: 20, nullable: true })
|
||||||
|
recipientPhone: string;
|
||||||
|
|
||||||
|
@Column({ name: 'recipient_device_id', type: 'varchar', length: 255, nullable: true })
|
||||||
|
recipientDeviceId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'template_id', type: 'uuid', nullable: true })
|
||||||
|
templateId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'template_code', type: 'varchar', length: 100, nullable: true })
|
||||||
|
templateCode: string;
|
||||||
|
|
||||||
|
@Column({ name: 'channel_type', type: 'varchar', length: 30 })
|
||||||
|
channelType: ChannelType;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'channel_id', type: 'uuid', nullable: true })
|
||||||
|
channelId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 500, nullable: true })
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
body: string;
|
||||||
|
|
||||||
|
@Column({ name: 'body_html', type: 'text', nullable: true })
|
||||||
|
bodyHtml: string;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
variables: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'context_type', type: 'varchar', length: 100, nullable: true })
|
||||||
|
contextType: string;
|
||||||
|
|
||||||
|
@Column({ name: 'context_id', type: 'uuid', nullable: true })
|
||||||
|
contextId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'normal' })
|
||||||
|
priority: NotificationPriority;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 20, default: 'pending' })
|
||||||
|
status: NotificationStatus;
|
||||||
|
|
||||||
|
@Column({ name: 'queued_at', type: 'timestamptz', nullable: true })
|
||||||
|
queuedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'sent_at', type: 'timestamptz', nullable: true })
|
||||||
|
sentAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'delivered_at', type: 'timestamptz', nullable: true })
|
||||||
|
deliveredAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'read_at', type: 'timestamptz', nullable: true })
|
||||||
|
readAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'failed_at', type: 'timestamptz', nullable: true })
|
||||||
|
failedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'error_message', type: 'text', nullable: true })
|
||||||
|
errorMessage: string;
|
||||||
|
|
||||||
|
@Column({ name: 'retry_count', type: 'int', default: 0 })
|
||||||
|
retryCount: number;
|
||||||
|
|
||||||
|
@Column({ name: 'max_retries', type: 'int', default: 3 })
|
||||||
|
maxRetries: number;
|
||||||
|
|
||||||
|
@Column({ name: 'next_retry_at', type: 'timestamptz', nullable: true })
|
||||||
|
nextRetryAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'provider_message_id', type: 'varchar', length: 255, nullable: true })
|
||||||
|
providerMessageId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'provider_response', type: 'jsonb', nullable: true })
|
||||||
|
providerResponse: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
|
||||||
|
expiresAt: Date;
|
||||||
|
|
||||||
|
@ManyToOne(() => NotificationTemplate, { onDelete: 'SET NULL', nullable: true })
|
||||||
|
@JoinColumn({ name: 'template_id' })
|
||||||
|
template: NotificationTemplate | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Channel, { onDelete: 'SET NULL', nullable: true })
|
||||||
|
@JoinColumn({ name: 'channel_id' })
|
||||||
|
channel: Channel | null;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
82
src/modules/notifications/entities/preference.entity.ts
Normal file
82
src/modules/notifications/entities/preference.entity.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* NotificationPreference Entity
|
||||||
|
* User notification preferences per tenant
|
||||||
|
* Compatible with erp-core preference.entity
|
||||||
|
*
|
||||||
|
* @module Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
Unique,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
|
export type DigestFrequency = 'instant' | 'hourly' | 'daily' | 'weekly';
|
||||||
|
|
||||||
|
@Entity({ name: 'notification_preferences', schema: 'notifications' })
|
||||||
|
@Unique(['userId', 'tenantId'])
|
||||||
|
export class NotificationPreference {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'user_id', type: 'uuid' })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
|
tenantId: string;
|
||||||
|
|
||||||
|
@Column({ name: 'global_enabled', type: 'boolean', default: true })
|
||||||
|
globalEnabled: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'quiet_hours_start', type: 'time', nullable: true })
|
||||||
|
quietHoursStart: string;
|
||||||
|
|
||||||
|
@Column({ name: 'quiet_hours_end', type: 'time', nullable: true })
|
||||||
|
quietHoursEnd: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 50, default: 'America/Mexico_City' })
|
||||||
|
timezone: string;
|
||||||
|
|
||||||
|
@Column({ name: 'email_enabled', type: 'boolean', default: true })
|
||||||
|
emailEnabled: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'sms_enabled', type: 'boolean', default: false })
|
||||||
|
smsEnabled: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'push_enabled', type: 'boolean', default: true })
|
||||||
|
pushEnabled: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'whatsapp_enabled', type: 'boolean', default: false })
|
||||||
|
whatsappEnabled: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'in_app_enabled', type: 'boolean', default: true })
|
||||||
|
inAppEnabled: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'category_preferences', type: 'jsonb', default: {} })
|
||||||
|
categoryPreferences: Record<string, any>;
|
||||||
|
|
||||||
|
@Column({ name: 'digest_frequency', type: 'varchar', length: 20, default: 'instant' })
|
||||||
|
digestFrequency: DigestFrequency;
|
||||||
|
|
||||||
|
@Column({ name: 'digest_day', type: 'int', nullable: true })
|
||||||
|
digestDay: number;
|
||||||
|
|
||||||
|
@Column({ name: 'digest_hour', type: 'int', nullable: true })
|
||||||
|
digestHour: number;
|
||||||
|
|
||||||
|
@Column({ type: 'jsonb', default: {} })
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
126
src/modules/notifications/entities/template.entity.ts
Normal file
126
src/modules/notifications/entities/template.entity.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* NotificationTemplate + TemplateTranslation Entities
|
||||||
|
* Template system with i18n support
|
||||||
|
* Compatible with erp-core template.entity
|
||||||
|
*
|
||||||
|
* @module Notifications
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Index,
|
||||||
|
Unique,
|
||||||
|
OneToMany,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { ChannelType } from './channel.entity';
|
||||||
|
|
||||||
|
export type TemplateCategory = 'system' | 'marketing' | 'transactional' | 'alert';
|
||||||
|
|
||||||
|
@Entity({ name: 'notification_templates', schema: 'notifications' })
|
||||||
|
@Unique(['tenantId', 'code', 'channelType'])
|
||||||
|
export class NotificationTemplate {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'tenant_id', type: 'uuid', nullable: true })
|
||||||
|
tenantId: string | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ type: 'varchar', length: 100 })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 200 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text', nullable: true })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 30, nullable: true })
|
||||||
|
category: TemplateCategory;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'channel_type', type: 'varchar', length: 30 })
|
||||||
|
channelType: ChannelType;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 500, nullable: true })
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
@Column({ name: 'body_template', type: 'text', nullable: true })
|
||||||
|
bodyTemplate: string;
|
||||||
|
|
||||||
|
@Column({ name: 'body_html', type: 'text', nullable: true })
|
||||||
|
bodyHtml: string;
|
||||||
|
|
||||||
|
@Column({ name: 'available_variables', type: 'jsonb', default: [] })
|
||||||
|
availableVariables: string[];
|
||||||
|
|
||||||
|
@Column({ name: 'default_locale', type: 'varchar', length: 10, default: 'es-MX' })
|
||||||
|
defaultLocale: string;
|
||||||
|
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ name: 'is_system', type: 'boolean', default: false })
|
||||||
|
isSystem: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'int', default: 1 })
|
||||||
|
version: number;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||||
|
createdBy: string | null;
|
||||||
|
|
||||||
|
@Column({ name: 'updated_by', type: 'uuid', nullable: true })
|
||||||
|
updatedBy: string | null;
|
||||||
|
|
||||||
|
@OneToMany(() => TemplateTranslation, (t) => t.template)
|
||||||
|
translations: TemplateTranslation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity({ name: 'template_translations', schema: 'notifications' })
|
||||||
|
@Unique(['templateId', 'locale'])
|
||||||
|
export class TemplateTranslation {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column({ name: 'template_id', type: 'uuid' })
|
||||||
|
templateId: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 10 })
|
||||||
|
locale: string;
|
||||||
|
|
||||||
|
@Column({ type: 'varchar', length: 500, nullable: true })
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
@Column({ name: 'body_template', type: 'text', nullable: true })
|
||||||
|
bodyTemplate: string;
|
||||||
|
|
||||||
|
@Column({ name: 'body_html', type: 'text', nullable: true })
|
||||||
|
bodyHtml: string;
|
||||||
|
|
||||||
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@ManyToOne(() => NotificationTemplate, (t) => t.translations, { onDelete: 'CASCADE' })
|
||||||
|
@JoinColumn({ name: 'template_id' })
|
||||||
|
template: NotificationTemplate;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user