Assist_Design/apps/bff/src/modules/support/support.service.ts
barsa 9e27380069 Update TypeScript configurations, improve module imports, and clean up Dockerfiles
- Adjusted TypeScript settings in tsconfig files for better alignment with ESNext standards.
- Updated pnpm-lock.yaml to reflect dependency changes and improve package management.
- Cleaned up Dockerfiles for both BFF and Portal applications to enhance build processes.
- Modified import statements across various modules to include file extensions for consistency.
- Removed outdated SHA256 files for backend and frontend tarballs to streamline project structure.
- Enhanced health check mechanisms in Dockerfiles for improved application startup reliability.
2025-12-10 16:08:34 +09:00

193 lines
5.4 KiB
TypeScript

import { Injectable, Inject, NotFoundException, ForbiddenException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import {
SUPPORT_CASE_PRIORITY,
SUPPORT_CASE_STATUS,
type SupportCase,
type SupportCaseFilter,
type SupportCaseList,
type CreateCaseRequest,
type CreateCaseResponse,
} from "@customer-portal/domain/support";
import { SalesforceCaseService } from "@bff/integrations/salesforce/services/salesforce-case.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { getErrorMessage } from "@bff/core/utils/error.util.js";
/**
* Status values that indicate an open/active case
* (Display values after mapping from Salesforce Japanese API names)
*/
const OPEN_STATUSES: string[] = [
SUPPORT_CASE_STATUS.NEW,
SUPPORT_CASE_STATUS.IN_PROGRESS,
SUPPORT_CASE_STATUS.AWAITING_APPROVAL,
];
/**
* Status values that indicate a resolved/closed case
* (Display values after mapping from Salesforce Japanese API names)
*/
const RESOLVED_STATUSES: string[] = [
SUPPORT_CASE_STATUS.VPN_PENDING,
SUPPORT_CASE_STATUS.PENDING,
SUPPORT_CASE_STATUS.RESOLVED,
SUPPORT_CASE_STATUS.CLOSED,
];
/**
* Priority values that indicate high priority
*/
const HIGH_PRIORITIES: string[] = [SUPPORT_CASE_PRIORITY.HIGH];
@Injectable()
export class SupportService {
constructor(
private readonly caseService: SalesforceCaseService,
private readonly mappingsService: MappingsService,
@Inject(Logger) private readonly logger: Logger
) {}
/**
* List cases for a user with optional filters
*/
async listCases(userId: string, filters?: SupportCaseFilter): Promise<SupportCaseList> {
const accountId = await this.getAccountIdForUser(userId);
try {
// SalesforceCaseService now returns SupportCase[] directly using domain mappers
const cases = await this.caseService.getCasesForAccount(accountId);
const filteredCases = this.applyFilters(cases, filters);
const summary = this.buildSummary(filteredCases);
return { cases: filteredCases, summary };
} catch (error) {
this.logger.error("Failed to list support cases", {
userId,
error: getErrorMessage(error),
});
throw error;
}
}
/**
* Get a single case by ID
*/
async getCase(userId: string, caseId: string): Promise<SupportCase> {
const accountId = await this.getAccountIdForUser(userId);
try {
// SalesforceCaseService now returns SupportCase directly using domain mappers
const supportCase = await this.caseService.getCaseById(caseId, accountId);
if (!supportCase) {
throw new NotFoundException("Support case not found");
}
return supportCase;
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
this.logger.error("Failed to get support case", {
userId,
caseId,
error: getErrorMessage(error),
});
throw error;
}
}
/**
* Create a new support case
*/
async createCase(userId: string, request: CreateCaseRequest): Promise<CreateCaseResponse> {
const accountId = await this.getAccountIdForUser(userId);
try {
const result = await this.caseService.createCase({
subject: request.subject,
description: request.description,
category: request.category,
priority: request.priority,
accountId,
});
this.logger.log("Support case created", {
userId,
caseId: result.id,
caseNumber: result.caseNumber,
});
return result;
} catch (error) {
this.logger.error("Failed to create support case", {
userId,
error: getErrorMessage(error),
});
throw error;
}
}
/**
* Get Salesforce account ID for a user
*/
private async getAccountIdForUser(userId: string): Promise<string> {
const mapping = await this.mappingsService.findByUserId(userId);
if (!mapping?.sfAccountId) {
this.logger.warn("No Salesforce account mapping found for user", { userId });
throw new ForbiddenException("Account not linked to Salesforce");
}
return mapping.sfAccountId;
}
/**
* Apply filters to cases
*/
private applyFilters(cases: SupportCase[], filters?: SupportCaseFilter): SupportCase[] {
if (!filters) {
return cases;
}
const search = filters.search?.toLowerCase().trim();
return cases.filter(supportCase => {
if (filters.status && supportCase.status !== filters.status) {
return false;
}
if (filters.priority && supportCase.priority !== filters.priority) {
return false;
}
if (filters.category && supportCase.category !== filters.category) {
return false;
}
if (search) {
const haystack =
`${supportCase.subject} ${supportCase.description} ${supportCase.caseNumber}`.toLowerCase();
if (!haystack.includes(search)) {
return false;
}
}
return true;
});
}
/**
* Build summary statistics for cases
*/
private buildSummary(cases: SupportCase[]): SupportCaseList["summary"] {
const open = cases.filter(c => OPEN_STATUSES.includes(c.status)).length;
const highPriority = cases.filter(c => HIGH_PRIORITIES.includes(c.priority)).length;
const resolved = cases.filter(c => RESOLVED_STATUSES.includes(c.status)).length;
return {
total: cases.length,
open,
highPriority,
resolved,
};
}
}