4bdb09068c
C 端门店列表拼接省市区县地址;门店多银行账户与默认打款账户;子账号独立 sa_ 关联码及统计维度;同步 v4.0.18 开发文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
5.3 KiB
TypeScript
172 lines
5.3 KiB
TypeScript
import type { PrismaClient } from '@prisma/client';
|
|
|
|
export type StoreBankAccountSnapshot = {
|
|
bankAccountName: string | null;
|
|
bankAccountNo: string | null;
|
|
bankBranch: string | null;
|
|
};
|
|
|
|
export type StoreBankAccountRow = StoreBankAccountSnapshot & {
|
|
id: bigint;
|
|
storeId: bigint;
|
|
isDefault: boolean;
|
|
status: string;
|
|
sortOrder: number;
|
|
};
|
|
|
|
/** 门店收款账户列表(默认账户优先,其次 sortOrder/创建顺序) */
|
|
export async function loadStoreBankAccounts(
|
|
prisma: PrismaClient,
|
|
storeId: bigint,
|
|
): Promise<StoreBankAccountRow[]> {
|
|
const rows = await prisma.storeBankAccount.findMany({
|
|
where: { storeId, status: 'ACTIVE' },
|
|
orderBy: [{ isDefault: 'desc' }, { sortOrder: 'asc' }, { id: 'asc' }],
|
|
});
|
|
return rows.map((r) => ({
|
|
id: r.id,
|
|
storeId: r.storeId,
|
|
bankAccountName: r.bankAccountName,
|
|
bankAccountNo: r.bankAccountNo,
|
|
bankBranch: r.bankBranch,
|
|
isDefault: r.isDefault === 1,
|
|
status: r.status,
|
|
sortOrder: r.sortOrder,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* 门店默认收款账户:优先 is_default=1;否则第一个 ACTIVE;再退回旧主账号字段(兼容未迁移数据)。
|
|
* 返回 null 表示无收款账户。
|
|
*/
|
|
export async function loadStoreDefaultBank(
|
|
prisma: PrismaClient,
|
|
storeId: bigint,
|
|
): Promise<StoreBankAccountSnapshot | null> {
|
|
const accounts = await loadStoreBankAccounts(prisma, storeId);
|
|
if (accounts.length) {
|
|
const def = accounts.find((a) => a.isDefault) ?? accounts[0];
|
|
return {
|
|
bankAccountName: def.bankAccountName,
|
|
bankAccountNo: def.bankAccountNo,
|
|
bankBranch: def.bankBranch,
|
|
};
|
|
}
|
|
return loadStorePrimaryBank(prisma, storeId);
|
|
}
|
|
|
|
export async function loadStorePrimaryBank(
|
|
prisma: PrismaClient,
|
|
storeId: bigint,
|
|
): Promise<StoreBankAccountSnapshot | null> {
|
|
const binding = await prisma.storeAccountStore.findFirst({
|
|
where: { storeId, storeAccount: { isPrimary: 1 } },
|
|
include: {
|
|
storeAccount: {
|
|
select: { bankAccountName: true, bankAccountNo: true, bankBranch: true },
|
|
},
|
|
},
|
|
});
|
|
if (binding) return binding.storeAccount;
|
|
const fallback = await prisma.storeAccountStore.findFirst({
|
|
where: { storeId },
|
|
orderBy: { storeAccountId: 'asc' },
|
|
include: {
|
|
storeAccount: {
|
|
select: { bankAccountName: true, bankAccountNo: true, bankBranch: true },
|
|
},
|
|
},
|
|
});
|
|
return fallback?.storeAccount ?? null;
|
|
}
|
|
|
|
export async function loadStorePrimaryBanksMap(
|
|
prisma: PrismaClient,
|
|
storeIds: bigint[],
|
|
): Promise<Map<string, StoreBankAccountSnapshot>> {
|
|
const map = new Map<string, StoreBankAccountSnapshot>();
|
|
if (!storeIds.length) return map;
|
|
|
|
// 优先从门店多账户表读取默认账户
|
|
const accounts = await prisma.storeBankAccount.findMany({
|
|
where: { storeId: { in: storeIds }, status: 'ACTIVE' },
|
|
orderBy: [{ storeId: 'asc' }, { isDefault: 'desc' }, { sortOrder: 'asc' }, { id: 'asc' }],
|
|
});
|
|
const seenStoreIds = new Set<string>();
|
|
for (const a of accounts) {
|
|
const key = String(a.storeId);
|
|
if (map.has(key)) continue;
|
|
map.set(key, {
|
|
bankAccountName: a.bankAccountName,
|
|
bankAccountNo: a.bankAccountNo,
|
|
bankBranch: a.bankBranch,
|
|
});
|
|
seenStoreIds.add(key);
|
|
}
|
|
|
|
// 未迁移/无多账户的门店,回退旧主账号字段
|
|
const missingIds = storeIds.filter((id) => !seenStoreIds.has(String(id)));
|
|
if (missingIds.length) {
|
|
const bindings = await prisma.storeAccountStore.findMany({
|
|
where: { storeId: { in: missingIds } },
|
|
include: {
|
|
storeAccount: {
|
|
select: {
|
|
isPrimary: true,
|
|
bankAccountName: true,
|
|
bankAccountNo: true,
|
|
bankBranch: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: [{ storeAccount: { isPrimary: 'desc' } }, { storeAccountId: 'asc' }],
|
|
});
|
|
for (const binding of bindings) {
|
|
const key = String(binding.storeId);
|
|
if (map.has(key)) continue;
|
|
map.set(key, {
|
|
bankAccountName: binding.storeAccount.bankAccountName,
|
|
bankAccountNo: binding.storeAccount.bankAccountNo,
|
|
bankBranch: binding.storeAccount.bankBranch,
|
|
});
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
export function formatWecomBankAccount(bank?: {
|
|
bankAccountName?: string | null;
|
|
bankName?: string | null;
|
|
bankBranch?: string | null;
|
|
bankAccountNo?: string | null;
|
|
} | null): string {
|
|
if (!bank) return '未登记收款账户';
|
|
const name = bank.bankAccountName?.trim() || '';
|
|
const no = bank.bankAccountNo?.trim() || '';
|
|
if (!name && !no) return '未登记收款账户';
|
|
const bankLine = bank.bankName?.trim() || bank.bankBranch?.trim() || '';
|
|
return [name, bankLine, no].filter(Boolean).join(' / ');
|
|
}
|
|
|
|
export async function loadWineryBankConfig(
|
|
prisma: PrismaClient,
|
|
): Promise<StoreBankAccountSnapshot & { bankName?: string | null }> {
|
|
const keys = [
|
|
'WINERY_BANK_ACCOUNT_NAME',
|
|
'WINERY_BANK_NAME',
|
|
'WINERY_BANK_BRANCH',
|
|
'WINERY_BANK_ACCOUNT_NO',
|
|
];
|
|
const rows = await prisma.systemConfig.findMany({
|
|
where: { configKey: { in: keys } },
|
|
select: { configKey: true, value: true },
|
|
});
|
|
const map = new Map(rows.map((r) => [r.configKey, r.value]));
|
|
return {
|
|
bankAccountName: map.get('WINERY_BANK_ACCOUNT_NAME') ?? null,
|
|
bankName: map.get('WINERY_BANK_NAME') ?? null,
|
|
bankBranch: map.get('WINERY_BANK_BRANCH') ?? null,
|
|
bankAccountNo: map.get('WINERY_BANK_ACCOUNT_NO') ?? null,
|
|
};
|
|
}
|