Compare commits

...

No commits in common. "main" and "div" have entirely different histories.
main ... div

244 changed files with 21051 additions and 101183 deletions

View File

@ -1,3 +0,0 @@
module.exports = {
extends: require.resolve('@umijs/max/eslint'),
};

7
.eslintrc.json Normal file
View File

@ -0,0 +1,7 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
"env": {
"jest": true
}
}

34
.gitignore vendored
View File

@ -1,19 +1,17 @@
/node_modules
/.env.local
/.umirc.local.ts
/config/config.local.ts
/src/.umi
/src/.umi-production
/src/.umi-test
/.umi
/.umi-production
/.umi-test
/dist
/.mfsu
.swc
/package-lock.json
/yarn.lock
*.yaml
#
/docs
logs/
npm-debug.log
yarn-error.log
node_modules/
coverage/
dist/
.idea/
run/
.DS_Store
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*
yarn.lock
**/config.prod.ts
**/config.local.ts
container

2
.npmrc
View File

@ -1,2 +0,0 @@
registry=https://registry.npmmirror.com/

View File

@ -1,4 +0,0 @@
export default {
schemaPath: 'http://127.0.0.1:7001/swagger-ui/index.json',
serversPath: './src/servers',
};

View File

@ -1,3 +0,0 @@
node_modules
.umi
.umi-production

View File

@ -1,8 +0,0 @@
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "never",
"overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"]
}

3
.prettierrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
...require('mwts/.prettierrc.json')
}

View File

@ -1,3 +0,0 @@
module.exports = {
extends: require.resolve('@umijs/max/stylelint'),
};

View File

@ -1,54 +0,0 @@
## 实现计划
### 1. 地图数据准备
- 获取加拿大和澳大利亚的省级地图数据JSON 格式)
- 将地图数据文件添加到项目的`public`目录下,命名为`canada.json`和`australia.json`
### 2. 组件实现
- 为加拿大创建地图组件:`/Users/zksu/Developer/work/workcode/WEB/src/pages/Area/Canada/index.tsx`
- 为澳大利亚创建地图组件:`/Users/zksu/Developer/work/workcode/WEB/src/pages/Area/Australia/index.tsx`
- 组件将基于现有的`Map/index.tsx`实现,修改地图数据加载和配置
### 3. 数据结构设计
- 定义省级数据接口,包含:
- 英文名称(用于匹配地图数据)
- 中文名称
- 简称
- 人口数据
### 4. 地图渲染配置
- 使用 ECharts 的 Map 组件渲染省级地图
- 配置 tooltip 显示中文名称、简称和人口数据
- 添加视觉映射组件,根据人口数据显示不同颜色
### 5. 数据获取与处理
- 定义本地数据结构,包含各个省的中文名称、简称和人口
- 将数据转换为 ECharts 需要的格式
### 6. 组件优化
- 添加加载状态
- 处理地图数据加载失败的情况
- 优化地图交互体验,如缩放、拖拽等
### 7. 实现步骤
1. 准备地图数据文件
2. 创建加拿大地图组件
3. 创建澳大利亚地图组件
4. 配置地图数据和样式
5. 添加省级数据
6. 测试地图渲染和交互
### 技术要点
- 使用 ECharts 的 MapChart 组件
- 动态加载地图 JSON 数据
- 数据映射和转换
- Tooltip 自定义格式化
- 视觉映射配置

370
.umirc.ts
View File

@ -1,370 +0,0 @@
import { defineConfig } from '@umijs/max';
import { codeInspectorPlugin } from 'code-inspector-plugin';
const isDev = process.env.NODE_ENV === 'development';
const UMI_APP_API_URL = isDev
? 'http://localhost:7001'
: 'https://api.yoone.ca';
export default defineConfig({
hash: true,
antd: {},
access: {},
model: {},
initialState: {},
request: {},
layout: {
title: 'YOONE',
},
esbuildMinifyIIFE: true,
define: {
UMI_APP_API_URL,
},
chainWebpack(config) {
config.plugin('code-inspector-plugin').use(
codeInspectorPlugin({
bundler: 'webpack',
}),
);
},
routes: [
{ path: '/', redirect: '/home' },
{ name: '追踪', path: '/track', component: './Track', layout: false },
{ name: '登录', path: '/login', component: './Login', layout: false },
{ name: '首页', path: '/home', component: './Home' },
{
name: '组织架构',
path: '/organiza',
access: 'canSeeOrganiza',
routes: [
{
name: '用户管理',
path: '/organiza/user',
component: './Organiza/User',
},
],
},
{
name: '地区管理',
path: '/area',
access: 'canSeeArea',
routes: [
{
name: '地区列表',
path: '/area/list',
component: './Area/List',
},
{
name: '地区地图',
path: '/area/map',
component: './Area/Map',
},
{
name: '加拿大地图',
path: '/area/canada',
component: './Area/Canada',
},
{
name: '澳大利亚地图',
path: '/area/australia',
component: './Area/Australia',
},
],
},
{
name: '站点管理',
path: '/site',
access: 'canSeeSite',
routes: [
{
name: '站点列表',
path: '/site/list',
component: './Site/List',
},
{
name: '店铺管理',
path: '/site/shop',
component: './Site/Shop/Layout',
routes: [
{
path: '/site/shop/:siteId/products',
component: './Site/Shop/Products',
},
{
path: '/site/shop/:siteId/orders',
component: './Site/Shop/Orders',
},
{
path: '/site/shop/:siteId/subscriptions',
component: './Site/Shop/Subscriptions',
},
{
path: '/site/shop/:siteId/logistics',
component: './Site/Shop/Logistics',
},
{
path: '/site/shop/:siteId/media',
component: './Site/Shop/Media',
},
{
path: '/site/shop/:siteId/customers',
component: './Site/Shop/Customers',
},
{
path: '/site/shop/:siteId/reviews',
component: './Site/Shop/Reviews',
},
{
path: '/site/shop/:siteId/webhooks',
component: './Site/Shop/Webhooks',
},
{
path: '/site/shop/:siteId/links',
component: './Site/Shop/Links',
},
],
},
{
name: 'Woo标签工具',
path: '/site/woocommerce/product/tool/tag',
component: './Woo/Product/TagTool',
},
],
},
{
name: '客户管理',
path: '/customer',
access: 'canSeeCustomer',
routes: [
{
name: '客户列表',
path: '/customer/list',
component: './Customer/List',
},
{
name: '数据分析列表',
path: '/customer/statistic/list',
component: './Customer/StatisticList',
},
// {
// name: '客户统计',
// path: '/customer/statistic/home',
// component: './Customer/Statistic',
// }
],
},
{
name: '产品管理',
path: '/product',
access: 'canSeeProduct',
routes: [
{
name: '产品列表',
path: '/product/list',
component: './Product/List',
},
{
name: '产品分类',
path: '/product/category',
component: './Product/Category',
},
{
name: '产品属性',
path: '/product/attribute',
component: './Product/Attribute',
},
{
name: '产品属性排列',
path: '/product/permutation',
component: './Product/Permutation',
},
{
name: '产品聚合空间',
path: '/product/groupBy',
component: './Product/GroupBy',
},
// sync
{
name: '同步产品',
path: '/product/sync',
component: './Product/Sync',
},
{
name: '产品CSV 工具',
path: '/product/csvtool',
component: './Product/CsvTool',
},
],
},
{
name: '库存管理',
path: '/stock',
access: 'canSeeStock',
routes: [
{
name: '库存列表',
path: '/stock/list',
component: './Stock/List',
},
{
name: '仓库点',
path: '/stock/warehouse',
component: './Stock/Warehouse',
},
{
name: '采购管理',
path: '/stock/purchaseOrder',
component: './Stock/PurchaseOrder',
},
{
name: '发货管理',
path: '/stock/transfer',
component: './Stock/Transfer',
},
{
name: '库存记录',
path: '/stock/record',
component: './Stock/Record',
},
],
},
{
name: '订单管理',
path: '/order',
access: 'canSeeOrder',
routes: [
{
name: '订单列表',
path: '/order/list',
component: './Order/List',
},
{
name: '待发货产品',
path: '/order/item',
component: './Order/PendingItems',
},
],
},
// 新增:订阅管理路由分组(权限复用 canSeeOrder)
{
name: '订阅管理',
path: '/subscription',
access: 'canSeeOrder',
routes: [
{
name: '订阅列表',
path: '/subscription/list',
component: './Subscription/List',
},
{
name: '订单管理',
path: '/subscription/orders',
component: './Subscription/Orders',
},
],
},
{
name: '物流管理',
path: '/logistics',
access: 'canSeeLogistics',
routes: [
{
name: '服务商',
path: '/logistics/services',
component: './Logistics/Services',
},
{
name: '地址管理',
path: '/logistics/address',
component: './Logistics/Address',
},
{
name: '物流列表',
path: '/logistics/list',
component: './Logistics/List',
},
],
},
{
name: '数据统计',
path: '/statistics',
access: 'canSeeStatistics',
routes: [
{
name: '销售统计',
path: '/statistics/sales',
component: './Statistics/Sales',
},
{
name: '订单统计',
path: '/statistics/order',
component: './Statistics/Order',
},
{
name: '订单来源',
path: '/statistics/orderSource',
component: './Statistics/OrderSource',
},
{
name: '客户统计',
path: '/statistics/customer',
component: './Statistics/Customer',
},
{
name: '库存预测',
path: '/statistics/inventoryForecast',
component: './Statistics/InventoryForecast',
},
{
name: '补货',
path: '/statistics/restocking',
component: './Statistics/Restocking',
},
],
},
{
name: '系统管理',
path: '/system',
access: 'canSeeSystem',
routes: [
{
name: '字典管理',
path: '/system/dict',
access: 'canSeeDict',
routes: [
{
name: '字典列表',
path: '/system/dict/list',
component: './Dict/List',
},
],
},
{
name: '模板管理',
path: '/system/template',
access: 'canSeeTemplate',
routes: [
{
name: '模板列表',
path: '/system/template/list',
component: './Template',
},
],
},
],
},
// {
// path: '*',
// component: './404',
// },
],
proxy: {
'/api': {
target: UMI_APP_API_URL,
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
},
npmClient: 'pnpm',
});

View File

@ -1,32 +0,0 @@
# 构建阶段
FROM node:18-alpine as builder
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖(使用 --legacy-peer-deps 解决依赖冲突)
RUN npm install --legacy-peer-deps
# 复制源代码
COPY . .
# 构建项目
RUN npm run build
# 生产阶段
FROM nginx:alpine
# 复制构建产物到 Nginx 静态目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制自定义 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 80
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1 +1,3 @@
# WEB
# API
中台api接口

2
bootstrap.js vendored Normal file
View File

@ -0,0 +1,2 @@
const { Bootstrap } = require('@midwayjs/bootstrap')
Bootstrap.run()

6
jest.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
};

View File

@ -1,20 +0,0 @@
const users = [
{ id: 0, name: 'Umi', nickName: 'U', gender: 'MALE' },
{ id: 1, name: 'Fish', nickName: 'B', gender: 'FEMALE' },
];
export default {
'GET /api/v1/queryUserList': (req: any, res: any) => {
res.json({
success: true,
data: { list: users },
errorCode: 0,
});
},
'PUT /api/v1/user/': (req: any, res: any) => {
res.json({
success: true,
errorCode: 0,
});
},
};

View File

@ -1,33 +0,0 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# API 代理配置
location /api {
proxy_pass http://api:7001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态文件缓存配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 错误页面配置
error_page 404 /index.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

4159
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +1,56 @@
{
"name": "my-midway-project",
"version": "1.0.0",
"description": "",
"private": true,
"author": "cll <931958862@qq.com>",
"scripts": {
"build": "max build",
"dev": "max dev",
"fix:openapi2ts": "sed -i '' 's/\r$//' ./node_modules/@umijs/openapi/dist/cli.js",
"format": "prettier --cache --write .",
"postinstall": "max setup",
"openapi2ts": "openapi2ts",
"prepare": "husky",
"setup": "max setup",
"start": "npm run dev"
},
"dependencies": {
"@ant-design/charts": "^2.2.6",
"@ant-design/icons": "^5.0.1",
"@ant-design/pro-components": "^2.4.4",
"@fingerprintjs/fingerprintjs": "^4.6.2",
"@monaco-editor/react": "^4.7.0",
"@tinymce/tinymce-react": "^6.3.0",
"@umijs/max": "^4.4.4",
"@umijs/max-plugin-openapi": "^2.0.3",
"@umijs/plugin-openapi": "^1.3.3",
"antd": "^5.4.0",
"dayjs": "^1.11.9",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.5",
"file-saver": "^2.0.5",
"i18n-iso-countries": "^7.14.0",
"print-js": "^1.6.0",
"react-json-view": "^1.21.3",
"react-phone-input-2": "^2.15.1",
"react-toastify": "^11.0.5",
"xlsx": "^0.18.5"
"@midwayjs/bootstrap": "^3.20.0",
"@midwayjs/core": "^3.20.0",
"@midwayjs/cron": "^3.20.0",
"@midwayjs/cross-domain": "^3.20.2",
"@midwayjs/decorator": "^3.20.0",
"@midwayjs/info": "^3.20.2",
"@midwayjs/jwt": "^3.20.2",
"@midwayjs/koa": "^3.20.2",
"@midwayjs/logger": "^3.1.0",
"@midwayjs/swagger": "^3.20.2",
"@midwayjs/typeorm": "^3.20.0",
"@midwayjs/validate": "^3.20.2",
"@woocommerce/woocommerce-rest-api": "^1.0.2",
"axios": "^1.7.9",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"dayjs": "^1.11.13",
"mysql2": "^3.11.5",
"nodemailer": "^7.0.5",
"swagger-ui-dist": "^5.18.2",
"typeorm": "^0.3.20",
"xml2js": "^0.6.2"
},
"engines": {
"node": ">=12.0.0"
},
"scripts": {
"start": "cross-env NODE_ENV=prod pm2 start ./bootstrap.js --name yoone -i 4",
"prod": "cross-env NODE_ENV=prod node ./bootstrap.js ",
"dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js",
"test": "cross-env NODE_ENV=unittest jest",
"cov": "jest --coverage",
"lint": "mwts check",
"lint:fix": "mwts fix",
"ci": "npm run cov",
"build": "mwtsc --cleanOutDir"
},
"repository": {
"type": "git",
"url": ""
},
"author": "anonymous",
"license": "MIT",
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"code-inspector-plugin": "^1.2.10",
"husky": "^9",
"lint-staged": "^13.2.0",
"openapi2ts": "^1.1.14",
"prettier": "^2.8.7",
"prettier-plugin-organize-imports": "^3.2.2",
"prettier-plugin-packagejson": "^2.4.3",
"typescript": "^5.7.3"
"@midwayjs/mock": "^3.20.11",
"cross-env": "^10.1.0",
"mwtsc": "^1.15.2",
"typescript": "^5.9.3"
}
}

2687
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +0,0 @@
export default (initialState: any) => {
const isSuper = initialState?.user?.isSuper ?? false;
const isAdmin = initialState?.user?.Admin ?? false;
const canSeeOrganiza =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('organiza') ?? false);
const canSeeProduct =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('product') ?? false);
const canSeeStock =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('stock') ?? false);
const canSeeOrder =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('order') ?? false) ||
(initialState?.user?.permissions?.includes('order-10-days') ?? false);
const canSeeCustomer =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('customer') ?? false);
const canSeeLogistics =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('logistics') ?? false);
const canSeeStatistics =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('statistics') ?? false);
const canSeeSite =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('site') ?? false);
const canSeeDict =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('dict') ?? false);
const canSeeTemplate =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('template') ?? false);
const canSeeArea =
isSuper ||
isAdmin ||
(initialState?.user?.permissions?.includes('area') ?? false);
const canSeeSystem = canSeeDict || canSeeTemplate;
return {
canSeeOrganiza,
canSeeProduct,
canSeeStock,
canSeeOrder,
canSeeCustomer,
canSeeLogistics,
canSeeStatistics,
canSeeSite,
canSeeDict,
canSeeTemplate,
canSeeArea,
canSeeSystem,
};
};

View File

