feat(store): add two-level store categories for admin and partner open-store
CI / verify (pull_request) Has been cancelled

Admin CRUD under stores menu; partner picks leaf category on create.
Default sync only inserts missing rows and never updates existing ones.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-22 12:10:02 +08:00
parent 823e439101
commit 9a550cbaaf
18 changed files with 848 additions and 26 deletions
@@ -0,0 +1,68 @@
import { Body, Controller, Delete, Get, Param, Post, Put, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
import { StoreCategoryService } from '../store/store-category.service';
import {
CreateStoreCategoryDto,
UpdateStoreCategoryDto,
} from './dto/admin-mutate.dto';
@Controller('admin/store-categories')
@UseGuards(HqAuthGuard)
export class AdminStoreCategoriesController {
constructor(private readonly categories: StoreCategoryService) {}
@Get()
list() {
return this.categories.listTree({ includeDisabled: true, ensure: true });
}
@Get('flat')
listFlat() {
return this.categories.listFlat({ includeDisabled: true, ensure: true });
}
@Post('ensure-defaults')
@HqOperation({
action: HqOperationAction.STORE_CATEGORY_ENSURE,
refType: 'STORE_CATEGORY',
includeBody: false,
})
async ensureDefaults() {
await this.categories.ensureDefaults();
return this.categories.listTree({ includeDisabled: true, ensure: false });
}
@Post()
@HqOperation({
action: HqOperationAction.STORE_CATEGORY_CREATE,
refType: 'STORE_CATEGORY',
refIdField: 'id',
includeBody: true,
})
create(@Body() dto: CreateStoreCategoryDto) {
return this.categories.create(dto);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.STORE_CATEGORY_UPDATE,
refType: 'STORE_CATEGORY',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateStoreCategoryDto) {
return this.categories.update(BigInt(id), dto);
}
@Delete(':id')
@HqOperation({
action: HqOperationAction.STORE_CATEGORY_DELETE,
refType: 'STORE_CATEGORY',
refIdParam: 'id',
})
remove(@Param('id') id: string) {
return this.categories.remove(BigInt(id));
}
}
@@ -1150,3 +1150,53 @@ export class AdminBenefitGrantDto {
@IsString()
remark?: string;
}
export class CreateStoreCategoryDto {
@IsString()
@IsNotEmpty()
code: string;
@IsString()
@IsNotEmpty()
name: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
sort?: number;
/** 空/不传 = 一级分类;传入一级 id = 二级分类 */
@IsOptional()
@IsString()
parentId?: string | null;
@IsOptional()
@IsIn(['ACTIVE', 'DISABLED'])
status?: string;
}
export class UpdateStoreCategoryDto {
@IsOptional()
@IsString()
@IsNotEmpty()
code?: string;
@IsOptional()
@IsString()
@IsNotEmpty()
name?: string;
@IsOptional()
@Type(() => Number)
@IsNumber()
sort?: number;
@IsOptional()
@ValidateIf((_, v) => v !== null && v !== undefined)
@IsString()
parentId?: string | null;
@IsOptional()
@IsIn(['ACTIVE', 'DISABLED'])
status?: string;
}
@@ -3,6 +3,7 @@ import { CityScopeModule } from '../city-scope/city-scope.module';
import { IamModule } from '../iam/iam.module';
import { TradeModule } from '../trade/trade.module';
import { FulfillmentModule } from '../fulfillment/fulfillment.module';
import { StoreModule } from '../store/store.module';
import { AdminDashboardController } from './admin-dashboard.controller';
import { AdminDashboardService } from './admin-dashboard.service';
import { AdminUsersController } from './admin-users.controller';
@@ -10,6 +11,7 @@ import { AdminUsersService } from './admin-users.service';
import { AdminOrdersController } from './admin-orders.controller';
import { AdminOrdersService } from './admin-orders.service';
import { AdminStoresController, AdminStoreAccountsController, AdminStoreMediaController } from './admin-stores.controller';
import { AdminStoreCategoriesController } from './admin-store-categories.controller';
import { AdminStoresService } from './admin-stores.service';
import { AdminPartnersController, AdminPartnerAccountsController } from './admin-partners.controller';
import { AdminPartnersService } from './admin-partners.service';
@@ -59,7 +61,7 @@ import { AdminSystemConfigController } from './admin-system-config.controller';
import { AdminFulfillmentProvidersController } from './admin-fulfillment-providers.controller';
@Module({
imports: [CityScopeModule, IamModule, TradeModule, FulfillmentModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule],
imports: [CityScopeModule, IamModule, TradeModule, FulfillmentModule, BenefitModule, CommonModule, IntegrationsModule, RedeemModule, StoreModule],
controllers: [
AdminDashboardController,
AdminDeployController,
@@ -68,6 +70,7 @@ import { AdminFulfillmentProvidersController } from './admin-fulfillment-provide
AdminStoresController,
AdminStoreAccountsController,
AdminStoreMediaController,
AdminStoreCategoriesController,
AdminPartnersController,
AdminPartnerAccountsController,
AdminCitiesController,