feat(store): v4.0.18 门店多收款账户与子账号继承二维码

C 端门店列表拼接省市区县地址;门店多银行账户与默认打款账户;子账号独立 sa_ 关联码及统计维度;同步 v4.0.18 开发文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-07 15:56:16 +08:00
parent a9c466930d
commit 4bdb09068c
26 changed files with 1193 additions and 102 deletions
@@ -6,6 +6,55 @@ export type StoreBankAccountSnapshot = {
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,
@@ -37,28 +86,50 @@ export async function loadStorePrimaryBanksMap(
): Promise<Map<string, StoreBankAccountSnapshot>> {
const map = new Map<string, StoreBankAccountSnapshot>();
if (!storeIds.length) return map;
const bindings = await prisma.storeAccountStore.findMany({
where: { storeId: { in: storeIds } },
include: {
storeAccount: {
select: {
isPrimary: true,
bankAccountName: true,
bankAccountNo: true,
bankBranch: true,
},
},
},
orderBy: [{ storeAccount: { isPrimary: 'desc' } }, { storeAccountId: 'asc' }],
// 优先从门店多账户表读取默认账户
const accounts = await prisma.storeBankAccount.findMany({
where: { storeId: { in: storeIds }, status: 'ACTIVE' },
orderBy: [{ storeId: 'asc' }, { isDefault: 'desc' }, { sortOrder: 'asc' }, { id: 'asc' }],
});
for (const binding of bindings) {
const key = String(binding.storeId);
const seenStoreIds = new Set<string>();
for (const a of accounts) {
const key = String(a.storeId);
if (map.has(key)) continue;
map.set(key, {
bankAccountName: binding.storeAccount.bankAccountName,
bankAccountNo: binding.storeAccount.bankAccountNo,
bankBranch: binding.storeAccount.bankBranch,
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;
}