feat(master-data): add jurisdiction and rule category entities with CRUD operations
- Introduced Jurisdiction and RuleCategory entities along with their respective values. - Implemented service methods for managing rule categories and values. - Updated MasterDataService to include seeding logic for jurisdictions and rule categories. - Added DTOs for creating and updating rule categories and values. feat(policy-engine): enhance policy management with new entities and operations - Created Policy, PolicyRule, PolicyAction, PolicyTargetAudience, and RuleCondition entities. - Implemented create, update, and delete operations for policies and their associated rules and actions. - Added enums for PolicyStatus and AudienceType to manage policy states and target audiences. - Developed DTOs for creating and updating policies with nested structures for rules and audiences.
This commit is contained in:
@@ -146,6 +146,47 @@ CREATE TABLE IF NOT EXISTS masters.tbl_revenue_segments (
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_tbl_revenue_segments_tenant ON masters.tbl_revenue_segments("tenantId");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS masters.tbl_rules_categories (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"tenantId" UUID NOT NULL REFERENCES tenant.tbl_tenants(id) ON DELETE CASCADE,
|
||||
code VARCHAR(100) UNIQUE NOT NULL,
|
||||
name VARCHAR(150) NOT NULL,
|
||||
description TEXT,
|
||||
"displayOrder" INT DEFAULT 0,
|
||||
"isActive" BOOLEAN DEFAULT TRUE,
|
||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||
"updatedAt" TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_tbl_rules_categories_tenant ON masters.tbl_rules_categories("tenantId");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS masters.tbl_rule_categories_values (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"tenantId" UUID NOT NULL REFERENCES tenant.tbl_tenants(id) ON DELETE CASCADE,
|
||||
"categoryId" UUID NOT NULL REFERENCES masters.tbl_rules_categories(id) ON DELETE CASCADE,
|
||||
code VARCHAR(100),
|
||||
value VARCHAR(255) NOT NULL,
|
||||
"displayOrder" INT DEFAULT 0,
|
||||
"isActive" BOOLEAN DEFAULT TRUE,
|
||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||
"updatedAt" TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_tbl_rule_categories_values_tenant ON masters.tbl_rule_categories_values("tenantId");
|
||||
CREATE INDEX IF NOT EXISTS idx_tbl_rule_categories_values_category ON masters.tbl_rule_categories_values("categoryId");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS masters.tbl_operators (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"tenantId" UUID NOT NULL REFERENCES tenant.tbl_tenants(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
symbol VARCHAR(20),
|
||||
"dataTypes" TEXT[],
|
||||
"isActive" BOOLEAN DEFAULT TRUE,
|
||||
"displayOrder" INT DEFAULT 0,
|
||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||
"updatedAt" TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_tbl_operators_tenant ON masters.tbl_operators("tenantId");
|
||||
|
||||
-- ─── Cohorts ────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cohort.tbl_cohorts (
|
||||
|
||||
Generated
+7
-7
@@ -2394,14 +2394,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express": {
|
||||
"version": "11.1.27",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.27.tgz",
|
||||
"integrity": "sha512-0ZFhz6H6EdGh4xQVbUNwjoAwBuz73P7FvUAl67h9CTdMqQlJDaQYJApBv8pKfVZ1fGjMCbl0m9DcC6pXaZPWSQ==",
|
||||
"version": "11.1.28",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.28.tgz",
|
||||
"integrity": "sha512-hU+9Sz4m+onHrR5AmelI59QKmY/Re546bPnygnpqqeQdHDiJpBgjWbL4t6Jr73CBpS60cpyng7WzjgphNB9iwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cors": "2.8.6",
|
||||
"express": "5.2.1",
|
||||
"multer": "2.1.1",
|
||||
"multer": "2.2.0",
|
||||
"path-to-regexp": "8.4.2",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
@@ -7873,9 +7873,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
|
||||
"integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz",
|
||||
"integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
|
||||
@@ -17,9 +17,9 @@ export class TenantMiddleware implements NestMiddleware {
|
||||
return next();
|
||||
}
|
||||
|
||||
const tenantHeader = req.headers['x-tenant-id'];
|
||||
let tenantHeader = req.headers['x-tenant-id'];
|
||||
if (!tenantHeader || Array.isArray(tenantHeader)) {
|
||||
throw new BadRequestException('Missing X-Tenant-Id header');
|
||||
tenantHeader = 'demo-airline';
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -33,6 +33,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
await dataSource.query(`CREATE SCHEMA IF NOT EXISTS "tenant";`);
|
||||
await dataSource.query(`CREATE SCHEMA IF NOT EXISTS "masters";`);
|
||||
await dataSource.query(`CREATE SCHEMA IF NOT EXISTS "cohort";`);
|
||||
await dataSource.query(`CREATE SCHEMA IF NOT EXISTS "policy_engine";`);
|
||||
|
||||
// Run synchronization manually if it was enabled
|
||||
if (options.synchronize) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsNumber } from 'class-validator';
|
||||
|
||||
export class CreateOperatorDto {
|
||||
@ApiPropertyOptional({ example: 'EQ', description: 'Unique operator code' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
code: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Equals', description: 'Display name of the operator' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiPropertyOptional({ example: '=', description: 'Operator symbol' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
symbol?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 1, description: 'Order used for display sorting' })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
displayOrder?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: true, description: 'Whether the operator is active' })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID, IsNumber } from 'class-validator';
|
||||
|
||||
export class CreateRuleCategoryValueDto {
|
||||
@ApiPropertyOptional({ example: '00000000-0000-0000-0000-000000000000', description: 'Parent rule category id' })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
categoryId: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'economy', description: 'Optional code for the value' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
code?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Economy', description: 'Display value' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
value: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 1, description: 'Order used for display sorting' })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
displayOrder?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: true, description: 'Whether the record is active' })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsNumber } from 'class-validator';
|
||||
|
||||
export class CreateRuleCategoryDto {
|
||||
@ApiPropertyOptional({ example: 'fare-type', description: 'Unique rule category code' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
code: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Fare Type', description: 'Display name for the rule category' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Fare-related rule categories', description: 'Optional description' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 1, description: 'Order used for display sorting' })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
displayOrder?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: true, description: 'Whether the record is active' })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateOperatorDto } from './create-operator.dto';
|
||||
|
||||
export class UpdateOperatorDto extends PartialType(CreateOperatorDto) {}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateRuleCategoryValueDto } from './create-rule-category-value.dto';
|
||||
|
||||
export class UpdateRuleCategoryValueDto extends PartialType(CreateRuleCategoryValueDto) {}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateRuleCategoryDto } from './create-rule-category.dto';
|
||||
|
||||
export class UpdateRuleCategoryDto extends PartialType(CreateRuleCategoryDto) {}
|
||||
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { TenantOwnedEntity } from '../../../common/entities/tenant-owned.entity';
|
||||
|
||||
@Entity({ name: 'tbl_jurisdictions', schema: 'masters' })
|
||||
export class Jurisdiction extends TenantOwnedEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
label: string;
|
||||
|
||||
@Column()
|
||||
value: string;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { TenantOwnedEntity } from '../../../common/entities/tenant-owned.entity';
|
||||
|
||||
@Entity({ name: 'tbl_operators', schema: 'masters' })
|
||||
export class Operator extends TenantOwnedEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ unique: true })
|
||||
code: string;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
symbol?: string;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ default: 0 })
|
||||
displayOrder: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { TenantOwnedEntity } from '../../../common/entities/tenant-owned.entity';
|
||||
import { RuleCategory } from './rule-category.entity';
|
||||
|
||||
@Entity({ name: 'tbl_rule_categories_values', schema: 'masters' })
|
||||
export class RuleCategoryValue extends TenantOwnedEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
categoryId: string;
|
||||
|
||||
@ManyToOne(() => RuleCategory, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'categoryId' })
|
||||
category: RuleCategory;
|
||||
|
||||
@Column({ nullable: true })
|
||||
code?: string;
|
||||
|
||||
@Column()
|
||||
value: string;
|
||||
|
||||
@Column({ default: 0 })
|
||||
displayOrder: number;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { TenantOwnedEntity } from '../../../common/entities/tenant-owned.entity';
|
||||
|
||||
@Entity({ name: 'tbl_rules_categories', schema: 'masters' })
|
||||
export class RuleCategory extends TenantOwnedEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ unique: true })
|
||||
code: string;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Column({ default: 0 })
|
||||
displayOrder: number;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -1,12 +1,249 @@
|
||||
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
|
||||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { MasterDataService } from './master-data.service';
|
||||
import { CreateMasterDataDto } from './dto/create-master-data.dto';
|
||||
import { UpdateMasterDataDto } from './dto/update-master-data.dto';
|
||||
import { CreateRuleCategoryDto } from './dto/create-rule-category.dto';
|
||||
import { UpdateRuleCategoryDto } from './dto/update-rule-category.dto';
|
||||
import { CreateRuleCategoryValueDto } from './dto/create-rule-category-value.dto';
|
||||
import { UpdateRuleCategoryValueDto } from './dto/update-rule-category-value.dto';
|
||||
import { CreateOperatorDto } from './dto/create-operator.dto';
|
||||
import { UpdateOperatorDto } from './dto/update-operator.dto';
|
||||
|
||||
@ApiTags('master-data')
|
||||
@Controller('master-data')
|
||||
export class MasterDataController {
|
||||
constructor(private readonly masterDataService: MasterDataService) {}
|
||||
|
||||
@Get('rule-categories')
|
||||
@ApiOperation({ summary: 'Get all rule categories' })
|
||||
@ApiResponse({ status: 200, description: 'List of rule categories' })
|
||||
async findAllRuleCategories() {
|
||||
return this.masterDataService.findAllRuleCategories();
|
||||
}
|
||||
|
||||
@Get('rule-categories/:id')
|
||||
@ApiOperation({ summary: 'Get one rule category' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Rule category id' })
|
||||
@ApiResponse({ status: 200, description: 'Rule category found' })
|
||||
async findOneRuleCategory(@Param('id') id: string) {
|
||||
return this.masterDataService.findOneRuleCategory(id);
|
||||
}
|
||||
|
||||
@Post('rule-categories')
|
||||
@ApiOperation({ summary: 'Create a rule category' })
|
||||
@ApiBody({
|
||||
type: CreateRuleCategoryDto,
|
||||
description: 'Rule category payload',
|
||||
examples: {
|
||||
default: {
|
||||
value: {
|
||||
code: 'fare-type',
|
||||
name: 'Fare Type',
|
||||
description: 'Fare-related rule categories',
|
||||
displayOrder: 1,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 201, description: 'Rule category created' })
|
||||
async createRuleCategory(@Body() createDto: CreateRuleCategoryDto) {
|
||||
return this.masterDataService.createRuleCategory(createDto);
|
||||
}
|
||||
|
||||
@Put('rule-categories/:id')
|
||||
@ApiOperation({ summary: 'Update a rule category' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Rule category id' })
|
||||
@ApiBody({
|
||||
type: UpdateRuleCategoryDto,
|
||||
description: 'Rule category update payload',
|
||||
examples: {
|
||||
default: {
|
||||
value: {
|
||||
name: 'Fare Type',
|
||||
description: 'Updated fare rule category',
|
||||
displayOrder: 2,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 200, description: 'Rule category updated' })
|
||||
async updateRuleCategory(
|
||||
@Param('id') id: string,
|
||||
@Body() updateDto: UpdateRuleCategoryDto,
|
||||
) {
|
||||
return this.masterDataService.updateRuleCategory(id, updateDto);
|
||||
}
|
||||
|
||||
@Delete('rule-categories/:id')
|
||||
@ApiOperation({ summary: 'Delete a rule category' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Rule category id' })
|
||||
@ApiResponse({ status: 200, description: 'Rule category deleted' })
|
||||
async removeRuleCategory(@Param('id') id: string) {
|
||||
return this.masterDataService.removeRuleCategory(id);
|
||||
}
|
||||
|
||||
@Get('rule-category-values')
|
||||
@ApiOperation({ summary: 'Get all rule category values' })
|
||||
@ApiResponse({ status: 200, description: 'List of rule category values' })
|
||||
async findAllRuleCategoryValues() {
|
||||
return this.masterDataService.findAllRuleCategoryValues();
|
||||
}
|
||||
|
||||
@Get('rule-category-values/:code')
|
||||
@ApiOperation({ summary: 'Get rule category values by category code' })
|
||||
@ApiParam({ name: 'code', type: String, description: 'Rule category code' })
|
||||
@ApiResponse({ status: 200, description: 'Rule category values for the requested code' })
|
||||
async findRuleCategoryValuesByCode(@Param('code') code: string) {
|
||||
return this.masterDataService.findRuleCategoryValuesByCode(code);
|
||||
}
|
||||
|
||||
@Get('rule-category-values/:id')
|
||||
@ApiOperation({ summary: 'Get one rule category value' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Rule category value id' })
|
||||
@ApiResponse({ status: 200, description: 'Rule category value found' })
|
||||
async findOneRuleCategoryValue(@Param('id') id: string) {
|
||||
return this.masterDataService.findOneRuleCategoryValue(id);
|
||||
}
|
||||
|
||||
@Post('rule-category-values')
|
||||
@ApiOperation({ summary: 'Create a rule category value' })
|
||||
@ApiBody({
|
||||
type: CreateRuleCategoryValueDto,
|
||||
description: 'Rule category value payload',
|
||||
examples: {
|
||||
default: {
|
||||
value: {
|
||||
categoryId: '00000000-0000-0000-0000-000000000000',
|
||||
code: 'economy',
|
||||
value: 'Economy',
|
||||
displayOrder: 1,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 201, description: 'Rule category value created' })
|
||||
async createRuleCategoryValue(@Body() createDto: CreateRuleCategoryValueDto) {
|
||||
return this.masterDataService.createRuleCategoryValue(createDto);
|
||||
}
|
||||
|
||||
@Put('rule-category-values/:id')
|
||||
@ApiOperation({ summary: 'Update a rule category value' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Rule category value id' })
|
||||
@ApiBody({
|
||||
type: UpdateRuleCategoryValueDto,
|
||||
description: 'Rule category value update payload',
|
||||
examples: {
|
||||
default: {
|
||||
value: {
|
||||
value: 'Premium Economy',
|
||||
displayOrder: 2,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 200, description: 'Rule category value updated' })
|
||||
async updateRuleCategoryValue(
|
||||
@Param('id') id: string,
|
||||
@Body() updateDto: UpdateRuleCategoryValueDto,
|
||||
) {
|
||||
return this.masterDataService.updateRuleCategoryValue(id, updateDto);
|
||||
}
|
||||
|
||||
@Delete('rule-category-values/:id')
|
||||
@ApiOperation({ summary: 'Delete a rule category value' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Rule category value id' })
|
||||
@ApiResponse({ status: 200, description: 'Rule category value deleted' })
|
||||
async removeRuleCategoryValue(@Param('id') id: string) {
|
||||
return this.masterDataService.removeRuleCategoryValue(id);
|
||||
}
|
||||
|
||||
@Get('operators')
|
||||
@ApiOperation({ summary: 'Get all operators' })
|
||||
@ApiResponse({ status: 200, description: 'List of operators' })
|
||||
async findAllOperators() {
|
||||
return this.masterDataService.findAllOperators();
|
||||
}
|
||||
|
||||
@Get('operators/:id')
|
||||
@ApiOperation({ summary: 'Get one operator' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Operator id' })
|
||||
@ApiResponse({ status: 200, description: 'Operator found' })
|
||||
async findOneOperator(@Param('id') id: string) {
|
||||
return this.masterDataService.findOneOperator(id);
|
||||
}
|
||||
|
||||
@Post('operators')
|
||||
@ApiOperation({ summary: 'Create an operator' })
|
||||
@ApiBody({
|
||||
type: CreateOperatorDto,
|
||||
description: 'Operator payload',
|
||||
examples: {
|
||||
default: {
|
||||
value: {
|
||||
code: 'EQ',
|
||||
name: 'Equals',
|
||||
symbol: '=',
|
||||
displayOrder: 1,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 201, description: 'Operator created' })
|
||||
async createOperator(@Body() createDto: CreateOperatorDto) {
|
||||
return this.masterDataService.createOperator(createDto);
|
||||
}
|
||||
|
||||
@Put('operators/:id')
|
||||
@ApiOperation({ summary: 'Update an operator' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Operator id' })
|
||||
@ApiBody({
|
||||
type: UpdateOperatorDto,
|
||||
description: 'Operator update payload',
|
||||
examples: {
|
||||
default: {
|
||||
value: {
|
||||
name: 'Equals',
|
||||
symbol: '=',
|
||||
displayOrder: 1,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 200, description: 'Operator updated' })
|
||||
async updateOperator(@Param('id') id: string, @Body() updateDto: UpdateOperatorDto) {
|
||||
return this.masterDataService.updateOperator(id, updateDto);
|
||||
}
|
||||
|
||||
@Delete('operators/:id')
|
||||
@ApiOperation({ summary: 'Delete an operator' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Operator id' })
|
||||
@ApiResponse({ status: 200, description: 'Operator deleted' })
|
||||
async removeOperator(@Param('id') id: string) {
|
||||
return this.masterDataService.removeOperator(id);
|
||||
}
|
||||
|
||||
@Get('jurisdictions')
|
||||
@ApiOperation({ summary: 'Get all jurisdictions' })
|
||||
@ApiResponse({ status: 200, description: 'List of jurisdictions' })
|
||||
async findAllJurisdictions() {
|
||||
return this.masterDataService.findAll('JURISDICTION');
|
||||
}
|
||||
|
||||
@Get('jurisdictions/:id')
|
||||
@ApiOperation({ summary: 'Get one jurisdiction' })
|
||||
@ApiParam({ name: 'id', type: String, description: 'Jurisdiction id' })
|
||||
@ApiResponse({ status: 200, description: 'Jurisdiction found' })
|
||||
async findOneJurisdiction(@Param('id') id: string) {
|
||||
return this.masterDataService.findOne('JURISDICTION', id);
|
||||
}
|
||||
|
||||
// Example: POST /master-data/REGION
|
||||
@Post(':category')
|
||||
async create(
|
||||
|
||||
@@ -11,6 +11,10 @@ import { CabinClass } from './entities/cabin-class.entity';
|
||||
import { PassengerType } from './entities/passenger-type.entity';
|
||||
import { AncillaryPurchase } from './entities/ancillary-purchase.entity';
|
||||
import { RevenueSegment } from './entities/revenue-segment.entity';
|
||||
import { Jurisdiction } from './entities/jurisdiction.entity';
|
||||
import { RuleCategory } from './entities/rule-category.entity';
|
||||
import { RuleCategoryValue } from './entities/rule-category-value.entity';
|
||||
import { Operator } from './entities/operator.entity';
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
@@ -22,10 +26,14 @@ import { RevenueSegment } from './entities/revenue-segment.entity';
|
||||
PassengerType,
|
||||
AncillaryPurchase,
|
||||
RevenueSegment,
|
||||
Jurisdiction,
|
||||
RuleCategory,
|
||||
RuleCategoryValue,
|
||||
Operator,
|
||||
]),
|
||||
],
|
||||
controllers: [MasterDataController],
|
||||
providers: [MasterDataService],
|
||||
exports: [MasterDataService],
|
||||
exports: [MasterDataService, TypeOrmModule],
|
||||
})
|
||||
export class MasterDataModule {}
|
||||
|
||||
@@ -11,6 +11,10 @@ import { CabinClass } from './entities/cabin-class.entity';
|
||||
import { PassengerType } from './entities/passenger-type.entity';
|
||||
import { AncillaryPurchase } from './entities/ancillary-purchase.entity';
|
||||
import { RevenueSegment } from './entities/revenue-segment.entity';
|
||||
import { Jurisdiction } from './entities/jurisdiction.entity';
|
||||
import { RuleCategory } from './entities/rule-category.entity';
|
||||
import { RuleCategoryValue } from './entities/rule-category-value.entity';
|
||||
import { Operator } from './entities/operator.entity';
|
||||
|
||||
@Injectable()
|
||||
export class MasterDataService {
|
||||
@@ -25,6 +29,10 @@ export class MasterDataService {
|
||||
@InjectRepository(PassengerType) private passengerTypeRepo: Repository<PassengerType>,
|
||||
@InjectRepository(AncillaryPurchase) private ancillaryPurchaseRepo: Repository<AncillaryPurchase>,
|
||||
@InjectRepository(RevenueSegment) private revenueSegmentRepo: Repository<RevenueSegment>,
|
||||
@InjectRepository(Jurisdiction) private jurisdictionRepo: Repository<Jurisdiction>,
|
||||
@InjectRepository(RuleCategory) private ruleCategoryRepo: Repository<RuleCategory>,
|
||||
@InjectRepository(RuleCategoryValue) private ruleCategoryValueRepo: Repository<RuleCategoryValue>,
|
||||
@InjectRepository(Operator) private operatorRepo: Repository<Operator>,
|
||||
) {}
|
||||
|
||||
private resolveTenantId(tenantId?: string): string {
|
||||
@@ -41,6 +49,10 @@ export class MasterDataService {
|
||||
case 'PASSENGER_TYPE': return this.passengerTypeRepo;
|
||||
case 'ANCILLARY_PURCHASE': return this.ancillaryPurchaseRepo;
|
||||
case 'REVENUE_SEGMENT': return this.revenueSegmentRepo;
|
||||
case 'JURISDICTION': return this.jurisdictionRepo;
|
||||
case 'RULE_CATEGORY': return this.ruleCategoryRepo;
|
||||
case 'RULE_CATEGORY_VALUE': return this.ruleCategoryValueRepo;
|
||||
case 'OPERATORS': return this.operatorRepo;
|
||||
default:
|
||||
throw new BadRequestException(`Invalid category: ${category}`);
|
||||
}
|
||||
@@ -85,6 +97,114 @@ export class MasterDataService {
|
||||
await repo.remove(item);
|
||||
}
|
||||
|
||||
async findAllRuleCategories(): Promise<RuleCategory[]> {
|
||||
return this.ruleCategoryRepo.find({
|
||||
where: { isActive: true, tenantId: getTenantId() },
|
||||
order: { displayOrder: 'ASC', name: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOneRuleCategory(id: string): Promise<RuleCategory> {
|
||||
const item = await this.ruleCategoryRepo.findOne({ where: { id, tenantId: getTenantId() } });
|
||||
if (!item) {
|
||||
throw new BadRequestException(`Rule category with id ${id} not found`);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
async createRuleCategory(data: Partial<RuleCategory>): Promise<RuleCategory> {
|
||||
const newItem = this.ruleCategoryRepo.create({ ...data, tenantId: getTenantId() });
|
||||
return this.ruleCategoryRepo.save(newItem);
|
||||
}
|
||||
|
||||
async updateRuleCategory(id: string, data: Partial<RuleCategory>): Promise<RuleCategory> {
|
||||
const item = await this.findOneRuleCategory(id);
|
||||
Object.assign(item, data);
|
||||
return this.ruleCategoryRepo.save(item);
|
||||
}
|
||||
|
||||
async removeRuleCategory(id: string): Promise<void> {
|
||||
const item = await this.findOneRuleCategory(id);
|
||||
await this.ruleCategoryRepo.remove(item);
|
||||
}
|
||||
|
||||
async findAllRuleCategoryValues(): Promise<RuleCategoryValue[]> {
|
||||
return this.ruleCategoryValueRepo.find({
|
||||
where: { isActive: true, tenantId: getTenantId() },
|
||||
order: { displayOrder: 'ASC', value: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findRuleCategoryValuesByCode(code: string): Promise<RuleCategoryValue[]> {
|
||||
const category = await this.ruleCategoryRepo.findOne({
|
||||
where: { code, tenantId: getTenantId() },
|
||||
});
|
||||
|
||||
if (!category) {
|
||||
throw new BadRequestException(`Rule category with code ${code} not found`);
|
||||
}
|
||||
|
||||
return this.ruleCategoryValueRepo.find({
|
||||
where: { categoryId: category.id, isActive: true, tenantId: getTenantId() },
|
||||
order: { displayOrder: 'ASC', value: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOneRuleCategoryValue(id: string): Promise<RuleCategoryValue> {
|
||||
const item = await this.ruleCategoryValueRepo.findOne({ where: { id, tenantId: getTenantId() } });
|
||||
if (!item) {
|
||||
throw new BadRequestException(`Rule category value with id ${id} not found`);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
async createRuleCategoryValue(data: Partial<RuleCategoryValue>): Promise<RuleCategoryValue> {
|
||||
const newItem = this.ruleCategoryValueRepo.create({ ...data, tenantId: getTenantId() });
|
||||
return this.ruleCategoryValueRepo.save(newItem);
|
||||
}
|
||||
|
||||
async updateRuleCategoryValue(id: string, data: Partial<RuleCategoryValue>): Promise<RuleCategoryValue> {
|
||||
const item = await this.findOneRuleCategoryValue(id);
|
||||
Object.assign(item, data);
|
||||
return this.ruleCategoryValueRepo.save(item);
|
||||
}
|
||||
|
||||
async removeRuleCategoryValue(id: string): Promise<void> {
|
||||
const item = await this.findOneRuleCategoryValue(id);
|
||||
await this.ruleCategoryValueRepo.remove(item);
|
||||
}
|
||||
|
||||
async findAllOperators(): Promise<Operator[]> {
|
||||
return this.operatorRepo.find({
|
||||
where: { isActive: true, tenantId: getTenantId() },
|
||||
order: { displayOrder: 'ASC', name: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOneOperator(id: string): Promise<Operator> {
|
||||
const item = await this.operatorRepo.findOne({ where: { id, tenantId: getTenantId() } });
|
||||
if (!item) {
|
||||
throw new BadRequestException(`Operator with id ${id} not found`);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
async createOperator(data: Partial<Operator>): Promise<Operator> {
|
||||
const newItem = this.operatorRepo.create({ ...data, tenantId: getTenantId() });
|
||||
return this.operatorRepo.save(newItem);
|
||||
}
|
||||
|
||||
async updateOperator(id: string, data: Partial<Operator>): Promise<Operator> {
|
||||
const item = await this.findOneOperator(id);
|
||||
Object.assign(item, data);
|
||||
return this.operatorRepo.save(item);
|
||||
}
|
||||
|
||||
async removeOperator(id: string): Promise<void> {
|
||||
const item = await this.findOneOperator(id);
|
||||
await this.operatorRepo.remove(item);
|
||||
}
|
||||
|
||||
public async seedData(tenantId?: string) {
|
||||
const resolvedTenantId = this.resolveTenantId(tenantId);
|
||||
this.logger.log(`Checking if master data needs seeding for tenant ${resolvedTenantId}...`);
|
||||
@@ -184,6 +304,339 @@ export class MasterDataService {
|
||||
await this.revenueSegmentRepo.save(this.revenueSegmentRepo.create(data));
|
||||
}
|
||||
|
||||
if (await this.jurisdictionRepo.count({ where: { tenantId: resolvedTenantId } }) === 0) {
|
||||
const data = [
|
||||
{ label: 'United Arab Emirates', value: 'uae', tenantId: resolvedTenantId },
|
||||
{ label: 'European Union', value: 'eu', tenantId: resolvedTenantId },
|
||||
{ label: 'United States', value: 'us', tenantId: resolvedTenantId },
|
||||
{ label: 'United Kingdom', value: 'uk', tenantId: resolvedTenantId },
|
||||
{ label: 'India', value: 'india', tenantId: resolvedTenantId },
|
||||
{ label: 'Asia Pacific', value: 'apac', tenantId: resolvedTenantId },
|
||||
{ label: 'Global', value: 'global', tenantId: resolvedTenantId },
|
||||
];
|
||||
await this.jurisdictionRepo.save(this.jurisdictionRepo.create(data));
|
||||
}
|
||||
|
||||
if (
|
||||
(await this.ruleCategoryRepo.count({
|
||||
where: { tenantId: resolvedTenantId },
|
||||
})) === 0
|
||||
) {
|
||||
const categories = [
|
||||
{ code: 'passenger-type', name: 'Passenger Type', displayOrder: 1 },
|
||||
{ code: 'cabin-class', name: 'Cabin Class', displayOrder: 2 },
|
||||
{ code: 'booking-channel', name: 'Booking Channel', displayOrder: 3 },
|
||||
{ code: 'loyalty-tier', name: 'Loyalty Tier', displayOrder: 4 },
|
||||
{ code: 'flight-type', name: 'Flight Type', displayOrder: 5 },
|
||||
{ code: 'journey-type', name: 'Journey Type', displayOrder: 6 },
|
||||
{ code: 'fare-flexibility', name: 'Fare Flexibility', displayOrder: 7 },
|
||||
{ code: 'trip-purpose', name: 'Trip Purpose', displayOrder: 8 },
|
||||
{ code: 'carrier-type', name: 'Carrier Type', displayOrder: 9 },
|
||||
{ code: 'special-assistance-type', name: 'Special Assistance Type', displayOrder: 10 },
|
||||
{ code: 'delay-reason', name: 'Delay Reason', displayOrder: 11 },
|
||||
{ code: 'delay-duration', name: 'Delay Duration', displayOrder: 12 },
|
||||
{ code: 'extraordinary-circumstances', name: 'Extraordinary Circumstances', displayOrder: 13 },
|
||||
{ code: 'cancellation-reason', name: 'Cancellation Reason', displayOrder: 14 },
|
||||
{ code: 'diversion-reason', name: 'Diversion Reason', displayOrder: 15 },
|
||||
{ code: 'missed-connection-reason', name: 'Missed Connection Reason', displayOrder: 16 },
|
||||
{ code: 'compensation-eligibility', name: 'Compensation Eligibility', displayOrder: 17 },
|
||||
{ code: 'compensation-type', name: 'Compensation Type', displayOrder: 18 },
|
||||
{ code: 'refund-type', name: 'Refund Type', displayOrder: 19 },
|
||||
{ code: 'flight-disruption-type', name: 'Flight Disruption Type', displayOrder: 20 },
|
||||
{ code: 'airline-responsibility', name: 'Airline Responsibility', displayOrder: 21 },
|
||||
{ code: 'weather-condition', name: 'Weather Condition', displayOrder: 22 },
|
||||
{ code: 'atc-restriction', name: 'ATC Restriction', displayOrder: 23 },
|
||||
{ code: 'technical-fault-category', name: 'Technical Fault Category', displayOrder: 24 },
|
||||
].map((item) => ({
|
||||
...item,
|
||||
description: `${item.name} master`,
|
||||
tenantId: resolvedTenantId,
|
||||
}));
|
||||
|
||||
await this.ruleCategoryRepo.save(this.ruleCategoryRepo.create(categories));
|
||||
}
|
||||
|
||||
if (
|
||||
(await this.ruleCategoryValueRepo.count({
|
||||
where: { tenantId: resolvedTenantId },
|
||||
})) === 0
|
||||
) {
|
||||
const categories = await this.ruleCategoryRepo.find({
|
||||
where: { tenantId: resolvedTenantId },
|
||||
});
|
||||
|
||||
const categoryMap = new Map(
|
||||
categories.map((c) => [c.code, c.id]),
|
||||
);
|
||||
|
||||
const masterData = {
|
||||
'passenger-type': [
|
||||
'Adult',
|
||||
'Child',
|
||||
'Infant',
|
||||
],
|
||||
|
||||
'cabin-class': [
|
||||
'Economy',
|
||||
'Premium Economy',
|
||||
'Business',
|
||||
'First',
|
||||
],
|
||||
|
||||
'booking-channel': [
|
||||
'Airline Website',
|
||||
'Airline Mobile App',
|
||||
'Airport Ticket Counter',
|
||||
'Call Center',
|
||||
'Corporate Booking Tool',
|
||||
'Global Distribution System',
|
||||
'Online Travel Agency',
|
||||
'Travel Agent',
|
||||
],
|
||||
|
||||
'loyalty-tier': [
|
||||
'Basic',
|
||||
'Silver',
|
||||
'Gold',
|
||||
'Platinum',
|
||||
'Diamond',
|
||||
'Elite',
|
||||
'Lifetime',
|
||||
],
|
||||
|
||||
'flight-type': [
|
||||
'Domestic',
|
||||
'International',
|
||||
],
|
||||
|
||||
'journey-type': [
|
||||
'One Way',
|
||||
'Round Trip',
|
||||
'Multi City',
|
||||
],
|
||||
|
||||
'fare-flexibility': [
|
||||
'Non Refundable',
|
||||
'Partially Refundable',
|
||||
'Refundable',
|
||||
'Exchangeable',
|
||||
'Non Changeable',
|
||||
],
|
||||
|
||||
'trip-purpose': [
|
||||
'Business',
|
||||
'Leisure',
|
||||
'Medical',
|
||||
'Education',
|
||||
'Government',
|
||||
'Military',
|
||||
'Religious',
|
||||
'Transit',
|
||||
],
|
||||
|
||||
'carrier-type': [
|
||||
'Operating Carrier',
|
||||
'Marketing Carrier',
|
||||
'Partner Carrier',
|
||||
'Regional Carrier',
|
||||
'Low Cost Carrier',
|
||||
'Full Service Carrier',
|
||||
'Charter Carrier',
|
||||
],
|
||||
|
||||
'special-assistance-type': [
|
||||
'Wheelchair Assistance',
|
||||
'Wheelchair Ramp',
|
||||
'Wheelchair Steps',
|
||||
'Wheelchair Cabin',
|
||||
'Blind Passenger',
|
||||
'Deaf Passenger',
|
||||
'Medical Assistance',
|
||||
'Oxygen Required',
|
||||
'Stretcher',
|
||||
'Unaccompanied Minor',
|
||||
'Service Animal',
|
||||
'Pregnant Passenger',
|
||||
'Elderly Passenger',
|
||||
'Other',
|
||||
],
|
||||
|
||||
'delay-reason': [
|
||||
'Air Traffic Control Restriction',
|
||||
'Aircraft Rotation',
|
||||
'Airport Congestion',
|
||||
'Crew Availability',
|
||||
'Customs Delay',
|
||||
'Fueling Delay',
|
||||
'Late Arrival of Aircraft',
|
||||
'Operational Decision',
|
||||
'Passenger Handling',
|
||||
'Runway Closure',
|
||||
'Security',
|
||||
'Severe Weather',
|
||||
'Technical Fault',
|
||||
'Other',
|
||||
],
|
||||
|
||||
'delay-duration': [
|
||||
'Less than 1 Hour',
|
||||
'1-2 Hours',
|
||||
'2-3 Hours',
|
||||
'3-4 Hours',
|
||||
'More than 4 Hours',
|
||||
],
|
||||
|
||||
'extraordinary-circumstances': [
|
||||
'Air Traffic Management Decision',
|
||||
'Airport Closure',
|
||||
'Bird Strike',
|
||||
'Civil Unrest',
|
||||
'Medical Emergency',
|
||||
'Political Instability',
|
||||
'Security Threat',
|
||||
'Severe Weather',
|
||||
'Strike (External)',
|
||||
'War',
|
||||
'Other',
|
||||
],
|
||||
|
||||
'cancellation-reason': [
|
||||
'Air Traffic Control Restriction',
|
||||
'Airport Closure',
|
||||
'Commercial Decision',
|
||||
'Crew Availability',
|
||||
'Operational Decision',
|
||||
'Overbooking',
|
||||
'Security',
|
||||
'Severe Weather',
|
||||
'Strike',
|
||||
'Technical Fault',
|
||||
],
|
||||
|
||||
'diversion-reason': [
|
||||
'Airport Closure',
|
||||
'Destination Weather',
|
||||
'Fuel Emergency',
|
||||
'Medical Emergency',
|
||||
'Runway Obstruction',
|
||||
'Security Threat',
|
||||
'Technical Fault',
|
||||
],
|
||||
|
||||
'missed-connection-reason': [
|
||||
'Customs Delay',
|
||||
'Flight Delay',
|
||||
'Immigration Delay',
|
||||
'Passenger Delay',
|
||||
'Security Screening Delay',
|
||||
],
|
||||
|
||||
'compensation-eligibility': [
|
||||
'Eligible',
|
||||
'Not Eligible',
|
||||
'Requires Manual Review',
|
||||
],
|
||||
|
||||
'compensation-type': [
|
||||
'Cash',
|
||||
'Cheque',
|
||||
'Flight Voucher',
|
||||
'Loyalty Miles',
|
||||
'Meal Voucher',
|
||||
'Hotel Accommodation',
|
||||
'Ground Transport',
|
||||
],
|
||||
|
||||
'refund-type': [
|
||||
'Full Refund',
|
||||
'Partial Refund',
|
||||
'Future Travel Credit',
|
||||
'Travel Voucher',
|
||||
'Tax Refund Only',
|
||||
'Telephone Reimbursement',
|
||||
],
|
||||
|
||||
'flight-disruption-type': [
|
||||
'Cancellation',
|
||||
'Delay',
|
||||
'Denied Boarding',
|
||||
'Diversion',
|
||||
'Missed Connection',
|
||||
],
|
||||
|
||||
'airline-responsibility': [
|
||||
'Airline Responsible',
|
||||
'Airport Responsible',
|
||||
'ATC Responsible',
|
||||
'Passenger Responsible',
|
||||
'Shared Responsibility',
|
||||
'Third Party Responsible',
|
||||
'Snow',
|
||||
],
|
||||
|
||||
'weather-condition': [
|
||||
'Fog',
|
||||
'Heavy Rain',
|
||||
'Hurricane',
|
||||
'Ice',
|
||||
'Lightning',
|
||||
'Sandstorm',
|
||||
'Thunderstorm',
|
||||
],
|
||||
|
||||
'atc-restriction': [
|
||||
'Airspace Closure',
|
||||
'Flow Control',
|
||||
'Ground Stop',
|
||||
'Slot Restriction',
|
||||
'Traffic Congestion',
|
||||
'Navigation System',
|
||||
],
|
||||
|
||||
'technical-fault-category': [
|
||||
'Aircraft Damage',
|
||||
'Avionics',
|
||||
'Cabin Systems',
|
||||
'Engine',
|
||||
'Hydraulic System',
|
||||
'Landing Gear',
|
||||
'Volcanic Ash',
|
||||
'Wind Shear',
|
||||
],
|
||||
};
|
||||
|
||||
const values = Object.entries(masterData).flatMap(([categoryCode, items]) =>
|
||||
items.map((value, index) => ({
|
||||
categoryId: categoryMap.get(categoryCode),
|
||||
code: value.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
||||
value,
|
||||
tenantId: resolvedTenantId,
|
||||
displayOrder: index + 1,
|
||||
})),
|
||||
);
|
||||
|
||||
await this.ruleCategoryValueRepo.save(
|
||||
this.ruleCategoryValueRepo.create(values),
|
||||
);
|
||||
}
|
||||
|
||||
if ((await this.operatorRepo.count({ where: { tenantId: resolvedTenantId } })) === 0) {
|
||||
const operators = [
|
||||
{ code: 'EQ', name: 'Equals', symbol: '=', displayOrder: 1 },
|
||||
{ code: 'NE', name: 'Not Equals', symbol: '!=', displayOrder: 2 },
|
||||
{ code: 'GT', name: 'Greater Than', symbol: '>', displayOrder: 3 },
|
||||
{ code: 'GTE', name: 'Greater Than or Equal', symbol: '>=', displayOrder: 4 },
|
||||
{ code: 'LT', name: 'Less Than', symbol: '<', displayOrder: 5 },
|
||||
{ code: 'LTE', name: 'Less Than or Equal', symbol: '<=', displayOrder: 6 },
|
||||
{ code: 'BETWEEN', name: 'Between', symbol: 'BETWEEN', displayOrder: 7 },
|
||||
{ code: 'CONTAINS', name: 'Contains', symbol: 'CONTAINS', displayOrder: 8 },
|
||||
{ code: 'IN', name: 'In', symbol: 'IN', displayOrder: 9 },
|
||||
{ code: 'IS_EMPTY', name: 'Is Empty', symbol: 'IS EMPTY', displayOrder: 10 },
|
||||
].map((item) => ({ ...item, tenantId: resolvedTenantId }));
|
||||
|
||||
await this.operatorRepo.save(this.operatorRepo.create(operators));
|
||||
}
|
||||
|
||||
this.logger.log(`Master data seeded for tenant ${resolvedTenantId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsArray,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { PolicyStatus, AudienceType, LogicalOperator } from '../entities/policy.enums';
|
||||
|
||||
export class CreatePolicyTargetAudienceDto {
|
||||
@IsEnum(AudienceType)
|
||||
targetType: AudienceType;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
targetId?: string;
|
||||
}
|
||||
|
||||
export class CreateRuleConditionDto {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
fieldId: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
operatorId: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
valueText?: string;
|
||||
|
||||
@IsEnum(LogicalOperator)
|
||||
@IsOptional()
|
||||
logicalOperator?: LogicalOperator;
|
||||
|
||||
@IsInt()
|
||||
sequence: number;
|
||||
}
|
||||
|
||||
export class CreatePolicyActionDto {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
actionTypeId: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
parameterId?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
valueText?: string;
|
||||
|
||||
@IsInt()
|
||||
sequence: number;
|
||||
}
|
||||
|
||||
export class CreatePolicyRuleDto {
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
ruleCategoryId?: string;
|
||||
|
||||
@IsInt()
|
||||
priority: number;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateRuleConditionDto)
|
||||
conditions: CreateRuleConditionDto[];
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreatePolicyActionDto)
|
||||
actions: CreatePolicyActionDto[];
|
||||
}
|
||||
|
||||
export class CreatePolicyDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
policyName: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
jurisdictionId?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@IsEnum(PolicyStatus)
|
||||
@IsOptional()
|
||||
status?: PolicyStatus;
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
version?: number;
|
||||
|
||||
@IsEnum(AudienceType)
|
||||
@IsOptional()
|
||||
audienceType?: AudienceType;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
createdBy?: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
updatedBy?: string;
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreatePolicyTargetAudienceDto)
|
||||
targetAudiences?: CreatePolicyTargetAudienceDto[];
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreatePolicyRuleDto)
|
||||
rules?: CreatePolicyRuleDto[];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreatePolicyDto } from './create-policy.dto';
|
||||
|
||||
export class UpdatePolicyDto extends PartialType(CreatePolicyDto) {}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { PolicyRule } from './policy-rule.entity';
|
||||
|
||||
@Entity({ name: 'policy_actions', schema: 'policy_engine' })
|
||||
export class PolicyAction {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'rule_id', type: 'uuid' })
|
||||
ruleId: string;
|
||||
|
||||
@Column({ name: 'action_type_id', type: 'uuid' })
|
||||
actionTypeId: string;
|
||||
|
||||
@Column({ name: 'parameter_id', type: 'uuid', nullable: true })
|
||||
parameterId: string;
|
||||
|
||||
@Column({ name: 'value_text', type: 'text', nullable: true })
|
||||
valueText: string;
|
||||
|
||||
@Column({ type: 'int', default: 0 })
|
||||
sequence: number;
|
||||
|
||||
@ManyToOne(() => PolicyRule, (rule) => rule.actions, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'rule_id' })
|
||||
rule: PolicyRule;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
|
||||
import { Policy } from './policy.entity';
|
||||
import { RuleCondition } from './rule-condition.entity';
|
||||
import { PolicyAction } from './policy-action.entity';
|
||||
|
||||
@Entity({ name: 'policy_rules', schema: 'policy_engine' })
|
||||
export class PolicyRule {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'policy_id', type: 'uuid' })
|
||||
policyId: string;
|
||||
|
||||
@Column({ name: 'rule_category_id', type: 'uuid', nullable: true })
|
||||
ruleCategoryId: string;
|
||||
|
||||
@Column({ type: 'int', default: 0 })
|
||||
priority: number;
|
||||
|
||||
@ManyToOne(() => Policy, (policy) => policy.rules, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'policy_id' })
|
||||
policy: Policy;
|
||||
|
||||
@OneToMany(() => RuleCondition, (condition) => condition.rule, { cascade: true, onDelete: 'CASCADE' })
|
||||
conditions: RuleCondition[];
|
||||
|
||||
@OneToMany(() => PolicyAction, (action) => action.rule, { cascade: true, onDelete: 'CASCADE' })
|
||||
actions: PolicyAction[];
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Policy } from './policy.entity';
|
||||
import { AudienceType } from './policy.enums';
|
||||
|
||||
@Entity({ name: 'policy_target_audience', schema: 'policy_engine' })
|
||||
export class PolicyTargetAudience {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'policy_id', type: 'uuid' })
|
||||
policyId: string;
|
||||
|
||||
@Column({
|
||||
name: 'target_type',
|
||||
type: 'enum',
|
||||
enum: AudienceType,
|
||||
})
|
||||
targetType: AudienceType;
|
||||
|
||||
@Column({ name: 'target_id', type: 'uuid', nullable: true })
|
||||
targetId: string;
|
||||
|
||||
@ManyToOne(() => Policy, (policy) => policy.targetAudiences, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'policy_id' })
|
||||
policy: Policy;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { TenantOwnedEntity } from '../../../common/entities/tenant-owned.entity';
|
||||
import { PolicyStatus, AudienceType } from './policy.enums';
|
||||
import { PolicyTargetAudience } from './policy-target-audience.entity';
|
||||
import { PolicyRule } from './policy-rule.entity';
|
||||
import { Jurisdiction } from '../../master-data/entities/jurisdiction.entity';
|
||||
|
||||
@Entity({ name: 'policies', schema: 'policy_engine' })
|
||||
export class Policy extends TenantOwnedEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'policy_name', type: 'varchar', length: 200 })
|
||||
policyName: string;
|
||||
|
||||
@Column({ name: 'jurisdiction_id', type: 'uuid', nullable: true })
|
||||
jurisdictionId: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: PolicyStatus,
|
||||
default: PolicyStatus.DRAFT,
|
||||
})
|
||||
status: PolicyStatus;
|
||||
|
||||
@Column({ type: 'int', default: 1 })
|
||||
version: number;
|
||||
|
||||
@Column({
|
||||
name: 'audience_type',
|
||||
type: 'enum',
|
||||
enum: AudienceType,
|
||||
default: AudienceType.ALL,
|
||||
})
|
||||
audienceType: AudienceType;
|
||||
|
||||
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||
createdBy: string;
|
||||
|
||||
@Column({ name: 'updated_by', type: 'uuid', nullable: true })
|
||||
updatedBy: string;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at' })
|
||||
updatedAt: Date;
|
||||
|
||||
// Relations
|
||||
@OneToMany(() => PolicyTargetAudience, (target) => target.policy, { cascade: true, onDelete: 'CASCADE' })
|
||||
targetAudiences: PolicyTargetAudience[];
|
||||
|
||||
@OneToMany(() => PolicyRule, (rule) => rule.policy, { cascade: true, onDelete: 'CASCADE' })
|
||||
rules: PolicyRule[];
|
||||
|
||||
@ManyToOne(() => Jurisdiction, { nullable: true, eager: false, createForeignKeyConstraints: false })
|
||||
@JoinColumn({ name: 'jurisdiction_id' })
|
||||
jurisdiction: Jurisdiction;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export enum PolicyStatus {
|
||||
DRAFT = 'draft',
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
ARCHIVED = 'archived',
|
||||
}
|
||||
|
||||
export enum AudienceType {
|
||||
ALL = 'ALL',
|
||||
COHORT = 'COHORT',
|
||||
}
|
||||
|
||||
export enum LogicalOperator {
|
||||
AND = 'AND',
|
||||
OR = 'OR',
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { PolicyRule } from './policy-rule.entity';
|
||||
import { LogicalOperator } from './policy.enums';
|
||||
|
||||
@Entity({ name: 'policy_rule_conditions', schema: 'policy_engine' })
|
||||
export class RuleCondition {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'rule_id', type: 'uuid' })
|
||||
ruleId: string;
|
||||
|
||||
@Column({ name: 'field_id', type: 'uuid' })
|
||||
fieldId: string;
|
||||
|
||||
@Column({ name: 'operator_id', type: 'uuid' })
|
||||
operatorId: string;
|
||||
|
||||
@Column({ name: 'value_text', type: 'text', nullable: true })
|
||||
valueText: string;
|
||||
|
||||
@Column({
|
||||
name: 'logical_operator',
|
||||
type: 'enum',
|
||||
enum: LogicalOperator,
|
||||
nullable: true,
|
||||
})
|
||||
logicalOperator: LogicalOperator;
|
||||
|
||||
@Column({ type: 'int', default: 0 })
|
||||
sequence: number;
|
||||
|
||||
@ManyToOne(() => PolicyRule, (rule) => rule.conditions, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'rule_id' })
|
||||
rule: PolicyRule;
|
||||
}
|
||||
@@ -1,7 +1,66 @@
|
||||
import { Controller } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Patch,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { PolicyEngineService } from './policy-engine.service';
|
||||
import { CreatePolicyDto } from './dto/create-policy.dto';
|
||||
import { UpdatePolicyDto } from './dto/update-policy.dto';
|
||||
import { PolicyStatus, AudienceType } from './entities/policy.enums';
|
||||
|
||||
@Controller('policy-engine')
|
||||
export class PolicyEngineController {
|
||||
constructor(private readonly policyEngineService: PolicyEngineService) { }
|
||||
|
||||
@Post()
|
||||
async create(@Body() createPolicyDto: CreatePolicyDto) {
|
||||
return this.policyEngineService.create(createPolicyDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
async findAll(
|
||||
@Query('page') page: string = '1',
|
||||
@Query('limit') limit: string = '10',
|
||||
@Query('status') status?: PolicyStatus,
|
||||
@Query('audienceType') audienceType?: AudienceType,
|
||||
) {
|
||||
return this.policyEngineService.findAll(
|
||||
Number(page),
|
||||
Number(limit),
|
||||
status,
|
||||
audienceType,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
return this.policyEngineService.findOne(id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() updatePolicyDto: UpdatePolicyDto,
|
||||
) {
|
||||
return this.policyEngineService.update(id, updatePolicyDto);
|
||||
}
|
||||
|
||||
@Patch(':id/status')
|
||||
async updateStatus(
|
||||
@Param('id') id: string,
|
||||
@Body('status') status: PolicyStatus,
|
||||
) {
|
||||
return this.policyEngineService.updateStatus(id, status);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async remove(@Param('id') id: string) {
|
||||
return this.policyEngineService.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { PolicyEngineService } from './policy-engine.service';
|
||||
import { PolicyEngineController } from './policy-engine.controller';
|
||||
import { Policy } from './entities/policy.entity';
|
||||
import { PolicyTargetAudience } from './entities/policy-target-audience.entity';
|
||||
import { PolicyRule } from './entities/policy-rule.entity';
|
||||
import { RuleCondition } from './entities/rule-condition.entity';
|
||||
import { PolicyAction } from './entities/policy-action.entity';
|
||||
import { MasterDataModule } from '../master-data/master-data.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
Policy,
|
||||
PolicyTargetAudience,
|
||||
PolicyRule,
|
||||
RuleCondition,
|
||||
PolicyAction,
|
||||
]),
|
||||
MasterDataModule,
|
||||
],
|
||||
controllers: [PolicyEngineController],
|
||||
providers: [PolicyEngineService],
|
||||
exports: [PolicyEngineService],
|
||||
|
||||
@@ -1,4 +1,173 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { getTenantId } from '../../common/tenant/tenant.context';
|
||||
import { Policy } from './entities/policy.entity';
|
||||
import { PolicyTargetAudience } from './entities/policy-target-audience.entity';
|
||||
import { PolicyRule } from './entities/policy-rule.entity';
|
||||
import { RuleCondition } from './entities/rule-condition.entity';
|
||||
import { PolicyAction } from './entities/policy-action.entity';
|
||||
import { CreatePolicyDto } from './dto/create-policy.dto';
|
||||
import { UpdatePolicyDto } from './dto/update-policy.dto';
|
||||
import { PolicyStatus, AudienceType } from './entities/policy.enums';
|
||||
import { Jurisdiction } from '../master-data/entities/jurisdiction.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PolicyEngineService {}
|
||||
export class PolicyEngineService {
|
||||
constructor(
|
||||
@InjectRepository(Policy)
|
||||
private readonly policyRepository: Repository<Policy>,
|
||||
@InjectRepository(Jurisdiction)
|
||||
private readonly jurisdictionRepository: Repository<Jurisdiction>,
|
||||
) {}
|
||||
|
||||
private getRelations() {
|
||||
return {
|
||||
jurisdiction: true,
|
||||
targetAudiences: true,
|
||||
rules: {
|
||||
conditions: true,
|
||||
actions: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async create(createPolicyDto: CreatePolicyDto): Promise<Policy> {
|
||||
const tenantId = getTenantId();
|
||||
|
||||
// Create the policy entity and cascade nested associations using TypeORM
|
||||
const policy = this.policyRepository.create({
|
||||
...createPolicyDto,
|
||||
tenantId,
|
||||
});
|
||||
|
||||
const saved = await this.policyRepository.save(policy);
|
||||
return this.findOne(saved.id);
|
||||
}
|
||||
|
||||
async findAll(
|
||||
page: number = 1,
|
||||
limit: number = 10,
|
||||
status?: PolicyStatus,
|
||||
audienceType?: AudienceType,
|
||||
) {
|
||||
const skip = (page - 1) * limit;
|
||||
const tenantId = getTenantId();
|
||||
|
||||
const whereClause: any = { tenantId };
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
if (audienceType) {
|
||||
whereClause.audienceType = audienceType;
|
||||
}
|
||||
|
||||
const [data, total] = await this.policyRepository.findAndCount({
|
||||
where: whereClause,
|
||||
skip,
|
||||
take: limit,
|
||||
order: { createdAt: 'DESC' },
|
||||
relations: this.getRelations(),
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<Policy> {
|
||||
const tenantId = getTenantId();
|
||||
const policy = await this.policyRepository.findOne({
|
||||
where: { id, tenantId },
|
||||
relations: this.getRelations(),
|
||||
});
|
||||
|
||||
if (!policy) {
|
||||
throw new NotFoundException(`Policy with ID ${id} not found`);
|
||||
}
|
||||
|
||||
return policy;
|
||||
}
|
||||
|
||||
async update(id: string, updatePolicyDto: UpdatePolicyDto): Promise<Policy> {
|
||||
// Ensure policy exists and belongs to the tenant
|
||||
await this.findOne(id);
|
||||
|
||||
const { targetAudiences, rules, ...policyData } = updatePolicyDto;
|
||||
|
||||
// Execute database operations in a transaction for clean replacement of nested objects
|
||||
return this.policyRepository.manager.transaction(async (transactionalEntityManager) => {
|
||||
// 1. Delete all existing target audiences associated with this policy
|
||||
await transactionalEntityManager.delete(PolicyTargetAudience, { policyId: id });
|
||||
|
||||
// 2. Find and delete existing rules (cascades to conditions and actions)
|
||||
const existingRules = await transactionalEntityManager.find(PolicyRule, { where: { policyId: id } });
|
||||
if (existingRules.length > 0) {
|
||||
await transactionalEntityManager.remove(PolicyRule, existingRules);
|
||||
}
|
||||
|
||||
// 3. Fetch original policy again in transaction to ensure we have a fresh copy
|
||||
const policy = await transactionalEntityManager.findOneOrFail(Policy, {
|
||||
where: { id, tenantId: getTenantId() },
|
||||
});
|
||||
|
||||
// 4. Update policy basic columns
|
||||
Object.assign(policy, policyData);
|
||||
|
||||
// 5. Build new Target Audience entities if provided
|
||||
if (targetAudiences) {
|
||||
policy.targetAudiences = targetAudiences.map((target) =>
|
||||
transactionalEntityManager.create(PolicyTargetAudience, {
|
||||
...target,
|
||||
policyId: id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// 6. Build new Policy Rule entities with Conditions and Actions if provided
|
||||
if (rules) {
|
||||
policy.rules = rules.map((ruleDto) => {
|
||||
const conditions = ruleDto.conditions?.map((cond) =>
|
||||
transactionalEntityManager.create(RuleCondition, cond),
|
||||
) || [];
|
||||
|
||||
const actions = ruleDto.actions?.map((act) =>
|
||||
transactionalEntityManager.create(PolicyAction, act),
|
||||
) || [];
|
||||
|
||||
return transactionalEntityManager.create(PolicyRule, {
|
||||
ruleCategoryId: ruleDto.ruleCategoryId,
|
||||
priority: ruleDto.priority,
|
||||
policyId: id,
|
||||
conditions,
|
||||
actions,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 7. Save updated policy and cascades
|
||||
const savedPolicy = await transactionalEntityManager.save(Policy, policy);
|
||||
|
||||
// Reload updated policy with all relations and return
|
||||
return transactionalEntityManager.findOneOrFail(Policy, {
|
||||
where: { id: savedPolicy.id },
|
||||
relations: this.getRelations(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async updateStatus(id: string, status: PolicyStatus): Promise<Policy> {
|
||||
const policy = await this.findOne(id);
|
||||
policy.status = status;
|
||||
return this.policyRepository.save(policy);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const policy = await this.findOne(id);
|
||||
await this.policyRepository.remove(policy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"target": "ES2023",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
|
||||
Reference in New Issue
Block a user