后端显示门店账单
This commit is contained in:
@@ -47,8 +47,10 @@ pnpm dev:partner # http://localhost:5175
|
|||||||
| 端 | 手机号 |
|
| 端 | 手机号 |
|
||||||
|----|--------|
|
|----|--------|
|
||||||
| C端用户 | 13800000001 |
|
| C端用户 | 13800000001 |
|
||||||
| 门店 | 13900000001 |
|
|
||||||
| 合伙人 | 13700000001 |
|
| 合伙人 | 13700000001 |
|
||||||
|
| HQ | 13600000001 |
|
||||||
|
|
||||||
|
门店登录使用录入门店时绑定的手机号;seed 仅预置「郑州老城店」联调账号 `13910000001`。
|
||||||
|
|
||||||
## 主链路冒烟
|
## 主链路冒烟
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import StoreMediaPage from './pages/StoreMediaPage';
|
|||||||
import ProductsPage from './pages/ProductsPage';
|
import ProductsPage from './pages/ProductsPage';
|
||||||
import ProductDetailTemplatesPage from './pages/ProductDetailTemplatesPage';
|
import ProductDetailTemplatesPage from './pages/ProductDetailTemplatesPage';
|
||||||
import ResourcesPage from './pages/ResourcesPage';
|
import ResourcesPage from './pages/ResourcesPage';
|
||||||
import StorePayoutsPage from './pages/StorePayoutsPage';
|
import StoreBillsPage from './pages/StoreBillsPage';
|
||||||
import PartnerBillsPage from './pages/PartnerBillsPage';
|
import PartnerBillsPage from './pages/PartnerBillsPage';
|
||||||
import TicketsPage from './pages/TicketsPage';
|
import TicketsPage from './pages/TicketsPage';
|
||||||
import UserLogsPage from './pages/UserLogsPage';
|
import UserLogsPage from './pages/UserLogsPage';
|
||||||
@@ -60,7 +60,8 @@ export default function App() {
|
|||||||
<Route path="/benefit/ledgers" element={<BenefitLedgersPage />} />
|
<Route path="/benefit/ledgers" element={<BenefitLedgersPage />} />
|
||||||
<Route path="/redeem-records" element={<RedeemRecordsPage />} />
|
<Route path="/redeem-records" element={<RedeemRecordsPage />} />
|
||||||
<Route path="/redeem/debug" element={<RedeemDebugPage />} />
|
<Route path="/redeem/debug" element={<RedeemDebugPage />} />
|
||||||
<Route path="/store-payouts" element={<StorePayoutsPage />} />
|
<Route path="/store-bills" element={<StoreBillsPage />} />
|
||||||
|
<Route path="/store-payouts" element={<Navigate to="/store-bills" replace />} />
|
||||||
<Route path="/partner-bills" element={<PartnerBillsPage />} />
|
<Route path="/partner-bills" element={<PartnerBillsPage />} />
|
||||||
<Route path="/tickets" element={<TicketsPage />} />
|
<Route path="/tickets" element={<TicketsPage />} />
|
||||||
<Route path="/logs/users" element={<UserLogsPage />} />
|
<Route path="/logs/users" element={<UserLogsPage />} />
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const MENU_ITEMS: MenuProps['items'] = [
|
|||||||
children: [
|
children: [
|
||||||
{ key: '/stores', label: '门店列表' },
|
{ key: '/stores', label: '门店列表' },
|
||||||
{ key: '/store-accounts', label: '门店账户' },
|
{ key: '/store-accounts', label: '门店账户' },
|
||||||
|
{ key: '/store-bills', label: '门店账单' },
|
||||||
{ key: '/store-media', label: '门店资源' },
|
{ key: '/store-media', label: '门店资源' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -62,7 +63,6 @@ const MENU_ITEMS: MenuProps['items'] = [
|
|||||||
{ key: '/benefit/ledgers', label: '流水' },
|
{ key: '/benefit/ledgers', label: '流水' },
|
||||||
{ key: '/redeem-records', label: '核销记录' },
|
{ key: '/redeem-records', label: '核销记录' },
|
||||||
{ key: '/redeem/debug', label: '核销调试' },
|
{ key: '/redeem/debug', label: '核销调试' },
|
||||||
{ key: '/store-payouts', label: '门店打款' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ key: '/partner-bills', icon: <TeamOutlined />, label: '合伙人结算' },
|
{ key: '/partner-bills', icon: <TeamOutlined />, label: '合伙人结算' },
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Button, Descriptions, Drawer, Form, Select, Space, Table, Tag, Typography, message } from 'antd';
|
||||||
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import { request, type Paginated } from '../lib/api';
|
||||||
|
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
|
||||||
|
import { useAdminList } from '../lib/useAdminList';
|
||||||
|
|
||||||
|
type Row = {
|
||||||
|
id: string;
|
||||||
|
redeemAmount: number;
|
||||||
|
payoutAmount: number;
|
||||||
|
settlementRate: number;
|
||||||
|
status: string;
|
||||||
|
expectedPayAt: string;
|
||||||
|
paidAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
store?: { id: string; name: string; cityName: string; phone?: string };
|
||||||
|
redeemRecord?: { redeemNo: string; amount?: number };
|
||||||
|
};
|
||||||
|
|
||||||
|
type StoreOption = { id: string; name: string; phone: string };
|
||||||
|
|
||||||
|
const PAYOUT_STATUS_LABELS: Record<string, string> = {
|
||||||
|
PENDING: '待打款',
|
||||||
|
PAID: '已打款',
|
||||||
|
};
|
||||||
|
|
||||||
|
const PAYOUT_STATUS_COLORS: Record<string, string> = {
|
||||||
|
PENDING: 'orange',
|
||||||
|
PAID: 'green',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function StoreBillsPage() {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [filters, setFilters] = useState<Record<string, string>>({});
|
||||||
|
const [stores, setStores] = useState<StoreOption[]>([]);
|
||||||
|
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
|
||||||
|
'/admin/store-payouts',
|
||||||
|
() => {
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (filters.status) qs.set('status', filters.status);
|
||||||
|
if (filters.storeId) qs.set('storeId', filters.storeId);
|
||||||
|
return qs;
|
||||||
|
},
|
||||||
|
[filters],
|
||||||
|
);
|
||||||
|
const [detail, setDetail] = useState<Record<string, unknown> | null>(null);
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void request<Paginated<StoreOption>>(`/admin/stores?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
|
||||||
|
.then((res) => setStores(res.items))
|
||||||
|
.catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function confirmPayout(id: string) {
|
||||||
|
await request(`/admin/store-payouts/${id}/confirm`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ remark: '财务确认打款' }),
|
||||||
|
});
|
||||||
|
message.success('已确认打款');
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnsType<Row> = [
|
||||||
|
{ title: '门店', dataIndex: ['store', 'name'], width: 140, ellipsis: true },
|
||||||
|
{ title: '登录手机', dataIndex: ['store', 'phone'], width: 120, render: (v) => v || '—' },
|
||||||
|
{ title: '城市', dataIndex: ['store', 'cityName'], width: 90 },
|
||||||
|
{
|
||||||
|
title: '核销单号',
|
||||||
|
dataIndex: ['redeemRecord', 'redeemNo'],
|
||||||
|
width: 160,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (v) => v || '—',
|
||||||
|
},
|
||||||
|
{ title: '核销面额', dataIndex: 'redeemAmount', width: 100, render: (v) => `¥${v}` },
|
||||||
|
{
|
||||||
|
title: '到账金额',
|
||||||
|
dataIndex: 'payoutAmount',
|
||||||
|
width: 100,
|
||||||
|
render: (v, row) => `¥${v}${row.settlementRate ? ` (${Math.round(row.settlementRate * 100)}%)` : ''}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '打款状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
width: 100,
|
||||||
|
render: (s) => <Tag color={PAYOUT_STATUS_COLORS[s] || 'default'}>{PAYOUT_STATUS_LABELS[s] || s}</Tag>,
|
||||||
|
},
|
||||||
|
{ title: '预计打款', dataIndex: 'expectedPayAt', width: 160, render: fmtTime },
|
||||||
|
{ title: '实际打款', dataIndex: 'paidAt', width: 160, render: (v) => (v ? fmtTime(String(v)) : '—') },
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
width: 140,
|
||||||
|
fixed: 'right',
|
||||||
|
render: (_, row) => (
|
||||||
|
<Space size={0}>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={async () => {
|
||||||
|
setDetail(await request(`/admin/store-payouts/${row.id}`));
|
||||||
|
setDrawerOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
{row.status === 'PENDING' && (
|
||||||
|
<Button type="link" size="small" onClick={() => void confirmPayout(row.id)}>
|
||||||
|
确认打款
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Space direction="vertical" size={0} style={{ marginBottom: 16 }}>
|
||||||
|
<Typography.Title level={4} style={{ margin: 0 }}>门店核销账单</Typography.Title>
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
每笔核销对应一条 T+1 打款账单,可按门店筛选查看
|
||||||
|
</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="inline"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
onFinish={(v) => {
|
||||||
|
setFilters(v);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item name="storeId" label="门店">
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
showSearch
|
||||||
|
placeholder="全部门店"
|
||||||
|
style={{ width: 200 }}
|
||||||
|
optionFilterProp="label"
|
||||||
|
options={stores.map((s) => ({
|
||||||
|
value: s.id,
|
||||||
|
label: `${s.name}(${s.phone})`,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="status" label="打款状态">
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
style={{ width: 120 }}
|
||||||
|
placeholder="全部"
|
||||||
|
options={Object.entries(PAYOUT_STATUS_LABELS).map(([value, label]) => ({ value, label }))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">查询</Button>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button onClick={() => { form.resetFields(); setFilters({}); setPage(1); }}>重置</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
className="admin-table-nowrap"
|
||||||
|
loading={loading}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data?.items ?? []}
|
||||||
|
scroll={{ x: 1200 }}
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize,
|
||||||
|
total: data?.total ?? 0,
|
||||||
|
showSizeChanger: true,
|
||||||
|
onChange: (p, ps) => {
|
||||||
|
setPage(p);
|
||||||
|
setPageSize(ps);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Drawer title="账单详情" width={520} open={drawerOpen} onClose={() => setDrawerOpen(false)}>
|
||||||
|
{detail && (
|
||||||
|
<Descriptions column={1} bordered size="small">
|
||||||
|
<Descriptions.Item label="门店">{String((detail.store as { name?: string })?.name ?? '—')}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="核销单号">
|
||||||
|
{String((detail.redeemRecord as { redeemNo?: string })?.redeemNo ?? '—')}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="核销面额">¥{String(detail.redeemAmount)}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="到账金额">¥{String(detail.payoutAmount)}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="结算比例">
|
||||||
|
{detail.settlementRate ? `${Math.round(Number(detail.settlementRate) * 100)}%` : '—'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="状态">
|
||||||
|
{PAYOUT_STATUS_LABELS[String(detail.status)] || String(detail.status)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="预计打款">{fmtTime(String(detail.expectedPayAt))}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="实际打款">
|
||||||
|
{detail.paidAt ? fmtTime(String(detail.paidAt)) : '—'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="创建时间">{fmtTime(String(detail.createdAt))}</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
)}
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { Button, Descriptions, Drawer, Form, Input, Select, Table, Typography, message } from 'antd';
|
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
|
||||||
import { request } from '../lib/api';
|
|
||||||
import { fmtTime } from '../lib/constants';
|
|
||||||
import { useAdminList } from '../lib/useAdminList';
|
|
||||||
|
|
||||||
type Row = {
|
|
||||||
id: string;
|
|
||||||
redeemAmount: number;
|
|
||||||
payoutAmount: number;
|
|
||||||
status: string;
|
|
||||||
expectedPayAt: string;
|
|
||||||
paidAt?: string;
|
|
||||||
store?: { name: string; cityName: string };
|
|
||||||
redeemRecord?: { redeemNo: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function StorePayoutsPage() {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [filters, setFilters] = useState<Record<string, string>>({});
|
|
||||||
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
|
|
||||||
'/admin/store-payouts',
|
|
||||||
() => {
|
|
||||||
const qs = new URLSearchParams();
|
|
||||||
if (filters.status) qs.set('status', filters.status);
|
|
||||||
if (filters.storeId) qs.set('storeId', filters.storeId);
|
|
||||||
return qs;
|
|
||||||
},
|
|
||||||
[filters],
|
|
||||||
);
|
|
||||||
const [detail, setDetail] = useState<Record<string, unknown> | null>(null);
|
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
||||||
|
|
||||||
async function confirmPayout(id: string) {
|
|
||||||
await request(`/admin/store-payouts/${id}/confirm`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ remark: '财务确认打款' }),
|
|
||||||
});
|
|
||||||
message.success('已确认打款');
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns: ColumnsType<Row> = [
|
|
||||||
{ title: '门店', dataIndex: ['store', 'name'] },
|
|
||||||
{ title: '城市', dataIndex: ['store', 'cityName'], width: 100 },
|
|
||||||
{ title: '核销额', dataIndex: 'redeemAmount', width: 90, render: (v) => `¥${v}` },
|
|
||||||
{ title: '打款额', dataIndex: 'payoutAmount', width: 90, render: (v) => `¥${v}` },
|
|
||||||
{ title: '状态', dataIndex: 'status', width: 90 },
|
|
||||||
{ title: '预计打款', dataIndex: 'expectedPayAt', width: 160, render: fmtTime },
|
|
||||||
{ title: '实际打款', dataIndex: 'paidAt', width: 160, render: (v) => (v ? fmtTime(String(v)) : '—') },
|
|
||||||
{
|
|
||||||
title: '操作', width: 140,
|
|
||||||
render: (_, row) => (
|
|
||||||
<>
|
|
||||||
<Button type="link" size="small" onClick={async () => {
|
|
||||||
setDetail(await request(`/admin/store-payouts/${row.id}`));
|
|
||||||
setDrawerOpen(true);
|
|
||||||
}}>详情</Button>
|
|
||||||
{row.status === 'PENDING' && (
|
|
||||||
<Button type="link" size="small" onClick={() => confirmPayout(row.id)}>确认打款</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Typography.Title level={4}>门店打款(T+1)</Typography.Title>
|
|
||||||
<Form form={form} layout="inline" style={{ marginBottom: 16 }} onFinish={(v) => { setFilters(v); setPage(1); }}>
|
|
||||||
<Form.Item name="status" label="状态">
|
|
||||||
<Select allowClear style={{ width: 120 }} options={[
|
|
||||||
{ value: 'PENDING', label: '待打款' },
|
|
||||||
{ value: 'PAID', label: '已打款' },
|
|
||||||
]} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="storeId" label="门店ID"><Input allowClear /></Form.Item>
|
|
||||||
<Button type="primary" htmlType="submit">筛选</Button>
|
|
||||||
</Form>
|
|
||||||
<Table rowKey="id" loading={loading} columns={columns} dataSource={data?.items ?? []} scroll={{ x: 1000 }}
|
|
||||||
pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
|
|
||||||
<Drawer title="打款详情" width={520} open={drawerOpen} onClose={() => setDrawerOpen(false)}>
|
|
||||||
{detail && (
|
|
||||||
<Descriptions column={1} bordered size="small">
|
|
||||||
<Descriptions.Item label="核销额">¥{String(detail.redeemAmount)}</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="打款额">¥{String(detail.payoutAmount)}</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="状态">{String(detail.status)}</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="预计打款">{fmtTime(String(detail.expectedPayAt))}</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
)}
|
|
||||||
</Drawer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ function maskPhone(phone: string) {
|
|||||||
return `${phone.slice(0, 3)} **** ${phone.slice(-4)}`;
|
return `${phone.slice(0, 3)} **** ${phone.slice(-4)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEV_DEFAULT_PHONE = import.meta.env.DEV ? '13900000001' : '';
|
|
||||||
const DEV_DEFAULT_CODE = import.meta.env.DEV ? '123456' : '';
|
const DEV_DEFAULT_CODE = import.meta.env.DEV ? '123456' : '';
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
@@ -18,7 +17,7 @@ export default function LoginPage() {
|
|||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
const quick = params.get('quick') === '1';
|
const quick = params.get('quick') === '1';
|
||||||
const savedProfile = getStoreProfile();
|
const savedProfile = getStoreProfile();
|
||||||
const [phone, setPhone] = useState(getLastPhone() || DEV_DEFAULT_PHONE);
|
const [phone, setPhone] = useState(getLastPhone());
|
||||||
const [code, setCode] = useState(DEV_DEFAULT_CODE);
|
const [code, setCode] = useState(DEV_DEFAULT_CODE);
|
||||||
const [agreed, setAgreed] = useState(false);
|
const [agreed, setAgreed] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ async function main() {
|
|||||||
console.log('5. Shop login + redeem preview');
|
console.log('5. Shop login + redeem preview');
|
||||||
const shopLogin = await loginSms(
|
const shopLogin = await loginSms(
|
||||||
'SHOP_H5',
|
'SHOP_H5',
|
||||||
'13900000001',
|
'13910000001',
|
||||||
'STORE_LOGIN',
|
'STORE_LOGIN',
|
||||||
'/shop/auth/login/sms',
|
'/shop/auth/login/sms',
|
||||||
'/shop/auth/sms/send',
|
'/shop/auth/sms/send',
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ async function main() {
|
|||||||
console.log('8. Shop confirm redeem');
|
console.log('8. Shop confirm redeem');
|
||||||
const shopLogin = await loginSms(
|
const shopLogin = await loginSms(
|
||||||
'SHOP_H5',
|
'SHOP_H5',
|
||||||
'13900000001',
|
'13910000001',
|
||||||
'STORE_LOGIN',
|
'STORE_LOGIN',
|
||||||
'/shop/auth/login/sms',
|
'/shop/auth/login/sms',
|
||||||
'/shop/auth/sms/send',
|
'/shop/auth/sms/send',
|
||||||
|
|||||||
@@ -40,17 +40,17 @@ async function main() {
|
|||||||
console.log('2. STORE_LOGIN bound phone');
|
console.log('2. STORE_LOGIN bound phone');
|
||||||
await req('SHOP_H5', '/shop/auth/sms/send', {
|
await req('SHOP_H5', '/shop/auth/sms/send', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ phone: '13900000001', scene: 'STORE_LOGIN' }),
|
body: JSON.stringify({ phone: '13910000001', scene: 'STORE_LOGIN' }),
|
||||||
});
|
});
|
||||||
const login = await req('SHOP_H5', '/shop/auth/login/sms', {
|
const login = await req('SHOP_H5', '/shop/auth/login/sms', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ phone: '13900000001', code: MOCK_SMS_CODE }),
|
body: JSON.stringify({ phone: '13910000001', code: MOCK_SMS_CODE }),
|
||||||
});
|
});
|
||||||
if (!login.accessToken || !login.refreshToken) throw new Error('login missing tokens');
|
if (!login.accessToken || !login.refreshToken) throw new Error('login missing tokens');
|
||||||
|
|
||||||
console.log('3. Shop session me + refresh');
|
console.log('3. Shop session me + refresh');
|
||||||
const me = await req('SHOP_H5', '/shop/auth/me', { token: login.accessToken });
|
const me = await req('SHOP_H5', '/shop/auth/me', { token: login.accessToken });
|
||||||
if (me.phone !== '13900000001') throw new Error('me phone mismatch');
|
if (me.phone !== '13910000001') throw new Error('me phone mismatch');
|
||||||
const refreshed = await req('SHOP_H5', '/shop/auth/token/refresh', {
|
const refreshed = await req('SHOP_H5', '/shop/auth/token/refresh', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ refreshToken: login.refreshToken }),
|
body: JSON.stringify({ refreshToken: login.refreshToken }),
|
||||||
@@ -69,7 +69,7 @@ async function main() {
|
|||||||
const occupied = await expectFail('HQ_WEB', '/admin/stores/phone/sms/send', {
|
const occupied = await expectFail('HQ_WEB', '/admin/stores/phone/sms/send', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
token: admin.accessToken,
|
token: admin.accessToken,
|
||||||
body: JSON.stringify({ phone: '13900000001' }),
|
body: JSON.stringify({ phone: '13910000001' }),
|
||||||
});
|
});
|
||||||
if (!occupied.includes('已绑定')) throw new Error(`unexpected: ${occupied}`);
|
if (!occupied.includes('已绑定')) throw new Error(`unexpected: ${occupied}`);
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ async function main() {
|
|||||||
partnerId: partner.id,
|
partnerId: partner.id,
|
||||||
categoryId: categories[0].id,
|
categoryId: categories[0].id,
|
||||||
name: '郑州老城店',
|
name: '郑州老城店',
|
||||||
phone: '0371-88880001',
|
phone: '13910000001',
|
||||||
province: '河南省',
|
province: '河南省',
|
||||||
cityName: '郑州市',
|
cityName: '郑州市',
|
||||||
district: '金水区',
|
district: '金水区',
|
||||||
@@ -165,7 +165,7 @@ async function main() {
|
|||||||
partnerId: partner.id,
|
partnerId: partner.id,
|
||||||
categoryId: categories[1].id,
|
categoryId: categories[1].id,
|
||||||
name: '郑州美食城店',
|
name: '郑州美食城店',
|
||||||
phone: '0371-88880002',
|
phone: '13910000002',
|
||||||
province: '河南省',
|
province: '河南省',
|
||||||
cityName: '郑州市',
|
cityName: '郑州市',
|
||||||
district: '二七区',
|
district: '二七区',
|
||||||
@@ -179,11 +179,7 @@ async function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await prisma.storeAccount.create({
|
await prisma.storeAccount.create({
|
||||||
data: { storeId: store1.id, phone: '13900000001', name: '老城店店长' },
|
data: { storeId: store1.id, phone: '13910000001', name: '郑州老城店' },
|
||||||
});
|
|
||||||
|
|
||||||
await prisma.storeAccount.create({
|
|
||||||
data: { storeId: store2.id, phone: '13900000002', name: '美食城店长' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
@@ -233,7 +229,7 @@ async function main() {
|
|||||||
stores: 2,
|
stores: 2,
|
||||||
testPhones: {
|
testPhones: {
|
||||||
user: '13800000001',
|
user: '13800000001',
|
||||||
store: '13900000001',
|
store: '13910000001',
|
||||||
partner: '13700000001',
|
partner: '13700000001',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -153,25 +153,23 @@ async function main() {
|
|||||||
const storeDefs = [
|
const storeDefs = [
|
||||||
{
|
{
|
||||||
name: '郑州老城店',
|
name: '郑州老城店',
|
||||||
phone: '0371-88880001',
|
phone: '13910000001',
|
||||||
district: '金水区',
|
district: '金水区',
|
||||||
address: '花园路100号',
|
address: '花园路100号',
|
||||||
intro: '正宗河南菜,欢迎核销好客权益',
|
intro: '正宗河南菜,欢迎核销好客权益',
|
||||||
img: 'https://picsum.photos/seed/store1/400/300',
|
img: 'https://picsum.photos/seed/store1/400/300',
|
||||||
categoryId: categories[0].id,
|
categoryId: categories[0].id,
|
||||||
accountPhone: '13900000001',
|
withAccount: true,
|
||||||
accountName: '老城店店长',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '郑州美食城店',
|
name: '郑州美食城店',
|
||||||
phone: '0371-88880002',
|
phone: '13910000002',
|
||||||
district: '二七区',
|
district: '二七区',
|
||||||
address: '大学路200号',
|
address: '大学路200号',
|
||||||
intro: '地方特色餐饮',
|
intro: '地方特色餐饮',
|
||||||
img: 'https://picsum.photos/seed/store2/400/300',
|
img: 'https://picsum.photos/seed/store2/400/300',
|
||||||
categoryId: categories[1].id,
|
categoryId: categories[1].id,
|
||||||
accountPhone: '13900000002',
|
withAccount: false,
|
||||||
accountName: '美食城店长',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -198,9 +196,11 @@ async function main() {
|
|||||||
});
|
});
|
||||||
const cover = await createMockResource(ResourceOwnerType.STORE, store.id, ResourceBizType.COVER, def.img);
|
const cover = await createMockResource(ResourceOwnerType.STORE, store.id, ResourceBizType.COVER, def.img);
|
||||||
await prisma.store.update({ where: { id: store.id }, data: { coverResourceId: cover.id } });
|
await prisma.store.update({ where: { id: store.id }, data: { coverResourceId: cover.id } });
|
||||||
await prisma.storeAccount.create({
|
if (def.withAccount) {
|
||||||
data: { storeId: store.id, phone: def.accountPhone, name: def.accountName },
|
await prisma.storeAccount.create({
|
||||||
});
|
data: { storeId: store.id, phone: def.phone, name: def.name },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
@@ -252,7 +252,6 @@ async function main() {
|
|||||||
stores: storeDefs.length,
|
stores: storeDefs.length,
|
||||||
testPhones: {
|
testPhones: {
|
||||||
user: '13800000001',
|
user: '13800000001',
|
||||||
store: '13900000001',
|
|
||||||
partner: '13700000001',
|
partner: '13700000001',
|
||||||
hq: '13600000001',
|
hq: '13600000001',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class SettlementService {
|
|||||||
skip: (page - 1) * pageSize,
|
skip: (page - 1) * pageSize,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
include: {
|
include: {
|
||||||
store: { select: { id: true, name: true, cityName: true } },
|
store: { select: { id: true, name: true, cityName: true, phone: true } },
|
||||||
redeemRecord: { select: { redeemNo: true, amount: true, userId: true } },
|
redeemRecord: { select: { redeemNo: true, amount: true, userId: true } },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user