fix(import): stop re-asking sms_charge and deleted fee alias
Import preview listed sms_charge_* and service_charge_percentage, but applyImport skipped every out-of-catalog key via isSystemProgramSettingKey, so "all projects" never persisted and the modal returned every time. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -206,11 +206,17 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
const local = await this.listTenantRows(tenantId);
|
const local = await this.listTenantRows(tenantId);
|
||||||
const catalogKeys = this.deployCatalogKeySet();
|
const catalogKeys = this.deployCatalogKeySet();
|
||||||
const seedCanEdit = this.deployCatalogCanEditMap();
|
const seedCanEdit = this.deployCatalogCanEditMap();
|
||||||
|
const knownTemplates = await this.prisma.settingTemplate.findMany({
|
||||||
|
where: { deletedAt: null },
|
||||||
|
select: { key: true },
|
||||||
|
});
|
||||||
|
const knownTemplateKeys = new Set(knownTemplates.map((t: { key: string }) => t.key));
|
||||||
const localByKey = new Map(local.map((row) => [String(row.key), row]));
|
const localByKey = new Map(local.map((row) => [String(row.key), row]));
|
||||||
const remoteByKey = new Map(
|
const remoteByKey = new Map(
|
||||||
remote
|
remote
|
||||||
.map((row) => this.normalizeIncoming(row))
|
.map((row) => this.normalizeIncoming(row))
|
||||||
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key))
|
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key))
|
||||||
|
.filter((row) => !this.isImportDecisionExcluded(row.key))
|
||||||
.map((row) => [row.key, row]),
|
.map((row) => [row.key, row]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -223,7 +229,9 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
for (const [key, row] of remoteByKey) {
|
for (const [key, row] of remoteByKey) {
|
||||||
if (catalogKeys.has(key)) continue;
|
// قالب deploy یا قالب DB قبلاً ساختهشده → بدون سؤال
|
||||||
|
if (catalogKeys.has(key) || knownTemplateKeys.has(key)) continue;
|
||||||
|
if (DEPLOY_SETTING_EDITABLE_KEYS.has(key)) continue;
|
||||||
const localRow = localByKey.get(key);
|
const localRow = localByKey.get(key);
|
||||||
const khanegiValue = String(row.value ?? '');
|
const khanegiValue = String(row.value ?? '');
|
||||||
const superadminValue = localRow ? String(localRow.value ?? '') : null;
|
const superadminValue = localRow ? String(localRow.value ?? '') : null;
|
||||||
@@ -272,10 +280,17 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
await this.ensureTemplatesSeeded();
|
await this.ensureTemplatesSeeded();
|
||||||
const remote = await this.fetchSystemSettings(apiBase, token);
|
const remote = await this.fetchSystemSettings(apiBase, token);
|
||||||
const catalogKeys = this.deployCatalogKeySet();
|
const catalogKeys = this.deployCatalogKeySet();
|
||||||
|
const knownTemplates = await this.prisma.settingTemplate.findMany({
|
||||||
|
where: { deletedAt: null },
|
||||||
|
select: { key: true },
|
||||||
|
});
|
||||||
|
const knownTemplateKeys = new Set(knownTemplates.map((t: { key: string }) => t.key));
|
||||||
const remoteByKey = new Map(
|
const remoteByKey = new Map(
|
||||||
remote
|
remote
|
||||||
.map((row) => this.normalizeIncoming(row))
|
.map((row) => this.normalizeIncoming(row))
|
||||||
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key))
|
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key))
|
||||||
|
.filter((row) => !this.isImportDecisionExcluded(row.key))
|
||||||
|
.filter((row) => !DEPLOY_SETTING_EDITABLE_KEYS.has(row.key))
|
||||||
.map((row) => [row.key, row]),
|
.map((row) => [row.key, row]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -283,7 +298,7 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
for (const d of decisions || []) {
|
for (const d of decisions || []) {
|
||||||
const key = String(d.key || '').trim();
|
const key = String(d.key || '').trim();
|
||||||
const scope = d.scope;
|
const scope = d.scope;
|
||||||
if (!key) continue;
|
if (!key || this.isImportDecisionExcluded(key)) continue;
|
||||||
if (scope !== 'none' && scope !== 'this' && scope !== 'all') {
|
if (scope !== 'none' && scope !== 'this' && scope !== 'all') {
|
||||||
throw new BadRequestException(`scope نامعتبر برای ${key}`);
|
throw new BadRequestException(`scope نامعتبر برای ${key}`);
|
||||||
}
|
}
|
||||||
@@ -302,10 +317,17 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
await this.ensureTemplateDefaultsForTenant(tx, tenantId);
|
await this.ensureTemplateDefaultsForTenant(tx, tenantId);
|
||||||
|
|
||||||
for (const [key, row] of remoteByKey) {
|
for (const [key, row] of remoteByKey) {
|
||||||
if (!this.isSystemProgramSettingKey(key)) continue;
|
const inCatalog = catalogKeys.has(key) || knownTemplateKeys.has(key);
|
||||||
const inCatalog = catalogKeys.has(key);
|
let scope: ImportDecisionScope | undefined;
|
||||||
const scope = inCatalog ? 'this' : (decisionMap.get(key) ?? 'this');
|
if (inCatalog) {
|
||||||
if (scope === 'none') continue;
|
scope = 'this';
|
||||||
|
} else if (decisionMap.has(key)) {
|
||||||
|
scope = decisionMap.get(key);
|
||||||
|
} else {
|
||||||
|
// خارج از قالب و بدون تصمیم کاربر → نادیده
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!scope || scope === 'none') continue;
|
||||||
|
|
||||||
const targetIds = scope === 'all' ? khanegiTenantIds : [tenantId];
|
const targetIds = scope === 'all' ? khanegiTenantIds : [tenantId];
|
||||||
for (const tid of targetIds) {
|
for (const tid of targetIds) {
|
||||||
@@ -318,12 +340,23 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
|
|
||||||
if (inCatalog) continue;
|
if (inCatalog) continue;
|
||||||
|
|
||||||
|
// خارج از قالب + تصمیم this/all → قالب بساز تا مدال دوباره نیاید
|
||||||
const tpl = await tx.settingTemplate.findUnique({ where: { key } });
|
const tpl = await tx.settingTemplate.findUnique({ where: { key } });
|
||||||
|
const tplData = this.templateDataFromRow({
|
||||||
|
...row,
|
||||||
|
key,
|
||||||
|
value: '',
|
||||||
|
can_edit: 0,
|
||||||
|
});
|
||||||
if (!tpl) {
|
if (!tpl) {
|
||||||
await tx.settingTemplate.create({
|
await tx.settingTemplate.create({ data: tplData });
|
||||||
data: this.templateDataFromRow(row),
|
} else if (tpl.deletedAt) {
|
||||||
|
await tx.settingTemplate.update({
|
||||||
|
where: { key },
|
||||||
|
data: { ...tplData, deletedAt: null },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
knownTemplateKeys.add(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tx.tenant.update({
|
await tx.tenant.update({
|
||||||
@@ -553,6 +586,19 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
return DEPLOY_SETTING_CATALOG_KEYS.has(k);
|
return DEPLOY_SETTING_CATALOG_KEYS.has(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* کلیدهایی که نباید در مدال تصمیم ایمپورت بیایند / نوشته شوند:
|
||||||
|
* - alias حذفشده کارمزد
|
||||||
|
* - شارژ پیامک (مالک: پنل SMS سوپرادمین، نه تنظیمات پیشرفته پروژه)
|
||||||
|
*/
|
||||||
|
private isImportDecisionExcluded(keyRaw: string): boolean {
|
||||||
|
const key = String(keyRaw || '').trim();
|
||||||
|
if (!key) return true;
|
||||||
|
if (key === 'service_charge_percentage') return true;
|
||||||
|
if (key.startsWith('sms_charge_')) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** حذف can_edit=1 از قالب و همهٔ پروژههای خانگی در DB سوپرادمین */
|
/** حذف can_edit=1 از قالب و همهٔ پروژههای خانگی در DB سوپرادمین */
|
||||||
async purgeEditableFromSuperAdmin() {
|
async purgeEditableFromSuperAdmin() {
|
||||||
const keys = [...DEPLOY_SETTING_EDITABLE_KEYS];
|
const keys = [...DEPLOY_SETTING_EDITABLE_KEYS];
|
||||||
@@ -904,7 +950,8 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
});
|
});
|
||||||
const items = this.asSettingsArray(data)
|
const items = this.asSettingsArray(data)
|
||||||
.filter((row) => this.isSystemSetting(row))
|
.filter((row) => this.isSystemSetting(row))
|
||||||
.filter((row) => !isRuntimeProgramSettingKey(String(row.key || '')));
|
.filter((row) => !isRuntimeProgramSettingKey(String(row.key || '')))
|
||||||
|
.filter((row) => !this.isImportDecisionExcluded(String(row.key || '')));
|
||||||
if (items.length) return items;
|
if (items.length) return items;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
|
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
|
||||||
@@ -923,7 +970,8 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
});
|
});
|
||||||
return this.asSettingsArray(data)
|
return this.asSettingsArray(data)
|
||||||
.filter((row) => this.isSystemSetting(row))
|
.filter((row) => this.isSystemSetting(row))
|
||||||
.filter((row) => !isRuntimeProgramSettingKey(String(row.key || '')));
|
.filter((row) => !isRuntimeProgramSettingKey(String(row.key || '')))
|
||||||
|
.filter((row) => !this.isImportDecisionExcluded(String(row.key || '')));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new BadGatewayException(
|
throw new BadGatewayException(
|
||||||
this.axiosMessage(error, 'خواندن جدول settings از اپ خانگی ناموفق بود'),
|
this.axiosMessage(error, 'خواندن جدول settings از اپ خانگی ناموفق بود'),
|
||||||
|
|||||||
Reference in New Issue
Block a user