fix(settings): skip deploy_settings_const keys in import decision modal

Catalog keys from the SQL template write onto this tenant automatically; only extra keys need a per-key decision.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 19:06:32 +03:30
co-authored by Cursor
parent 71f1fa1092
commit 0b2e4b05f9
2 changed files with 37 additions and 14 deletions
@@ -1,4 +1,8 @@
/** کلیدهای deploy_settings_const — داخل باندل کامپایل می‌شود تا ایمپورت به فایل prisma وابسته نباشد. */ /**
* کلیدهای INSERT در khaneghi_server/database/sql/deploy_settings_const.sql
* منبع قالب ایمپورت سوپرادمین — داخل باندل تا به فایل prisma وابسته نباشد.
* این ۱۹۵ کلید با SQL فعلی هم‌تراز است؛ کلید داخل این مجموعه در مودال «تصمیم برای هر کلید» نمی‌آید.
*/
export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
"accounting_provider", "accounting_provider",
"admin_url", "admin_url",
@@ -183,7 +183,7 @@ export class ProgramSettingsService implements OnModuleInit {
}; };
} }
/** پیش‌نمایش ایمپورت — برای هر کلید تصمیم‌گیری لازم است */ /** پیش‌نمایش ایمپورت — تصمیم فقط برای کلید خارج از قالب deploy_settings_const */
async previewImport(tenantId: string, mobile: string, password: string) { async previewImport(tenantId: string, mobile: string, password: string) {
const tenant = await this.requireKhanegiTenant(tenantId); const tenant = await this.requireKhanegiTenant(tenantId);
const mobileNorm = this.normalizeAdminMobile(mobile); const mobileNorm = this.normalizeAdminMobile(mobile);
@@ -194,7 +194,8 @@ 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 local = await this.listTenantRows(tenantId); const local = await this.listTenantRows(tenantId);
const catalogKeys = await this.catalogKeySet(); const catalogKeys = this.deployCatalogKeySet();
const seedCanEdit = this.deployCatalogCanEditMap();
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
@@ -212,6 +213,7 @@ export class ProgramSettingsService implements OnModuleInit {
}> = []; }> = [];
for (const [key, row] of remoteByKey) { for (const [key, row] of remoteByKey) {
if (catalogKeys.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;
@@ -225,7 +227,13 @@ export class ProgramSettingsService implements OnModuleInit {
}); });
} }
const onlySuperAdmin = [...catalogKeys].filter((key) => !remoteByKey.has(key)); const onlySuperAdmin = [...catalogKeys].filter((key) => {
if (isRuntimeProgramSettingKey(key)) return false;
if (remoteByKey.has(key)) return false;
const canEdit = seedCanEdit.get(key);
if (canEdit === 1) return false;
return true;
});
return { return {
tenant: this.tenantMeta(tenant), tenant: this.tenantMeta(tenant),
@@ -234,7 +242,7 @@ export class ProgramSettingsService implements OnModuleInit {
onlySuperAdminKeys: onlySuperAdmin, onlySuperAdminKeys: onlySuperAdmin,
importableCount: remoteByKey.size, importableCount: remoteByKey.size,
note: note:
'مقدار کلیدهای can_edit=0 خانگی روی همین پروژه نوشته می‌شود. اگر تصمیم نفرستی، پیش‌فرض «فقط این پروژه» است.', 'کلیدهای قالب deploy_settings_const بدون سؤال روی همین پروژه نوشته می‌شوند. فقط کلید خارج از قالب اینجا می‌آید.',
}; };
} }
@@ -253,6 +261,7 @@ export class ProgramSettingsService implements OnModuleInit {
const { apiBase, token } = await this.loginKhanegiAdmin(tenant, mobileNorm, password); const { apiBase, token } = await this.loginKhanegiAdmin(tenant, mobileNorm, password);
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 remoteByKey = new Map( const remoteByKey = new Map(
remote remote
.map((row) => this.normalizeIncoming(row)) .map((row) => this.normalizeIncoming(row))
@@ -283,7 +292,8 @@ 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) {
const scope = decisionMap.get(key) ?? 'this'; const inCatalog = catalogKeys.has(key);
const scope = inCatalog ? 'this' : (decisionMap.get(key) ?? 'this');
if (scope === 'none') continue; if (scope === 'none') continue;
const targetIds = scope === 'all' ? khanegiTenantIds : [tenantId]; const targetIds = scope === 'all' ? khanegiTenantIds : [tenantId];
@@ -295,7 +305,8 @@ export class ProgramSettingsService implements OnModuleInit {
}); });
} }
// کلید جدید از خانگی → وارد قالب هم بشود اگر نبود if (inCatalog) continue;
const tpl = await tx.settingTemplate.findUnique({ where: { key } }); const tpl = await tx.settingTemplate.findUnique({ where: { key } });
if (!tpl) { if (!tpl) {
await tx.settingTemplate.create({ await tx.settingTemplate.create({
@@ -510,21 +521,29 @@ export class ProgramSettingsService implements OnModuleInit {
} }
} }
/** کلیدهای deploy const (داخل باندل) + فایل seed + جدول قالب. */ /**
private async catalogKeySet() { * قالب ایمپورت = کلیدهای INSERT در deploy_settings_const.sql
* (باندل + JSON هم‌تراز همان فایل). جدول setting_templates منبع قالب نیست.
*/
private deployCatalogKeySet() {
const keys = new Set<string>(DEPLOY_SETTING_CATALOG_KEYS); const keys = new Set<string>(DEPLOY_SETTING_CATALOG_KEYS);
for (const row of this.readSeedRows()) { for (const row of this.readSeedRows()) {
const key = String(row.key || '').trim(); const key = String(row.key || '').trim();
if (key) keys.add(key); if (key) keys.add(key);
} }
const rows = await this.prisma.settingTemplate.findMany({
where: { deletedAt: null },
select: { key: true },
});
for (const row of rows as Array<{ key: string }>) keys.add(row.key);
return keys; return keys;
} }
private deployCatalogCanEditMap() {
const map = new Map<string, number>();
for (const row of this.readSeedRows()) {
const key = String(row.key || '').trim();
if (!key) continue;
map.set(key, this.toSmallInt(row.can_edit, 0));
}
return map;
}
private async seedEmptyKhanegiTenants() { private async seedEmptyKhanegiTenants() {
const tenants = await this.prisma.tenant.findMany({ const tenants = await this.prisma.tenant.findMany({
where: { product: ProductType.khanegi }, where: { product: ProductType.khanegi },