@ -1,131 +0,0 @@
// 运行时配置
import { LogoutOutlined, UserOutlined } from '@ant-design/icons';
import {
ProLayoutProps,
ProSchemaValueEnumObj,
ProTable,
} from '@ant-design/pro-components';
import { RequestConfig, history, useModel } from '@umijs/max';
import { App, Avatar, ConfigProvider, Dropdown, MenuProps } from 'antd';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import { usercontrollerGetuser } from './servers/api/user';
// 设置 dayjs 全局语言为中文
dayjs.locale('zh-cn');
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
export async function getInitialState(): Promise<{
user?: Record<string, any>;
categoryList?: ProSchemaValueEnumObj;
sites?: API.SiteConfig[];
}> {
if (!localStorage.getItem('token') || history.location.pathname === '/login')
return {};
const { data: user } = await usercontrollerGetuser();
return { user };
}
export const layout = (): ProLayoutProps => {
const { initialState } = useModel('@@initialState');
const items: MenuProps['items'] = [
{
key: '1',
label: '我的账号',
disabled: true,
},
{
type: 'divider',
},
{
key: '3',
label: '退出登录',
icon: <LogoutOutlined />,
onClick: async () => {
//TODO 清理服务器登陆状态
localStorage.removeItem('token');
history.push('/login');
},
},
];
return {
menu: {
locale: false,
},
menuDataRender: (menuData) => {
return menuData;
},
layout: 'mix',
actionsRender: () => (
<Dropdown key="avatar" menu={{ items }}>
<div style={{ cursor: 'pointer' }}>
<Avatar size="large" icon={<UserOutlined />} />
<span style={{ marginLeft: 8 }}>{initialState?.user?.name}</span>
</div>
</Dropdown>
),
};
};
export const request: RequestConfig = {
baseURL: '/api', // baseURL: UMI_APP_API_URL,
requestInterceptors: [
(url: string, options: any) => {
const token = localStorage.getItem('token');
return {
url,
options: {
...options,
headers: {
...options.headers,
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
},
};
},
],
errorConfig: {
errorHandler: (error: any) => {
if (error?.response?.status === 401) {
localStorage.removeItem('token');
history.push('/login');
}
},
},
};
export const onRouteChange = ({ location }: { location: Location }) => {
const token = localStorage.getItem('token');
// 白名单,不需要登录的页面
const whiteList = ['/login', '/track'];
if (!token && !whiteList.includes(location.pathname)) {
// 没有 token 且不在白名单内,跳转到登录页
history.push('/login');
}
};
export function rootContainer(container: React.ReactNode) {
// 全局过滤空字段逻辑
(ProTable as any).defaultProps = {
...(ProTable as any).defaultProps,
beforeSearchSubmit: (params: any) => {
return Object.fromEntries(
Object.entries(params).filter(
([_, value]) => value !== undefined && value !== null && value !== '',
),
);
},
};
return (
<ConfigProvider>
<App>{container}</App>
</ConfigProvider>
);
}

View File

@ -1,38 +0,0 @@
import React from 'react';
interface AddressProps {
address: {
address_1?: string;
address_2?: string;
city?: string;
state?: string;
postcode?: string;
country?: string;
phone?: string;
};
style?: React.CSSProperties;
}
const Address: React.FC<AddressProps> = ({ address, style }) => {
if (!address) {
return <span>-</span>;
}
const { address_1, address_2, city, state, postcode, country, phone } =
address;
return (
<div style={{ fontSize: 12, ...style }}>
<div>
{address_1} {address_2}
</div>
<div>
{city}, {state}, {postcode}
</div>
<div>{country}</div>
<div>{phone}</div>
</div>
);
};
export default Address;

View File

@ -1,84 +0,0 @@
import { Input, Select } from 'antd';
import React, { useEffect } from 'react';
const { Option } = Select;
// 定义国家代码和扩展码的映射关系
const phoneExtensions: { [key: string]: string } = {
CA: '1',
CN: '86',
US: '1',
};
interface InternationalPhoneInputProps {
value?: { country?: string; phone?: string; extension?: string };
onChange?: (value: {
country: string;
phone: string;
extension?: string;
}) => void;
}
const InternationalPhoneInput: React.FC<InternationalPhoneInputProps> = ({
value = {},
onChange,
}) => {
const {
country = 'CA',
phone = '',
extension = phoneExtensions['CA'],
} = value || {};
useEffect(() => {
triggerChange({ extension });
}, []);
const triggerChange = (
changedValue: Partial<{
country: string;
phone: string;
extension?: string;
}>,
) => {
if (onChange) {
onChange({
country,
phone,
...value,
...changedValue,
});
}
};
const onCountryChange = (newCountry: string) => {
triggerChange({
country: newCountry,
extension: phoneExtensions[newCountry],
});
};
const onPhoneChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
triggerChange({ phone: e.target.value });
};
return (
<Input
addonBefore={
<Select
value={country}
onChange={onCountryChange}
style={{ width: 100 }}
>
<Option value="CA">🇨🇦 +1</Option>
<Option value="CN">🇨🇳 +86</Option>
<Option value="US">🇺🇸 +1</Option>
{/* 添加更多国家代码 */}
</Select>
}
value={phone}
onChange={onPhoneChange}
placeholder="请输入联系电话"
/>
);
};
export default InternationalPhoneInput;

View File

@ -1,122 +0,0 @@
import { sitecontrollerAll } from '@/servers/api/site';
import { SyncOutlined } from '@ant-design/icons';
import {
ActionType,
DrawerForm,
ProForm,
ProFormDateRangePicker,
ProFormSelect,
} from '@ant-design/pro-components';
import { Button } from 'antd';
import dayjs from 'dayjs';
import React from 'react';
// 定义SyncForm组件的props类型
interface SyncFormProps {
tableRef: React.MutableRefObject<ActionType | undefined>;
onFinish: (values: any) => Promise<void>;
siteId?: string;
initialValues?: any;
}
/**
*
* @param {SyncFormProps} props
* @returns {React.ReactElement}
*/
const SyncForm: React.FC<SyncFormProps> = ({
tableRef,
onFinish,
siteId,
initialValues = {
// 默认一星期
dateRange: [dayjs().subtract(1, 'week'), dayjs()],
},
}) => {
// 使用 antd 的 App 组件提供的 message API
const [loading, setLoading] = React.useState(false);
if (siteId) {
return (
<Button
key="syncSite"
type="primary"
loading={loading}
onClick={async () => {
try {
setLoading(true);
await onFinish({ siteId: Number(siteId) });
} finally {
setLoading(false);
}
}}
>
<SyncOutlined />
</Button>
);
}
// 返回一个抽屉表单
return (
<DrawerForm<API.ordercontrollerSyncorderParams>
initialValues={initialValues}
title="同步订单"
// 表单的触发器,一个带图标的按钮
trigger={
<Button key="syncSite" type="primary">
<SyncOutlined />
</Button>
}
// 自动聚焦第一个输入框
autoFocusFirstInput
// 抽屉关闭时销毁内部组件
drawerProps={{
destroyOnHidden: true,
}}
// 表单提交成功后的回调
onFinish={async (values) => {
const normalValues = {
...values,
dateRange: values.dateRange
? [
dayjs(values.dateRange[0]).format('YYYY-MM-DDTHH:mm:ss[Z]'),
dayjs(values.dateRange[1]).add(1, 'day').format('YYYY-MM-DDTHH:mm:ss[Z]'),
]
: [],
};
await onFinish(normalValues);
}}
>
{/* 站点选择框 */}
<ProFormSelect
name="siteId"
width="lg"
label="站点"
placeholder="请选择站点"
// 异步请求站点列表数据
request={async () => {
const { data = [] } = await sitecontrollerAll();
// 将返回的数据格式化为 ProFormSelect 需要的格式
return data.map((item: any) => ({
label: item.name || String(item.id),
value: item.id,
}));
}}
/>
<ProFormDateRangePicker
name="dateRange"
label="同步日期范围"
placeholder={['开始日期', '结束日期']}
fieldProps={{
showTime: false,
}}
/>
</DrawerForm>
);
};
export default SyncForm;

View File

@ -1,91 +0,0 @@
import { message } from 'antd';
import React from 'react';
// 定义同步结果的数据类型
export interface SyncResultData {
total?: number;
processed?: number;
synced?: number;
created?: number;
updated?: number;
errors?: Array<{
identifier: string;
error: string;
}>;
}
// 定义组件的 Props 类型
interface SyncResultMessageProps {
data?: SyncResultData;
entityType?: string; // 实体类型,如"订单"、"客户"等
}
// 显示同步结果的函数
export const showSyncResult = (
data: SyncResultData,
entityType: string = '订单',
) => {
const result = data || {};
const {
total = 0,
processed = 0,
synced = 0,
created = 0,
updated = 0,
errors = [],
} = result;
// 构建结果消息
let resultMessage = `同步完成!共处理 ${processed}${entityType}(总数 ${total} 个):`;
if (created > 0) resultMessage += ` 新建 ${created}`;
if (updated > 0) resultMessage += ` 更新 ${updated}`;
if (synced > 0) resultMessage += ` 同步成功 ${synced}`;
if (errors.length > 0) resultMessage += ` 失败 ${errors.length}`;
// 根据是否有错误显示不同的消息类型
if (errors.length > 0) {
// 如果有错误,显示警告消息
message.warning({
content: (
<div>
<div>{resultMessage}</div>
<div style={{ marginTop: 8, fontSize: 12, color: '#faad14' }}>
:
{errors
.slice(0, 3)
.map((err: any) => `${err.identifier}: ${err.error}`)
.join(', ')}
{errors.length > 3 && `${errors.length - 3} 个错误...`}
</div>
</div>
),
duration: 8,
key: 'sync-result',
});
} else {
// 完全成功
message.success({
content: resultMessage,
duration: 4,
key: 'sync-result',
});
}
};
// 同步结果显示组件
const SyncResultMessage: React.FC<SyncResultMessageProps> = ({
data,
entityType = '订单',
}) => {
// 当组件挂载时显示结果
React.useEffect(() => {
if (data) {
showSyncResult(data, entityType);
}
}, [data, entityType]);
// 这个组件不渲染任何内容,只用于显示消息
return null;
};
export default SyncResultMessage;

View File

