feat(ops,store,partner): 用户权益列 + 门店合同多图上传

- admin 用户列表新增剩余/已用/累计好客权益金额三列(benefitCoupon groupBy 聚合,排除 VOID)
- admin/partner 门店入驻合同支持多张照片与 PDF(CommonResource 多记录,复用 ENV 多图逻辑)
- 新增 contract-urls.util 归一化工具,向后兼容旧 contractUrl 字段并标记 deprecated

需求1/2/3
This commit is contained in:
2026-08-12 22:48:02 +08:00
parent 478291e2b3
commit 88053353ab
13 changed files with 356 additions and 59 deletions
@@ -4,6 +4,7 @@ import { validateBusinessHours } from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { mapStoreCompat } from '../../common/compat/v31-compat';
import { contractMediaType, normalizeContractUrls } from '../../common/store-media/contract-urls.util';
import type { AdminStoreAccountsQueryDto, AdminStoreMediaQueryDto, AdminStoresQueryDto } from './dto/admin-query.dto';
import { PartnerCityService } from '../city-scope/partner-city.service';
import { StoreCategoryService } from '../store/store-category.service';
@@ -446,24 +447,23 @@ export class AdminStoresService {
}
}
if (dto.contractUrl !== undefined) {
const contractUrl = dto.contractUrl?.trim() || '';
if (dto.contractUrls !== undefined || dto.contractUrl !== undefined) {
const contractUrls = normalizeContractUrls(dto.contractUrls, dto.contractUrl);
await tx.commonResource.updateMany({
where: { ownerType: 'STORE', ownerId: id, bizType: 'CONTRACT', status: 'ACTIVE' },
data: { status: 'DELETED' },
});
if (contractUrl) {
const isPdf = /\.pdf(\?|$)/i.test(contractUrl);
for (let i = 0; i < contractUrls.length; i++) {
await tx.commonResource.create({
data: {
ownerType: 'STORE',
ownerId: id,
bizType: 'CONTRACT',
mediaType: isPdf ? 'FILE' : 'IMAGE',
mediaType: contractMediaType(contractUrls[i]),
ossBucket: 'legacy',
ossKey: contractUrl,
url: contractUrl,
sortOrder: 0,
ossKey: contractUrls[i],
url: contractUrls[i],
sortOrder: i,
},
});
}
@@ -642,16 +642,18 @@ export class AdminStoresService {
});
}
if (dto.contractUrl) {
const contractUrls = normalizeContractUrls(dto.contractUrls, dto.contractUrl);
for (let i = 0; i < contractUrls.length; i++) {
await this.prisma.commonResource.create({
data: {
ownerType: 'STORE',
ownerId: store.id,
bizType: 'CONTRACT',
mediaType: 'FILE',
mediaType: contractMediaType(contractUrls[i]),
ossBucket: 'legacy',
ossKey: dto.contractUrl,
url: dto.contractUrl,
ossKey: contractUrls[i],
url: contractUrls[i],
sortOrder: i,
},
});
}