[TASK-2026-01-24-GAPS] feat: Add new entities for products/partners + health module
Changes: - Add ProductAttribute, ProductAttributeValue, ProductVariant entities - Add PartnerTaxInfo, PartnerSegment entities - Add Health module (service, controller, module) - Update index.ts and module.ts files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8d201c5b58
commit
7d3ad15968
@ -1,3 +1,4 @@
|
||||
// Core auth entities
|
||||
export { Tenant, TenantStatus } from './tenant.entity.js';
|
||||
export { Company } from './company.entity.js';
|
||||
export { User, UserStatus } from './user.entity.js';
|
||||
@ -18,3 +19,8 @@ export { ProfileTool } from './profile-tool.entity.js';
|
||||
export { ProfileModule } from './profile-module.entity.js';
|
||||
export { UserProfileAssignment } from './user-profile-assignment.entity.js';
|
||||
export { Device } from './device.entity.js';
|
||||
|
||||
// NOTE: The following entities are also available in their specific modules:
|
||||
// - UserProfile, ProfileTool, ProfileModule, UserProfileAssignment, Person -> profiles/entities/
|
||||
// - Device, BiometricCredential, DeviceSession, DeviceActivityLog -> biometrics/entities/
|
||||
// Import directly from those modules if needed.
|
||||
|
||||
78
src/modules/health/health.controller.ts
Normal file
78
src/modules/health/health.controller.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { HealthService } from './health.service';
|
||||
|
||||
export class HealthController {
|
||||
public router: Router;
|
||||
|
||||
constructor(private healthService: HealthService) {
|
||||
this.router = Router();
|
||||
this.initializeRoutes();
|
||||
}
|
||||
|
||||
private initializeRoutes(): void {
|
||||
/**
|
||||
* @swagger
|
||||
* /health:
|
||||
* get:
|
||||
* summary: Get health status
|
||||
* tags: [Health]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Service is healthy
|
||||
* 503:
|
||||
* description: Service is unhealthy
|
||||
*/
|
||||
this.router.get('/', this.getHealth.bind(this));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /health/live:
|
||||
* get:
|
||||
* summary: Liveness probe
|
||||
* tags: [Health]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Service is alive
|
||||
*/
|
||||
this.router.get('/live', this.getLiveness.bind(this));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /health/ready:
|
||||
* get:
|
||||
* summary: Readiness probe
|
||||
* tags: [Health]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Service is ready
|
||||
* 503:
|
||||
* description: Service is not ready
|
||||
*/
|
||||
this.router.get('/ready', this.getReadiness.bind(this));
|
||||
}
|
||||
|
||||
private async getHealth(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const health = await this.healthService.getHealth();
|
||||
const statusCode = health.status === 'healthy' ? 200 : health.status === 'degraded' ? 200 : 503;
|
||||
res.status(statusCode).json(health);
|
||||
} catch (error) {
|
||||
res.status(503).json({
|
||||
status: 'unhealthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async getLiveness(req: Request, res: Response): Promise<void> {
|
||||
const result = await this.healthService.getLiveness();
|
||||
res.status(200).json(result);
|
||||
}
|
||||
|
||||
private async getReadiness(req: Request, res: Response): Promise<void> {
|
||||
const result = await this.healthService.getReadiness();
|
||||
const statusCode = result.status === 'ready' ? 200 : 503;
|
||||
res.status(statusCode).json(result);
|
||||
}
|
||||
}
|
||||
34
src/modules/health/health.module.ts
Normal file
34
src/modules/health/health.module.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { HealthService } from './health.service';
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
export interface HealthModuleOptions {
|
||||
dataSource: DataSource;
|
||||
basePath?: string;
|
||||
}
|
||||
|
||||
export class HealthModule {
|
||||
public router: Router;
|
||||
public healthService: HealthService;
|
||||
private basePath: string;
|
||||
|
||||
constructor(options: HealthModuleOptions) {
|
||||
this.basePath = options.basePath || '';
|
||||
this.router = Router();
|
||||
this.initializeServices(options.dataSource);
|
||||
this.initializeRoutes();
|
||||
}
|
||||
|
||||
private initializeServices(dataSource: DataSource): void {
|
||||
this.healthService = new HealthService(dataSource);
|
||||
}
|
||||
|
||||
private initializeRoutes(): void {
|
||||
const healthController = new HealthController(this.healthService);
|
||||
this.router.use(`${this.basePath}/health`, healthController.router);
|
||||
}
|
||||
}
|
||||
|
||||
export { HealthService } from './health.service';
|
||||
export { HealthController } from './health.controller';
|
||||
100
src/modules/health/health.service.ts
Normal file
100
src/modules/health/health.service.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
export interface HealthStatus {
|
||||
status: 'healthy' | 'unhealthy' | 'degraded';
|
||||
timestamp: string;
|
||||
version: string;
|
||||
uptime: number;
|
||||
checks: {
|
||||
database: HealthCheck;
|
||||
memory: HealthCheck;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HealthCheck {
|
||||
status: 'up' | 'down';
|
||||
responseTime?: number;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class HealthService {
|
||||
private startTime: number;
|
||||
|
||||
constructor(private dataSource: DataSource) {
|
||||
this.startTime = Date.now();
|
||||
}
|
||||
|
||||
async getHealth(): Promise<HealthStatus> {
|
||||
const databaseCheck = await this.checkDatabase();
|
||||
const memoryCheck = this.checkMemory();
|
||||
|
||||
const isHealthy = databaseCheck.status === 'up' && memoryCheck.status === 'up';
|
||||
const isDegraded = databaseCheck.status === 'up' || memoryCheck.status === 'up';
|
||||
|
||||
return {
|
||||
status: isHealthy ? 'healthy' : isDegraded ? 'degraded' : 'unhealthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: process.env.APP_VERSION || '1.0.0',
|
||||
uptime: Math.floor((Date.now() - this.startTime) / 1000),
|
||||
checks: {
|
||||
database: databaseCheck,
|
||||
memory: memoryCheck,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async checkDatabase(): Promise<HealthCheck> {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
if (!this.dataSource.isInitialized) {
|
||||
return {
|
||||
status: 'down',
|
||||
details: { error: 'Database not initialized' },
|
||||
};
|
||||
}
|
||||
|
||||
await this.dataSource.query('SELECT 1');
|
||||
return {
|
||||
status: 'up',
|
||||
responseTime: Date.now() - startTime,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'down',
|
||||
responseTime: Date.now() - startTime,
|
||||
details: { error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
checkMemory(): HealthCheck {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
const heapUsedPercent = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100;
|
||||
|
||||
return {
|
||||
status: heapUsedPercent < 90 ? 'up' : 'down',
|
||||
details: {
|
||||
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024),
|
||||
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024),
|
||||
external: Math.round(memoryUsage.external / 1024 / 1024),
|
||||
rss: Math.round(memoryUsage.rss / 1024 / 1024),
|
||||
heapUsedPercent: Math.round(heapUsedPercent * 100) / 100,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getLiveness(): Promise<{ status: 'ok' }> {
|
||||
return { status: 'ok' };
|
||||
}
|
||||
|
||||
async getReadiness(): Promise<{ status: 'ready' | 'not_ready'; details?: Record<string, unknown> }> {
|
||||
const databaseCheck = await this.checkDatabase();
|
||||
if (databaseCheck.status !== 'up') {
|
||||
return {
|
||||
status: 'not_ready',
|
||||
details: { database: databaseCheck },
|
||||
};
|
||||
}
|
||||
return { status: 'ready' };
|
||||
}
|
||||
}
|
||||
3
src/modules/health/index.ts
Normal file
3
src/modules/health/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { HealthModule, HealthModuleOptions } from './health.module';
|
||||
export { HealthService, HealthStatus, HealthCheck } from './health.service';
|
||||
export { HealthController } from './health.controller';
|
||||
@ -2,6 +2,8 @@ export { Partner } from './partner.entity';
|
||||
export { PartnerAddress } from './partner-address.entity';
|
||||
export { PartnerContact } from './partner-contact.entity';
|
||||
export { PartnerBankAccount } from './partner-bank-account.entity';
|
||||
export { PartnerTaxInfo } from './partner-tax-info.entity';
|
||||
export { PartnerSegment } from './partner-segment.entity';
|
||||
|
||||
// Type aliases
|
||||
export type PartnerType = 'customer' | 'supplier' | 'both';
|
||||
|
||||
79
src/modules/partners/entities/partner-segment.entity.ts
Normal file
79
src/modules/partners/entities/partner-segment.entity.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* Partner Segment Entity (schema: partners.partner_segments)
|
||||
*
|
||||
* Defines customer/supplier segments for grouping and analytics.
|
||||
* Examples: VIP Customers, Wholesale Buyers, Local Suppliers, etc.
|
||||
*/
|
||||
@Entity({ name: 'partner_segments', schema: 'partners' })
|
||||
export class PartnerSegment {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: 'varchar', length: 30 })
|
||||
code: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
name: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string;
|
||||
|
||||
// Segment type
|
||||
@Column({ name: 'segment_type', type: 'varchar', length: 20, default: 'customer' })
|
||||
segmentType: 'customer' | 'supplier' | 'both';
|
||||
|
||||
// Styling
|
||||
@Column({ type: 'varchar', length: 20, nullable: true })
|
||||
color: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
icon: string;
|
||||
|
||||
// Rules for auto-assignment (stored as JSON)
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
rules: Record<string, unknown>;
|
||||
|
||||
// Benefits/conditions
|
||||
@Column({ name: 'default_discount', type: 'decimal', precision: 5, scale: 2, default: 0 })
|
||||
defaultDiscount: number;
|
||||
|
||||
@Column({ name: 'default_payment_terms', type: 'int', default: 0 })
|
||||
defaultPaymentTerms: number;
|
||||
|
||||
@Column({ name: 'priority', type: 'int', default: 0 })
|
||||
priority: number;
|
||||
|
||||
// State
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ name: 'sort_order', type: 'int', default: 0 })
|
||||
sortOrder: number;
|
||||
|
||||
// Metadata
|
||||
@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;
|
||||
|
||||
@Column({ name: 'updated_by', type: 'uuid', nullable: true })
|
||||
updatedBy: string;
|
||||
}
|
||||
78
src/modules/partners/entities/partner-tax-info.entity.ts
Normal file
78
src/modules/partners/entities/partner-tax-info.entity.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Partner } from './partner.entity';
|
||||
|
||||
/**
|
||||
* Partner Tax Info Entity (schema: partners.partner_tax_info)
|
||||
*
|
||||
* Extended tax/fiscal information for partners.
|
||||
* Stores additional fiscal details required for invoicing and compliance.
|
||||
*/
|
||||
@Entity({ name: 'partner_tax_info', schema: 'partners' })
|
||||
export class PartnerTaxInfo {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'partner_id', type: 'uuid' })
|
||||
partnerId: string;
|
||||
|
||||
@ManyToOne(() => Partner, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'partner_id' })
|
||||
partner: Partner;
|
||||
|
||||
// Fiscal identification
|
||||
@Column({ name: 'tax_id_type', type: 'varchar', length: 20, nullable: true })
|
||||
taxIdType: string; // RFC, CURP, EIN, VAT
|
||||
|
||||
@Column({ name: 'tax_id_country', type: 'varchar', length: 3, default: 'MEX' })
|
||||
taxIdCountry: string;
|
||||
|
||||
// SAT (Mexico) specific
|
||||
@Column({ name: 'sat_regime', type: 'varchar', length: 10, nullable: true })
|
||||
satRegime: string; // 601, 603, 612, etc.
|
||||
|
||||
@Column({ name: 'sat_regime_name', type: 'varchar', length: 200, nullable: true })
|
||||
satRegimeName: string;
|
||||
|
||||
@Column({ name: 'cfdi_use', type: 'varchar', length: 10, nullable: true })
|
||||
cfdiUse: string; // G01, G02, G03, etc.
|
||||
|
||||
@Column({ name: 'cfdi_use_name', type: 'varchar', length: 200, nullable: true })
|
||||
cfdiUseName: string;
|
||||
|
||||
@Column({ name: 'fiscal_zip_code', type: 'varchar', length: 10, nullable: true })
|
||||
fiscalZipCode: string;
|
||||
|
||||
// Withholding taxes
|
||||
@Column({ name: 'withholding_isr', type: 'decimal', precision: 5, scale: 2, default: 0 })
|
||||
withholdingIsr: number;
|
||||
|
||||
@Column({ name: 'withholding_iva', type: 'decimal', precision: 5, scale: 2, default: 0 })
|
||||
withholdingIva: number;
|
||||
|
||||
// Validation
|
||||
@Column({ name: 'is_verified', type: 'boolean', default: false })
|
||||
isVerified: boolean;
|
||||
|
||||
@Column({ name: 'verified_at', type: 'timestamptz', nullable: true })
|
||||
verifiedAt: Date;
|
||||
|
||||
@Column({ name: 'verification_source', type: 'varchar', length: 50, nullable: true })
|
||||
verificationSource: string; // SAT, MANUAL, API
|
||||
|
||||
// Metadata
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { PartnersService } from './services';
|
||||
import { PartnersController } from './controllers';
|
||||
import { Partner, PartnerAddress, PartnerContact, PartnerBankAccount } from './entities';
|
||||
import { Partner, PartnerAddress, PartnerContact, PartnerBankAccount, PartnerTaxInfo, PartnerSegment } from './entities';
|
||||
|
||||
export interface PartnersModuleOptions {
|
||||
dataSource: DataSource;
|
||||
@ -43,6 +43,6 @@ export class PartnersModule {
|
||||
}
|
||||
|
||||
static getEntities(): Function[] {
|
||||
return [Partner, PartnerAddress, PartnerContact, PartnerBankAccount];
|
||||
return [Partner, PartnerAddress, PartnerContact, PartnerBankAccount, PartnerTaxInfo, PartnerSegment];
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,3 +2,6 @@ export { ProductCategory } from './product-category.entity';
|
||||
export { Product } from './product.entity';
|
||||
export { ProductPrice } from './product-price.entity';
|
||||
export { ProductSupplier } from './product-supplier.entity';
|
||||
export { ProductAttribute } from './product-attribute.entity';
|
||||
export { ProductAttributeValue } from './product-attribute-value.entity';
|
||||
export { ProductVariant } from './product-variant.entity';
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { ProductAttribute } from './product-attribute.entity';
|
||||
|
||||
/**
|
||||
* Product Attribute Value Entity (schema: products.product_attribute_values)
|
||||
*
|
||||
* Represents specific values for product attributes.
|
||||
* Example: For attribute "Color", values could be "Red", "Blue", "Green".
|
||||
*/
|
||||
@Entity({ name: 'product_attribute_values', schema: 'products' })
|
||||
export class ProductAttributeValue {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'attribute_id', type: 'uuid' })
|
||||
attributeId: string;
|
||||
|
||||
@ManyToOne(() => ProductAttribute, (attribute) => attribute.values, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'attribute_id' })
|
||||
attribute: ProductAttribute;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
code: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'html_color', type: 'varchar', length: 20, nullable: true })
|
||||
htmlColor: string;
|
||||
|
||||
@Column({ name: 'image_url', type: 'varchar', length: 500, nullable: true })
|
||||
imageUrl: string;
|
||||
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ name: 'sort_order', type: 'int', default: 0 })
|
||||
sortOrder: number;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
60
src/modules/products/entities/product-attribute.entity.ts
Normal file
60
src/modules/products/entities/product-attribute.entity.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
OneToMany,
|
||||
} from 'typeorm';
|
||||
import { ProductAttributeValue } from './product-attribute-value.entity';
|
||||
|
||||
/**
|
||||
* Product Attribute Entity (schema: products.product_attributes)
|
||||
*
|
||||
* Represents configurable attributes for products like color, size, material.
|
||||
* Each attribute can have multiple values (e.g., Color: Red, Blue, Green).
|
||||
*/
|
||||
@Entity({ name: 'product_attributes', schema: 'products' })
|
||||
export class ProductAttribute {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
code: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
name: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({ name: 'display_type', type: 'varchar', length: 20, default: 'radio' })
|
||||
displayType: 'radio' | 'select' | 'color' | 'pills';
|
||||
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ name: 'sort_order', type: 'int', default: 0 })
|
||||
sortOrder: number;
|
||||
|
||||
@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;
|
||||
|
||||
@Column({ name: 'updated_by', type: 'uuid', nullable: true })
|
||||
updatedBy: string;
|
||||
|
||||
@OneToMany(() => ProductAttributeValue, (value) => value.attribute)
|
||||
values: ProductAttributeValue[];
|
||||
}
|
||||
72
src/modules/products/entities/product-variant.entity.ts
Normal file
72
src/modules/products/entities/product-variant.entity.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Product } from './product.entity';
|
||||
|
||||
/**
|
||||
* Product Variant Entity (schema: products.product_variants)
|
||||
*
|
||||
* Represents product variants generated from attribute combinations.
|
||||
* Example: "Blue T-Shirt - Size M" is a variant of product "T-Shirt".
|
||||
*/
|
||||
@Entity({ name: 'product_variants', schema: 'products' })
|
||||
export class ProductVariant {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'product_id', type: 'uuid' })
|
||||
productId: string;
|
||||
|
||||
@ManyToOne(() => Product, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'product_id' })
|
||||
product: Product;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
sku: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
barcode: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'price_extra', type: 'decimal', precision: 15, scale: 4, default: 0 })
|
||||
priceExtra: number;
|
||||
|
||||
@Column({ name: 'cost_extra', type: 'decimal', precision: 15, scale: 4, default: 0 })
|
||||
costExtra: number;
|
||||
|
||||
@Column({ name: 'stock_qty', type: 'decimal', precision: 15, scale: 4, default: 0 })
|
||||
stockQty: number;
|
||||
|
||||
@Column({ name: 'image_url', type: 'varchar', length: 500, nullable: true })
|
||||
imageUrl: string;
|
||||
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@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;
|
||||
|
||||
@Column({ name: 'updated_by', type: 'uuid', nullable: true })
|
||||
updatedBy: string;
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { Router } from 'express';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ProductsService } from './services';
|
||||
import { ProductsController, CategoriesController } from './controllers';
|
||||
import { Product, ProductCategory } from './entities';
|
||||
import { Product, ProductCategory, ProductAttribute, ProductAttributeValue, ProductVariant } from './entities';
|
||||
|
||||
export interface ProductsModuleOptions {
|
||||
dataSource: DataSource;
|
||||
@ -39,6 +39,6 @@ export class ProductsModule {
|
||||
}
|
||||
|
||||
static getEntities(): Function[] {
|
||||
return [Product, ProductCategory];
|
||||
return [Product, ProductCategory, ProductAttribute, ProductAttributeValue, ProductVariant];
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user