@ -0,0 +1,132 @@
import { MidwayConfig } from '@midwayjs/core';
import { Product } from '../entity/product.entity';
import { Category } from '../entity/category.entity';
import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity';
import { User } from '../entity/user.entity';
import { PurchaseOrder } from '../entity/purchase_order.entity';
import { PurchaseOrderItem } from '../entity/purchase_order_item.entity';
import { Stock } from '../entity/stock.entity';
import { StockPoint } from '../entity/stock_point.entity';
import { StockRecord } from '../entity/stock_record.entity';
import { Order } from '../entity/order.entity';
import { OrderItem } from '../entity/order_item.entity';
import { OrderCoupon } from '../entity/order_copon.entity';
import { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity';
import { OrderRefundItem } from '../entity/order_retund_item.entity';
import { OrderSale } from '../entity/order_sale.entity';
import { OrderSaleOriginal } from '../entity/order_item_original.entity';
import { OrderShipping } from '../entity/order_shipping.entity';
import { Service } from '../entity/service.entity';
import { ShippingAddress } from '../entity/shipping_address.entity';
import { OrderNote } from '../entity/order_note.entity';
import { OrderShipment } from '../entity/order_shipment.entity';
import { Shipment } from '../entity/shipment.entity';
import { ShipmentItem } from '../entity/shipment_item.entity';
import { Transfer } from '../entity/transfer.entity';
import { TransferItem } from '../entity/transfer_item.entity';
import { Strength } from '../entity/strength.entity';
import { Flavors } from '../entity/flavors.entity';
import { CustomerTag } from '../entity/customer_tag.entity';
import { Customer } from '../entity/customer.entity';
import { DeviceWhitelist } from '../entity/device_whitelist';
import { AuthCode } from '../entity/auth_code';
import { Subscription } from '../entity/subscription.entity';
import { Site } from '../entity/site.entity';
export default {
// use for cookie sign key, should change to your own and keep security
keys: '1733728588817_720',
typeorm: {
default: {
entities: [
Product,
Category,
Strength,
Flavors,
WpProduct,
Variation,
User,
PurchaseOrder,
PurchaseOrderItem,
Stock,
StockPoint,
StockRecord,
Order,
OrderItem,
OrderCoupon,
OrderFee,
OrderRefund,
OrderRefundItem,
OrderSale,
OrderSaleOriginal,
OrderShipment,
ShipmentItem,
Shipment,
OrderShipping,
Service,
ShippingAddress,
OrderNote,
Transfer,
TransferItem,
CustomerTag,
Customer,
DeviceWhitelist,
AuthCode,
Subscription,
Site,
],
synchronize: true,
logging: false,
},
dataSource: {
default: {
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'inventory',
},
},
},
// cors: {
// origin: '*', // 允许所有来源跨域请求
// allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
// allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头
// credentials: true, // 允许携带凭据cookies等
// },
// jwt: {
// secret: 'YOONE2024!@abc',
// expiresIn: '7d',
// },
// wpSite: [
// {
// id: '2',
// wpApiUrl: 'http://localhost:10004',
// consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
// consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
// siteName: 'Local',
// email: 'tom@yoonevape.com',
// emailPswd: '',
// },
// ],
swagger: {
auth: {
name: 'authorization',
authType: 'bearer',
description: 'Bearer Auth',
addSecurityRequirements: true,
},
},
mailer: {
host: 'smtphz.qiye.163.com',
port: 465,
secure: true,
auth: {
user: 'info@canpouches.com',
pass: 'WWqQ4aZq4Jrm9uwz',
},
}
} as MidwayConfig;

View File

@ -0,0 +1,86 @@
import { MidwayConfig } from '@midwayjs/core';
export default {
koa: {
port: 7001,
},
// typeorm: {
// dataSource: {
// default: {
// host: '13.212.62.127',
// username: 'root',
// password: 'Yoone!@.2025',
// },
// },
// },
typeorm: {
dataSource: {
default: {
host: 'localhost',
username: 'root',
password: '12345678',
},
},
},
cors: {
origin: '*', // 允许所有来源跨域请求
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头
credentials: true, // 允许携带凭据cookies等
},
jwt: {
secret: 'YOONE2024!@abc',
expiresIn: '7d',
},
wpSite: [
{
id: '-1',
siteName: 'Admin',
email: '2469687281@qq.com',
},
{
id: '2',
wpApiUrl: 'http://t2-shop.local/',
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
siteName: 'Local',
email: '2469687281@qq.com',
emailPswd: 'lulin91.',
},
{
id: '3',
wpApiUrl: 'http://t1-shop.local/',
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
siteName: 'Local-test-2',
email: '2469687281@qq.com',
emailPswd: 'lulin91.',
},
// {
// id: '2',
// wpApiUrl: 'http://localhost:10004',
// consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
// consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
// siteName: 'Local',
// email: 'tom@yoonevape.com',
// emailPswd: 'lulin91.',
// },
],
freightcom: {
url: 'https://customer-external-api.ssd-test.freightcom.com',
token: '6zGj1qPTL1jIkbLmgaiYc6SwHUIXJ2t25htUF8uuFYiCg8ILCY6xnBEbvrX1p79L',
},
canadaPost: {
url: 'https://ct.soa-gw.canadapost.ca',
username: '65d23d3a75d7baf7',
password: '56443bb98b68dfdd60f52e',
customerNumber: '0006122480',
contractId: '0044168528',
},
uniExpress: {
url: 'https://sjqa.uniexpress.org', // 测试环境url
// url: 'https://sj.uniexpress.ca', //正式环境url
clientId: '101018',
clientSecret: 'cbcb51bea204f3f69c47b5280064408e',
customerNo: 2067,
}
} as MidwayConfig;

View File

@ -0,0 +1,7 @@
import { MidwayConfig } from '@midwayjs/core';
export default {
koa: {
port: null,
},
} as MidwayConfig;

82
src/configuration.ts Normal file
View File

@ -0,0 +1,82 @@
import {
Configuration,
App,
Inject,
MidwayDecoratorService,
} from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import * as validate from '@midwayjs/validate';
import * as info from '@midwayjs/info';
import * as orm from '@midwayjs/typeorm';
import { join } from 'path';
// import { DefaultErrorFilter } from './filter/default.filter';
// import { NotFoundFilter } from './filter/notfound.filter';
import { ReportMiddleware } from './middleware/report.middleware';
import * as swagger from '@midwayjs/swagger';
import * as crossDomain from '@midwayjs/cross-domain';
import * as cron from '@midwayjs/cron';
import * as jwt from '@midwayjs/jwt';
import { USER_KEY } from './decorator/user.decorator';
import { SiteService } from './service/site.service';
import { AuthMiddleware } from './middleware/auth.middleware';
@Configuration({
imports: [
koa,
validate,
{
component: info,
enabledEnvironment: ['local', 'prod'],
},
orm,
swagger,
crossDomain,
cron,
jwt,
],
importConfigs: [join(__dirname, './config')],
})
export class MainConfiguration {
@App('koa')
app: koa.Application;
@Inject()
decoratorService: MidwayDecoratorService;
@Inject()
jwtService: jwt.JwtService; // 注入 JwtService 实例
@Inject()
siteService: SiteService;
async onReady() {
// add middleware
this.app.useMiddleware([ReportMiddleware, AuthMiddleware]);
// add filter
// this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
this.decoratorService.registerParameterHandler(
USER_KEY,
async (
options
): Promise<{
id: number;
userName: string;
}> => {
const ctx = options.originArgs[0];
const token = ctx.headers['authorization']?.split(' ')[1];
const config = ctx.app.getConfig('jwt'); // 动态获取配置项
if (!token) {
ctx.throw(401, 'Token not found');
}
try {
const decoded: any = this.jwtService.verify(token, config.secret); // 替换为你的密钥
return decoded;
} catch (error) {
ctx.throw(401, 'Invalid token');
}
}
);
}
}

View File

@ -1,120 +0,0 @@
import { ProSchemaValueEnumObj } from '@ant-design/pro-components';
export const DEFAULT_NAME = 'YOONE';
export const PRODUCT_STATUS_ENUM: ProSchemaValueEnumObj = {
publish: {
text: '已发布',
status: 'success',
},
draft: {
text: '草稿',
status: 'draft',
},
pending: {
text: '待审核',
status: 'pending',
},
private: {
text: '私有',
status: 'private',
},
trash: {
text: '已删除',
status: 'error',
},
'auto-draft': {
text: '自动草稿',
status: 'auto-draft',
},
future: {
text: '定时发布',
status: 'success',
},
inherit: {
text: '继承状态',
status: 'inherit',
},
};
export const PRODUCT_STOCK_STATUS_ENUM: ProSchemaValueEnumObj = {
instock: {
text: '上架',
status: 'instock',
},
outofstock: {
text: 'out of stock',
status: 'outofstock',
},
onbackorder: {
text: 'on back order',
status: 'onbackorder',
},
};
export const Purchase_Order_STATUS_ENUM: ProSchemaValueEnumObj = {
draft: {
text: '草稿',
status: 'default',
},
submitted: {
text: '已发货',
status: 'warning',
},
received: {
text: '已到达',
status: 'success',
disabled: true,
},
};
export const ORDER_STATUS_ENUM: ProSchemaValueEnumObj = {
pending: {
text: '待确认',
status: 'default',
},
processing: {
text: '待发货',
status: 'success',
},
completed: {
text: '已完成',
status: 'success',
},
cancelled: {
text: '已取消',
status: 'error',
},
refunded: {
text: '已退款',
status: 'warning',
},
failed: {
text: '失败',
status: 'error',
},
after_sale_pending: {
text: '售后处理中',
status: 'warning',
},
pending_reshipment: {
text: '待补发',
status: 'warning',
},
pending_refund: {
text: '待退款',
status: 'warning',
},
refund_requested: {
text: '已申请退款',
status: 'refund_approved',
},
refund_approved: {
text: '退款申请已通过',
status: 'refund_approved',
},
refund_cancelled: {
text: '已取消退款',
status: 'refund_cancelled',
},
};

View File

@ -0,0 +1,9 @@
import { Inject, Controller } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
@Controller('/')
export class APIController {
@Inject()
ctx: Context;
}

View File

@ -0,0 +1,82 @@
import {
Body,
Context,
Controller,
Del,
Get,
Inject,
Post,
Put,
Query,
} from '@midwayjs/core';
import { CustomerService } from '../service/customer.service';
import { errorResponse, successResponse } from '../utils/response.util';
import { ApiOkResponse } from '@midwayjs/swagger';
import { BooleanRes } from '../dto/reponse.dto';
import { CustomerTagDTO, QueryCustomerListDTO } from '../dto/customer.dto';
@Controller('/customer')
export class CustomerController {
@Inject()
ctx: Context;
@Inject()
customerService: CustomerService;
@ApiOkResponse()
@Get('/list')
async getCustomerList(@Query() param: QueryCustomerListDTO) {
try {
const data = await this.customerService.getCustomerList(param);
return successResponse(data);
} catch (error) {
console.log(error)
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/tag/add')
async addTag(@Body() dto: CustomerTagDTO) {
try {
await this.customerService.addTag(dto.email, dto.tag);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: BooleanRes })
@Del('/tag/del')
async delTag(@Body() dto: CustomerTagDTO) {
try {
await this.customerService.delTag(dto.email, dto.tag);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/tags')
async getTags() {
try {
const data = await this.customerService.getTags();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: BooleanRes })
@Put('/rate')
async setRate(@Body() params: { id: number; rate: number }) {
try {
await this.customerService.setRate(params);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
}

View File

@ -0,0 +1,288 @@
import {
Inject,
Controller,
Post,
Body,
Get,
Put,
Param,
Del,
Query,
} from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { ApiOkResponse } from '@midwayjs/swagger';
import {
BooleanRes,
RateLitRes,
ServiceListRes,
ShippingAddressListRes,
} from '../dto/reponse.dto';
import { FreightcomService } from '../service/freightcom.service';
import { errorResponse, successResponse } from '../utils/response.util';
import { LogisticsService } from '../service/logistics.service';
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
import { ShippingAddress } from '../entity/shipping_address.entity';
import { QueryServiceDTO, ShipmentBookDTO, ShipmentFeeBookDTO } from '../dto/logistics.dto';
import { User } from '../decorator/user.decorator';
@Controller('/logistics')
export class LogisticsController {
@Inject()
ctx: Context;
@Inject()
freightcomService: FreightcomService;
@Inject()
logisticsService: LogisticsService;
@ApiOkResponse({
type: BooleanRes,
})
@Post('/syncServices')
async syncServices() {
try {
await this.freightcomService.syncServices();
return successResponse(true);
} catch (error) {
return errorResponse('同步失败');
}
}
@ApiOkResponse({
description: '服务商列表',
type: ServiceListRes,
})
@Get('/getServiceList')
async getServiceList(
@Query()
param: QueryServiceDTO
) {
try {
const data = await this.logisticsService.getServiceList(param);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/toggleActive')
async toggleActive(@Body() body: { id: string; isActive: boolean }) {
try {
await this.logisticsService.toggleServiceActive(body.id, body.isActive);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({
type: RateLitRes,
})
@Post('/getRateList')
async getRateList(@Body() details: ShippingDetailsDTO) {
try {
const rates = await this.logisticsService.getRateList(details);
return successResponse(rates);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/createShippingAddress')
async createShippingAddress(@Body() shippingAddress: ShippingAddress) {
try {
await this.logisticsService.createShippingAddress(shippingAddress);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Put('/updateShippingAddress/:id')
async updateShippingAddress(
@Body() shippingAddress: ShippingAddress,
@Param('id') id: number
) {
try {
await this.logisticsService.updateShippingAddress(id, shippingAddress);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@ApiOkResponse({
type: ShippingAddressListRes,
})
@Get('/getShippingAddressList')
async getShippingAddressList() {
try {
const data = await this.logisticsService.getShippingAddressList();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Del('/delShippingAddress/:id')
async delShippingAddress(@Param('id') id: number) {
try {
const boolen = await this.logisticsService.delShippingAddress(id);
return successResponse(boolen);
} catch (error) {
return errorResponse(error?.message || '删除失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/deleteShipment/:id')
async deleteShipment(@Param('id') id: number) {
try {
const res = await this.logisticsService.removeShipment(id);
return successResponse(res);
} catch (error) {
return errorResponse(error?.message || '删除运单失败' );
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/createShipment/:orderId')
async createShipment(
@Param('orderId') orderId: number,
@Body() data: ShipmentBookDTO,
@User() user
) {
try {
const res: any = await this.logisticsService.createShipment(orderId, data, user.id);
return successResponse(res.data);
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse(
{type: BooleanRes}
)
@Post('/getShipmentFee')
async getShipmentFee(
@Body() data: ShipmentFeeBookDTO
) {
try {
const fee = await this.logisticsService.getShipmentFee(data);
return successResponse(fee);
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse(
{type: BooleanRes}
)
@Post('/getShipmentLabel/:shipmentId')
async getShipmentLabel(
@Param('shipmentId') shipmentId: number
) {
try {
const res = await this.logisticsService.getShipmentLabel(shipmentId);
if (res.data.data[0].status === 'Success') {
return successResponse({ content: res.data.data[0].labelContent });
} else {
return errorResponse(res.data.data[0].errors);
}
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Post('/getPaymentMethods')
async getpaymentmethods() {
try {
const data = await this.freightcomService.getPaymentMethods();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/updateState/:shipmentId')
async updateShipmentState(
@Param('shipmentId') shipmentId: number
) {
try {
const data = await this.logisticsService.updateShipmentStateById(shipmentId);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '更新运单状态失败')
}
}
@ApiOkResponse()
@Del('/shipment/:id')
async delShipment(@Param('id') id: number, @User() user) {
try {
const data = await this.logisticsService.delShipment(id, user.id);
return successResponse(data);
} catch (error) {
console.log(error);
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/getOrderList')
async getOrderList(@Query('number') number: string) {
try {
return successResponse(
await this.logisticsService.getOrderList(number)
);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/getListByOrderId')
async getListByOrderId(@Query('id') orderId: number) {
try {
return successResponse(
await this.logisticsService.getListByOrderId(orderId)
);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({
description: '物流列表',
})
@Get('/list')
async getList(
@Query()
param: Record<string, any>
) {
try {
const data = await this.logisticsService.getList(param);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
}

View File

@ -0,0 +1,255 @@
import {
Body,
Controller,
Del,
Get,
Inject,
Param,
Post,
Put,
Query,
} from '@midwayjs/core';
import { ApiOkResponse } from '@midwayjs/swagger';
import {
BooleanRes,
OrderDetailRes,
OrderListRes,
OrderSaleListRes,
} from '../dto/reponse.dto';
import { OrderService } from '../service/order.service';
import { errorResponse, successResponse } from '../utils/response.util';
import {
CreateOrderNoteDTO,
QueryOrderDTO,
QueryOrderSalesDTO,
QueryOrderItemDTO,
} from '../dto/order.dto';
import { User } from '../decorator/user.decorator';
import { ErpOrderStatus } from '../enums/base.enum';
@Controller('/order')
export class OrderController {
@Inject()
orderService: OrderService;
@ApiOkResponse({
type: BooleanRes,
})
@Post('/syncOrder/:siteId')
async syncOrder(@Param('siteId') siteId: string) {
try {
await this.orderService.syncOrders(siteId);
return successResponse(true);
} catch (error) {
console.log(error);
return errorResponse('同步失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/syncOrder/:siteId/order/:orderId')
async syncOrderById(
@Param('siteId') siteId: string,
@Param('orderId') orderId: string
) {
try {
await this.orderService.syncOrderById(siteId, orderId);
return successResponse(true);
} catch (error) {
console.log(error);
return errorResponse('同步失败');
}
}
@ApiOkResponse({
type: OrderListRes,
})
@Get('/getOrders')
async getOrders(
@Query()
param: QueryOrderDTO,
@User() user
) {
try {
const count = await this.orderService.getOrderStatus(param);
const data = await this.orderService.getOrders(param, user.id);
return successResponse({
...data,
count,
});
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({
type: OrderSaleListRes,
})
@Get('/getOrderSales')
async getOrderSales(@Query() param: QueryOrderSalesDTO) {
try {
if (param.isSource)
return successResponse(await this.orderService.getOrderItems(param));
return successResponse(await this.orderService.getOrderSales(param));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Get('/getOrderItems')
async getOrderItems(@Query() param: QueryOrderSalesDTO) {
try {
return successResponse(await this.orderService.getOrderItems(param));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Get('/getOrderItemList')
async getOrderItemList(@Query() param: QueryOrderItemDTO) {
try {
return successResponse(await this.orderService.getOrderItemList(param));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({
type: OrderDetailRes,
})
@Get('/:orderId')
async getOrderDetail(@Param('orderId') orderId: number) {
try {
return successResponse(await this.orderService.getOrderDetail(orderId));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Get('/:orderId/related')
async getRelatedByOrder(@Param('orderId') orderId: number) {
try {
return successResponse(await this.orderService.getRelatedByOrder(orderId));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/updateOrderItems/:orderId')
async updateOrderItems(
@Param('orderId') orderId: number,
@Body() data: any,
) {
try {
const res = await this.orderService.updateExchangeOrder(orderId, data);
return successResponse(res);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Del('/:id')
async delOrder(@Param('id') id: number) {
try {
return successResponse(await this.orderService.delOrder(id));
} catch (error) {
return errorResponse(error?.message || '删除失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/createNote')
async createNote(@Body() data: CreateOrderNoteDTO, @User() user) {
try {
return successResponse(await this.orderService.createNote(user.id, data));
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Post('/getOrderByNumber')
async getOrderByNumber(@Body('number') number: string) {
try {
return successResponse(await this.orderService.getOrderByNumber(number));
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Post('/order/cancel/:id')
async cancelOrder(@Param('id') id: number) {
try {
return successResponse(await this.orderService.cancelOrder(id));
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Post('/order/refund/:id')
async refundOrder(@Param('id') id: number) {
try {
return successResponse(await this.orderService.refundOrder(id));
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Post('/order/completed/:id')
async completedOrder(@Param('id') id: number) {
try {
return successResponse(await this.orderService.completedOrder(id));
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Put('/order/status/:id')
async changeStatus(
@Param('id') id: number,
@Body('status') status: ErpOrderStatus
) {
try {
return successResponse(await this.orderService.changeStatus(id, status));
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Post('/order/create')
async createOrder(@Body() data: Record<string, any>) {
try {
return successResponse(await this.orderService.createOrder(data));
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Post('/order/pengding/items')
async pengdingItems(@Body() data: Record<string, any>) {
try {
return successResponse(await this.orderService.pengdingItems(data));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
}

View File

@ -0,0 +1,405 @@
import {
Inject,
Post,
Put,
Get,
Body,
Param,
Del,
Query,
Controller,
} from '@midwayjs/core';
import { ProductService } from '../service/product.service';
import { errorResponse, successResponse } from '../utils/response.util';
import {
BatchSetSkuDTO,
CreateCategoryDTO,
CreateFlavorsDTO,
CreateProductDTO,
CreateStrengthDTO,
QueryCategoryDTO,
QueryFlavorsDTO,
QueryProductDTO,
QueryStrengthDTO,
UpdateCategoryDTO,
UpdateFlavorsDTO,
UpdateProductDTO,
UpdateStrengthDTO,
} from '../dto/product.dto';
import { ApiOkResponse } from '@midwayjs/swagger';
import {
BooleanRes,
ProductCatListRes,
ProductCatRes,
ProductListRes,
ProductRes,
ProductsRes,
} from '../dto/reponse.dto';
@Controller('/product')
export class ProductController {
@Inject()
productService: ProductService;
ProductRes;
@ApiOkResponse({
description: '通过name搜索产品',
type: ProductsRes,
})
@Get('/search')
async searchProducts(@Query('name') name: string) {
try {
// 调用服务获取产品数据
const products = await this.productService.findProductsByName(name);
return successResponse(products);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
@ApiOkResponse({
type: ProductRes,
})
@Get('/sku/:sku')
async productBySku(@Param('sku') sku: string) {
try {
// 调用服务获取产品数据
const product = await this.productService.findProductBySku(sku);
return successResponse(product);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
@ApiOkResponse({
description: '成功返回产品列表',
type: ProductListRes,
})
@Get('/list')
async getProductList(
@Query() query: QueryProductDTO
): Promise<ProductListRes> {
const { current = 1, pageSize = 10, name, categoryId } = query;
try {
const data = await this.productService.getProductList(
{ current, pageSize },
name,
categoryId
);
return successResponse(data);
} catch (error) {
console.log(error);
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: ProductRes,
})
@Post('/')
async createProduct(@Body() productData: CreateProductDTO) {
try {
const data = this.productService.createProduct(productData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: ProductRes,
})
@Put('/:id')
async updateProduct(
@Param('id') id: number,
@Body() productData: UpdateProductDTO
) {
try {
const data = this.productService.updateProduct(id, productData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: ProductRes,
})
@Put('updateNameCn/:id/:nameCn')
async updateProductNameCn(
@Param('id') id: number,
@Param('nameCn') nameCn: string
) {
try {
const data = this.productService.updateProductNameCn(id, nameCn);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Del('/:id')
async deleteProduct(@Param('id') id: number) {
try {
const data = await this.productService.deleteProduct(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: ProductCatListRes,
})
@Get('/categories')
async getCategories(@Query() query: QueryCategoryDTO) {
const { current = 1, pageSize = 10, name } = query;
try {
let data = await this.productService.getCategoryList(
{ current, pageSize },
name
);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/categorieAll')
async getCategorieAll() {
try {
let data = await this.productService.getCategoryAll();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: ProductCatRes,
})
@Post('/category')
async createCategory(@Body() categoryData: CreateCategoryDTO) {
try {
const hasCategory = await this.productService.hasCategory(
categoryData.name
);
if (hasCategory) {
return errorResponse('分类已存在');
}
let data = await this.productService.createCategory(categoryData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: ProductCatRes,
})
@Put('/category/:id')
async updateCategory(
@Param('id') id: number,
@Body() categoryData: UpdateCategoryDTO
) {
try {
const hasCategory = await this.productService.hasCategory(
categoryData.name
);
if (hasCategory) {
return errorResponse('分类已存在');
}
const data = this.productService.updateCategory(id, categoryData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Del('/category/:id')
async deleteCategory(@Param('id') id: number) {
try {
const hasProducts = await this.productService.hasProductsInCategory(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除');
const data = await this.productService.deleteCategory(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@Post('/batchSetSku')
@ApiOkResponse({
description: '批量设置 sku 的响应结果',
type: BooleanRes,
})
async batchSetSku(@Body() body: BatchSetSkuDTO) {
try {
const result = await this.productService.batchSetSku(body.skus);
return successResponse(result, '批量设置 sku 成功');
} catch (error) {
return errorResponse(error.message, 400);
}
}
@ApiOkResponse()
@Get('/flavorsAll')
async getFlavorsAll() {
try {
let data = await this.productService.getFlavorsAll();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/flavors')
async getFlavors(@Query() query: QueryFlavorsDTO) {
const { current = 1, pageSize = 10, name } = query;
try {
let data = await this.productService.getFlavorsList(
{ current, pageSize },
name
);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Post('/flavors')
async createFlavors(@Body() flavorsData: CreateFlavorsDTO) {
try {
const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
if (hasFlavors) {
return errorResponse('分类已存在');
}
let data = await this.productService.createFlavors(flavorsData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Put('/flavors/:id')
async updateFlavors(
@Param('id') id: number,
@Body() flavorsData: UpdateFlavorsDTO
) {
try {
const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
if (hasFlavors) {
return errorResponse('分类已存在');
}
const data = this.productService.updateFlavors(id, flavorsData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Del('/flavors/:id')
async deleteFlavors(@Param('id') id: number) {
try {
const hasProducts = await this.productService.hasProductsInFlavors(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除');
const data = await this.productService.deleteFlavors(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/strengthAll')
async getStrengthAll() {
try {
let data = await this.productService.getStrengthAll();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/strength')
async getStrength(@Query() query: QueryStrengthDTO) {
const { current = 1, pageSize = 10, name } = query;
try {
let data = await this.productService.getStrengthList(
{ current, pageSize },
name
);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Post('/strength')
async createStrength(@Body() strengthData: CreateStrengthDTO) {
try {
const hasStrength = await this.productService.hasStrength(
strengthData.name
);
if (hasStrength) {
return errorResponse('分类已存在');
}
let data = await this.productService.createStrength(strengthData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Put('/strength/:id')
async updateStrength(
@Param('id') id: number,
@Body() strengthData: UpdateStrengthDTO
) {
try {
const hasStrength = await this.productService.hasStrength(
strengthData.name
);
if (hasStrength) {
return errorResponse('分类已存在');
}
const data = this.productService.updateStrength(id, strengthData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Del('/strength/:id')
async deleteStrength(@Param('id') id: number) {
try {
const hasProducts = await this.productService.hasProductsInStrength(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除');
const data = await this.productService.deleteStrength(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
}

View File

@ -0,0 +1,75 @@
import { Body, Controller, Get, Inject, Param, Put, Post, Query } from '@midwayjs/core';
import { ApiOkResponse } from '@midwayjs/swagger';
import { WpSitesResponse } from '../dto/reponse.dto';
import { errorResponse, successResponse } from '../utils/response.util';
import { SiteService } from '../service/site.service';
import { CreateSiteDTO, DisableSiteDTO, QuerySiteDTO, UpdateSiteDTO } from '../dto/site.dto';
@Controller('/site')
export class SiteController {
@Inject()
siteService: SiteService;
@ApiOkResponse({ description: '关联网站', type: WpSitesResponse })
@Get('/all')
async all() {
try {
const { items } = await this.siteService.list({ current: 1, pageSize: 1000, isDisabled: false });
return successResponse(items.map((v: any) => ({ id: v.id, siteName: v.siteName })));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@Post('/create')
async create(@Body() body: CreateSiteDTO) {
try {
await this.siteService.create(body);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@Put('/update/:id')
async update(@Param('id') id: string, @Body() body: UpdateSiteDTO) {
try {
await this.siteService.update(Number(id), body);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@Get('/get/:id')
async get(@Param('id') id: string) {
try {
const data = await this.siteService.get(Number(id), false);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@Get('/list')
async list(@Query() query: QuerySiteDTO) {
try {
const data = await this.siteService.list(query, false);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
// 批量查询改为使用 /site/list?ids=1,2,3
@Put('/disable/:id')
async disable(@Param('id') id: string, @Body() body: DisableSiteDTO) {
try {
await this.siteService.disable(Number(id), body.disabled);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
}

View File

@ -0,0 +1,99 @@
import { Body, Controller, Get, Inject, Post, Query } from '@midwayjs/core';
import { StatisticsService } from '../service/statistics.service';
import { OrderStatisticsParams } from '../dto/statistics.dto';
import { errorResponse, successResponse } from '../utils/response.util';
import { ApiOkResponse } from '@midwayjs/swagger';
@Controller('/statistics')
export class StatisticsController {
@Inject()
statisticsService: StatisticsService;
@ApiOkResponse()
@Post('/order')
async getOrderStatistics(@Body() params: OrderStatisticsParams) {
try {
return successResponse(
await this.statisticsService.getOrderStatistics(params)
);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/orderByDate')
async getOrderByDate(@Body('date') date: string) {
try {
return successResponse(await this.statisticsService.getOrderByDate(date));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/orderByEmail')
async getOrderByEmail(@Body('email') email: string) {
try {
return successResponse(
await this.statisticsService.getOrderByEmail(email)
);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/getCustomerOrders')
async getCustomerOrders(@Body('month') month) {
try {
return successResponse(
await this.statisticsService.getCustomerOrders(month)
);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/stockForecast')
async stockForecast(@Body() params) {
try {
return successResponse(
await this.statisticsService.stockForecast(params)
);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Post('/restocking')
async restocking(@Body() params) {
try {
return successResponse(await this.statisticsService.restocking(params));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Get('/orderSource')
async getOrderSorce(@Query() params) {
try {
return successResponse(await this.statisticsService.getOrderSorce(params));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse()
@Get('/inactiveUsersByMonth')
async getInativeUsersByMonth(@Query('month') month: string) {
try {
return successResponse(await this.statisticsService.getInativeUsersByMonth(month));
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
}

View File

@ -0,0 +1,273 @@
import {
Controller,
Post,
Get,
Body,
Inject,
Del,
Param,
Query,
Put,
} from '@midwayjs/core';
import { StockService } from '../service/stock.service';
import { errorResponse, successResponse } from '../utils/response.util';
import {
CreatePurchaseOrderDTO,
CreateStockPointDTO,
QueryPointDTO,
QueryPurchaseOrderDTO,
QueryStockDTO,
QueryStockRecordDTO,
UpdatePurchaseOrderDTO,
UpdateStockDTO,
UpdateStockPointDTO,
} from '../dto/stock.dto';
import { ApiOkResponse } from '@midwayjs/swagger';
import {
BooleanRes,
PurchaseOrderListRes,
StockListRes,
StockPointAllRespone,
StockPointListRes,
StockRecordListRes,
} from '../dto/reponse.dto';
import { User } from '../decorator/user.decorator';
@Controller('/stock')
export class StockController {
@Inject()
private readonly stockService: StockService;
@ApiOkResponse({ type: BooleanRes, description: '创建库存点' })
@Post('/stock-point')
async createStockPoint(@Body() body: CreateStockPointDTO) {
try {
const data = await this.stockService.createStockPoint(body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取库存列表失败');
}
}
@ApiOkResponse({ type: BooleanRes, description: '创建库存点' })
@Put('/stock-point/:id')
async updateStockPoint(
@Param('id') id: number,
@Body() body: UpdateStockPointDTO
) {
try {
const data = await this.stockService.updateStockPoint(id, body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取库存列表失败');
}
}
@ApiOkResponse({ type: StockPointListRes, description: '获取库存点列表' })
@Get('/stock-point')
async getStockPoints(@Query() query: QueryPointDTO) {
try {
const data = await this.stockService.getStockPoints(query);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取库存列表失败');
}
}
@ApiOkResponse({ type: StockPointAllRespone, description: '获取所有库存' })
@Get('/stock-point/all')
async getAllStockPoints() {
try {
const data = await this.stockService.getAllStockPoints();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
@ApiOkResponse({ type: BooleanRes, description: '删除库存点' })
@Del('/stock-point/:id')
async delStockPoints(@Param('id') id: number) {
try {
await this.stockService.delStockPoints(id);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '删除库存点失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/purchase-order')
async createPurchaseOrder(@Body() body: CreatePurchaseOrderDTO) {
try {
const data = await this.stockService.createPurchaseOrder(body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Put('/purchase-order/:id')
async updatePurchaseOrder(
@Param('id') id: number,
@Body() body: UpdatePurchaseOrderDTO
) {
try {
const data = await this.stockService.updatePurchaseOrder(id, body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@ApiOkResponse({ type: PurchaseOrderListRes, description: '获取采购列表' })
@Get('/purchase-order')
async getPurchaseOrders(@Query() query: QueryPurchaseOrderDTO) {
try {
const data = await this.stockService.getPurchaseOrders(query);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取库存记录列表失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Del('/purchase-order/:id')
async delPurchaseOrder(@Param('id') id: number) {
try {
const data = await this.stockService.delPurchaseOrder(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '删除失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/purchase-order/:id')
async receivePurchaseOrder(@Param('id') id: number, @User() user) {
try {
const data = await this.stockService.receivePurchaseOrder(id, user.id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Get('/purchase-order/:orderNumber')
async getPurchaseOrder(@Param('orderNumber') orderNumber: string) {
try {
const data = await this.stockService.getPurchaseOrder(orderNumber);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@ApiOkResponse({ type: StockListRes, description: '获取库存列表' })
@Get('/')
async getStocks(@Query() query: QueryStockDTO) {
try {
const data = await this.stockService.getStocks(query);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取库存列表失败');
}
}
@ApiOkResponse({
type: BooleanRes,
description: '更新库存(入库、出库、调整)',
})
@Post('/update')
async updateStock(@Body() body: UpdateStockDTO) {
try {
await this.stockService.updateStock(body);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '更新库存失败');
}
}
@ApiOkResponse({ type: StockRecordListRes, description: '获取库存记录列表' })
@Get('/records')
async getStockRecords(@Query() query: QueryStockRecordDTO) {
try {
const data = await this.stockService.getStockRecords(query);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取库存记录列表失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/transfer')
async createTransfer(@Body() body: Record<string, any>, @User() user) {
try {
const data = await this.stockService.createTransfer(body, user.id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse()
@Get('/transfer')
async getTransfers(@Query() query: Record<string, any>) {
try {
const data = await this.stockService.getTransfers(query);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取调拨列表失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/cancelTransfer/:id')
async cancelTransfer(@Param('id') id: number, @User() user) {
try {
const data = await this.stockService.cancelTransfer(id, user.id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '删除失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/receiveTransfer/:id')
async receiveTransfer(@Param('id') id: number, @User() user) {
try {
const data = await this.stockService.receiveTransfer(id, user.id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/lostTransfer/:id')
async lostTransfer(@Param('id') id: number) {
try {
const data = await this.stockService.lostTransfer(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Put('/receiveTransfer/:id')
async updateTransfer(
@Param('id') id: number,
@Body() body: Record<string, any>,
@User() user
) {
try {
const data = await this.stockService.updateTransfer(id, body, user.id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '更新失败');
}
}
}

View File

@ -0,0 +1,36 @@
import { Controller, Inject, Param, Post, Get, Query } from '@midwayjs/core';
import { ApiOkResponse } from '@midwayjs/swagger';
import { SubscriptionService } from '../service/subscription.service';
import { errorResponse, successResponse } from '../utils/response.util';
import { BooleanRes, SubscriptionListRes } from '../dto/reponse.dto';
import { QuerySubscriptionDTO } from '../dto/subscription.dto';
@Controller('/subscription')
export class SubscriptionController {
@Inject()
subscriptionService: SubscriptionService;
// 同步订阅:根据站点 ID 拉取并更新本地订阅数据
@ApiOkResponse({ type: BooleanRes })
@Post('/sync/:siteId')
async sync(@Param('siteId') siteId: string) {
try {
await this.subscriptionService.syncSubscriptions(siteId);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || '同步失败');
}
}
// 订阅列表:分页 + 筛选
@ApiOkResponse({ type: SubscriptionListRes })
@Get('/list')
async list(@Query() query: QuerySubscriptionDTO) {
try {
const data = await this.subscriptionService.getSubscriptionList(query);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || '获取失败');
}
}
}

View File

@ -0,0 +1,74 @@
// src/controller/user.controller.ts
import { Controller, Post, Get, Body, Query } from '@midwayjs/core';
import { Inject } from '@midwayjs/decorator';
import { UserService } from '../service/user.service';
import { errorResponse, successResponse } from '../utils/response.util';
import { ApiOkResponse } from '@midwayjs/swagger';
import { BooleanRes, LoginRes } from '../dto/reponse.dto';
import { User } from '../decorator/user.decorator';
@Controller('/user')
export class UserController {
@Inject()
userService: UserService;
@Inject()
ctx;
@ApiOkResponse({
type: LoginRes,
})
@Post('/login')
async login(@Body() body) {
this.ctx.logger.info('ip:', this.ctx.ip, '; path:', this.ctx.path, '; user:', body?.username);
try {
const result = await this.userService.login(body);
return successResponse(result, '登录成功');
} catch (error) {
return errorResponse(error?.message || '登录失败', error?.code);
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/logout')
async logout() {
// 可选:在这里处理服务端缓存的 token 或 session
return successResponse(true);
}
@Post('/add')
async addUser(@Body() body: { username: string; password: string }) {
const { username, password } = body;
try {
await this.userService.addUser(username, password);
return successResponse(true);
} catch (error) {
console.log(error);
return errorResponse('添加用户失败');
}
}
@Get('/list')
async listUsers(@Query() query: { current: number; pageSize: number }) {
const { current = 1, pageSize = 10 } = query;
return successResponse(await this.userService.listUsers(current, pageSize));
}
@Post('/toggleActive')
async toggleActive(@Body() body: { userId: number; isActive: boolean }) {
return this.userService.toggleUserActive(body.userId, body.isActive);
}
@ApiOkResponse()
@Get()
async getUser(@User() user) {
try {
return successResponse(await this.userService.getUser(user.id));
} catch (error) {
return errorResponse('获取失败');
}
}
}

View File

@ -0,0 +1,140 @@
import { HttpStatus, Inject } from '@midwayjs/core';
import {
Controller,
Post,
Body,
Headers,
Get,
Query,
} from '@midwayjs/decorator';
import { Context } from '@midwayjs/koa';
import * as crypto from 'crypto';
import { WpProductService } from '../service/wp_product.service';
import { WPService } from '../service/wp.service';
import { SiteService } from '../service/site.service';
import { OrderService } from '../service/order.service';
@Controller('/webhook')
export class WebhookController {
private secret = 'YOONE24kd$kjcdjflddd';
@Inject()
private readonly wpProductService: WpProductService;
@Inject()
private readonly wpApiService: WPService;
@Inject()
private readonly orderService: OrderService;
@Inject()
ctx: Context;
@Inject()
private readonly siteService: SiteService;
// 移除配置中的站点数组,来源统一改为数据库
@Get('/')
async test() {
return 'webhook';
}
@Post('/woocommerce')
async handleWooWebhook(
@Body() body: any,
@Query('siteId') siteId: string,
@Headers() header: any
) {
const signature = header['x-wc-webhook-signature'];
const topic = header['x-wc-webhook-topic'];
const source = header['x-wc-webhook-source'];
// 从数据库获取站点配置
const site = await this.siteService.get(Number(siteId), true);
if (!site || !source.includes(site.apiUrl)) {
console.log('domain not match');
return {
code: HttpStatus.BAD_REQUEST,
success: false,
message: 'domain not match',
};
}
if (!signature) {
return {
code: HttpStatus.BAD_REQUEST,
success: false,
message: 'Signature missing',
};
}
const rawBody = this.ctx.request.rawBody;
const hash = crypto
.createHmac('sha256', this.secret)
.update(rawBody)
.digest('base64');
try {
if (hash === signature) {
switch (topic) {
case 'product.created':
case 'product.updated':
// 变体更新
if (body.type === 'variation') {
const variation = await this.wpApiService.getVariation(
site,
body.parent_id,
body.id
);
this.wpProductService.syncVariation(
siteId,
body.parent_id,
variation
);
break;
}
const variations =
body.type === 'variable'
? await this.wpApiService.getVariations(site, body.id)
: [];
await this.wpProductService.syncProductAndVariations(
String(site.id),
body,
variations
);
break;
case 'product.deleted':
await this.wpProductService.delWpProduct(String(site.id), body.id);
break;
case 'order.created':
case 'order.updated':
await this.orderService.syncSingleOrder(siteId, body);
break;
case 'order.deleted':
break;
case 'customer.created':
break;
case 'customer.updated':
break;
case 'customer.deleted':
break;
default:
console.log('Unhandled event:', body.event);
}
return {
code: 200,
success: true,
message: 'Webhook processed successfully',
};
} else {
return {
code: 403,
success: false,
message: 'Webhook verification failed',
};
}
} catch (error) {
console.log(error);
}
}
}

View File

@ -0,0 +1,200 @@
import {
Controller,
Param,
Post,
Inject,
Get,
Query,
Put,
Body,
} from '@midwayjs/core';
import { WpProductService } from '../service/wp_product.service';
import { errorResponse, successResponse } from '../utils/response.util';
import { ApiOkResponse } from '@midwayjs/swagger';
import { BooleanRes, WpProductListRes } from '../dto/reponse.dto';
import {
QueryWpProductDTO,
SetConstitutionDTO,
UpdateVariationDTO,
UpdateWpProductDTO,
} from '../dto/wp_product.dto';
import { WPService } from '../service/wp.service';
import { SiteService } from '../service/site.service';
import {
ProductsRes,
} from '../dto/reponse.dto';
@Controller('/wp_product')
export class WpProductController {
// 移除控制器内的配置站点引用,统一由服务层处理站点数据
@Inject()
private readonly wpProductService: WpProductService;
@Inject()
private readonly wpApiService: WPService;
@Inject()
private readonly siteService: SiteService;
@ApiOkResponse({
type: BooleanRes,
})
@Post('/sync/:siteId')
async syncProducts(@Param('siteId') siteId: string) {
try {
await this.wpProductService.syncSite(siteId);
return successResponse(true);
} catch (error) {
console.log(error);
return errorResponse('同步失败');
}
}
@ApiOkResponse({
type: WpProductListRes,
})
@Get('/list')
async getWpProducts(@Query() query: QueryWpProductDTO) {
try {
const data = await this.wpProductService.getProductList(query);
return successResponse(data);
} catch (error) {
return errorResponse(error.message);
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Put('/:id/constitution')
async setConstitution(
@Param('id') id: number,
@Body()
body: SetConstitutionDTO
) {
const { isProduct, constitution } = body;
try {
await this.wpProductService.setConstitution(id, isProduct, constitution);
return successResponse(true);
} catch (error) {
return errorResponse(error.message);
}
}
@ApiOkResponse({
type: BooleanRes
})
@Post('/updateState/:id')
async updateWPProductState(
@Param('id') id: number,
@Body() body: any, // todo
) {
try {
const res = await this.wpProductService.updateProductStatus(id, body?.status, body?.stock_status);
return successResponse(res);
} catch (error) {
return errorResponse(error.message);
}
}
/**
*
* @param productId ID
* @param body
*/
@ApiOkResponse({
type: BooleanRes,
})
@Put('/siteId/:siteId/products/:productId')
async updateProduct(
@Param('siteId') siteId: string,
@Param('productId') productId: string,
@Body() body: UpdateWpProductDTO
) {
try {
const isDuplicate = await this.wpProductService.isSkuDuplicate(
body.sku,
siteId,
productId
);
if (isDuplicate) {
return errorResponse('SKU已存在');
}
const site = await this.siteService.get(Number(siteId), true);
const result = await this.wpApiService.updateProduct(
site,
productId,
body
);
if (result) {
this.wpProductService.updateWpProduct(siteId, productId, body);
return successResponse(result, '产品更新成功');
}
return errorResponse('产品更新失败');
} catch (error) {
console.error('更新产品失败:', error);
return errorResponse(error.message || '产品更新失败');
}
}
/**
*
* @param productId ID
* @param variationId ID
* @param body
*/
@Put('/siteId/:siteId/products/:productId/variations/:variationId')
async updateVariation(
@Param('siteId') siteId: string,
@Param('productId') productId: string,
@Param('variationId') variationId: string,
@Body() body: UpdateVariationDTO
) {
try {
const isDuplicate = await this.wpProductService.isSkuDuplicate(
body.sku,
siteId,
productId,
variationId
);
if (isDuplicate) {
return errorResponse('SKU已存在');
}
const site = await this.siteService.get(Number(siteId), true);
const result = await this.wpApiService.updateVariation(
site,
productId,
variationId,
body
);
if (result) {
this.wpProductService.updateWpProductVaritation(
siteId,
productId,
variationId,
body
);
return successResponse(result, '产品变体更新成功');
}
return errorResponse('变体更新失败');
} catch (error) {
console.error('更新变体失败:', error);
return errorResponse(error.message || '产品变体更新失败');
}
}
@ApiOkResponse({
description: '通过name搜索产品/订单',
type: ProductsRes,
})
@Get('/search')
async searchProducts(@Query('name') name: string) {
try {
// 调用服务获取产品数据
const products = await this.wpProductService.findProductsByName(name);
return successResponse(products);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
}

View File

@ -0,0 +1,8 @@
import { createCustomParamDecorator } from '@midwayjs/core';
export const USER_KEY = 'USER_KEY';
// 定义装饰器
export function User(): ParameterDecorator {
return createCustomParamDecorator(USER_KEY, {});
}

41
src/dto/customer.dto.ts Normal file
View File

@ -0,0 +1,41 @@
import { ApiProperty } from '@midwayjs/swagger';
export class QueryCustomerListDTO {
@ApiProperty()
current: string;
@ApiProperty()
pageSize: string;
@ApiProperty()
email: string;
@ApiProperty()
tags: string;
@ApiProperty()
sorterKey: string;
@ApiProperty()
sorterValue: string;
@ApiProperty()
state: string;
@ApiProperty()
first_purchase_date: string;
@ApiProperty()
customerId: number;
@ApiProperty()
phone: string;
}
export class CustomerTagDTO {
@ApiProperty()
email: string;
@ApiProperty()
tag: string;
}

306
src/dto/freightcom.dto.ts Normal file
View File

@ -0,0 +1,306 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
// 定义包装类型的联合类型
export type PackagingType =
// | PackagingPallet
PackagingPackage;
// | PackagingCourierPak
// | PackagingEnvelope;
// 定义包装类型的枚举,用于 API 文档描述
export enum PackagingTypeEnum {
Pallet = 'pallet',
Package = 'package',
CourierPak = 'courier-pak',
Envelope = 'envelope',
}
export class Address {
@ApiProperty()
@Rule(RuleType.string())
address_line_1: string;
@ApiProperty()
@Rule(RuleType.string())
city: string;
@ApiProperty()
@Rule(RuleType.string())
region: string;
@ApiProperty()
@Rule(RuleType.string())
country: string;
@ApiProperty()
@Rule(RuleType.string())
postal_code: string;
}
export class PhoneNumber {
@ApiProperty()
@Rule(RuleType.string())
number: string;
@ApiProperty()
@Rule(RuleType.string())
extension: string;
}
export class Location {
@ApiProperty()
@Rule(RuleType.string())
name: string;
@ApiProperty({ type: Address })
@Rule(RuleType.object<Address>())
address: Address;
@ApiProperty({ type: PhoneNumber })
@Rule(RuleType.object<PhoneNumber>())
phone_number: PhoneNumber;
@ApiProperty()
@Rule(RuleType.array<string>())
email_addresses: string[];
contact_name?: string;
}
export class Time {
@ApiProperty()
@Rule(RuleType.string())
hour: string;
@ApiProperty()
@Rule(RuleType.string())
minute: string;
}
export enum SignatureRequirementEnum {
NOTREQUIRED = 'not-required',
REQUIRED = 'required',
ADULTREQUIRED = 'adult-required',
}
export class Destination extends Location {
@ApiProperty({ type: Time })
@Rule(RuleType.object<Time>())
ready_at: Time;
@ApiProperty({ type: Time })
@Rule(RuleType.object<Time>())
ready_until: Time;
@ApiProperty({ type: SignatureRequirementEnum })
@Rule(RuleType.string().valid(...Object.values(SignatureRequirementEnum)))
SignatureRequirementEnum: SignatureRequirementEnum;
}
export class Date {
@ApiProperty()
@Rule(RuleType.string())
year: string;
@ApiProperty()
@Rule(RuleType.string())
month: string;
@ApiProperty()
@Rule(RuleType.string())
day: string;
}
export enum UnitEnum {
KG = 'kg',
LB = 'lb',
G = 'g',
OZ = 'oz',
}
export class Cubid {
@ApiProperty()
@Rule(RuleType.number())
w: number;
@ApiProperty()
@Rule(RuleType.number())
h: number;
@ApiProperty()
@Rule(RuleType.number())
l: number;
@ApiProperty()
@Rule(RuleType.string())
unit: string;
}
export class Weight {
@ApiProperty({ enum: UnitEnum })
@Rule(RuleType.string().valid(...Object.values(UnitEnum)))
unit: UnitEnum;
@ApiProperty()
@Rule(RuleType.number())
value: number;
}
export class Measurements {
@ApiProperty({ type: Cubid })
cuboid: Cubid;
@ApiProperty({ type: Cubid })
weight: Weight;
}
export class Pallets {
@ApiProperty({ type: Measurements })
@Rule(RuleType.object<Measurements>())
measurements: Measurements;
@ApiProperty()
@Rule(RuleType.string())
description: string;
@ApiProperty()
@Rule(RuleType.string())
freight_class: string;
}
export class PackagingPallet {
@ApiProperty()
@Rule(RuleType.string())
pallet_type: string;
@ApiProperty({ type: Pallets })
@Rule(RuleType.object<Pallets>())
pallets: Pallets;
}
export class Package {
@ApiProperty({ type: Measurements })
@Rule(RuleType.object<Measurements>())
measurements: Measurements;
@ApiProperty()
@Rule(RuleType.string())
description: string;
}
export class PackagingPackage {
@ApiProperty({ type: Package, isArray: true })
@Rule(RuleType.array<Package>())
packages: Package[];
}
export class PackagingCourierPak {
@ApiProperty({ type: Package, isArray: true })
@Rule(RuleType.array<Package>())
courier_paks: Package[];
}
export class PackagingEnvelope {
// 添加必要的属性和装饰器
@ApiProperty()
@Rule(RuleType.boolean())
includes_return_label?: boolean;
}
// export enum InsuranceTypeEnum {
// INTERNAL = 'internal',
// CARRIER = 'carrier',
// }
// export class Insurance {
// @ApiProperty({ enum: InsuranceTypeEnum })
// @Rule(RuleType.string().valid(...Object.values(InsuranceTypeEnum)))
// type: InsuranceTypeEnum;
// }
export class ShippingDetailsDTO {
@ApiProperty()
shipmentFee: number;
@ApiProperty({ type: Location })
@Rule(RuleType.object<Location>())
origin: Location;
@ApiProperty({ type: Destination })
@Rule(RuleType.object<Destination>())
destination: Destination;
@ApiProperty({ type: Date })
@Rule(RuleType.object<Date>())
expected_ship_date: Date;
@ApiProperty({ enum: PackagingTypeEnum })
@Rule(RuleType.string().valid(...Object.values(PackagingTypeEnum)))
packaging_type: PackagingTypeEnum;
@ApiProperty({
type: () => [
// PackagingPallet,
PackagingPackage,
// PackagingCourierPak,
// PackagingEnvelope,
],
})
@Rule(RuleType.object<PackagingType>())
packaging_properties: PackagingType;
// @ApiProperty({ type: Insurance })
// @Rule(RuleType.object<Insurance>())
// insurance?: Insurance;
@ApiProperty()
reference_codes: string[];
}
export class Money {
@ApiProperty()
currency: string;
@ApiProperty()
value: string;
}
export class Surcharges {
@ApiProperty()
type: string;
@ApiProperty({ type: Money })
amount: Money;
}
export class RateDTO {
@ApiProperty()
carrier_name: string;
@ApiProperty()
service_name: string;
@ApiProperty()
service_id: string;
@ApiProperty({ type: Date })
valid_until: Date;
@ApiProperty({ type: Money })
total: Money;
@ApiProperty({ type: Money })
base: Money;
@ApiProperty({ type: Surcharges, isArray: true })
surcharges: Surcharges[];
@ApiProperty({ type: Money, isArray: true })
taxes: Money[];
@ApiProperty()
transit_time_days: number;
@ApiProperty()
transit_time_not_available: boolean;
}

109
src/dto/logistics.dto.ts Normal file
View File

@ -0,0 +1,109 @@
import { ApiProperty } from '@midwayjs/swagger';
import { ShippingDetailsDTO } from './freightcom.dto';
import { Rule, RuleType } from '@midwayjs/validate';
import { OrderSale } from '../entity/order_sale.entity';
export class ShipmentBookDTO {
@ApiProperty({ type: OrderSale, isArray: true })
@Rule(RuleType.array<OrderSale>())
sales: OrderSale[];
@ApiProperty({ type: ShippingDetailsDTO })
@Rule(RuleType.object<ShippingDetailsDTO>())
details: ShippingDetailsDTO;
@ApiProperty()
@Rule(RuleType.number())
stockPointId: number;
@ApiProperty({ type: 'number', isArray: true })
@Rule(RuleType.array<number>().default([]))
orderIds?: number[];
@ApiProperty()
@Rule(RuleType.string())
shipmentPlatform: string;
@ApiProperty()
@Rule(RuleType.any())
courierCompany: string;
}
export class ShipmentFeeBookDTO {
@ApiProperty()
shipmentPlatform: string;
@ApiProperty()
courierCompany: string;
@ApiProperty()
stockPointId: number;
@ApiProperty()
sender: string;
@ApiProperty()
startPhone: string|any;
@ApiProperty()
startPostalCode: string;
@ApiProperty()
pickupAddress: string;
// pickupWarehouse: number; // 此处用 stockPointId 到后端解析
@ApiProperty()
shipperCountryCode: string;
@ApiProperty()
receiver: string;
@ApiProperty()
city: string;
@ApiProperty()
province: string;
@ApiProperty()
country: string;
@ApiProperty()
postalCode: string;
@ApiProperty()
deliveryAddress: string;
@ApiProperty()
receiverPhone: string;
@ApiProperty()
receiverEmail: string;
@ApiProperty()
length: number;
@ApiProperty()
width: number;
@ApiProperty()
height: number;
@ApiProperty()
dimensionUom: string;
@ApiProperty()
weight: number;
@ApiProperty()
weightUom: string;
@ApiProperty()
address_id: number;
}
export class PaymentMethodDTO {
@ApiProperty()
id: string;
@ApiProperty()
type: string;
@ApiProperty()
label: string;
}
export class QueryServiceDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number;
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number;
@ApiProperty()
@Rule(RuleType.string())
carrier_name: string;
@ApiProperty()
@Rule(RuleType.bool())
isActive: boolean;
}

181
src/dto/order.dto.ts Normal file
View File

@ -0,0 +1,181 @@
import { ApiProperty } from '@midwayjs/swagger';
import { ErpOrderStatus } from '../enums/base.enum';
import { Rule, RuleType } from '@midwayjs/validate';
// import { Shipment } from '../entity/shipment.entity';
// import { ShipmentItem } from '../entity/shipment_item.entity';
export class OrderAddress {
@ApiProperty()
first_name: string;
@ApiProperty()
last_name: string;
@ApiProperty()
company: string;
@ApiProperty()
address_1: string;
@ApiProperty()
address_2: string;
@ApiProperty()
city: string;
@ApiProperty()
state: string;
@ApiProperty()
postcode: string;
@ApiProperty()
country: string;
@ApiProperty()
email: string;
@ApiProperty()
phone: string;
}
export class OrderStatusCountDTO {
@ApiProperty()
status: ErpOrderStatus;
@ApiProperty()
count: number;
}
export class QueryOrderDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number;
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number;
@ApiProperty()
@Rule(RuleType.string())
externalOrderId: string;
@ApiProperty()
@Rule(RuleType.string())
siteId: string;
@ApiProperty()
@Rule(RuleType.string().allow(''))
customer_email: string;
@ApiProperty()
@Rule(RuleType.string().allow(''))
billing_phone: string;
@ApiProperty()
@Rule(RuleType.string().allow(null))
keyword: string;
@ApiProperty()
@Rule(RuleType.date())
startDate: Date;
@ApiProperty()
@Rule(RuleType.date())
endDate: Date;
@ApiProperty({ type: 'enum', enum: ErpOrderStatus })
@Rule(RuleType.string().valid(...Object.values(ErpOrderStatus)))
status: ErpOrderStatus;
@ApiProperty()
@Rule(RuleType.string())
payment_method: string;
@ApiProperty({ description: '仅订阅订单(父订阅订单或包含订阅商品)' })
@Rule(RuleType.bool().default(false))
isSubscriptionOnly?: boolean;
}
export class QueryOrderSalesDTO {
@ApiProperty()
@Rule(RuleType.bool().default(false))
isSource: boolean;
@ApiProperty()
@Rule(RuleType.bool().default(false))
exceptPackage: boolean;
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number;
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number;
@ApiProperty()
@Rule(RuleType.string())
siteId: string;
@ApiProperty()
@Rule(RuleType.string())
name: string;
@ApiProperty()
@Rule(RuleType.date())
startDate: Date;
@ApiProperty()
@Rule(RuleType.date())
endDate: Date;
}
// export class Tracking extends Shipment {
// @ApiProperty({ type: ShipmentItem, isArray: true })
// products?: ShipmentItem[];
// }
export class CreateOrderNoteDTO {
@ApiProperty()
@Rule(RuleType.number())
orderId: number;
@ApiProperty()
@Rule(RuleType.string())
content: string;
}
export class QueryOrderItemDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number;
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number;
@ApiProperty()
@Rule(RuleType.string().allow(''))
siteId: string;
@ApiProperty()
@Rule(RuleType.string().allow(''))
name: string; // 商品名称关键字
@ApiProperty()
@Rule(RuleType.string().allow(''))
externalProductId: string;
@ApiProperty()
@Rule(RuleType.string().allow(''))
externalVariationId: string;
@ApiProperty()
@Rule(RuleType.date())
startDate: Date;
@ApiProperty()
@Rule(RuleType.date())
endDate: Date;
}

174
src/dto/product.dto.ts Normal file
View File

@ -0,0 +1,174 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
/**
* DTO
*/
export class CreateProductDTO {
@ApiProperty({
example: 'ZYN 6MG WINTERGREEN',
description: '产品名称',
required: true,
})
@Rule(RuleType.string().required().empty({ message: '产品名称不能为空' }))
name: string;
@ApiProperty({ example: '产品描述', description: '产品描述' })
@Rule(RuleType.string())
description: string;
@ApiProperty({ example: '1', description: '分类 ID' })
@Rule(RuleType.number())
categoryId: number;
@ApiProperty()
@Rule(RuleType.number())
strengthId: number;
@ApiProperty()
@Rule(RuleType.number())
flavorsId: number;
@ApiProperty()
@Rule(RuleType.string())
humidity: string;
}
/**
* DTO
*/
export class UpdateProductDTO extends CreateProductDTO {
@ApiProperty({ example: 'ZYN 6MG WINTERGREEN', description: '产品名称' })
@Rule(RuleType.string())
name: string;
}
/**
* DTO
*/
export class QueryProductDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number;
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number;
@ApiProperty({ example: 'ZYN', description: '关键字' })
@Rule(RuleType.string())
name: string;
@ApiProperty({ example: '1', description: '分类 ID' })
@Rule(RuleType.string())
categoryId: number;
}
/**
* DTO
*/
export class CreateCategoryDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
name: string;
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string;
}
/**
* DTO
*/
export class UpdateCategoryDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' })
@Rule(RuleType.string())
name: string;
}
/**
* DTO
*/
export class QueryCategoryDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number; // 每页大小
@ApiProperty({ example: 'ZYN', description: '关键字' })
@Rule(RuleType.string())
name: string; // 搜索关键字(支持模糊查询)
}
export class CreateFlavorsDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
name: string;
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string;
}
export class UpdateFlavorsDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' })
@Rule(RuleType.string())
name: string;
}
export class QueryFlavorsDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number; // 每页大小
@ApiProperty({ example: 'ZYN', description: '关键字' })
@Rule(RuleType.string())
name: string; // 搜索关键字(支持模糊查询)
}
export class CreateStrengthDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
name: string;
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string;
}
export class UpdateStrengthDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' })
@Rule(RuleType.string())
name: string;
}
export class QueryStrengthDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number; // 每页大小
@ApiProperty({ example: 'ZYN', description: '关键字' })
@Rule(RuleType.string())
name: string; // 搜索关键字(支持模糊查询)
}
export class SkuItemDTO {
@ApiProperty({ description: '产品 ID' })
productId: number;
@ApiProperty({ description: 'sku 编码' })
sku: string;
}
export class BatchSetSkuDTO {
@ApiProperty({ description: 'sku 数据列表', type: [SkuItemDTO] })
skus: SkuItemDTO[];
}

125
src/dto/reponse.dto.ts Normal file
View File

@ -0,0 +1,125 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Category } from '../entity/category.entity';
import { Order } from '../entity/order.entity';
import { Product } from '../entity/product.entity';
import { StockPoint } from '../entity/stock_point.entity';
import { PaginatedWrapper } from '../utils/paginated-response.util';
import {
SuccessArrayWrapper,
SuccessWrapper,
} from '../utils/response-wrapper.util';
import { OrderStatusCountDTO } from './order.dto';
import { SiteConfig } from './site.dto';
import { PurchaseOrderDTO, StockDTO, StockRecordDTO } from './stock.dto';
import { LoginResDTO } from './user.dto';
import { WpProductDTO } from './wp_product.dto';
import { OrderSale } from '../entity/order_sale.entity';
import { Service } from '../entity/service.entity';
import { RateDTO } from './freightcom.dto';
import { ShippingAddress } from '../entity/shipping_address.entity';
import { OrderItem } from '../entity/order_item.entity';
import { OrderRefundItem } from '../entity/order_retund_item.entity';
import { OrderNote } from '../entity/order_note.entity';
import { PaymentMethodDTO } from './logistics.dto';
import { Flavors } from '../entity/flavors.entity';
import { Strength } from '../entity/strength.entity';
import { Subscription } from '../entity/subscription.entity';
export class BooleanRes extends SuccessWrapper(Boolean) {}
//网站配置返回数据
export class WpSitesResponse extends SuccessArrayWrapper(SiteConfig) {}
//产品分页数据
export class ProductPaginatedResponse extends PaginatedWrapper(Product) {}
//产品分页返回数据
export class ProductListRes extends SuccessWrapper(ProductPaginatedResponse) {}
//产品返回数据
export class ProductRes extends SuccessWrapper(Product) {}
export class ProductsRes extends SuccessArrayWrapper(Product) {}
//产品分类返分页数据
export class CategoryPaginatedResponse extends PaginatedWrapper(Category) {}
export class FlavorsPaginatedResponse extends PaginatedWrapper(Flavors) {}
export class StrengthPaginatedResponse extends PaginatedWrapper(Strength) {}
//产品分类返分页返回数据
export class ProductCatListRes extends SuccessWrapper(
CategoryPaginatedResponse
) {}
//产品分类返所有数据
export class ProductCatAllRes extends SuccessArrayWrapper(Category) {}
//产品分类返回数据
export class ProductCatRes extends SuccessWrapper(Category) {}
//产品分页数据
export class WpProductPaginatedResponse extends PaginatedWrapper(
WpProductDTO
) {}
//产品分页返回数据
export class WpProductListRes extends SuccessWrapper(
WpProductPaginatedResponse
) {}
export class LoginRes extends SuccessWrapper(LoginResDTO) {}
export class StockPaginatedRespone extends PaginatedWrapper(StockDTO) {}
export class StockListRes extends SuccessWrapper(StockPaginatedRespone) {}
export class StockRecordPaginatedRespone extends PaginatedWrapper(
StockRecordDTO
) {}
export class StockRecordListRes extends SuccessWrapper(
StockRecordPaginatedRespone
) {}
export class StockPointAllRespone extends SuccessArrayWrapper(StockPoint) {}
export class StockPointPaginatedRespone extends PaginatedWrapper(StockPoint) {}
export class StockPointListRes extends SuccessWrapper(
StockPointPaginatedRespone
) {}
export class PurchaseOrderPaginatedRespone extends PaginatedWrapper(
PurchaseOrderDTO
) {}
export class PurchaseOrderListRes extends SuccessWrapper(
PurchaseOrderPaginatedRespone
) {}
export class OrderStatusCountRes extends SuccessArrayWrapper(
OrderStatusCountDTO
) {}
export class OrderPaginatedRespone extends PaginatedWrapper(Order) {
@ApiProperty({ type: OrderStatusCountDTO, isArray: true })
count: OrderStatusCountDTO;
}
export class OrderListRes extends SuccessWrapper(OrderPaginatedRespone) {}
export class OrderSaleDTO extends OrderSale {
@ApiProperty()
totalQuantity: number;
}
export class OrderSalePaginatedRespone extends PaginatedWrapper(OrderSaleDTO) {}
export class OrderSaleListRes extends SuccessWrapper(
OrderSalePaginatedRespone
) {}
export class ServiceListRes extends SuccessArrayWrapper(Service) {}
export class RateLitRes extends SuccessArrayWrapper(RateDTO) {}
export class ShippingAddressListRes extends SuccessArrayWrapper(
ShippingAddress
) {}
export class OrderDetail extends Order {
@ApiProperty({ type: OrderItem, isArray: true })
items: OrderItem[];
@ApiProperty({ type: OrderSale, isArray: true })
sales: OrderSale[];
@ApiProperty({ type: OrderRefundItem, isArray: true })
refundItems: OrderRefundItem[];
// @ApiProperty({ type: Tracking, isArray: true })
// trackings: Tracking[];
@ApiProperty({ type: OrderNote, isArray: true })
notes: OrderNote[];
}
export class OrderDetailRes extends SuccessWrapper(OrderDetail) {}
export class PaymentMethodListRes extends SuccessArrayWrapper(
PaymentMethodDTO
) {}
// 订阅分页数据(列表 + 总数等分页信息)
export class SubscriptionPaginatedResponse extends PaginatedWrapper(Subscription) {}
// 订阅分页返回数据(统一成功包装)
export class SubscriptionListRes extends SuccessWrapper(SubscriptionPaginatedResponse) {}

82
src/dto/site.dto.ts Normal file
View File

@ -0,0 +1,82 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
export class SiteConfig {
@ApiProperty({ example: '1', description: '站点 ID' })
@Rule(RuleType.string())
id: string;
@ApiProperty({ description: '站点 URL' })
@Rule(RuleType.string())
apiUrl: string;
@ApiProperty({ description: '站点 rest key' })
@Rule(RuleType.string())
consumerKey: string;
@ApiProperty({ description: '站点 rest 秘钥' })
@Rule(RuleType.string())
consumerSecret: string;
@ApiProperty({ description: '站点名' })
@Rule(RuleType.string())
siteName: string;
@ApiProperty({ description: '平台类型', enum: ['woocommerce', 'shopyy'] })
@Rule(RuleType.string().valid('woocommerce', 'shopyy'))
type: string;
@ApiProperty({ description: 'SKU 前缀' })
@Rule(RuleType.string())
skuPrefix: string;
}
export class CreateSiteDTO {
@Rule(RuleType.string().optional())
apiUrl?: string;
@Rule(RuleType.string().optional())
consumerKey?: string;
@Rule(RuleType.string().optional())
consumerSecret?: string;
@Rule(RuleType.string())
siteName: string;
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
type?: string;
@Rule(RuleType.string().optional())
skuPrefix?: string;
}
export class UpdateSiteDTO {
@Rule(RuleType.string().optional())
apiUrl?: string;
@Rule(RuleType.string().optional())
consumerKey?: string;
@Rule(RuleType.string().optional())
consumerSecret?: string;
@Rule(RuleType.string().optional())
siteName?: string;
@Rule(RuleType.boolean().optional())
isDisabled?: boolean;
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
type?: string;
@Rule(RuleType.string().optional())
skuPrefix?: string;
}
export class QuerySiteDTO {
@Rule(RuleType.number().optional())
current?: number;
@Rule(RuleType.number().optional())
pageSize?: number;
@Rule(RuleType.string().optional())
keyword?: string;
@Rule(RuleType.boolean().optional())
isDisabled?: boolean;
@Rule(RuleType.string().optional())
ids?: string;
}
export class DisableSiteDTO {
@Rule(RuleType.boolean())
disabled: boolean;
}

36
src/dto/statistics.dto.ts Normal file
View File

@ -0,0 +1,36 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
import dayjs = require('dayjs');
export class OrderStatisticsParams {
@ApiProperty()
@Rule(RuleType.date().default(dayjs().subtract(1, 'month')))
startDate: Date;
@ApiProperty()
@Rule(RuleType.date().default(dayjs().subtract(1, 'month')))
endDate: Date;
@ApiProperty()
@Rule(RuleType.string().allow(null))
keyword?: string;
@ApiProperty()
@Rule(RuleType.number().allow(null))
siteId?: number;
@ApiProperty({
enum: ['all', 'first_purchase', 'repeat_purchase'],
default: 'all',
})
@Rule(RuleType.string().valid('all', 'first_purchase', 'repeat_purchase'))
purchaseType: string;
@ApiProperty({ enum: ['all', 'cpc', 'non_cpc'], default: 'all' })
@Rule(RuleType.string().valid('all', 'cpc', 'non_cpc'))
orderType: string;
@ApiProperty({ enum: ['all', 'zyn', 'yoone', 'zolt'], default: 'all' })
@Rule(RuleType.string().valid('all', 'zyn', 'yoone', 'zolt'))
brand: string;
}

185
src/dto/stock.dto.ts Normal file
View File

@ -0,0 +1,185 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
import { Stock } from '../entity/stock.entity';
import {
PurchaseOrderStatus,
StockRecordOperationType,
} from '../enums/base.enum';
import { PurchaseOrderItem } from '../entity/purchase_order_item.entity';
import { PurchaseOrder } from '../entity/purchase_order.entity';
import { StockRecord } from '../entity/stock_record.entity';
export class QueryStockDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number; // 每页大小
@ApiProperty()
@Rule(RuleType.string())
productName: string;
}
export class QueryPointDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number; // 每页大小
}
export class QueryStockRecordDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number; // 每页大小
@ApiProperty()
@Rule(RuleType.number())
stockPointId: number;
@ApiProperty()
@Rule(RuleType.string())
productSku: string;
@ApiProperty()
@Rule(RuleType.string())
productName: string;
@ApiProperty()
@Rule(RuleType.string())
operationType: string;
@ApiProperty()
@Rule(RuleType.date())
startDate: Date;
@ApiProperty()
@Rule(RuleType.date())
endDate: Date;
}
export class QueryPurchaseOrderDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number; // 每页大小
@ApiProperty()
@Rule(RuleType.string())
orderNumber?: string;
@ApiProperty()
@Rule(RuleType.number())
stockPointId?: number;
}
export class StockDTO extends Stock {
@ApiProperty()
@Rule(RuleType.string())
productName: string;
@ApiProperty({
type: 'object',
properties: {
id: { type: 'number' },
name: { type: 'string' },
quantity: { type: 'number' },
},
isArray: true,
})
@Rule(RuleType.array())
stockPoint: Array<{
id: number;
name: string;
quantity: number;
}>;
}
export class StockRecordDTO extends StockRecord {
@ApiProperty()
@Rule(RuleType.string())
productName: string;
}
export class PurchaseOrderDTO extends PurchaseOrder {
@ApiProperty({ type: PurchaseOrderItem, isArray: true })
@Rule(RuleType.array())
items: PurchaseOrderItem[];
}
export class UpdateStockDTO {
@ApiProperty()
@Rule(RuleType.number())
stockPointId: number;
@ApiProperty()
@Rule(RuleType.string())
productSku: string;
@ApiProperty()
@Rule(RuleType.number())
quantityChange: number;
@ApiProperty({ type: 'enum', enum: StockRecordOperationType })
@Rule(RuleType.string().valid(...Object.values(StockRecordOperationType)))
operationType: StockRecordOperationType;
@ApiProperty()
@Rule(RuleType.number())
operatorId: number;
@ApiProperty()
@Rule(RuleType.string().allow(''))
note: string;
}
export class CreateStockPointDTO {
@ApiProperty()
@Rule(RuleType.string())
name: string;
@ApiProperty()
@Rule(RuleType.string())
location: string;
@ApiProperty()
@Rule(RuleType.string())
contactPerson: string;
@ApiProperty()
@Rule(RuleType.string())
contactPhone: string;
}
export class UpdateStockPointDTO extends CreateStockPointDTO {}
export class CreatePurchaseOrderDTO {
@ApiProperty()
@Rule(RuleType.number())
stockPointId: number;
@ApiProperty()
@Rule(RuleType.date())
expectedArrivalTime: Date;
@ApiProperty({ type: 'enum', enum: PurchaseOrderStatus })
@Rule(RuleType.string().valid(...Object.values(PurchaseOrderStatus)))
status: PurchaseOrderStatus;
@ApiProperty()
@Rule(RuleType.string().allow(''))
note?: string;
@ApiProperty({
type: PurchaseOrderItem,
isArray: true,
})
@Rule(RuleType.array())
items: PurchaseOrderItem[];
}
export class UpdatePurchaseOrderDTO extends CreatePurchaseOrderDTO {}

View File

@ -0,0 +1,36 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
import { SubscriptionStatus } from '../enums/base.enum';
// 订阅列表查询参数(分页与筛选)
export class QuerySubscriptionDTO {
// 当前页码(从 1 开始)
@ApiProperty({ example: 1, description: '页码' })
@Rule(RuleType.number().default(1))
current: number;
// 每页数量
@ApiProperty({ example: 10, description: '每页大小' })
@Rule(RuleType.number().default(10))
pageSize: number;
// 站点 ID可选
@ApiProperty({ description: '站点ID' })
@Rule(RuleType.string().allow(''))
siteId: string;
// 订阅状态筛选(可选),支持枚举值
@ApiProperty({ description: '订阅状态', enum: SubscriptionStatus })
@Rule(RuleType.string().valid(...Object.values(SubscriptionStatus)).allow(''))
status: SubscriptionStatus | '';
// 客户邮箱(模糊匹配,可选)
@ApiProperty({ description: '客户邮箱' })
@Rule(RuleType.string().allow(''))
customer_email: string;
// 关键字订阅ID、邮箱等模糊匹配可选
@ApiProperty({ description: '关键字订阅ID、邮箱等' })
@Rule(RuleType.string().allow(''))
keyword: string;
}

26
src/dto/user.dto.ts Normal file
View File

@ -0,0 +1,26 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
export class LoginResDTO {
@ApiProperty({ type: String })
token: string;
@ApiProperty({ type: Number })
userId: number;
@ApiProperty({ type: String })
username: string;
@ApiProperty({ type: String, isArray: true })
permissions: string[];
}
export class LoginDTO {
@ApiProperty({ type: String })
@Rule(RuleType.string())
username: string;
@ApiProperty({ type: String })
@Rule(RuleType.string())
password: string;
}

98
src/dto/wp_product.dto.ts Normal file
View File

@ -0,0 +1,98 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Variation } from '../entity/variation.entity';
import { WpProduct } from '../entity/wp_product.entity';
import { Rule, RuleType } from '@midwayjs/validate';
import { ProductStatus } from '../enums/base.enum';
export class VariationDTO extends Variation {}
export class WpProductDTO extends WpProduct {
@ApiProperty({ description: '变体列表', type: VariationDTO, isArray: true })
variations?: VariationDTO[];
}
export class UpdateVariationDTO {
@ApiProperty({ description: '产品名称' })
@Rule(RuleType.string())
name: string;
@ApiProperty({ description: 'SKU' })
@Rule(RuleType.string().allow(''))
sku: string;
@ApiProperty({ description: '常规价格', type: Number })
@Rule(RuleType.number())
regular_price: number; // 常规价格
@ApiProperty({ description: '销售价格', type: Number })
@Rule(RuleType.number())
sale_price: number; // 销售价格
@ApiProperty({ description: '是否促销中', type: Boolean })
@Rule(RuleType.boolean())
on_sale: boolean; // 是否促销中
}
export class UpdateWpProductDTO {
@ApiProperty({ description: '变体名称' })
@Rule(RuleType.string())
name: string;
@ApiProperty({ description: 'SKU' })
@Rule(RuleType.string().allow(''))
sku: string;
@ApiProperty({ description: '常规价格', type: Number })
@Rule(RuleType.number())
regular_price: number; // 常规价格
@ApiProperty({ description: '销售价格', type: Number })
@Rule(RuleType.number())
sale_price: number; // 销售价格
@ApiProperty({ description: '是否促销中', type: Boolean })
@Rule(RuleType.boolean())
on_sale: boolean; // 是否促销中
}
export class QueryWpProductDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number;
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number;
@ApiProperty({ example: 'ZYN', description: '产品名' })
@Rule(RuleType.string())
name?: string;
@ApiProperty({ example: '1', description: '站点ID' })
@Rule(RuleType.string())
siteId?: string;
@ApiProperty({ description: '产品状态', enum: ProductStatus })
@Rule(RuleType.string().valid(...Object.values(ProductStatus)))
status?: ProductStatus;
}
export class SetConstitutionDTO {
@ApiProperty({ type: Boolean })
@Rule(RuleType.boolean())
isProduct: boolean;
@ApiProperty({
description: '构成成分',
type: 'array',
items: {
type: 'object',
properties: {
sku: { type: 'string' },
quantity: { type: 'number' },
},
},
})
@Rule(RuleType.array())
constitution: { sku: string; quantity: number }[] | null;
}

13
src/entity/auth_code.ts Normal file
View File

@ -0,0 +1,13 @@
import { Entity, Column, PrimaryColumn } from 'typeorm';
@Entity('auth_code')
export class AuthCode {
@PrimaryColumn({ length: 255 })
device_id: string;
@Column({ length: 10 })
code: string;
@Column()
expires_at: Date;
}

View File

@ -0,0 +1,53 @@
import {
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Entity,
} from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger';
@Entity()
export class Category {
@ApiProperty({
example: '1',
description: '分类 ID',
type: 'number',
required: true,
})
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
example: '分类名称',
description: '分类名称',
type: 'string',
required: true,
})
@Column()
name: string;
@ApiProperty({
description: '唯一识别key',
type: 'string',
required: true,
})
@Column()
unique_key: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,13 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('customer')
export class Customer {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column({ default: 0})
rate: number;
}

View File

@ -0,0 +1,25 @@
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('customer_tag')
export class CustomerTag {
@PrimaryGeneratedColumn()
id: number;
@Column()
email: string;
@Column()
tag: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,14 @@
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity('device_whitelist')
export class DeviceWhitelist {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'device_id', unique: true })
deviceId: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}

View File

@ -0,0 +1,43 @@
import {
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Entity,
} from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger';
@Entity()
export class Flavors {
@ApiProperty()
@PrimaryGeneratedColumn()
id: number;
@ApiProperty()
@Column()
name: string;
@ApiProperty({
description: '唯一识别key',
type: 'string',
required: true,
})
@Column()
unique_key: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

290
src/entity/order.entity.ts Normal file
View File

@ -0,0 +1,290 @@
import { ApiProperty } from '@midwayjs/swagger';
import {
BeforeInsert,
BeforeUpdate,
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
OneToOne,
JoinColumn
} from 'typeorm';
import { ErpOrderStatus, OrderStatus } from '../enums/base.enum';
import { OrderAddress } from '../dto/order.dto';
import { Exclude, Expose } from 'class-transformer';
import { Shipment } from './shipment.entity';
@Entity('order')
@Exclude()
export class Order {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty({ type: OrderStatus })
@Column({ type: 'enum', enum: OrderStatus })
@Expose()
status: OrderStatus; // WooCommerce 订单 状态
@ApiProperty({ type: ErpOrderStatus })
@Column({
type: 'enum',
enum: ErpOrderStatus,
default: ErpOrderStatus.PENDING,
})
@Expose()
orderStatus: ErpOrderStatus; // 订单状态
@ApiProperty()
@Column({ name: 'shipment_id', nullable: true })
@Expose()
shipmentId: number;
@OneToOne(() => Shipment)
@JoinColumn({ name: 'shipment_id' })
shipment: Shipment;
@ApiProperty()
@Column()
@Expose()
currency: string;
@ApiProperty()
@Column()
@Expose()
currency_symbol: string;
@ApiProperty()
@Column({ default: false })
@Expose()
prices_include_tax: boolean;
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
date_created: Date;
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
date_modified: Date;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
discount_total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
discount_tax: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
shipping_total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
shipping_tax: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
cart_tax: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
total_tax: number;
@ApiProperty()
@Column({ default: 0 })
@Expose()
customer_id: number;
@ApiProperty()
@Column()
@Expose()
customer_email: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
order_key: string;
@ApiProperty({ type: OrderAddress })
@Column({ type: 'json', nullable: true })
@Expose()
billing: OrderAddress;
@ApiProperty({ type: OrderAddress })
@Column({ type: 'json', nullable: true })
@Expose()
shipping: OrderAddress;
@ApiProperty()
@Column({ default: '' })
@Expose()
payment_method: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
payment_method_title: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
transaction_id: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
customer_ip_address: string;
@ApiProperty()
@Column({
type: 'varchar',
length: 1024,
nullable: true,
})
@Expose()
customer_user_agent: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
created_via: string;
@ApiProperty()
@Column({
type: 'mediumtext', // 设置字段类型为 MEDIUMTEXT
nullable: true, // 可选:是否允许为 NULL
})
@Expose()
customer_note: string;
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
date_completed: Date;
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
date_paid: Date;
@ApiProperty()
@Column({ default: '' })
@Expose()
cart_hash: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
number: string;
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
meta_data: any[];
@ApiProperty()
@Column({ default: '' })
@Expose()
payment_url: string;
@ApiProperty()
@Column({ default: false })
@Expose()
is_editable: boolean;
@ApiProperty()
@Column({ default: false })
@Expose()
needs_payment: boolean;
@ApiProperty()
@Column({ default: false })
@Expose()
needs_processing: boolean;
@ApiProperty()
@Column({ default: '' })
@Expose()
device_type: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
source_type: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
utm_source: string;
@ApiProperty()
@Column({ default: false })
@Expose()
is_exchange: boolean;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 0, default: 0 })
@Expose()
exchange_frequency: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
// 在插入或更新前处理用户代理字符串
@BeforeInsert()
@BeforeUpdate()
truncateUserAgent() {
const maxLength = 1024; // 根据数据库限制的实际长度
if (
this.customer_user_agent &&
this.customer_user_agent.length > maxLength
) {
this.customer_user_agent = this.customer_user_agent.substring(
0,
maxLength
);
}
}
}

View File

@ -0,0 +1,86 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_coupon')
@Exclude()
export class OrderCoupon {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Column()
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty()
@Column()
@Expose()
externalOrderCouponId: string; // WooCommerce 订单coupon ID
@ApiProperty()
@Column()
@Expose()
code: string;
@ApiProperty()
@Column()
@Expose()
discount: string;
@ApiProperty()
@Column()
@Expose()
discount_tax: string;
@ApiProperty()
@Column()
@Expose()
discount_type: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
nominal_amount: number;
@ApiProperty()
@Column()
@Expose()
free_shipping: boolean;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,86 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_fee')
@Exclude()
export class OrderFee {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id?: number;
@ApiProperty()
@Column()
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
siteId: string;
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty()
@Column()
@Expose()
externalOrderFeeId: string; // WooCommerce 订单fee ID
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty()
@Column()
@Expose()
tax_class: string;
@ApiProperty()
@Column()
@Expose()
tax_status: string;
@ApiProperty()
@Column()
@Expose()
amount: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
total: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
total_tax: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt?: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt?: Date;
}

View File

@ -0,0 +1,151 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_item')
@Exclude()
export class OrderItem {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column()
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty()
@Column({ nullable: true })
@Expose()
externalOrderItemId: string; // WooCommerce 订单item ID
@ApiProperty()
@Column()
@Expose()
externalProductId: string; // WooCommerce 产品 ID
@ApiProperty()
@Column()
@Expose()
externalVariationId: string; // WooCommerce 变体 ID
@ApiProperty()
@Column()
@Expose()
quantity: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
subtotal: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
subtotal_tax: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
total_tax: number;
@ApiProperty()
@Column({ nullable: true })
@Expose()
tax_class?: string; // 税类(来自 line_items.tax_class
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
taxes?: any[]; // 税明细(来自 line_items.taxes数组
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
meta_data?: any[]; // 行项目元数据(包含订阅相关键值)
@ApiProperty()
@Column({ nullable: true })
@Expose()
sku?: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
global_unique_id?: string; // 全局唯一ID部分主题/插件会提供)
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
price: number;
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
image?: { id?: string | number; src?: string }; // 商品图片(对象,包含 id/src
@ApiProperty()
@Column({ nullable: true })
@Expose()
parent_name?: string; // 父商品名称(组合/捆绑时可能使用)
@ApiProperty()
@Column({ nullable: true })
@Expose()
bundled_by?: string; // 捆绑来源标识bundled_by
@ApiProperty()
@Column({ nullable: true })
@Expose()
bundled_item_title?: string; // 捆绑项标题
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
bundled_items?: any[]; // 捆绑项列表(数组)
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,82 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Order } from './order.entity';
@Entity('order_sale_original')
@Exclude()
export class OrderSaleOriginal {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id?: number;
@ApiProperty()
@ManyToOne(() => Order)
@JoinColumn({ name: 'order_id' })
@Column({ name: 'order_id' })
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column({ nullable: true })
@Expose()
externalOrderItemId: string; // WooCommerce 订单item ID
@ApiProperty()
@Column()
@Expose()
productId: number;
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty({ description: 'sku', type: 'string' })
@Expose()
@Column()
sku: string;
@ApiProperty()
@Column()
@Expose()
quantity: number;
@ApiProperty()
@Column({ default: false })
@Expose()
isPackage: boolean;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
})
@CreateDateColumn()
@Expose()
createdAt?: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
})
@UpdateDateColumn()
@Expose()
updatedAt?: Date;
}

View File

@ -0,0 +1,113 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Order } from './order.entity';
@Entity('order_item_original')
@Exclude()
export class OrderItemOriginal {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@ManyToOne(() => Order)
@JoinColumn({ name: 'order_id' })
@Column({ name: 'order_id' })
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty()
@Column({ nullable: true })
@Expose()
externalOrderItemId: string; // WooCommerce 订单item ID
@ApiProperty()
@Column()
@Expose()
externalProductId: string; // WooCommerce 产品 ID
@ApiProperty()
@Column()
@Expose()
externalVariationId: string; // WooCommerce 变体 ID
@ApiProperty()
@Column()
@Expose()
quantity: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
subtotal: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
subtotal_tax: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
total_tax: number;
@ApiProperty()
@Column({ nullable: true })
@Expose()
sku?: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
price: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,49 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_note')
@Exclude()
export class OrderNote {
@PrimaryGeneratedColumn()
id: number;
@ApiProperty()
@Column()
@Expose()
userId: number;
@ApiProperty()
@Column()
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
content: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,86 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_refund')
@Exclude()
export class OrderRefund {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Column()
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty()
@Column()
@Expose()
externalRefundId: string; // WooCommerce refund ID
@ApiProperty()
@Column({ type: 'timestamp' })
@Expose()
date_created: Date;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
amount: number;
@ApiProperty()
@Column()
@Expose()
reason: string;
@ApiProperty()
@Column()
@Expose()
refunded_by: number;
@ApiProperty()
@Column()
@Expose()
refunded_payment: boolean;
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
meta_data: [];
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,111 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_refund_item')
@Exclude()
export class OrderRefundItem {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id?: number;
@ApiProperty()
@Column()
@Expose()
refundId: number; // 订单 refund ID
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column()
@Expose()
externalRefundId: string; // WooCommerce refund ID
@ApiProperty()
@Column()
@Expose()
externalRefundItemId: string; // WooCommerce refund item ID
@ApiProperty()
@Column()
@Expose()
externalProductId: string; // WooCommerce 产品 ID
@ApiProperty()
@Column()
@Expose()
externalVariationId: string; // WooCommerce 变体 ID
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty()
@Column()
@Expose()
quantity: number;
@ApiProperty()
@Column()
@Expose()
tax_class: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
subtotal: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
subtotal_tax: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
total_tax: number;
@ApiProperty()
@Column({ nullable: true })
@Expose()
sku?: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
price: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt?: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt?: Date;
}

View File

@ -0,0 +1,117 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
BeforeInsert,
BeforeUpdate,
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_sale')
@Exclude()
export class OrderSale {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id?: number;
@ApiProperty()
@Column()
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column({ nullable: true })
@Expose()
externalOrderItemId: string; // WooCommerce 订单item ID
@ApiProperty()
@Column()
@Expose()
productId: number;
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty({ description: 'sku', type: 'string' })
@Expose()
@Column()
sku: string;
@ApiProperty()
@Column()
@Expose()
quantity: number;
@ApiProperty()
@Column({ default: false })
@Expose()
isPackage: boolean;
@ApiProperty()
@Column({ default: false })
@Expose()
isYoone: boolean;
@ApiProperty()
@Column({ default: false })
@Expose()
isZex: boolean;
@ApiProperty({ nullable: true })
@Column({ type: 'int', nullable: true })
@Expose()
size: number | null;
@ApiProperty()
@Column({ default: false })
@Expose()
isYooneNew: boolean;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
})
@CreateDateColumn()
@Expose()
createdAt?: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
})
@UpdateDateColumn()
@Expose()
updatedAt?: Date;
// === 自动计算逻辑 ===
@BeforeInsert()
@BeforeUpdate()
setFlags() {
if (!this.name) return;
const lower = this.name.toLowerCase();
this.isYoone = lower.includes('yoone');
this.isZex = lower.includes('zex');
this.isYooneNew = this.isYoone && lower.includes('new');
let size: number | null = null;
const sizes = [3, 6, 9, 12, 15, 18];
for (const s of sizes) {
if (lower.includes(s.toString())) {
size = s;
break;
}
}
this.size = size;
}
}

View File

@ -0,0 +1,21 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('order_shipment')
export class OrderShipment {
@ApiProperty()
@PrimaryGeneratedColumn()
id: number;
@ApiProperty()
@Column()
order_id: number;
@ApiProperty()
@Column()
shipment_id: number;
@ApiProperty()
@Column()
stockPointId: number;
}

View File

@ -0,0 +1,81 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_shipping')
@Exclude()
export class OrderShipping {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Column()
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
siteId: string;
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty()
@Column()
@Expose()
externalOrderShippingId: string; // WooCommerce 订单快递 ID
@ApiProperty()
@Column()
@Expose()
method_title: string;
@ApiProperty()
@Column()
@Expose()
method_id: string;
@ApiProperty()
@Column()
@Expose()
instance_id: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
total_tax: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,73 @@
import {
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Entity,
} from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger';
@Entity()
export class Product {
@ApiProperty({
example: '1',
description: 'ID',
type: 'number',
required: true,
})
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
example: 'ZYN 6MG WINTERGREEN',
description: '产品名称',
type: 'string',
required: true,
})
@Column()
name: string;
@ApiProperty()
@Column({ default: ''})
nameCn: string;
@ApiProperty({ example: '产品描述', description: '产品描述', type: 'string' })
@Column({ nullable: true })
description?: string;
@ApiProperty({ example: '1', description: '分类 ID', type: 'number' })
@Column()
categoryId: number;
@ApiProperty()
@Column()
flavorsId: number;
@ApiProperty()
@Column()
strengthId: number;
@ApiProperty()
@Column()
humidity: string;
@ApiProperty({ description: 'sku', type: 'string' })
@Column({ nullable: true })
sku?: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,61 @@
// src/entity/PurchaseOrder.ts
import { ApiProperty } from '@midwayjs/swagger';
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { PurchaseOrderStatus } from '../enums/base.enum';
@Entity('purchase_order')
export class PurchaseOrder {
@ApiProperty({ type: 'number' })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ type: Number })
@Column()
stockPointId: number;
@ApiProperty({ type: 'string' })
@Column()
orderNumber: string;
@ApiProperty({ type: PurchaseOrderStatus })
@Column({
type: 'enum',
enum: PurchaseOrderStatus,
default: 'draft',
})
status: PurchaseOrderStatus;
@ApiProperty()
@Column({ nullable: true, type: 'text' })
note: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '预计时间',
required: true,
})
@Column({ type: 'timestamp', nullable: true })
expectedArrivalTime: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,30 @@
// src/entity/PurchaseOrderItem.ts
import { ApiProperty } from '@midwayjs/swagger';
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('purchase_order_item')
export class PurchaseOrderItem {
@ApiProperty({ type: Number })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ type: String })
@Column()
productSku: string;
@ApiProperty({ type: String })
@Column()
productName: string;
@ApiProperty({ type: Number })
@Column()
quantity: number;
@ApiProperty({ type: Number })
@Column({ type: 'decimal', precision: 10, scale: 2 })
price: number;
@ApiProperty({ type: Number })
@Column()
purchaseOrderId: number;
}

View File

@ -0,0 +1,51 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity()
@Exclude()
export class Service {
@ApiProperty()
@PrimaryColumn()
@Expose()
id?: string;
@ApiProperty()
@Column()
@Expose()
carrier_name: string;
@ApiProperty()
@Column()
@Expose()
service_name: string;
@ApiProperty()
@Column({ default: false })
@Expose()
isActive: boolean;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt?: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt?: Date;
}

View File

@ -0,0 +1,140 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
ManyToOne,
OneToOne,
JoinColumn
} from 'typeorm';
import { StockPoint } from './stock_point.entity';
import { Order } from './order.entity';
@Entity('shipment')
@Exclude()
export class Shipment {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Column({ nullable: true })
order_id: number;
@ApiProperty()
@OneToOne(() => Order)
@JoinColumn({ name: 'order_id' })
order: Order;
@ApiProperty()
@Column({ name: 'stock_point_id' })
@Expose()
stockPointId: string;
@ManyToOne(() => StockPoint)
@JoinColumn({ name: 'stock_point_id' })
stockPoint: StockPoint;
@Column({ nullable: false, default: 0})
@Expose()
fee: number;
@ApiProperty()
@Column({ nullable: true })
@Expose()
tracking_id: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
tracking_provider?: string;
@ApiProperty()
@Column()
@Expose()
unique_id: string;
@Column({ nullable: true })
@Expose()
state?: string;
@Column({ nullable: false, default: false })
@Expose()
finished: boolean;
@Column({ nullable: true })
@Expose()
type?: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
transaction_number?: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
primary_tracking_number?: string;
@ApiProperty({ type: [String] })
@Column({ type: 'json', nullable: true })
@Expose()
tracking_numbers?: string[];
@ApiProperty()
@Column({ nullable: true })
@Expose()
tracking_url?: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
return_tracking_number?: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
bol_number?: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
pickup_confirmation_number?: string;
@ApiProperty()
@Column({ nullable: true })
@Expose()
customs_invoice_url?: string;
@ApiProperty({ type: Object })
@Column({ type: 'json', nullable: true })
@Expose()
rate?: Record<string, any>;
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
labels?: Array<any>;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt?: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt?: Date;
}

View File

@ -0,0 +1,67 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Shipment } from './shipment.entity';
import { Order } from './order.entity';
@Entity('shipment_item')
@Exclude()
export class ShipmentItem {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Expose()
@ManyToOne(() => Shipment)
@JoinColumn({ name: 'shipment_id' })
shipment_id: number;
@ApiProperty()
@Expose()
@ManyToOne(() => Order)
@JoinColumn({ name: 'order_id' })
order_id: number;
@ApiProperty()
@Column({ nullable: true })
@Expose()
name: string;
@ApiProperty({ description: 'sku', type: 'string' })
@Expose()
@Column()
sku: string;
@ApiProperty()
@Column()
@Expose()
quantity: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,67 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Address } from '../dto/freightcom.dto';
@Entity()
@Exclude()
export class ShippingAddress {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id?: number;
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty()
@Column()
@Expose()
stockPointId: number;
@ApiProperty({ type: Address })
@Column({ type: 'json', nullable: true })
@Expose()
address: Address;
@ApiProperty()
@Column()
@Expose()
phone_number: string;
@ApiProperty()
@Column()
@Expose()
phone_number_extension: string;
@ApiProperty()
@Column()
@Expose()
phone_number_country: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt?: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt?: Date;
}

28
src/entity/site.entity.ts Normal file
View File

@ -0,0 +1,28 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('site')
export class Site {
@PrimaryGeneratedColumn({ type: 'int' })
id: number;
@Column({ type: 'varchar', length: 255, nullable: true })
apiUrl: string;
@Column({ type: 'varchar', length: 255, nullable: true })
consumerKey: string;
@Column({ type: 'varchar', length: 255, nullable: true })
consumerSecret: string;
@Column({ type: 'varchar', length: 255, unique: true })
siteName: string;
@Column({ type: 'varchar', length: 32, default: 'woocommerce' })
type: string; // 平台类型woocommerce | shopyy
@Column({ type: 'varchar', length: 64, nullable: true })
skuPrefix: string;
@Column({ type: 'tinyint', default: 0 })
isDisabled: number;
}

View File

@ -0,0 +1,44 @@
// src/entity/Stock.ts
import { ApiProperty } from '@midwayjs/swagger';
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('stock')
export class Stock {
@ApiProperty({ type: Number })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ type: Number })
@Column()
stockPointId: number;
@ApiProperty({ type: String })
@Column()
productSku: string;
@ApiProperty({ type: Number })
@Column()
quantity: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,75 @@
import { ApiProperty } from '@midwayjs/swagger';
import {
BaseEntity,
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { Shipment } from './shipment.entity';
@Entity('stock_point')
export class StockPoint extends BaseEntity {
@ApiProperty({ type: 'number' })
@PrimaryGeneratedColumn()
id: number;
@OneToMany(() => Shipment, shipment => shipment.stockPoint)
shipments: Shipment[];
@ApiProperty({ type: 'string' })
@Column()
name: string;
@ApiProperty({ type: 'string' })
@Column()
location: string;
@ApiProperty({ type: 'string' })
@Column()
contactPerson: string;
@ApiProperty({ type: 'string' })
@Column()
contactPhone: string;
@ApiProperty()
@Column({ default: false })
ignore: boolean;
@ApiProperty()
@Column({ default: false })
inCanada: boolean;
@ApiProperty()
@Column({ default: false })
isB: boolean;
@Column({ default: 'uniuni' })
upStreamName: string;
@Column()
upStreamStockPointId: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt: Date; // 软删除时间
}

View File

@ -0,0 +1,48 @@
// src/entity/StockRecord.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
} from 'typeorm';
import { StockRecordOperationType } from '../enums/base.enum';
import { ApiProperty } from '@midwayjs/swagger';
@Entity('stock_record')
export class StockRecord {
@ApiProperty({ type: Number })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ type: Number })
@Column()
stockPointId: number;
@ApiProperty({ type: String })
@Column()
productSku: string;
@ApiProperty({ type: StockRecordOperationType })
@Column({ type: 'enum', enum: StockRecordOperationType })
operationType: StockRecordOperationType;
@ApiProperty({ type: Number })
@Column()
quantityChange: number;
@ApiProperty()
@Column()
operatorId: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({ type: String })
@Column({ nullable: true })
note: string;
}

View File

@ -0,0 +1,43 @@
import {
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Entity,
} from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger';
@Entity()
export class Strength {
@ApiProperty()
@PrimaryGeneratedColumn()
id: number;
@ApiProperty()
@Column()
name: string;
@ApiProperty({
description: '唯一识别key',
type: 'string',
required: true,
})
@Column()
unique_key: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,128 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { SubscriptionStatus } from '../enums/base.enum';
@Entity('subscription')
@Exclude()
export class Subscription {
// 本地主键,自增 ID
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
// 站点唯一标识,用于区分不同来源站点
@ApiProperty({ description: '来源站点唯一标识' })
@Column()
@Expose()
siteId: string;
// WooCommerce 订阅的原始 ID字符串化用于幂等更新
@ApiProperty({ description: 'WooCommerce 订阅 ID' })
@Column()
@Expose()
externalSubscriptionId: string;
// 订阅状态active/cancelled/on-hold 等)
@ApiProperty({ type: SubscriptionStatus })
@Column({ type: 'enum', enum: SubscriptionStatus })
@Expose()
status: SubscriptionStatus;
// 货币代码,例如 USD/CAD
@ApiProperty()
@Column({ default: '' })
@Expose()
currency: string;
// 总金额,保留两位小数
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, default: 0 })
@Expose()
total: number;
// 计费周期day/week/month/year
@ApiProperty({ description: '计费周期 e.g. day/week/month/year' })
@Column({ default: '' })
@Expose()
billing_period: string;
// 计费周期间隔(例如 1/3/12
@ApiProperty({ description: '计费周期间隔 e.g. 1/3/12' })
@Column({ type: 'int', default: 0 })
@Expose()
billing_interval: number;
// 客户 IDWooCommerce 用户 ID
@ApiProperty()
@Column({ type: 'int', default: 0 })
@Expose()
customer_id: number;
// 客户邮箱(从 billing.email 或 customer_email 提取)
@ApiProperty()
@Column({ default: '' })
@Expose()
customer_email: string;
// 父订单/订阅 ID如有
@ApiProperty({ description: '父订单/父订阅ID如有' })
@Column({ type: 'int', default: 0 })
@Expose()
parent_id: number;
// 订阅开始时间
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
start_date: Date;
// 试用结束时间
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
trial_end: Date;
// 下次支付时间
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
next_payment_date: Date;
// 订阅结束时间
@ApiProperty()
@Column({ type: 'timestamp', nullable: true })
@Expose()
end_date: Date;
// 商品项(订阅行项目)
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
line_items: any[];
// 额外元数据(键值对)
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
meta_data: any[];
// 创建时间(数据库自动生成)
@ApiProperty({ example: '2022-12-12 11:11:11', description: '创建时间', required: true })
@CreateDateColumn()
@Expose()
createdAt: Date;
// 更新时间(数据库自动生成)
@ApiProperty({ example: '2022-12-12 11:11:11', description: '更新时间', required: true })
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -0,0 +1,67 @@
import { ApiProperty } from '@midwayjs/swagger';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity()
export class Transfer {
@ApiProperty()
@PrimaryGeneratedColumn()
id?: number;
@ApiProperty({ type: 'string' })
@Column()
orderNumber: string;
@ApiProperty({ type: Number })
@Column()
sourceStockPointId: number;
@ApiProperty({ type: Number })
@Column()
destStockPointId: number;
@ApiProperty({})
@Column({ default: false })
isCancel: boolean;
@ApiProperty({})
@Column({ default: false })
isArrived: boolean;
@ApiProperty({})
@Column({ default: false })
isLost: boolean;
@ApiProperty()
@Column({ nullable: true, type: 'text' })
note: string;
@ApiProperty()
@Column({ nullable: true, type: Date })
sendAt: Date;
@ApiProperty()
@Column({ nullable: true, type: Date })
arriveAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,25 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('transfer_item')
export class TransferItem {
@ApiProperty({ type: Number })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ type: String })
@Column()
productSku: string;
@ApiProperty({ type: String })
@Column()
productName: string;
@ApiProperty({ type: Number })
@Column()
quantity: number;
@ApiProperty({ type: Number })
@Column()
transferId: number;
}

31
src/entity/user.entity.ts Normal file
View File

@ -0,0 +1,31 @@
// src/entity/user.entity.ts
import { Exclude } from 'class-transformer';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('user')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
username: string;
@Column()
@Exclude()
password: string;
// @Column() // 默认角色为管理员
// roleId: number; // 角色 (如admin, editor, viewer)
@Column({ type: 'simple-array', nullable: true })
permissions: string[]; // 自定义权限 (如:['user:add', 'user:edit'])
@Column({ default: false })
isSuper: boolean; // 超级管理员
@Column({ default: false })
isAdmin: boolean; // 管理员
@Column({ default: true })
isActive: boolean; // 用户是否启用
}

View File

@ -0,0 +1,119 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
Unique,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger';
@Entity('variation')
@Unique(['siteId', 'externalProductId', 'externalVariationId']) // 确保变体的唯一性
export class Variation {
@ApiProperty({
example: '1',
description: 'ID',
type: 'number',
required: true,
})
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
example: '1',
description: 'wp网站ID',
type: 'string',
required: true,
})
@Column()
siteId: string; // 来源站点唯一标识
@ApiProperty({
example: '1',
description: 'wp产品ID',
type: 'string',
required: true,
})
@Column()
externalProductId: string; // WooCommerce 产品 ID
@ApiProperty({
example: '1',
description: 'wp变体ID',
type: 'string',
required: true,
})
@Column()
externalVariationId: string; // WooCommerce 变体 ID
@ApiProperty({
example: '1',
description: '对应WP产品表的ID',
type: 'number',
required: true,
})
@Column()
productId: number; // 对应WP产品表的 ID
@ApiProperty({ description: 'sku', type: 'string' })
@Column({ nullable: true })
sku?: string; // sku 编码
@ApiProperty({
description: '变体名称',
type: 'string',
required: true,
})
@Column()
name: string;
@ApiProperty({ description: '常规价格', type: Number })
@Column('decimal', { precision: 10, scale: 2, nullable: true })
regular_price: number; // 常规价格
@ApiProperty({ description: '销售价格', type: Number })
@Column('decimal', { precision: 10, scale: 2, nullable: true })
sale_price: number; // 销售价格
@ApiProperty({ description: '是否促销中', type: Boolean })
@Column({ nullable: true, type: Boolean })
on_sale: boolean; // 是否促销中
@ApiProperty({ description: '是否删除', type: Boolean })
@Column({ nullable: true, type: Boolean , default: false })
on_delete: boolean; // 是否删除
@Column({ type: 'json', nullable: true })
attributes: Record<string, any>; // 变体的属性
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
@ApiProperty({
description: '变体构成成分',
type: 'array',
items: {
type: 'object',
properties: {
sku: { type: 'string' },
quantity: { type: 'number' },
},
},
})
@Column('json', { nullable: true, comment: '变体构成成分' })
constitution: { sku: string; quantity: number }[] | null;
}

View File

@ -0,0 +1,125 @@
import {
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Unique,
Entity,
} from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger';
import { ProductStatus, ProductStockStatus, ProductType } from '../enums/base.enum';
@Entity('wp_product')
@Unique(['siteId', 'externalProductId']) // 确保产品的唯一性
export class WpProduct {
@ApiProperty({
example: '1',
description: 'ID',
type: 'number',
required: true,
})
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
example: '1',
description: 'wp网站ID',
type: 'string',
required: true,
})
@Column()
siteId: string;
@ApiProperty({
example: '1',
description: 'wp产品ID',
type: 'string',
required: true,
})
@Column()
externalProductId: string;
@ApiProperty({ description: 'sku', type: 'string' })
@Column({ nullable: true })
sku?: string;
@ApiProperty({
example: 'ZYN 6MG WINTERGREEN',
description: '产品名称',
type: 'string',
required: true,
})
@Column()
name: string;
@ApiProperty({ description: '产品状态', enum: ProductStatus })
@Column({ type: 'enum', enum: ProductStatus })
status: ProductStatus;
@ApiProperty({ description: '上下架状态', enum: ProductStockStatus })
@Column({
name: 'stock_status',
type: 'enum',
enum: ProductStockStatus,
default: ProductStockStatus.INSTOCK
})
stockStatus: ProductStockStatus;
@ApiProperty({ description: '常规价格', type: Number })
@Column('decimal', { precision: 10, scale: 2, nullable: true })
regular_price: number; // 常规价格
@ApiProperty({ description: '销售价格', type: Number })
@Column('decimal', { precision: 10, scale: 2, nullable: true })
sale_price: number; // 销售价格
@ApiProperty({ description: '是否促销中', type: Boolean })
@Column({ nullable: true, type: Boolean })
on_sale: boolean; // 是否促销中
@ApiProperty({ description: '是否删除', type: Boolean })
@Column({ nullable: true, type: Boolean , default: false })
on_delete: boolean; // 是否删除
@ApiProperty({
description: '产品类型',
enum: ProductType,
})
@Column({ type: 'enum', enum: ProductType })
type: ProductType;
@Column({ type: 'json', nullable: true })
metadata: Record<string, any>; // 产品的其他扩展字段
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
@ApiProperty({
description: '产品构成成分',
type: 'array',
items: {
type: 'object',
properties: {
sku: { type: 'string' },
quantity: { type: 'number' },
},
},
})
@Column('json', { nullable: true, comment: '产品构成成分' })
constitution: { sku: string; quantity: number }[] | null;
}

87
src/enums/base.enum.ts Normal file
View File

@ -0,0 +1,87 @@
export enum ProductStatus {
PUBLISH = 'publish', // 已发布
DRAFT = 'draft', // 草稿
PENDING = 'pending', // 待审核
PRIVATE = 'private', // 私有
TRASH = 'trash', // 回收站
AUTO_DRAFT = 'auto-draft', // 自动草稿
FUTURE = 'future', // 定时发布
INHERIT = 'inherit', // 继承状态
}
export enum ProductStockStatus {
INSTOCK = 'instock',
OUT_OF_STOCK = 'outofstock',
ON_BACK_ORDER = 'onbackorder',
}
export enum ProductType {
SIMPLE = 'simple',
VARIABLE = 'variable',
WOOSB = 'woosb',
}
export enum PurchaseOrderStatus {
DRAFT = 'draft',
SUBMITTED = 'submitted',
RECEIVED = 'received',
}
export enum StockRecordOperationType {
IN = 'in',
OUT = 'out',
}
// Order status. Options: pending, processing, on-hold, completed, cancelled, refunded, failed and trash. Default is pending.
// 原始订单状态
export enum OrderStatus {
PENDING = 'pending', // default // 待付款
PROCESSING = 'processing', // 正在处理
ON_HOLD = 'on-hold', // 保留
COMPLETED = 'completed', // 已完成
CANCEL = 'cancelled', // 已取消
REFUNDED = 'refunded', // 已退款
FAILED = 'failed', // 失败订单
DRAFT = 'draft', // 草稿
AUTO_DRAFT = 'auto-draft', // 自动草稿
// TRASH = 'trash',
// refund 也就是退款相关的状态
RETURN_REQUESTED = 'return-requested', // 已申请退款
RETURN_APPROVED = 'return-approved', // 退款申请已通过
RETURN_CANCELLED = 'return-cancelled', // 已取消退款
}
export enum ErpOrderStatus {
PENDING = 'pending', // 待确认
PROCESSING = 'processing', //待发货
COMPLETED = 'completed', //已完成
CANCEL = 'cancelled', //已取消
REFUNDED = 'refunded', //已退款
FAILED = 'failed', //失败
AFTER_SALE_PROCESSING = 'after_sale_pending', // 售后处理中
PENDING_RESHIPMENT = 'pending_reshipment', // 待补发
PENDING_REFUND = 'pending_refund', // 待退款
RETURN_REQUESTED = 'return-requested', // 已申请退款
RETURN_APPROVED = 'return-approved', // 退款申请已通过
RETURN_CANCELLED = 'return-cancelled', // 已取消退款
}
export enum ShipmentType {
CANADAPOST = 'canadapost',
FREIGHTCOM = 'freightcom',
}
export enum staticValue {
// 万能验证码
STATIC_CAPTCHA = 'yoone2025!@YOONE0923'
}
// WooCommerce Subscription status
// Reference: https://woocommerce.com/document/subscriptions/statuses/
export enum SubscriptionStatus {
ACTIVE = 'active', // 活跃
PENDING = 'pending', // 待处理/待激活
ON_HOLD = 'on-hold', // 暂停
CANCELLED = 'cancelled', // 已取消
EXPIRED = 'expired', // 已过期
PENDING_CANCELLATION = 'pending-cancel', // 待取消
}

View File

@ -0,0 +1,13 @@
import { Catch } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
@Catch()
export class DefaultErrorFilter {
async catch(err: Error, ctx: Context) {
// 所有的未分类错误会到这里
return {
success: false,
message: err.message,
};
}
}

View File

@ -0,0 +1,10 @@
import { Catch, httpError, MidwayHttpError } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
@Catch(httpError.NotFoundError)
export class NotFoundFilter {
async catch(err: MidwayHttpError, ctx: Context) {
// 404 错误会到这里
ctx.redirect('/404.html');
}
}

View File

@ -1,33 +0,0 @@
import FingerprintJS from '@fingerprintjs/fingerprintjs';
import { useEffect, useState } from 'react';
/**
* Hook: 获取设备指纹(visitorId)
*/
export function useDeviceFingerprint() {
const [fingerprint, setFingerprint] = useState<string | null>(null);
useEffect(() => {
let isMounted = true;
async function loadFingerprint() {
try {
const fp = await FingerprintJS.load();
const result = await fp.get();
if (isMounted) {
setFingerprint(result.visitorId);
}
} catch (err) {
console.error('获取设备指纹失败:', err);
}
}
loadFingerprint();
return () => {
isMounted = false;
};
}, []);
return fingerprint; // 初始为 null,加载后返回指纹 ID
}

Some files were not shown because too many files have changed in this diff Show More