import { Injectable } from '@nestjs/common'; import { LinkedAttributes } from 'src/module/linked_attributes/entity/linked_attribute.entity'; import { AttributeMaster } from 'src/module/meta/entity/attribute-master.entity'; import { EntityRelation } from 'src/module/meta/entity/entity-relation.entity'; import { EntityServiceImpl } from 'src/module/meta/service/entity-service-impl.service'; import { LoggingService } from 'src/utils/service/loggingUtil.service'; import { DataSource } from 'typeorm'; import { EntityJSONRepository } from './entityJson.repository'; import { FilterService } from '../../filter/service/filter.service'; @Injectable() export class EntityJSONService extends EntityServiceImpl { constructor( private readonly dataSource: DataSource, private readonly loggerService: LoggingService, private readonly EntityJSONRepository: EntityJSONRepository, private readonly filterService: FilterService, ) { super(); } async getAttributeForFlatJSON( entityType: string, loggedInUser: any, flag?: 'flat_json' | 'dropdown' | 'all', ) { const entId = loggedInUser.entId; await this.loggerService.log('info', 'EntityJSONService', 'getAttributeForFlatJSON', `Loading attributes for entity: ${entityType}, ent: ${entId}`); const mainAttributes = await this.dataSource .getRepository(AttributeMaster) .createQueryBuilder('attr') .select(['attr.id', 'attr.name', 'attr.flat_json_key', 'attr.attribute_key']) .where('attr.mapped_entity_type = :entityType', { entityType }) .andWhere('attr.enterprise_id = :entId', { entId }) .getMany(); await this.loggerService.log('debug', 'EntityJSONService', 'getAttributeForFlatJSON', `Loaded ${mainAttributes.length} main attributes`); const relatedEntityTypes = await this.dataSource .getRepository(EntityRelation) .createQueryBuilder('rel') .select(['rel.target_entity_type']) .where('rel.source_entity_type = :entityType', { entityType }) .andWhere('rel.enterprise_id = :entId', { entId }) .andWhere('rel.relation_type = :relationType', { relationType: 'ONE_TO_ONE' }) .getRawMany(); const relatedTypesList = relatedEntityTypes.map(x => x.rel_target_entity_type); await this.loggerService.log('debug', 'EntityJSONService', 'getAttributeForFlatJSON', `Found ${relatedTypesList.length} ONE-TO-ONE related entity types`); const relatedAttributes = relatedTypesList.length ? await this.dataSource .getRepository(AttributeMaster) .createQueryBuilder('attr') .select(['attr.id', 'attr.name', 'attr.flat_json_key', 'attr.mapped_entity_type', 'attr.attribute_key']) .where('attr.mapped_entity_type IN (:...types)', { types: relatedTypesList }) .andWhere('attr.enterprise_id = :entId', { entId }) .getMany() : []; await this.loggerService.log('debug', 'EntityJSONService', 'getAttributeForFlatJSON', `Loaded ${relatedAttributes.length} related attributes`); const linkedAttributes = await this.dataSource .getRepository(LinkedAttributes) .createQueryBuilder('fla') .innerJoin( AttributeMaster, 'attr', 'fla.applicable_entity_type = attr.mapped_entity_type AND fla.applicable_attribute_key = attr.attribute_key', ) .select([ 'fla.applicable_entity_type AS applicable_entity_type', 'fla.applicable_attribute_key AS applicable_attribute_key', 'fla.attribute_key AS target_attribute_key', 'fla.saved_filter_code AS saved_filter_code', 'attr.name AS name', 'attr.id AS id', ]) .where('attr.enterprise_id = :entId', { entId }) .getRawMany(); await this.loggerService.log('debug', 'EntityJSONService', 'getAttributeForFlatJSON', `Loaded ${linkedAttributes.length} linked attributes`); if (flag === 'flat_json' || flag === 'all') { const result: Record = {}; mainAttributes.forEach(attr => { if (attr.flat_json_key) result[attr.flat_json_key] = null; }); relatedAttributes.forEach(attr => { if (attr.flat_json_key) result[attr.flat_json_key] = null; }); linkedAttributes.forEach(link => { if (link.target_attribute_key) result[link.target_attribute_key] = null; }); if (flag === 'all') { return { flat_json: result, attributes: { mainAttributes, relatedAttributes, linkedAttributes } }; } return result; } const dropdown: any[] = []; dropdown.push(...mainAttributes.map(a => ({ label: a.name, value: a.flat_json_key }))); dropdown.push(...relatedAttributes.map(a => ({ label: a.name, value: a.flat_json_key }))); if (linkedAttributes.length > 0) { dropdown.push(...linkedAttributes.map(a => ({ label: a.name, value: a.attribute_key }))); } return dropdown; } async updateEntityJSON(entityType: string, entityId: number, loggedInUser) { await this.loggerService.log('info', 'EntityJSONService', 'updateEntityJSON', `Building flat JSON for entity: ${entityType}#${entityId}`); const response = await this.getAttributeForFlatJSON(entityType, loggedInUser, 'all'); if (!response || !('flat_json' in response) || !('attributes' in response) || !response.attributes) { await this.loggerService.log('error', 'EntityJSONService', 'updateEntityJSON', `getAttributeForFlatJSON() did not return expected structure`); return null; } const { flat_json: flatJson, attributes } = response; if (!flatJson) return null; const safeAttributes = { mainAttributes: attributes.mainAttributes || [], relatedAttributes: attributes.relatedAttributes || [], linkedAttributes: attributes.linkedAttributes || [], }; const attrMap: Record = {}; const allAttrs = [...safeAttributes.mainAttributes, ...safeAttributes.relatedAttributes]; allAttrs.forEach(attr => { if (attr.attribute_key) attrMap[attr.attribute_key] = attr.flat_json_key || attr.attribute_key; }); const mainData = await this.getResolvedEntityData(entityType, entityId, loggedInUser); this.mergeEntityDataIntoFlatJson(flatJson, mainData, attrMap); const relations = await this.dataSource .getRepository('frm_entity_relation_data') .createQueryBuilder('erd') .select(['erd.target_entity_id AS target_entity_id', 'erd.target_entity_type AS target_entity_type']) .where('erd.source_entity_type = :entityType', { entityType }) .andWhere('erd.source_entity_id = :entityId', { entityId }) .andWhere('erd.relation_type = :rt', { rt: 'ONE_TO_ONE' }) .getRawMany(); for (const rel of relations) { const relatedData = await this.getResolvedEntityData(rel.target_entity_type, rel.target_entity_id, loggedInUser); this.mergeEntityDataIntoFlatJson(flatJson, relatedData, attrMap); } for (const linkAttr of safeAttributes.linkedAttributes) { const childEntityType = linkAttr.applicable_entity_type; const sourceKey = linkAttr.applicable_attribute_key; const targetKey = linkAttr.target_attribute_key; if (!childEntityType || !sourceKey || !targetKey) continue; const mappingValue = mainData?.[sourceKey] ?? null; const value = await this.applyLinkedFilterUsingSavedFilter( childEntityType, linkAttr.saved_filter_code, sourceKey, mappingValue, targetKey, loggedInUser, entityId, ); if (value !== null && value !== undefined) { flatJson[targetKey] = value; } } await this.loggerService.log('info', 'EntityJSONService', 'updateEntityJSON', `Saving flat JSON for entity: ${entityType}#${entityId}`); let JsonData = { entity_type: entityType, entity_id: entityId, json_data: flatJson, created_by: loggedInUser.id, }; await this.EntityJSONRepository.create(JsonData); return flatJson; } private mergeEntityDataIntoFlatJson(flatJson: Record, entityData: any | any[], attrMap: Record) { const records = Array.isArray(entityData) ? entityData : [entityData]; for (const record of records) { if (!record || typeof record !== 'object') continue; for (const key of Object.keys(record)) { const flatKey = attrMap[key] || key; if (flatJson.hasOwnProperty(flatKey)) { flatJson[flatKey] = record[key]; } } } } private async applyLinkedFilterUsingSavedFilter( childEntityType: string, savedFilterCode: string, childFilterAttribute: string, mappingValue: any, targetAttribute: string, loggedInUser, entity_id, ) { if (!savedFilterCode && (mappingValue === null || mappingValue === undefined)) return null; const dto: any = { entity_type: childEntityType, savedFilterCode, page: 1, size: 1, }; if (mappingValue !== null && mappingValue !== undefined && mappingValue !== '') { dto.quickFilter = [ { filter_attribute: childFilterAttribute, filter_operator: 'equal', filter_value: mappingValue }, ]; } dto.quickFilter = [ { filter_attribute: 'parent_id', filter_operator: 'equal', filter_value: [entity_id] }, ]; const result = await this.filterService.applyFilter(dto); const rows = result?.data?.entity_list || []; return rows.length ? (rows[0][childFilterAttribute] ?? null) : null; } }