增加核销接口

增加hq管理端日志
This commit is contained in:
2026-07-06 19:17:27 +08:00
parent f0b99ea9da
commit 4c38a9dd2b
29 changed files with 904 additions and 37 deletions
+2
View File
@@ -25,6 +25,7 @@ import StorePayoutsPage from './pages/StorePayoutsPage';
import PartnerBillsPage from './pages/PartnerBillsPage';
import TicketsPage from './pages/TicketsPage';
import UserLogsPage from './pages/UserLogsPage';
import HqLogsPage from './pages/HqLogsPage';
import ThirdPartyLogsPage from './pages/ThirdPartyLogsPage';
function RequireAuth({ children }: { children: React.ReactNode }) {
@@ -63,6 +64,7 @@ export default function App() {
<Route path="/partner-bills" element={<PartnerBillsPage />} />
<Route path="/tickets" element={<TicketsPage />} />
<Route path="/logs/users" element={<UserLogsPage />} />
<Route path="/logs/hq" element={<HqLogsPage />} />
<Route path="/logs/third-party" element={<ThirdPartyLogsPage />} />
<Route path="/deliveries" element={<DeliveriesPage />} />
<Route path="/deliveries/xiaofeixia" element={<XiaofeixiaTestPage />} />
@@ -73,6 +73,7 @@ const MENU_ITEMS: MenuProps['items'] = [
label: '日志',
children: [
{ key: '/logs/users', label: '用户日志' },
{ key: '/logs/hq', label: 'HQ 操作日志' },
{ key: '/logs/third-party', label: '第三方日志' },
],
},
+42
View File
@@ -0,0 +1,42 @@
export const HQ_OPERATION_ACTION_OPTIONS = [
{ value: 'CITY_CREATE', label: '新增开城城市' },
{ value: 'CITY_UPDATE', label: '编辑开城城市' },
{ value: 'PARTNER_CREATE', label: '新增城市合伙人' },
{ value: 'PARTNER_UPDATE', label: '编辑城市合伙人' },
{ value: 'PARTNER_ACCOUNT_CREATE', label: '新增合伙人账户' },
{ value: 'PARTNER_ACCOUNT_UPDATE', label: '编辑合伙人账户' },
{ value: 'HQ_ACCOUNT_CREATE', label: '新增 HQ 管理员' },
{ value: 'HQ_ACCOUNT_UPDATE', label: '编辑 HQ 管理员/权限' },
{ value: 'USER_DELETE', label: '删除用户' },
{ value: 'USER_BATCH_DELETE', label: '批量删除用户' },
{ value: 'ORDER_SHIP', label: '订单发货' },
{ value: 'ORDER_STATUS_DEBUG', label: '订单状态调试' },
{ value: 'ORDER_BATCH_DELETE', label: '批量删除订单' },
{ value: 'STORE_CREATE', label: '新增门店' },
{ value: 'STORE_UPDATE', label: '编辑门店' },
{ value: 'STORE_STATUS', label: '变更门店状态' },
{ value: 'STORE_AUDIT', label: '门店审核' },
{ value: 'STORE_ACCOUNT_CREATE', label: '新增门店账户' },
{ value: 'STORE_ACCOUNT_UPDATE', label: '编辑门店账户' },
{ value: 'PRODUCT_CREATE', label: '新增商品' },
{ value: 'PRODUCT_UPDATE', label: '编辑商品' },
{ value: 'PRODUCT_DELETE', label: '删除商品' },
{ value: 'BENEFIT_COUPON_VOID', label: '作废权益券' },
{ value: 'DELIVERY_UPDATE', label: '编辑配送单' },
{ value: 'TICKET_APPROVE', label: '工单通过' },
{ value: 'TICKET_REJECT', label: '工单驳回' },
{ value: 'STORE_PAYOUT_CONFIRM', label: '门店打款确认' },
{ value: 'STORE_PAYOUT_BATCH_CONFIRM', label: '批量门店打款' },
{ value: 'PARTNER_BILL_GENERATE', label: '生成合伙人账单' },
{ value: 'PARTNER_BILL_CONFIRM', label: '确认合伙人账单' },
{ value: 'PARTNER_BILL_MARK_PAID', label: '合伙人账单结算' },
] as const;
export const HQ_OPERATION_ACTION_LABELS: Record<string, string> = Object.fromEntries(
HQ_OPERATION_ACTION_OPTIONS.map((o) => [o.value, o.label]),
);
export function resolveHqOperationLabel(action: string | null | undefined): string {
if (!action) return '—';
return HQ_OPERATION_ACTION_LABELS[action] || action;
}
+196
View File
@@ -0,0 +1,196 @@
import { useEffect, useState } from 'react';
import {
Button, Descriptions, Drawer, Form, Input, Select, Space, Table, Tag, Typography,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { useSearchParams } from 'react-router-dom';
import { request } from '../lib/api';
import { fmtTime } from '../lib/constants';
import { HQ_OPERATION_ACTION_OPTIONS, resolveHqOperationLabel } from '../lib/hq-log';
import { useAdminList } from '../lib/useAdminList';
type Row = {
id: string;
hqAccountId: string | null;
hqName: string | null;
hqPhone: string | null;
hqRole: string | null;
action: string | null;
actionLabel: string;
refType: string | null;
refId: string | null;
status: string | null;
remark: string | null;
detail: Record<string, unknown> | null;
createdAt: string;
};
export default function HqLogsPage() {
const [searchParams, setSearchParams] = useSearchParams();
const [form] = Form.useForm();
const [filters, setFilters] = useState<Record<string, string>>(() => ({
hqAccountId: searchParams.get('hqAccountId') ?? '',
action: searchParams.get('action') ?? '',
refType: searchParams.get('refType') ?? '',
}));
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
'/admin/logs/hq',
() => {
const qs = new URLSearchParams();
if (filters.hqAccountId) qs.set('hqAccountId', filters.hqAccountId);
if (filters.action) qs.set('action', filters.action);
if (filters.refType) qs.set('refType', filters.refType);
return qs;
},
[filters],
);
const [detail, setDetail] = useState<Row | null>(null);
const [drawerOpen, setDrawerOpen] = useState(false);
useEffect(() => {
form.setFieldsValue(filters);
}, [form, filters]);
const columns: ColumnsType<Row> = [
{ title: '时间', dataIndex: 'createdAt', width: 160, render: fmtTime },
{
title: '操作人',
width: 160,
render: (_, r) => (
<div>
<div>{r.hqName || '—'}</div>
<div style={{ color: '#999', fontSize: 12 }}>
{r.hqPhone || ''} {r.hqAccountId ? `(#${r.hqAccountId})` : ''}
</div>
</div>
),
},
{
title: '行为',
dataIndex: 'actionLabel',
width: 160,
render: (v, r) => <Tag color="blue">{v || resolveHqOperationLabel(r.action)}</Tag>,
},
{ title: '对象类型', dataIndex: 'refType', width: 120, render: (v) => v || '—' },
{ title: '对象 ID', dataIndex: 'refId', width: 100, render: (v) => v || '—' },
{
title: '操作',
width: 80,
render: (_, row) => (
<Button
type="link"
size="small"
onClick={async () => {
const res = await request<Row>(`/admin/logs/hq/${row.id}`);
setDetail(res);
setDrawerOpen(true);
}}
>
</Button>
),
},
];
return (
<div>
<Typography.Title level={4}>HQ </Typography.Title>
<Typography.Paragraph type="secondary">
</Typography.Paragraph>
<Form
form={form}
layout="inline"
style={{ marginBottom: 16 }}
onFinish={(values) => {
setFilters(values);
setPage(1);
const qs = new URLSearchParams();
if (values.hqAccountId) qs.set('hqAccountId', values.hqAccountId);
if (values.action) qs.set('action', values.action);
if (values.refType) qs.set('refType', values.refType);
setSearchParams(qs);
}}
>
<Form.Item name="hqAccountId" label="HQ 账户 ID">
<Input allowClear style={{ width: 140 }} />
</Form.Item>
<Form.Item name="action" label="行为">
<Select
allowClear
showSearch
optionFilterProp="label"
style={{ width: 180 }}
options={HQ_OPERATION_ACTION_OPTIONS.map((o) => ({ value: o.value, label: o.label }))}
/>
</Form.Item>
<Form.Item name="refType" label="对象类型">
<Input allowClear placeholder="ORDER / USER / CITY..." style={{ width: 140 }} />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit"></Button>
<Button onClick={() => {
form.resetFields();
setFilters({ hqAccountId: '', action: '', refType: '' });
setSearchParams({});
setPage(1);
void reload();
}}
>
</Button>
</Space>
</Form.Item>
</Form>
<Table
rowKey="id"
loading={loading}
columns={columns}
dataSource={data?.items ?? []}
scroll={{ x: 900 }}
pagination={{
current: page,
pageSize,
total: data?.total ?? 0,
showSizeChanger: true,
onChange: (p, ps) => {
setPage(p);
setPageSize(ps);
},
}}
/>
<Drawer title="操作详情" width={640} open={drawerOpen} onClose={() => setDrawerOpen(false)}>
{detail && (
<>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="日志 ID">{detail.id}</Descriptions.Item>
<Descriptions.Item label="时间">{fmtTime(detail.createdAt)}</Descriptions.Item>
<Descriptions.Item label="操作人">
{detail.hqName} / {detail.hqPhone} (ID: {detail.hqAccountId})
</Descriptions.Item>
<Descriptions.Item label="角色">{detail.hqRole || '—'}</Descriptions.Item>
<Descriptions.Item label="行为">
{detail.actionLabel || resolveHqOperationLabel(detail.action)}
</Descriptions.Item>
<Descriptions.Item label="对象">{detail.refType} / {detail.refId}</Descriptions.Item>
<Descriptions.Item label="状态">{detail.status || '—'}</Descriptions.Item>
<Descriptions.Item label="备注">{detail.remark || '—'}</Descriptions.Item>
</Descriptions>
<Typography.Title level={5} style={{ marginTop: 16 }}>/</Typography.Title>
<pre style={{
margin: 0, padding: 12, background: '#f5f5f5', borderRadius: 4,
maxHeight: 400, overflow: 'auto', fontSize: 12,
}}
>
{JSON.stringify(detail.detail, null, 2)}
</pre>
</>
)}
</Drawer>
</div>
);
}
+2
View File
@@ -16,6 +16,7 @@ import { AnalyticsModule } from './modules/analytics/analytics.module';
import { JobsModule } from './jobs/jobs.module';
import { OpsModule } from './modules/ops/ops.module';
import { CommonModule } from './modules/common/common.module';
import { HqOperationModule } from './common/hq-operation/hq-operation.module';
import { CallbacksModule } from './callbacks/callbacks.module';
@Module({
@@ -41,6 +42,7 @@ import { CallbacksModule } from './callbacks/callbacks.module';
JobsModule,
OpsModule,
CommonModule,
HqOperationModule,
CallbacksModule,
],
})
@@ -65,3 +65,53 @@ export function orderStatusLogWhere(orderId: bigint): Prisma.CommonEventWhereInp
refId: orderId,
};
}
export function buildHqOperationEvent(data: {
hqAccountId: bigint;
action: string;
refType: string;
refId: bigint;
status?: string;
detail?: Record<string, unknown>;
remark?: string;
}): Prisma.CommonEventCreateInput {
return {
eventType: 'HQ_OPERATION',
refType: data.refType,
refId: data.refId,
actorType: 'HQ',
actorId: data.hqAccountId,
status: data.status,
param1: data.action,
param1Desc: 'action',
param2: data.refType,
param2Desc: 'target_type',
param3: data.refId.toString(),
param3Desc: 'target_id',
remark: data.remark,
extraJson: data.detail as Prisma.InputJsonValue,
};
}
export function hqOperationLogWhere(filters?: {
hqAccountId?: bigint;
action?: string;
refType?: string;
from?: Date;
to?: Date;
}): Prisma.CommonEventWhereInput {
return {
eventType: 'HQ_OPERATION',
...(filters?.hqAccountId ? { actorType: 'HQ' as const, actorId: filters.hqAccountId } : {}),
...(filters?.action ? { param1: filters.action } : {}),
...(filters?.refType ? { param2: filters.refType } : {}),
...(filters?.from || filters?.to
? {
createdAt: {
...(filters.from ? { gte: filters.from } : {}),
...(filters.to ? { lte: filters.to } : {}),
},
}
: {}),
};
}
@@ -0,0 +1,30 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.module';
import { buildHqOperationEvent } from '../event/event.helpers';
export type LogHqOperationInput = {
hqAccountId: bigint;
action: string;
refType: string;
refId: bigint;
status?: string;
detail?: Record<string, unknown>;
remark?: string;
};
@Injectable()
export class HqOperationLogService {
constructor(private readonly prisma: PrismaService) {}
async log(input: LogHqOperationInput): Promise<void> {
await this.prisma.commonEvent.create({
data: buildHqOperationEvent(input),
});
}
logSafe(input: LogHqOperationInput): void {
void this.log(input).catch((err) => {
console.error('[HqOperationLog] write failed', err);
});
}
}
@@ -0,0 +1,95 @@
/** HQ 后台写操作审计 action(写入 common_event.param1 */
export const HqOperationAction = {
CITY_CREATE: 'CITY_CREATE',
CITY_UPDATE: 'CITY_UPDATE',
PARTNER_CREATE: 'PARTNER_CREATE',
PARTNER_UPDATE: 'PARTNER_UPDATE',
PARTNER_ACCOUNT_CREATE: 'PARTNER_ACCOUNT_CREATE',
PARTNER_ACCOUNT_UPDATE: 'PARTNER_ACCOUNT_UPDATE',
HQ_ACCOUNT_CREATE: 'HQ_ACCOUNT_CREATE',
HQ_ACCOUNT_UPDATE: 'HQ_ACCOUNT_UPDATE',
USER_DELETE: 'USER_DELETE',
USER_BATCH_DELETE: 'USER_BATCH_DELETE',
ORDER_SHIP: 'ORDER_SHIP',
ORDER_STATUS_DEBUG: 'ORDER_STATUS_DEBUG',
ORDER_BATCH_DELETE: 'ORDER_BATCH_DELETE',
STORE_CREATE: 'STORE_CREATE',
STORE_UPDATE: 'STORE_UPDATE',
STORE_STATUS: 'STORE_STATUS',
STORE_AUDIT: 'STORE_AUDIT',
STORE_ACCOUNT_CREATE: 'STORE_ACCOUNT_CREATE',
STORE_ACCOUNT_UPDATE: 'STORE_ACCOUNT_UPDATE',
STORE_MEDIA_CREATE: 'STORE_MEDIA_CREATE',
STORE_MEDIA_UPDATE: 'STORE_MEDIA_UPDATE',
STORE_MEDIA_DELETE: 'STORE_MEDIA_DELETE',
PRODUCT_CREATE: 'PRODUCT_CREATE',
PRODUCT_UPDATE: 'PRODUCT_UPDATE',
PRODUCT_DELETE: 'PRODUCT_DELETE',
PRODUCT_TEMPLATE_CREATE: 'PRODUCT_TEMPLATE_CREATE',
PRODUCT_TEMPLATE_UPDATE: 'PRODUCT_TEMPLATE_UPDATE',
BENEFIT_COUPON_VOID: 'BENEFIT_COUPON_VOID',
DELIVERY_UPDATE: 'DELIVERY_UPDATE',
TICKET_APPROVE: 'TICKET_APPROVE',
TICKET_REJECT: 'TICKET_REJECT',
STORE_PAYOUT_CONFIRM: 'STORE_PAYOUT_CONFIRM',
STORE_PAYOUT_BATCH_CONFIRM: 'STORE_PAYOUT_BATCH_CONFIRM',
PARTNER_BILL_GENERATE: 'PARTNER_BILL_GENERATE',
PARTNER_BILL_CONFIRM: 'PARTNER_BILL_CONFIRM',
PARTNER_BILL_MARK_PAID: 'PARTNER_BILL_MARK_PAID',
REDEEM_DEBUG_CREATE_TOKEN: 'REDEEM_DEBUG_CREATE_TOKEN',
REDEEM_DEBUG_CONFIRM: 'REDEEM_DEBUG_CONFIRM',
} as const;
export type HqOperationActionCode = (typeof HqOperationAction)[keyof typeof HqOperationAction];
export const HQ_OPERATION_ACTION_LABELS: Record<string, string> = {
[HqOperationAction.CITY_CREATE]: '新增开城城市',
[HqOperationAction.CITY_UPDATE]: '编辑开城城市',
[HqOperationAction.PARTNER_CREATE]: '新增城市合伙人',
[HqOperationAction.PARTNER_UPDATE]: '编辑城市合伙人',
[HqOperationAction.PARTNER_ACCOUNT_CREATE]: '新增合伙人账户',
[HqOperationAction.PARTNER_ACCOUNT_UPDATE]: '编辑合伙人账户',
[HqOperationAction.HQ_ACCOUNT_CREATE]: '新增 HQ 管理员',
[HqOperationAction.HQ_ACCOUNT_UPDATE]: '编辑 HQ 管理员/权限',
[HqOperationAction.USER_DELETE]: '删除用户',
[HqOperationAction.USER_BATCH_DELETE]: '批量删除用户',
[HqOperationAction.ORDER_SHIP]: '订单发货',
[HqOperationAction.ORDER_STATUS_DEBUG]: '订单状态调试',
[HqOperationAction.ORDER_BATCH_DELETE]: '批量删除订单',
[HqOperationAction.STORE_CREATE]: '新增门店',
[HqOperationAction.STORE_UPDATE]: '编辑门店',
[HqOperationAction.STORE_STATUS]: '变更门店状态',
[HqOperationAction.STORE_AUDIT]: '门店审核',
[HqOperationAction.STORE_ACCOUNT_CREATE]: '新增门店账户',
[HqOperationAction.STORE_ACCOUNT_UPDATE]: '编辑门店账户',
[HqOperationAction.STORE_MEDIA_CREATE]: '新增门店资源',
[HqOperationAction.STORE_MEDIA_UPDATE]: '编辑门店资源',
[HqOperationAction.STORE_MEDIA_DELETE]: '删除门店资源',
[HqOperationAction.PRODUCT_CREATE]: '新增商品',
[HqOperationAction.PRODUCT_UPDATE]: '编辑商品',
[HqOperationAction.PRODUCT_DELETE]: '删除商品',
[HqOperationAction.PRODUCT_TEMPLATE_CREATE]: '新增详情模板',
[HqOperationAction.PRODUCT_TEMPLATE_UPDATE]: '编辑详情模板',
[HqOperationAction.BENEFIT_COUPON_VOID]: '作废权益券',
[HqOperationAction.DELIVERY_UPDATE]: '编辑配送单',
[HqOperationAction.TICKET_APPROVE]: '工单通过',
[HqOperationAction.TICKET_REJECT]: '工单驳回',
[HqOperationAction.STORE_PAYOUT_CONFIRM]: '门店打款确认',
[HqOperationAction.STORE_PAYOUT_BATCH_CONFIRM]: '批量门店打款',
[HqOperationAction.PARTNER_BILL_GENERATE]: '生成合伙人账单',
[HqOperationAction.PARTNER_BILL_CONFIRM]: '确认合伙人账单',
[HqOperationAction.PARTNER_BILL_MARK_PAID]: '合伙人账单结算',
[HqOperationAction.REDEEM_DEBUG_CREATE_TOKEN]: '核销调试-生成码',
[HqOperationAction.REDEEM_DEBUG_CONFIRM]: '核销调试-确认核销',
STORE_PAYOUT: '门店打款确认',
};
export function resolveHqOperationLabel(action: string | null | undefined, refType?: string | null): string {
if (action && HQ_OPERATION_ACTION_LABELS[action]) {
return HQ_OPERATION_ACTION_LABELS[action];
}
if (refType && HQ_OPERATION_ACTION_LABELS[refType]) {
return HQ_OPERATION_ACTION_LABELS[refType];
}
return action || refType || 'HQ 操作';
}
@@ -0,0 +1,21 @@
import { SetMetadata } from '@nestjs/common';
import type { HqOperationActionCode } from './hq-operation.constants';
export const HQ_OPERATION_KEY = 'hq_operation';
export type HqOperationMeta = {
action: HqOperationActionCode | string;
refType: string;
/** 从路由 params 取 refId */
refIdParam?: string;
/** 从响应体字段取 refId,默认 id */
refIdField?: string;
/** 批量操作无单一 refId 时用 0 */
batch?: boolean;
/** 记录请求体到 extraJson */
includeBody?: boolean;
/** 记录响应体到 extraJson(截断) */
includeResponse?: boolean;
};
export const HqOperation = (meta: HqOperationMeta) => SetMetadata(HQ_OPERATION_KEY, meta);
@@ -0,0 +1,103 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable, tap } from 'rxjs';
import type { AuthUser } from '../guards/jwt-auth.guard';
import { HQ_OPERATION_KEY, type HqOperationMeta } from './hq-operation.decorator';
import { HqOperationLogService } from './hq-operation-log.service';
function pickRefId(value: unknown): bigint | null {
if (value == null || value === '') return null;
try {
return BigInt(String(value));
} catch {
return null;
}
}
function sanitizeBody(body: unknown): unknown {
if (!body || typeof body !== 'object') return body;
const copy = { ...(body as Record<string, unknown>) };
for (const key of Object.keys(copy)) {
if (/password|secret|token/i.test(key)) {
copy[key] = '***';
}
}
return copy;
}
function summarizeResponse(data: unknown): unknown {
if (data == null) return null;
if (typeof data !== 'object') return data;
const obj = data as Record<string, unknown>;
const summary: Record<string, unknown> = {};
for (const key of ['id', 'ok', 'deleted', 'orderNo', 'userNo', 'redeemNo', 'message', 'status']) {
if (obj[key] !== undefined) summary[key] = obj[key];
}
if (Array.isArray(obj.items)) {
summary.itemCount = obj.items.length;
}
if (Array.isArray(obj.orderNos)) {
summary.orderNos = obj.orderNos;
}
return Object.keys(summary).length ? summary : obj;
}
@Injectable()
export class HqOperationInterceptor implements NestInterceptor {
constructor(
private readonly reflector: Reflector,
private readonly logService: HqOperationLogService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const meta = this.reflector.get<HqOperationMeta | undefined>(
HQ_OPERATION_KEY,
context.getHandler(),
);
if (!meta) return next.handle();
const req = context.switchToHttp().getRequest();
const user = req.user as AuthUser | undefined;
if (!user || user.actorType !== 'HQ') {
return next.handle();
}
return next.handle().pipe(
tap((data) => {
const refId = meta.batch
? 0n
: pickRefId(meta.refIdParam ? req.params?.[meta.refIdParam] : null)
?? pickRefId(
meta.refIdField
? (data as Record<string, unknown> | null)?.[meta.refIdField]
: (data as Record<string, unknown> | null)?.id,
)
?? 0n;
const detail: Record<string, unknown> = {
method: req.method,
path: req.originalUrl ?? req.url,
};
if (meta.includeBody && req.body) {
detail.requestBody = sanitizeBody(req.body);
}
if (meta.includeResponse !== false && data != null) {
detail.response = summarizeResponse(data);
}
this.logService.logSafe({
hqAccountId: user.actorId,
action: meta.action,
refType: meta.refType,
refId,
detail,
});
}),
);
}
}
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { HqOperationLogService } from './hq-operation-log.service';
import { HqOperationInterceptor } from './hq-operation.interceptor';
@Module({
providers: [
HqOperationLogService,
HqOperationInterceptor,
{
provide: APP_INTERCEPTOR,
useClass: HqOperationInterceptor,
},
],
exports: [HqOperationLogService],
})
export class HqOperationModule {}
@@ -1,5 +1,7 @@
import { Controller, Get, Param, Post, Query, 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 { AdminBenefitService } from './admin-benefit.service';
import { AdminBenefitCouponsQueryDto, AdminBenefitLedgersQueryDto } from './dto/admin-query.dto';
@@ -19,6 +21,7 @@ export class AdminBenefitCouponsController {
}
@Post(':id/void')
@HqOperation({ action: HqOperationAction.BENEFIT_COUPON_VOID, refType: 'BENEFIT_COUPON', refIdParam: 'id' })
voidCoupon(@Param('id') id: string) {
return this.service.voidCoupon(BigInt(id));
}
@@ -1,9 +1,10 @@
import { Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { Body, Controller, Get, Param, Post, Put, Query, 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 { AdminCitiesService } from './admin-cities.service';
import { AdminCitiesQueryDto } from './dto/admin-query.dto';
import { CreateCityDto, UpdateCityDto } from './dto/admin-mutate.dto';
import { Body } from '@nestjs/common';
@Controller('admin/cities')
@UseGuards(HqAuthGuard)
@@ -21,11 +22,18 @@ export class AdminCitiesController {
}
@Post()
@HqOperation({ action: HqOperationAction.CITY_CREATE, refType: 'CITY', refIdField: 'id', includeBody: true })
create(@Body() dto: CreateCityDto) {
return this.service.create(dto);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.CITY_UPDATE,
refType: 'CITY',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateCityDto) {
return this.service.update(BigInt(id), dto);
}
@@ -1,6 +1,8 @@
import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
import { AdminHqAccountsService } from './admin-hq-accounts.service';
import { AdminHqAccountsQueryDto } from './dto/admin-query.dto';
import { CreateHqAccountDto, UpdateHqAccountDto } from './dto/admin-mutate.dto';
@@ -22,12 +24,24 @@ export class AdminHqAccountsController {
@Post()
@UseGuards(SuperAdminGuard)
@HqOperation({
action: HqOperationAction.HQ_ACCOUNT_CREATE,
refType: 'HQ_ACCOUNT',
refIdField: 'id',
includeBody: true,
})
create(@Body() dto: CreateHqAccountDto) {
return this.service.create(dto);
}
@Put(':id')
@UseGuards(SuperAdminGuard)
@HqOperation({
action: HqOperationAction.HQ_ACCOUNT_UPDATE,
refType: 'HQ_ACCOUNT',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateHqAccountDto) {
return this.service.update(BigInt(id), dto);
}
@@ -0,0 +1,20 @@
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { AdminHqLogsService } from './admin-hq-logs.service';
import { AdminHqLogsQueryDto } from './dto/admin-query.dto';
@Controller('admin/logs/hq')
@UseGuards(HqAuthGuard)
export class AdminHqLogsController {
constructor(private readonly service: AdminHqLogsService) {}
@Get()
list(@Query() query: AdminHqLogsQueryDto) {
return this.service.list(query);
}
@Get(':id')
detail(@Param('id') id: string) {
return this.service.detail(BigInt(id));
}
}
@@ -0,0 +1,96 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { hqOperationLogWhere } from '../../common/event/event.helpers';
import { resolveHqOperationLabel } from '../../common/hq-operation/hq-operation.constants';
import type { AdminHqLogsQueryDto } from './dto/admin-query.dto';
@Injectable()
export class AdminHqLogsService {
constructor(private readonly prisma: PrismaService) {}
async list(query: AdminHqLogsQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where = hqOperationLogWhere({
hqAccountId: query.hqAccountId ? BigInt(query.hqAccountId) : undefined,
action: query.action,
refType: query.refType,
from: query.from ? new Date(query.from) : undefined,
to: query.to ? new Date(query.to) : undefined,
});
const [rows, total] = await Promise.all([
this.prisma.commonEvent.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.commonEvent.count({ where }),
]);
const hqIds = [...new Set(rows.map((r) => r.actorId).filter((id): id is bigint => id != null))];
const hqAccounts = hqIds.length
? await this.prisma.hqAccount.findMany({
where: { id: { in: hqIds } },
select: { id: true, name: true, phone: true, adminRole: true },
})
: [];
const hqMap = new Map(hqAccounts.map((a) => [a.id.toString(), a]));
return serializeBigInt({
items: rows.map((row) => {
const hq = row.actorId ? hqMap.get(row.actorId.toString()) : undefined;
const action = row.param1Desc === 'action' ? row.param1 : row.param1;
return {
id: row.id,
hqAccountId: row.actorId,
hqName: hq?.name ?? null,
hqPhone: hq?.phone ?? null,
hqRole: hq?.adminRole ?? null,
action,
actionLabel: resolveHqOperationLabel(action, row.refType),
refType: row.param2Desc === 'target_type' ? row.param2 : row.refType,
refId: row.param3Desc === 'target_id' ? row.param3 : row.refId.toString(),
status: row.status,
remark: row.remark,
detail: row.extraJson,
createdAt: row.createdAt,
};
}),
total,
page,
pageSize,
});
}
async detail(id: bigint) {
const row = await this.prisma.commonEvent.findFirst({
where: { id, eventType: 'HQ_OPERATION' },
});
if (!row) throw new NotFoundException('操作日志不存在');
const hq = row.actorId
? await this.prisma.hqAccount.findUnique({
where: { id: row.actorId },
select: { id: true, name: true, phone: true, adminRole: true },
})
: null;
const action = row.param1Desc === 'action' ? row.param1 : row.param1;
return serializeBigInt({
id: row.id,
hqAccountId: row.actorId,
hqAccount: hq,
action,
actionLabel: resolveHqOperationLabel(action, row.refType),
refType: row.param2Desc === 'target_type' ? row.param2 : row.refType,
refId: row.param3Desc === 'target_id' ? row.param3 : row.refId.toString(),
status: row.status,
remark: row.remark,
detail: row.extraJson,
createdAt: row.createdAt,
});
}
}
@@ -1,6 +1,8 @@
import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
import { AdminOrdersService } from './admin-orders.service';
import { AdminShipOrderDto, BatchDeleteOrdersDto, UpdateOrderStatusDto } from './dto/admin-mutate.dto';
import { AdminOrdersQueryDto } from './dto/admin-query.dto';
@@ -17,6 +19,12 @@ export class AdminOrdersController {
@Post('batch-delete')
@UseGuards(SuperAdminGuard)
@HqOperation({
action: HqOperationAction.ORDER_BATCH_DELETE,
refType: 'ORDER',
batch: true,
includeBody: true,
})
batchDelete(@Body() dto: BatchDeleteOrdersDto) {
return this.ordersService.batchDeleteOrders(dto.ids.map((id) => BigInt(id)));
}
@@ -33,12 +41,24 @@ export class AdminOrdersController {
/** HQ 发货:调用小飞侠创建运单并更新配送信息 */
@Post(':id/ship')
@HqOperation({
action: HqOperationAction.ORDER_SHIP,
refType: 'ORDER',
refIdParam: 'id',
includeBody: true,
})
ship(@Param('id') id: string, @Body() dto: AdminShipOrderDto) {
return this.ordersService.shipOrder(BigInt(id), dto);
}
/** preV1 调试:直接改订单状态,不走业务校验 */
@Put(':id/status')
@HqOperation({
action: HqOperationAction.ORDER_STATUS_DEBUG,
refType: 'ORDER',
refIdParam: 'id',
includeBody: true,
})
updateStatus(@Param('id') id: string, @Body() dto: UpdateOrderStatusDto) {
return this.ordersService.updateStatusDebug(BigInt(id), dto.status);
}
@@ -1,5 +1,7 @@
import { Body, Controller, Get, Param, Post, Put, Query, 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 { AdminPartnersService } from './admin-partners.service';
import { AdminPartnerAccountsQueryDto, AdminPartnersQueryDto } from './dto/admin-query.dto';
import {
@@ -25,11 +27,23 @@ export class AdminPartnersController {
}
@Post()
@HqOperation({
action: HqOperationAction.PARTNER_CREATE,
refType: 'PARTNER',
refIdField: 'id',
includeBody: true,
})
create(@Body() dto: CreatePartnerDto) {
return this.service.createPartner(dto);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.PARTNER_UPDATE,
refType: 'PARTNER',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdatePartnerDto) {
return this.service.updatePartner(BigInt(id), dto);
}
@@ -51,11 +65,23 @@ export class AdminPartnerAccountsController {
}
@Post()
@HqOperation({
action: HqOperationAction.PARTNER_ACCOUNT_CREATE,
refType: 'PARTNER_ACCOUNT',
refIdField: 'id',
includeBody: true,
})
create(@Body() dto: CreatePartnerAccountDto) {
return this.service.createPartnerAccount(dto);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.PARTNER_ACCOUNT_UPDATE,
refType: 'PARTNER_ACCOUNT',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdatePartnerAccountDto) {
return this.service.updatePartnerAccount(BigInt(id), dto);
}
@@ -1,5 +1,7 @@
import { Body, Controller, Get, Param, Post, Put, Query, 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 { AdminProductDetailTemplatesService } from './admin-product-detail-templates.service';
import { AdminProductDetailTemplatesQueryDto } from './dto/admin-query.dto';
import {
@@ -23,11 +25,23 @@ export class AdminProductDetailTemplatesController {
}
@Post()
@HqOperation({
action: HqOperationAction.PRODUCT_TEMPLATE_CREATE,
refType: 'PRODUCT_TEMPLATE',
refIdField: 'id',
includeBody: true,
})
create(@Body() dto: CreateProductDetailTemplateDto) {
return this.service.create(dto);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.PRODUCT_TEMPLATE_UPDATE,
refType: 'PRODUCT_TEMPLATE',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateProductDetailTemplateDto) {
return this.service.update(BigInt(id), dto);
}
@@ -1,5 +1,7 @@
import { Body, Controller, Delete, Get, Param, Post, Put, Query, 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 { AdminProductsService } from './admin-products.service';
import { AdminProductsQueryDto } from './dto/admin-query.dto';
import { CreateProductDto, UpdateProductDto } from './dto/admin-mutate.dto';
@@ -20,16 +22,19 @@ export class AdminProductsController {
}
@Post()
@HqOperation({ action: HqOperationAction.PRODUCT_CREATE, refType: 'PRODUCT', refIdField: 'id', includeBody: true })
create(@Body() dto: CreateProductDto) {
return this.service.create(dto);
}
@Put(':id')
@HqOperation({ action: HqOperationAction.PRODUCT_UPDATE, refType: 'PRODUCT', refIdParam: 'id', includeBody: true })
update(@Param('id') id: string, @Body() dto: UpdateProductDto) {
return this.service.update(BigInt(id), dto);
}
@Delete(':id')
@HqOperation({ action: HqOperationAction.PRODUCT_DELETE, refType: 'PRODUCT', refIdParam: 'id' })
remove(@Param('id') id: string) {
return this.service.remove(BigInt(id));
}
@@ -1,5 +1,7 @@
import { Body, Controller, Post, 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 { AdminRedeemDebugService } from './admin-redeem-debug.service';
import type {
AdminRedeemDebugCreateTokenDto,
@@ -13,6 +15,12 @@ export class AdminRedeemDebugController {
/** preV1 调试:为用户生成核销码 */
@Post('create-token')
@HqOperation({
action: HqOperationAction.REDEEM_DEBUG_CREATE_TOKEN,
refType: 'REDEEM_DEBUG',
batch: true,
includeBody: true,
})
createToken(@Body() dto: AdminRedeemDebugCreateTokenDto) {
return this.service.createToken(dto);
}
@@ -25,6 +33,12 @@ export class AdminRedeemDebugController {
/** preV1 调试:门店侧确认核销 */
@Post('confirm')
@HqOperation({
action: HqOperationAction.REDEEM_DEBUG_CONFIRM,
refType: 'REDEEM_DEBUG',
batch: true,
includeBody: true,
})
confirm(@Body() dto: AdminRedeemDebugStoreTokenDto) {
return this.service.confirm(dto);
}
@@ -1,5 +1,7 @@
import { Body, Controller, Get, Param, Put, Query, 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 { AdminRedeemService, AdminDeliveriesService } from './admin-redeem.service';
import { AdminDeliveriesQueryDto, AdminRedeemRecordsQueryDto } from './dto/admin-query.dto';
import { UpdateDeliveryDto } from './dto/admin-mutate.dto';
@@ -36,6 +38,12 @@ export class AdminDeliveriesController {
}
@Put(':id')
@HqOperation({
action: HqOperationAction.DELIVERY_UPDATE,
refType: 'DELIVERY',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateDeliveryDto) {
return this.service.update(BigInt(id), dto);
}
@@ -1,5 +1,7 @@
import { Body, Controller, Delete, Get, Param, Post, Put, Query, 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 { AdminStoresService } from './admin-stores.service';
import {
AdminStoreAccountsQueryDto,
@@ -32,21 +34,25 @@ export class AdminStoresController {
}
@Post()
@HqOperation({ action: HqOperationAction.STORE_CREATE, refType: 'STORE', refIdField: 'id', includeBody: true })
create(@Body() dto: CreateStoreDto) {
return this.service.createStore(dto);
}
@Put(':id')
@HqOperation({ action: HqOperationAction.STORE_UPDATE, refType: 'STORE', refIdParam: 'id', includeBody: true })
update(@Param('id') id: string, @Body() dto: UpdateStoreDto) {
return this.service.updateStore(BigInt(id), dto);
}
@Put(':id/status')
@HqOperation({ action: HqOperationAction.STORE_STATUS, refType: 'STORE', refIdParam: 'id', includeBody: true })
updateStatus(@Param('id') id: string, @Body() dto: UpdateStoreStatusDto) {
return this.service.updateStoreStatus(BigInt(id), dto);
}
@Put(':id/audit')
@HqOperation({ action: HqOperationAction.STORE_AUDIT, refType: 'STORE', refIdParam: 'id', includeBody: true })
audit(@Param('id') id: string, @Body() body: { approved: boolean; remark?: string }) {
return this.service.auditStore(BigInt(id), body);
}
@@ -68,11 +74,23 @@ export class AdminStoreAccountsController {
}
@Post()
@HqOperation({
action: HqOperationAction.STORE_ACCOUNT_CREATE,
refType: 'STORE_ACCOUNT',
refIdField: 'id',
includeBody: true,
})
create(@Body() dto: CreateStoreAccountDto) {
return this.service.createStoreAccount(dto);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.STORE_ACCOUNT_UPDATE,
refType: 'STORE_ACCOUNT',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateStoreAccountDto) {
return this.service.updateStoreAccount(BigInt(id), dto);
}
@@ -89,16 +107,29 @@ export class AdminStoreMediaController {
}
@Post()
@HqOperation({
action: HqOperationAction.STORE_MEDIA_CREATE,
refType: 'STORE_MEDIA',
refIdField: 'id',
includeBody: true,
})
create(@Body() dto: CreateStoreMediaDto) {
return this.service.createStoreMedia(dto);
}
@Put(':id')
@HqOperation({
action: HqOperationAction.STORE_MEDIA_UPDATE,
refType: 'STORE_MEDIA',
refIdParam: 'id',
includeBody: true,
})
update(@Param('id') id: string, @Body() dto: UpdateStoreMediaDto) {
return this.service.updateStoreMedia(BigInt(id), dto);
}
@Delete(':id')
@HqOperation({ action: HqOperationAction.STORE_MEDIA_DELETE, refType: 'STORE_MEDIA', refIdParam: 'id' })
remove(@Param('id') id: string) {
return this.service.deleteStoreMedia(BigInt(id));
}
@@ -1,5 +1,7 @@
import { Body, Controller, Get, Param, Post, Query, 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 { AdminTicketsService } from './admin-tickets.service';
import { TicketListQueryDto } from '../common/dto/common-query.dto';
@@ -19,11 +21,23 @@ export class AdminTicketsController {
}
@Post(':id/approve')
@HqOperation({
action: HqOperationAction.TICKET_APPROVE,
refType: 'TICKET',
refIdParam: 'id',
includeBody: true,
})
approve(@Param('id') id: string, @Body() body: { remark?: string }) {
return this.service.approve(BigInt(id), body.remark);
}
@Post(':id/reject')
@HqOperation({
action: HqOperationAction.TICKET_REJECT,
refType: 'TICKET',
refIdParam: 'id',
includeBody: true,
})
reject(@Param('id') id: string, @Body() body: { remark?: string }) {
return this.service.reject(BigInt(id), body.remark);
}
@@ -1,6 +1,8 @@
import { Body, Controller, Delete, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
import { AdminUsersService } from './admin-users.service';
import { AdminUsersQueryDto } from './dto/admin-query.dto';
import { BatchDeleteUsersConfirmDto, BatchDeleteUsersDto } from './dto/admin-mutate.dto';
@@ -23,6 +25,12 @@ export class AdminUsersController {
@Post('batch-delete')
@UseGuards(SuperAdminGuard)
@HqOperation({
action: HqOperationAction.USER_BATCH_DELETE,
refType: 'USER',
batch: true,
includeBody: true,
})
batchDelete(@Body() dto: BatchDeleteUsersConfirmDto) {
return this.usersService.batchDeleteUsers(
dto.ids.map((id) => BigInt(id)),
@@ -37,6 +45,11 @@ export class AdminUsersController {
@Delete(':id')
@UseGuards(SuperAdminGuard)
@HqOperation({
action: HqOperationAction.USER_DELETE,
refType: 'USER',
refIdParam: 'id',
})
remove(@Param('id') id: string) {
return this.usersService.deleteUser(BigInt(id));
}
@@ -275,6 +275,28 @@ export class AdminUserLogsQueryDto extends PaginationQueryDto {
to?: string;
}
export class AdminHqLogsQueryDto extends PaginationQueryDto {
@IsOptional()
@IsString()
hqAccountId?: string;
@IsOptional()
@IsString()
action?: string;
@IsOptional()
@IsString()
refType?: string;
@IsOptional()
@IsString()
from?: string;
@IsOptional()
@IsString()
to?: string;
}
export class AdminStoreMediaQueryDto extends PaginationQueryDto {
@IsOptional()
@IsString()
@@ -23,6 +23,8 @@ import { AdminProductsController } from './admin-products.controller';
import { AdminProductsService } from './admin-products.service';
import { AdminUserLogsController } from './admin-user-logs.controller';
import { AdminUserLogsService } from './admin-user-logs.service';
import { AdminHqLogsController } from './admin-hq-logs.controller';
import { AdminHqLogsService } from './admin-hq-logs.service';
import { AdminTicketsController } from './admin-tickets.controller';
import { AdminTicketsService } from './admin-tickets.service';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
@@ -56,6 +58,7 @@ import { AdminRedeemDebugService } from './admin-redeem-debug.service';
AdminHqAccountsController,
AdminProductsController,
AdminUserLogsController,
AdminHqLogsController,
AdminTicketsController,
AdminXiaofeixiaController,
AdminProductDetailTemplatesController,
@@ -74,6 +77,7 @@ import { AdminRedeemDebugService } from './admin-redeem-debug.service';
AdminHqAccountsService,
AdminProductsService,
AdminUserLogsService,
AdminHqLogsService,
AdminTicketsService,
AdminXiaofeixiaService,
AdminProductDetailTemplatesService,
@@ -3,6 +3,8 @@ import { SettlementService } from './settlement.service';
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
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 { PrismaService } from '../../common/prisma/prisma.module';
@Controller('partner/settlement')
@@ -52,11 +54,23 @@ export class AdminStorePayoutController {
}
@Post(':id/confirm')
@HqOperation({
action: HqOperationAction.STORE_PAYOUT_CONFIRM,
refType: 'STORE_PAYOUT',
refIdParam: 'id',
includeBody: true,
})
confirm(@Param('id') id: string, @Body() body: { paymentRef?: string; batchNo?: string; remark?: string }) {
return this.settlementService.confirmStorePayout(BigInt(id), body);
}
@Post('batch-confirm')
@HqOperation({
action: HqOperationAction.STORE_PAYOUT_BATCH_CONFIRM,
refType: 'STORE_PAYOUT',
batch: true,
includeBody: true,
})
batchConfirm(@Body() body: { ids: string[]; batchNo?: string }) {
return this.settlementService.batchConfirmStorePayouts(body.ids ?? [], body);
}
@@ -73,6 +87,12 @@ export class AdminPartnerBillController {
constructor(private readonly settlementService: SettlementService) {}
@Post('generate')
@HqOperation({
action: HqOperationAction.PARTNER_BILL_GENERATE,
refType: 'PARTNER_BILL',
refIdField: 'id',
includeBody: true,
})
generate(@Body() body: { partnerId: string; year: number; month: number }) {
return this.settlementService.generatePartnerBill(body);
}
@@ -101,11 +121,22 @@ export class AdminPartnerBillController {
}
@Post(':id/confirm')
@HqOperation({
action: HqOperationAction.PARTNER_BILL_CONFIRM,
refType: 'PARTNER_BILL',
refIdParam: 'id',
})
confirm(@Param('id') id: string) {
return this.settlementService.confirmPartnerBill(BigInt(id));
}
@Post(':id/mark-paid')
@HqOperation({
action: HqOperationAction.PARTNER_BILL_MARK_PAID,
refType: 'PARTNER_BILL',
refIdParam: 'id',
includeBody: true,
})
markPaid(@Param('id') id: string, @Body() body: { paymentRef?: string }) {
return this.settlementService.markPartnerBillPaid(BigInt(id), body);
}
@@ -106,18 +106,6 @@ export class SettlementService {
},
});
await this.prisma.commonEvent.create({
data: {
eventType: 'HQ_OPERATION',
refType: 'STORE_PAYOUT',
refId: id,
actorType: 'HQ',
status: 'PAID',
param1: dto.paymentRef ?? '',
remark: dto.remark ?? '门店 T+1 打款确认',
},
});
return serializeBigInt(updated);
}
@@ -269,17 +257,6 @@ export class SettlementService {
data: { status: 'CONFIRMED', confirmedAt: new Date() },
});
await this.prisma.commonEvent.create({
data: {
eventType: 'HQ_OPERATION',
refType: 'PARTNER_BILL',
refId: id,
actorType: 'HQ',
status: 'CONFIRMED',
amount1: Number(updated.totalAmount),
},
});
return serializeBigInt(updated);
}
@@ -293,18 +270,6 @@ export class SettlementService {
data: { status: 'PAID', paidAt: new Date() },
});
await this.prisma.commonEvent.create({
data: {
eventType: 'HQ_OPERATION',
refType: 'PARTNER_BILL',
refId: id,
actorType: 'HQ',
status: 'PAID',
param1: dto.paymentRef ?? '',
amount1: Number(updated.totalAmount),
},
});
return serializeBigInt(updated);
}