forked from Yara724/api
car body implemented
This commit is contained in:
@@ -49,13 +49,31 @@ import {
|
||||
pickStoredCarBodyPolicyId,
|
||||
type FanavaranClaimProduct,
|
||||
} from "./fanavaran-claim-product";
|
||||
import {
|
||||
actualPremiumFromHullPolicyRecord,
|
||||
customerIdFromHullPolicyRecord,
|
||||
FANAVARAN_DEFAULT_HULL_CULPRIT_TYPE_ID,
|
||||
fanavaranHullNestedClaimId,
|
||||
toFanavaranHullBaseClaimPayload,
|
||||
} from "./fanavaran-hull-base-claim";
|
||||
import {
|
||||
collectFanavaranExpertiseReadinessWarnings,
|
||||
hullExpertiseAssertFields,
|
||||
mergeHullVehicleIdentityFromFanavaranVehicle,
|
||||
pickHullVehicleIdentity,
|
||||
toFanavaranHullExpertiseDmgSection,
|
||||
toFanavaranHullExpertisePayload,
|
||||
vehicleCurrentValueFromHullPolicyRecord,
|
||||
} from "./fanavaran-hull-expertise";
|
||||
import {
|
||||
asHullDmgAccessoryRows,
|
||||
FANAVARAN_DEFAULT_HULL_DMG_COST_KIND_ID,
|
||||
FANAVARAN_DEFAULT_HULL_DMG_KIND_ID,
|
||||
FANAVARAN_HULL_DMG_COST_KIND_CAPTION,
|
||||
FANAVARAN_HULL_DMG_KIND_CAPTION,
|
||||
findLookupIdByCaption,
|
||||
resolveVehicleHullAccessoryId,
|
||||
} from "./fanavaran-hull-expertise-lookups";
|
||||
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
|
||||
import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto";
|
||||
import {
|
||||
@@ -4219,6 +4237,307 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
/** GEN.03 hull: TotalPremium + parties inquiry (AccidentCulpritId) before hull field whitelist. */
|
||||
private async enrichCarBodyHullBaseClaimPayload(input: {
|
||||
clientKey: FanavaranClientKey;
|
||||
payload: Record<string, unknown>;
|
||||
blameCase: { parties?: unknown[] };
|
||||
/** Blame `Party` row (FIRST party for CAR_BODY); person shape is read via pickPerson* helpers. */
|
||||
firstParty?: { person?: unknown };
|
||||
logPrefix: string;
|
||||
}): Promise<void> {
|
||||
const policyId = parseFanavaranId(input.payload.PolicyId);
|
||||
if (policyId == null) return;
|
||||
|
||||
let policyRecord: Record<string, unknown> | null = null;
|
||||
try {
|
||||
policyRecord = asObjectRecord(
|
||||
await this.fanavaranLookupService.bodyPolicyById(
|
||||
input.clientKey,
|
||||
policyId,
|
||||
{
|
||||
contractIdOverride: resolveFanavaranProductContractId(
|
||||
input.clientKey,
|
||||
"car-body",
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`${input.logPrefix} hull base claim body policy GET ${policyId} failed: ${
|
||||
error instanceof Error ? error.message : error
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const actualPremium = actualPremiumFromHullPolicyRecord(policyRecord);
|
||||
if (actualPremium != null) {
|
||||
input.payload.ActualPremium = actualPremium;
|
||||
}
|
||||
|
||||
const person = input.firstParty?.person as
|
||||
| {
|
||||
driverIsInsurer?: boolean;
|
||||
nationalCodeOfDriver?: unknown;
|
||||
nationalCodeOfInsurer?: unknown;
|
||||
nationalCode?: unknown;
|
||||
birthday?: unknown;
|
||||
driverBirthday?: unknown;
|
||||
insurerBirthday?: unknown;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
const nationalCode =
|
||||
pickPersonNationalCode(person) ??
|
||||
pickCarBodyPolicyNationalCode(
|
||||
input.blameCase as { parties?: unknown[] },
|
||||
);
|
||||
const birthday = pickPersonBirthday(person);
|
||||
const driverIsInsurer = person?.driverIsInsurer ?? true;
|
||||
|
||||
let accidentCulpritId: number | null = null;
|
||||
if (nationalCode && birthday) {
|
||||
accidentCulpritId = await this.resolveDriverFanavaranId(
|
||||
input.clientKey,
|
||||
nationalCode,
|
||||
birthday,
|
||||
driverIsInsurer,
|
||||
);
|
||||
}
|
||||
if (accidentCulpritId == null) {
|
||||
accidentCulpritId = customerIdFromHullPolicyRecord(policyRecord);
|
||||
if (accidentCulpritId != null) {
|
||||
this.logger.log(
|
||||
`${input.logPrefix} AccidentCulpritId from policy CustomerId=${accidentCulpritId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (accidentCulpritId != null) {
|
||||
input.payload.AccidentCulpritId = accidentCulpritId;
|
||||
}
|
||||
|
||||
const culpritTypeId =
|
||||
parseFanavaranId(input.payload.CulpritTypeId) ??
|
||||
FANAVARAN_DEFAULT_HULL_CULPRIT_TYPE_ID;
|
||||
if (
|
||||
input.payload.CustomerFaultPercent == null &&
|
||||
culpritTypeId === 300
|
||||
) {
|
||||
input.payload.CustomerFaultPercent = 100;
|
||||
}
|
||||
}
|
||||
|
||||
/** GEN.06: VehicleCurrentValue from hull policy when price-drop / frontend value is missing. */
|
||||
private async resolveHullVehicleCurrentValueFromPolicy(
|
||||
clientKey: FanavaranClientKey,
|
||||
claimCase: {
|
||||
fanavaranSync?: {
|
||||
baseClaim?: { policyId?: unknown; lastPayload?: { PolicyId?: unknown } };
|
||||
};
|
||||
},
|
||||
): Promise<number | null> {
|
||||
const policyId = parseFanavaranId(
|
||||
claimCase?.fanavaranSync?.baseClaim?.policyId ??
|
||||
claimCase?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId,
|
||||
);
|
||||
if (policyId == null) return null;
|
||||
try {
|
||||
const policy = asObjectRecord(
|
||||
await this.fanavaranLookupService.bodyPolicyById(
|
||||
clientKey,
|
||||
policyId,
|
||||
{
|
||||
contractIdOverride: resolveFanavaranProductContractId(
|
||||
clientKey,
|
||||
"car-body",
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
return vehicleCurrentValueFromHullPolicyRecord(policy);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`[resolveHullVehicleCurrentValueFromPolicy] body policy GET ${policyId} failed: ${
|
||||
error instanceof Error ? error.message : error
|
||||
}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private parseHullClaimPolicyId(claimCase: {
|
||||
fanavaranSync?: {
|
||||
baseClaim?: { policyId?: unknown; lastPayload?: { PolicyId?: unknown } };
|
||||
};
|
||||
}): number | null {
|
||||
return parseFanavaranId(
|
||||
claimCase?.fanavaranSync?.baseClaim?.policyId ??
|
||||
claimCase?.fanavaranSync?.baseClaim?.lastPayload?.PolicyId,
|
||||
);
|
||||
}
|
||||
|
||||
private async buildCarBodyFanavaranExpertisePayload(input: {
|
||||
clientKey: FanavaranClientKey;
|
||||
claimCase: any;
|
||||
blame: { parties?: unknown[] } | null;
|
||||
profile: ReturnType<typeof getFanavaranClientProfile>;
|
||||
parts: any[];
|
||||
claimExpertId: number;
|
||||
submittedAt: Date;
|
||||
priceDropTotal: number;
|
||||
priceDropCarPrice: unknown;
|
||||
warnings: string[];
|
||||
replyKey?: string;
|
||||
}): Promise<{
|
||||
payload: Record<string, unknown>;
|
||||
warnings: string[];
|
||||
replyKey?: string;
|
||||
}> {
|
||||
const policyId = this.parseHullClaimPolicyId(input.claimCase);
|
||||
const contractOptions = {
|
||||
contractIdOverride: resolveFanavaranProductContractId(
|
||||
input.clientKey,
|
||||
"car-body",
|
||||
),
|
||||
};
|
||||
|
||||
const [dmgKindRows, dmgCostKindRows, policyRecord, accessoriesRaw] =
|
||||
await Promise.all([
|
||||
this.getFanavaranLookupRows(input.clientKey, "vehicle-hull-dmg-kind"),
|
||||
this.getFanavaranLookupRows(
|
||||
input.clientKey,
|
||||
"vehicle-hull-dmg-cost-kinds",
|
||||
),
|
||||
policyId != null
|
||||
? this.fanavaranLookupService.bodyPolicyById(
|
||||
input.clientKey,
|
||||
policyId,
|
||||
contractOptions,
|
||||
)
|
||||
: Promise.resolve(null),
|
||||
policyId != null
|
||||
? this.fanavaranLookupService.vehicleHullDmgAccessoriesByPolicyId(
|
||||
input.clientKey,
|
||||
policyId,
|
||||
contractOptions,
|
||||
)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
|
||||
const dmgKindId =
|
||||
findLookupIdByCaption(dmgKindRows, FANAVARAN_HULL_DMG_KIND_CAPTION) ??
|
||||
FANAVARAN_DEFAULT_HULL_DMG_KIND_ID;
|
||||
const dmgCostKindId =
|
||||
findLookupIdByCaption(
|
||||
dmgCostKindRows,
|
||||
FANAVARAN_HULL_DMG_COST_KIND_CAPTION,
|
||||
) ?? FANAVARAN_DEFAULT_HULL_DMG_COST_KIND_ID;
|
||||
|
||||
const accessories = asHullDmgAccessoryRows(accessoriesRaw);
|
||||
if (policyId != null && accessories.length === 0) {
|
||||
input.warnings.push(
|
||||
`No hull dmg-accessories for policy ${policyId}; VehicleHullAccessoryId may be missing.`,
|
||||
);
|
||||
}
|
||||
|
||||
let vehicle = pickHullVehicleIdentity(input.blame);
|
||||
let colorId: number | null = null;
|
||||
const policy = asObjectRecord(policyRecord);
|
||||
const vehicleId = pickVehicleId(policy?.VehicleId ?? policy?.vehicleId);
|
||||
const versionNo = parseFanavaranId(policy?.VehicleVersionNo);
|
||||
if (vehicleId != null) {
|
||||
try {
|
||||
const vehicleRow = asObjectRecord(
|
||||
await this.fanavaranLookupService.vehicleById(
|
||||
input.clientKey,
|
||||
vehicleId,
|
||||
versionNo ?? undefined,
|
||||
contractOptions,
|
||||
),
|
||||
);
|
||||
vehicle = mergeHullVehicleIdentityFromFanavaranVehicle(
|
||||
vehicle,
|
||||
vehicleRow,
|
||||
);
|
||||
colorId = parseFanavaranId(vehicleRow.ColorId);
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`[buildCarBodyFanavaranExpertisePayload] vehicle GET ${vehicleId} failed: ${
|
||||
error instanceof Error ? error.message : error
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const wage = input.parts.reduce(
|
||||
(sum, part) => sum + this.parseFanavaranMoney(part?.salary),
|
||||
0,
|
||||
);
|
||||
const componentReplacementCost = input.parts.reduce(
|
||||
(sum, part) => sum + this.parseFanavaranMoney(part?.price),
|
||||
0,
|
||||
);
|
||||
const wasteValue = input.parts.reduce(
|
||||
(sum, part) => sum + this.fanavaranDaghiWasteValue(part?.daghi),
|
||||
0,
|
||||
);
|
||||
|
||||
const hullSections = input.parts.map((part: any) => {
|
||||
const accessoryKindId = parseCatalogPartIdInput(part?.partId);
|
||||
const vehicleHullAccessoryId = resolveVehicleHullAccessoryId(
|
||||
accessories,
|
||||
accessoryKindId,
|
||||
);
|
||||
if (vehicleHullAccessoryId == null) {
|
||||
input.warnings.push(
|
||||
`No VehicleHullAccessoryId for part "${this.fanavaranPartLabel(part)}" (AccessoryKindId=${accessoryKindId ?? "none"}).`,
|
||||
);
|
||||
}
|
||||
return toFanavaranHullExpertiseDmgSection({
|
||||
partId: part?.partId,
|
||||
desc: this.fanavaranPartLabel(part),
|
||||
wasteValue: this.fanavaranDaghiWasteValue(part?.daghi),
|
||||
amount:
|
||||
this.parseFanavaranMoney(part?.price) +
|
||||
this.parseFanavaranMoney(part?.salary),
|
||||
dmgKindId,
|
||||
dmgCostKindId,
|
||||
vehicleHullAccessoryId,
|
||||
});
|
||||
});
|
||||
|
||||
let vehicleCurrentValue =
|
||||
this.parseFanavaranMoney(input.priceDropCarPrice) || null;
|
||||
if (vehicleCurrentValue == null) {
|
||||
vehicleCurrentValue = vehicleCurrentValueFromHullPolicyRecord(policy);
|
||||
}
|
||||
|
||||
const repairDuration =
|
||||
typeof input.profile.defaults.HullExpertiseRepairDuration === "number"
|
||||
? input.profile.defaults.HullExpertiseRepairDuration
|
||||
: null;
|
||||
|
||||
return {
|
||||
replyKey: input.replyKey,
|
||||
warnings: input.warnings,
|
||||
payload: toFanavaranHullExpertisePayload({
|
||||
claimExpertId: input.claimExpertId,
|
||||
dmgAssessmentDate: this.convertToPersianDate(input.submittedAt),
|
||||
inspectionTime: this.getTime24Hour(input.submittedAt),
|
||||
wage,
|
||||
componentReplacementCost,
|
||||
wasteValue,
|
||||
dropAmount: input.priceDropTotal || 0,
|
||||
vehicleCurrentValue,
|
||||
vehicle,
|
||||
colorId,
|
||||
repairDuration,
|
||||
dmgSections: hullSections,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private async buildFanavaranDamageCasePayload(input: {
|
||||
claimCase: any;
|
||||
blameCase?: any;
|
||||
@@ -5743,6 +6062,19 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
if (fanavaranClaimProductFromBlameType(blameCase.type) === "car-body") {
|
||||
await this.enrichCarBodyHullBaseClaimPayload({
|
||||
clientKey,
|
||||
payload,
|
||||
blameCase,
|
||||
firstParty,
|
||||
logPrefix,
|
||||
});
|
||||
const hull = toFanavaranHullBaseClaimPayload(payload);
|
||||
for (const key of Object.keys(payload)) delete payload[key];
|
||||
Object.assign(payload, hull);
|
||||
}
|
||||
|
||||
if (persistPayload) {
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||
$set: {
|
||||
@@ -6153,7 +6485,34 @@ export class ClaimRequestManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/files`;
|
||||
await this.syncFanavaranHullBaseClaim(claimCaseId, clientKey);
|
||||
claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
const nestedClaimId = fanavaranHullNestedClaimId({
|
||||
claimId: claimCase?.claimId,
|
||||
claimNo: claimCase?.claimNo,
|
||||
});
|
||||
if (nestedClaimId == null) {
|
||||
return {
|
||||
clientKey,
|
||||
claimCaseId,
|
||||
totalLocalImages: candidates.length,
|
||||
skippedAlreadySubmitted: candidates.length - pending.length,
|
||||
attempted: 0,
|
||||
submitted: 0,
|
||||
failed: 0,
|
||||
skipped: pending.length,
|
||||
warning: "Fanavaran base claimId is missing",
|
||||
results: pending.map((c) => ({
|
||||
attempted: false,
|
||||
submitted: false,
|
||||
skipped: true,
|
||||
skipReason: "Fanavaran base claimId is missing",
|
||||
fileName: c.fileName,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${nestedClaimId}/files`;
|
||||
const fileTypeId = profile.defaults.ClaimFileTypeId;
|
||||
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||
|
||||
@@ -6166,7 +6525,7 @@ export class ClaimRequestManagementService {
|
||||
const content = {
|
||||
FileName: candidate.fileName,
|
||||
FileTypeId: fileTypeId,
|
||||
...(product === "car-body" ? { ClaimId: claimCase.claimId } : {}),
|
||||
...(product === "car-body" ? { ClaimId: nestedClaimId } : {}),
|
||||
};
|
||||
|
||||
this.logger.log(
|
||||
@@ -6188,7 +6547,7 @@ export class ClaimRequestManagementService {
|
||||
results.push({
|
||||
attempted: true,
|
||||
submitted: true,
|
||||
claimId: claimCase.claimId,
|
||||
claimId: nestedClaimId,
|
||||
fileName: candidate.fileName,
|
||||
submitUrl: url,
|
||||
fanavaranResponse: response.data,
|
||||
@@ -6203,7 +6562,7 @@ export class ClaimRequestManagementService {
|
||||
submitted: false,
|
||||
warning,
|
||||
fileName: candidate.fileName,
|
||||
claimId: claimCase.claimId,
|
||||
claimId: nestedClaimId,
|
||||
submitUrl: url,
|
||||
});
|
||||
|
||||
@@ -6752,33 +7111,19 @@ export class ClaimRequestManagementService {
|
||||
String(input.claimCase.blameRequestId),
|
||||
)
|
||||
: null;
|
||||
const hullSections = parts.map((part: any) =>
|
||||
toFanavaranHullExpertiseDmgSection({
|
||||
partId: part?.partId,
|
||||
desc: this.fanavaranPartLabel(part),
|
||||
wasteValue: this.fanavaranDaghiWasteValue(part?.daghi),
|
||||
amount:
|
||||
this.parseFanavaranMoney(part?.price) +
|
||||
this.parseFanavaranMoney(part?.salary),
|
||||
}),
|
||||
);
|
||||
return {
|
||||
replyKey: active?.replyKey,
|
||||
return this.buildCarBodyFanavaranExpertisePayload({
|
||||
clientKey: input.clientKey,
|
||||
claimCase: input.claimCase,
|
||||
blame,
|
||||
profile,
|
||||
parts,
|
||||
claimExpertId,
|
||||
submittedAt,
|
||||
priceDropTotal,
|
||||
priceDropCarPrice: priceDrop?.carPrice,
|
||||
warnings,
|
||||
payload: toFanavaranHullExpertisePayload({
|
||||
claimExpertId,
|
||||
dmgAssessmentDate: this.convertToPersianDate(submittedAt),
|
||||
inspectionTime: this.getTime24Hour(submittedAt),
|
||||
wage: repairWage,
|
||||
componentReplacementCost,
|
||||
wasteValue,
|
||||
dropAmount: priceDropTotal || 0,
|
||||
vehicleCurrentValue:
|
||||
this.parseFanavaranMoney(priceDrop?.carPrice) || null,
|
||||
vehicle: pickHullVehicleIdentity(blame),
|
||||
dmgSections: hullSections,
|
||||
}),
|
||||
};
|
||||
replyKey: active?.replyKey,
|
||||
});
|
||||
}
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
@@ -6940,7 +7285,18 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
);
|
||||
}
|
||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${claimCase.claimId}/expertise`;
|
||||
await this.syncFanavaranHullBaseClaim(claimCaseId, clientKey);
|
||||
claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
const nestedClaimId = fanavaranHullNestedClaimId({
|
||||
claimId: claimCase?.claimId,
|
||||
claimNo: claimCase?.claimNo,
|
||||
});
|
||||
if (nestedClaimId == null) {
|
||||
throw new BadRequestException(
|
||||
"Fanavaran claimId is required before submitting expertise",
|
||||
);
|
||||
}
|
||||
const url = `${await this.fanavaranClaimsUrlForClaim(claimCaseId)}/${nestedClaimId}/expertise`;
|
||||
const startedAt = Date.now();
|
||||
const auditSession: FanavaranAuditSession = {
|
||||
trackingCode: this.fanavaranAuditService.generateTrackingCode(),
|
||||
@@ -7332,6 +7688,70 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
private async putFanavaranJson(
|
||||
url: string,
|
||||
payload: Record<string, unknown>,
|
||||
clientKey: FanavaranClientKey,
|
||||
auditSession?: FanavaranAuditSession,
|
||||
claimCaseId?: string,
|
||||
) {
|
||||
this.fanavaranAuthService.assertNotInBackoff(clientKey);
|
||||
const headers = await this.getFanavaranAuthHeaders(
|
||||
clientKey,
|
||||
auditSession,
|
||||
claimCaseId,
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.put(url, payload, {
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}),
|
||||
);
|
||||
this.fanavaranAuthService.clearBackoff(clientKey);
|
||||
return response;
|
||||
} catch (error) {
|
||||
this.fanavaranAuthService.registerFailure(clientKey, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested hull files/expertise look up a بدنه پرونده. Create often echoed a ثالث-shaped
|
||||
* body; PUT the GEN.03 hull shape onto the existing Id before follow-up stages.
|
||||
*/
|
||||
private async syncFanavaranHullBaseClaim(
|
||||
claimCaseId: string,
|
||||
clientKey: FanavaranClientKey,
|
||||
): Promise<void> {
|
||||
const product = await this.resolveFanavaranClaimProduct(claimCaseId);
|
||||
if (product !== "car-body") return;
|
||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
const nestedId = fanavaranHullNestedClaimId({
|
||||
claimId: claimCase?.claimId,
|
||||
claimNo: claimCase?.claimNo,
|
||||
});
|
||||
if (nestedId == null) return;
|
||||
|
||||
const built = await this.previewFanavaranSubmitV2(claimCaseId, clientKey, {
|
||||
requirePolicyId: true,
|
||||
persistPayload: true,
|
||||
});
|
||||
const hull = toFanavaranHullBaseClaimPayload(
|
||||
built && typeof built === "object" && "payload" in built
|
||||
? (built as { payload: Record<string, unknown> }).payload
|
||||
: built,
|
||||
);
|
||||
const url = `${fanavaranClaimsBaseUrl("car-body")}/${nestedId}`;
|
||||
this.logger.log(
|
||||
`[Fanavaran hull sync] PUT ${url} keys=${Object.keys(hull).join(",")}`,
|
||||
);
|
||||
await this.putFanavaranJson(url, hull, clientKey, undefined, claimCaseId);
|
||||
}
|
||||
|
||||
private async postFanavaranMultipart(
|
||||
url: string,
|
||||
content: Record<string, unknown>,
|
||||
|
||||
Reference in New Issue
Block a user