refactor(实体): 重构产品相关实体及字典系统

- 将分类、口味、规格实体重构为字典系统
- 新增dict和dict_item实体实现通用字典管理
- 修改product实体字段从categoryId改为brandId
- 修复order_coupon和order_refund_item实体文件名拼写错误
- 更新typeorm配置和种子数据初始化逻辑
- 调整相关DTO和控制器接口适配新字典系统
- 更新package.json依赖版本和脚本
This commit is contained in:
tikkhun 2025-11-27 15:32:45 +08:00
parent 3c1da145d3
commit d9800f341f
22 changed files with 2224 additions and 531 deletions

1118
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,14 +17,17 @@
"@midwayjs/typeorm": "^3.20.0", "@midwayjs/typeorm": "^3.20.0",
"@midwayjs/validate": "^3.20.2", "@midwayjs/validate": "^3.20.2",
"@woocommerce/woocommerce-rest-api": "^1.0.2", "@woocommerce/woocommerce-rest-api": "^1.0.2",
"axios": "^1.7.9", "axios": "^1.13.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"mysql2": "^3.11.5", "mysql2": "^3.11.5",
"nodemailer": "^7.0.5", "nodemailer": "^7.0.5",
"npm-check-updates": "^19.1.2",
"swagger-ui-dist": "^5.18.2", "swagger-ui-dist": "^5.18.2",
"typeorm": "^0.3.20", "typeorm": "^0.3.27",
"typeorm-extension": "^3.7.2",
"xlsx": "^0.18.5",
"xml2js": "^0.6.2" "xml2js": "^0.6.2"
}, },
"engines": { "engines": {
@ -36,10 +39,13 @@
"dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js", "dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js",
"test": "cross-env NODE_ENV=unittest jest", "test": "cross-env NODE_ENV=unittest jest",
"cov": "jest --coverage", "cov": "jest --coverage",
"lint": "mwts check", "lint": "mwtsc check",
"lint:fix": "mwts fix", "lint:fix": "mwtsc fix",
"ci": "npm run cov", "ci": "npm run cov",
"build": "mwtsc --cleanOutDir" "build": "mwtsc --cleanOutDir",
"seed": "ts-node src/db/seed/index.ts",
"seed:run": "ts-node ./node_modules/typeorm-extension/bin/cli.cjs seed:run -d src/db/datasource.ts",
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -51,6 +57,7 @@
"@midwayjs/mock": "^3.20.11", "@midwayjs/mock": "^3.20.11",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"mwtsc": "^1.15.2", "mwtsc": "^1.15.2",
"tsx": "^4.20.6",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }

View File

@ -65,12 +65,18 @@ importers:
nodemailer: nodemailer:
specifier: ^7.0.5 specifier: ^7.0.5
version: 7.0.10 version: 7.0.10
npm-check-updates:
specifier: ^19.1.2
version: 19.1.2
swagger-ui-dist: swagger-ui-dist:
specifier: ^5.18.2 specifier: ^5.18.2
version: 5.30.2 version: 5.30.2
typeorm: typeorm:
specifier: ^0.3.20 specifier: ^0.3.20
version: 0.3.27(mysql2@3.15.3)(reflect-metadata@0.2.2) version: 0.3.27(mysql2@3.15.3)(reflect-metadata@0.2.2)
xlsx:
specifier: ^0.18.5
version: 0.18.5
xml2js: xml2js:
specifier: ^0.6.2 specifier: ^0.6.2
version: 0.6.2 version: 0.6.2
@ -84,6 +90,9 @@ importers:
mwtsc: mwtsc:
specifier: ^1.15.2 specifier: ^1.15.2
version: 1.15.2 version: 1.15.2
tsx:
specifier: ^4.20.6
version: 4.20.6
typescript: typescript:
specifier: ^5.9.3 specifier: ^5.9.3
version: 5.9.3 version: 5.9.3
@ -97,6 +106,162 @@ packages:
'@epic-web/invariant@1.0.0': '@epic-web/invariant@1.0.0':
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.25.12':
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.25.12':
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.25.12':
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.25.12':
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.25.12':
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.25.12':
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.25.12':
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.25.12':
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.25.12':
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.25.12':
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.25.12':
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.25.12':
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.25.12':
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.25.12':
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.25.12':
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.25.12':
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.25.12':
resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.25.12':
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.25.12':
resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.25.12':
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.25.12':
resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.25.12':
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.25.12':
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.25.12':
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.25.12':
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@hapi/bourne@3.0.0': '@hapi/bourne@3.0.0':
resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
@ -314,6 +479,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
adler-32@1.3.1:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -415,6 +584,10 @@ packages:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
cfb@1.2.2:
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
engines: {node: '>=0.8'}
chokidar@3.6.0: chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
@ -446,6 +619,10 @@ packages:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
color-convert@2.0.1: color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
@ -488,6 +665,11 @@ packages:
core-util-is@1.0.3: core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
hasBin: true
create-hash@1.2.0: create-hash@1.2.0:
resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
@ -606,6 +788,11 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
esbuild@0.25.12:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
hasBin: true
escalade@3.2.0: escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -651,6 +838,10 @@ packages:
formidable@2.1.5: formidable@2.1.5:
resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==} resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==}
frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'}
fresh@0.5.2: fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -961,6 +1152,11 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
npm-check-updates@19.1.2:
resolution: {integrity: sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==}
engines: {node: '>=20.0.0', npm: '>=8.12.1'}
hasBin: true
oauth-1.0a@2.2.6: oauth-1.0a@2.2.6:
resolution: {integrity: sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==} resolution: {integrity: sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==}
@ -1160,6 +1356,10 @@ packages:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
ssf@0.11.2:
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
engines: {node: '>=0.8'}
statuses@1.5.0: statuses@1.5.0:
resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -1228,6 +1428,11 @@ packages:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
engines: {node: '>=0.6.x'} engines: {node: '>=0.6.x'}
tsx@4.20.6:
resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
engines: {node: '>=18.0.0'}
hasBin: true
type-is@1.6.18: type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -1327,6 +1532,14 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
hasBin: true hasBin: true
wmf@1.0.2:
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
engines: {node: '>=0.8'}
word@0.3.0:
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
engines: {node: '>=0.8'}
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1338,6 +1551,11 @@ packages:
wrappy@1.0.2: wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
xlsx@0.18.5:
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
engines: {node: '>=0.8'}
hasBin: true
xml2js@0.6.2: xml2js@0.6.2:
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
@ -1373,6 +1591,84 @@ snapshots:
'@epic-web/invariant@1.0.0': {} '@epic-web/invariant@1.0.0': {}
'@esbuild/aix-ppc64@0.25.12':
optional: true
'@esbuild/android-arm64@0.25.12':
optional: true
'@esbuild/android-arm@0.25.12':
optional: true
'@esbuild/android-x64@0.25.12':
optional: true
'@esbuild/darwin-arm64@0.25.12':
optional: true
'@esbuild/darwin-x64@0.25.12':
optional: true
'@esbuild/freebsd-arm64@0.25.12':
optional: true
'@esbuild/freebsd-x64@0.25.12':
optional: true
'@esbuild/linux-arm64@0.25.12':
optional: true
'@esbuild/linux-arm@0.25.12':
optional: true
'@esbuild/linux-ia32@0.25.12':
optional: true
'@esbuild/linux-loong64@0.25.12':
optional: true
'@esbuild/linux-mips64el@0.25.12':
optional: true
'@esbuild/linux-ppc64@0.25.12':
optional: true
'@esbuild/linux-riscv64@0.25.12':
optional: true
'@esbuild/linux-s390x@0.25.12':
optional: true
'@esbuild/linux-x64@0.25.12':
optional: true
'@esbuild/netbsd-arm64@0.25.12':
optional: true
'@esbuild/netbsd-x64@0.25.12':
optional: true
'@esbuild/openbsd-arm64@0.25.12':
optional: true
'@esbuild/openbsd-x64@0.25.12':
optional: true
'@esbuild/openharmony-arm64@0.25.12':
optional: true
'@esbuild/sunos-x64@0.25.12':
optional: true
'@esbuild/win32-arm64@0.25.12':
optional: true
'@esbuild/win32-ia32@0.25.12':
optional: true
'@esbuild/win32-x64@0.25.12':
optional: true
'@hapi/bourne@3.0.0': {} '@hapi/bourne@3.0.0': {}
'@hapi/hoek@9.3.0': {} '@hapi/hoek@9.3.0': {}
@ -1645,6 +1941,8 @@ snapshots:
mime-types: 2.1.35 mime-types: 2.1.35
negotiator: 0.6.3 negotiator: 0.6.3
adler-32@1.3.1: {}
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
ansi-regex@6.2.2: {} ansi-regex@6.2.2: {}
@ -1735,6 +2033,11 @@ snapshots:
call-bind-apply-helpers: 1.0.2 call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
cfb@1.2.2:
dependencies:
adler-32: 1.3.1
crc-32: 1.2.2
chokidar@3.6.0: chokidar@3.6.0:
dependencies: dependencies:
anymatch: 3.1.3 anymatch: 3.1.3
@ -1779,6 +2082,8 @@ snapshots:
co@4.6.0: {} co@4.6.0: {}
codepage@1.15.0: {}
color-convert@2.0.1: color-convert@2.0.1:
dependencies: dependencies:
color-name: 1.1.4 color-name: 1.1.4
@ -1812,6 +2117,8 @@ snapshots:
core-util-is@1.0.3: {} core-util-is@1.0.3: {}
crc-32@1.2.2: {}
create-hash@1.2.0: create-hash@1.2.0:
dependencies: dependencies:
cipher-base: 1.0.7 cipher-base: 1.0.7
@ -1919,6 +2226,35 @@ snapshots:
has-tostringtag: 1.0.2 has-tostringtag: 1.0.2
hasown: 2.0.2 hasown: 2.0.2
esbuild@0.25.12:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.12
'@esbuild/android-arm': 0.25.12
'@esbuild/android-arm64': 0.25.12
'@esbuild/android-x64': 0.25.12
'@esbuild/darwin-arm64': 0.25.12
'@esbuild/darwin-x64': 0.25.12
'@esbuild/freebsd-arm64': 0.25.12
'@esbuild/freebsd-x64': 0.25.12
'@esbuild/linux-arm': 0.25.12
'@esbuild/linux-arm64': 0.25.12
'@esbuild/linux-ia32': 0.25.12
'@esbuild/linux-loong64': 0.25.12
'@esbuild/linux-mips64el': 0.25.12
'@esbuild/linux-ppc64': 0.25.12
'@esbuild/linux-riscv64': 0.25.12
'@esbuild/linux-s390x': 0.25.12
'@esbuild/linux-x64': 0.25.12
'@esbuild/netbsd-arm64': 0.25.12
'@esbuild/netbsd-x64': 0.25.12
'@esbuild/openbsd-arm64': 0.25.12
'@esbuild/openbsd-x64': 0.25.12
'@esbuild/openharmony-arm64': 0.25.12
'@esbuild/sunos-x64': 0.25.12
'@esbuild/win32-arm64': 0.25.12
'@esbuild/win32-ia32': 0.25.12
'@esbuild/win32-x64': 0.25.12
escalade@3.2.0: {} escalade@3.2.0: {}
escape-html@1.0.3: {} escape-html@1.0.3: {}
@ -1967,6 +2303,8 @@ snapshots:
once: 1.4.0 once: 1.4.0
qs: 6.14.0 qs: 6.14.0
frac@1.1.2: {}
fresh@0.5.2: {} fresh@0.5.2: {}
fsevents@2.3.3: fsevents@2.3.3:
@ -2313,6 +2651,8 @@ snapshots:
normalize-path@3.0.0: {} normalize-path@3.0.0: {}
npm-check-updates@19.1.2: {}
oauth-1.0a@2.2.6: {} oauth-1.0a@2.2.6: {}
object-inspect@1.13.4: {} object-inspect@1.13.4: {}
@ -2494,6 +2834,10 @@ snapshots:
sqlstring@2.3.3: {} sqlstring@2.3.3: {}
ssf@0.11.2:
dependencies:
frac: 1.1.2
statuses@1.5.0: {} statuses@1.5.0: {}
statuses@2.0.1: {} statuses@2.0.1: {}
@ -2582,6 +2926,13 @@ snapshots:
tsscmp@1.0.6: {} tsscmp@1.0.6: {}
tsx@4.20.6:
dependencies:
esbuild: 0.25.12
get-tsconfig: 4.13.0
optionalDependencies:
fsevents: 2.3.3
type-is@1.6.18: type-is@1.6.18:
dependencies: dependencies:
media-typer: 0.3.0 media-typer: 0.3.0
@ -2647,6 +2998,10 @@ snapshots:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
wmf@1.0.2: {}
word@0.3.0: {}
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
@ -2661,6 +3016,16 @@ snapshots:
wrappy@1.0.2: {} wrappy@1.0.2: {}
xlsx@0.18.5:
dependencies:
adler-32: 1.3.1
cfb: 1.2.2
codepage: 1.15.0
crc-32: 1.2.2
ssf: 0.11.2
wmf: 1.0.2
word: 0.3.0
xml2js@0.6.2: xml2js@0.6.2:
dependencies: dependencies:
sax: 1.4.3 sax: 1.4.3

View File

@ -1,6 +1,5 @@
import { MidwayConfig } from '@midwayjs/core'; import { MidwayConfig } from '@midwayjs/core';
import { Product } from '../entity/product.entity'; import { Product } from '../entity/product.entity';
import { Category } from '../entity/category.entity';
import { WpProduct } from '../entity/wp_product.entity'; import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity'; import { Variation } from '../entity/variation.entity';
import { User } from '../entity/user.entity'; import { User } from '../entity/user.entity';
@ -11,10 +10,10 @@ import { StockPoint } from '../entity/stock_point.entity';
import { StockRecord } from '../entity/stock_record.entity'; import { StockRecord } from '../entity/stock_record.entity';
import { Order } from '../entity/order.entity'; import { Order } from '../entity/order.entity';
import { OrderItem } from '../entity/order_item.entity'; import { OrderItem } from '../entity/order_item.entity';
import { OrderCoupon } from '../entity/order_copon.entity'; import { OrderCoupon } from '../entity/order_coupon.entity';
import { OrderFee } from '../entity/order_fee.entity'; import { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity'; import { OrderRefund } from '../entity/order_refund.entity';
import { OrderRefundItem } from '../entity/order_retund_item.entity'; import { OrderRefundItem } from '../entity/order_refund_item.entity';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { OrderSaleOriginal } from '../entity/order_item_original.entity'; import { OrderSaleOriginal } from '../entity/order_item_original.entity';
import { OrderShipping } from '../entity/order_shipping.entity'; import { OrderShipping } from '../entity/order_shipping.entity';
@ -26,14 +25,14 @@ import { Shipment } from '../entity/shipment.entity';
import { ShipmentItem } from '../entity/shipment_item.entity'; import { ShipmentItem } from '../entity/shipment_item.entity';
import { Transfer } from '../entity/transfer.entity'; import { Transfer } from '../entity/transfer.entity';
import { TransferItem } from '../entity/transfer_item.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 { CustomerTag } from '../entity/customer_tag.entity';
import { Customer } from '../entity/customer.entity'; import { Customer } from '../entity/customer.entity';
import { DeviceWhitelist } from '../entity/device_whitelist'; import { DeviceWhitelist } from '../entity/device_whitelist';
import { AuthCode } from '../entity/auth_code'; import { AuthCode } from '../entity/auth_code';
import { Subscription } from '../entity/subscription.entity'; import { Subscription } from '../entity/subscription.entity';
import { Site } from '../entity/site.entity'; import { Site } from '../entity/site.entity';
import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity';
export default { export default {
// use for cookie sign key, should change to your own and keep security // use for cookie sign key, should change to your own and keep security
@ -42,9 +41,6 @@ export default {
default: { default: {
entities: [ entities: [
Product, Product,
Category,
Strength,
Flavors,
WpProduct, WpProduct,
Variation, Variation,
User, User,
@ -76,6 +72,8 @@ export default {
AuthCode, AuthCode,
Subscription, Subscription,
Site, Site,
Dict,
DictItem,
], ],
synchronize: true, synchronize: true,
logging: false, logging: false,

View File

@ -13,15 +13,15 @@ import { ProductService } from '../service/product.service';
import { errorResponse, successResponse } from '../utils/response.util'; import { errorResponse, successResponse } from '../utils/response.util';
import { import {
BatchSetSkuDTO, BatchSetSkuDTO,
CreateCategoryDTO, CreateBrandDTO,
CreateFlavorsDTO, CreateFlavorsDTO,
CreateProductDTO, CreateProductDTO,
CreateStrengthDTO, CreateStrengthDTO,
QueryCategoryDTO, QueryBrandDTO,
QueryFlavorsDTO, QueryFlavorsDTO,
QueryProductDTO, QueryProductDTO,
QueryStrengthDTO, QueryStrengthDTO,
UpdateCategoryDTO, UpdateBrandDTO,
UpdateFlavorsDTO, UpdateFlavorsDTO,
UpdateProductDTO, UpdateProductDTO,
UpdateStrengthDTO, UpdateStrengthDTO,
@ -29,8 +29,8 @@ import {
import { ApiOkResponse } from '@midwayjs/swagger'; import { ApiOkResponse } from '@midwayjs/swagger';
import { import {
BooleanRes, BooleanRes,
ProductCatListRes, ProductBrandListRes,
ProductCatRes, ProductBrandRes,
ProductListRes, ProductListRes,
ProductRes, ProductRes,
ProductsRes, ProductsRes,
@ -79,12 +79,12 @@ export class ProductController {
async getProductList( async getProductList(
@Query() query: QueryProductDTO @Query() query: QueryProductDTO
): Promise<ProductListRes> { ): Promise<ProductListRes> {
const { current = 1, pageSize = 10, name, categoryId } = query; const { current = 1, pageSize = 10, name, brandId } = query;
try { try {
const data = await this.productService.getProductList( const data = await this.productService.getProductList(
{ current, pageSize }, { current, pageSize },
name, name,
categoryId brandId
); );
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {
@ -138,8 +138,6 @@ export class ProductController {
} }
} }
@ApiOkResponse({ @ApiOkResponse({
type: BooleanRes, type: BooleanRes,
}) })
@ -154,13 +152,13 @@ export class ProductController {
} }
@ApiOkResponse({ @ApiOkResponse({
type: ProductCatListRes, type: ProductBrandListRes,
}) })
@Get('/categories') @Get('/brands')
async getCategories(@Query() query: QueryCategoryDTO) { async getBrands(@Query() query: QueryBrandDTO) {
const { current = 1, pageSize = 10, name } = query; const { current = 1, pageSize = 10, name } = query;
try { try {
let data = await this.productService.getCategoryList( let data = await this.productService.getBrandList(
{ current, pageSize }, { current, pageSize },
name name
); );
@ -171,10 +169,10 @@ export class ProductController {
} }
@ApiOkResponse() @ApiOkResponse()
@Get('/categorieAll') @Get('/brandAll')
async getCategorieAll() { async getBrandAll() {
try { try {
let data = await this.productService.getCategoryAll(); let data = await this.productService.getBrandAll();
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {
return errorResponse(error?.message || error); return errorResponse(error?.message || error);
@ -182,18 +180,18 @@ export class ProductController {
} }
@ApiOkResponse({ @ApiOkResponse({
type: ProductCatRes, type: ProductBrandRes,
}) })
@Post('/category') @Post('/brand')
async createCategory(@Body() categoryData: CreateCategoryDTO) { async createBrand(@Body() brandData: CreateBrandDTO) {
try { try {
const hasCategory = await this.productService.hasCategory( const hasBrand = await this.productService.hasBrand(
categoryData.name brandData.name
); );
if (hasCategory) { if (hasBrand) {
return errorResponse('分类已存在'); return errorResponse('品牌已存在');
} }
let data = await this.productService.createCategory(categoryData); let data = await this.productService.createBrand(brandData);
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {
return errorResponse(error?.message || error); return errorResponse(error?.message || error);
@ -201,21 +199,21 @@ export class ProductController {
} }
@ApiOkResponse({ @ApiOkResponse({
type: ProductCatRes, type: ProductBrandRes,
}) })
@Put('/category/:id') @Put('/brand/:id')
async updateCategory( async updateBrand(
@Param('id') id: number, @Param('id') id: number,
@Body() categoryData: UpdateCategoryDTO @Body() brandData: UpdateBrandDTO
) { ) {
try { try {
const hasCategory = await this.productService.hasCategory( const hasBrand = await this.productService.hasBrand(
categoryData.name brandData.name
); );
if (hasCategory) { if (hasBrand) {
return errorResponse('分类已存在'); return errorResponse('品牌已存在');
} }
const data = this.productService.updateCategory(id, categoryData); const data = this.productService.updateBrand(id, brandData);
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {
return errorResponse(error?.message || error); return errorResponse(error?.message || error);
@ -225,12 +223,12 @@ export class ProductController {
@ApiOkResponse({ @ApiOkResponse({
type: BooleanRes, type: BooleanRes,
}) })
@Del('/category/:id') @Del('/brand/:id')
async deleteCategory(@Param('id') id: number) { async deleteBrand(@Param('id') id: number) {
try { try {
const hasProducts = await this.productService.hasProductsInCategory(id); const hasProducts = await this.productService.hasProductsInBrand(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除'); if (hasProducts) throw new Error('该品牌下有商品,无法删除');
const data = await this.productService.deleteCategory(id); const data = await this.productService.deleteBrand(id);
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {
return errorResponse(error?.message || error); return errorResponse(error?.message || error);
@ -283,7 +281,7 @@ export class ProductController {
try { try {
const hasFlavors = await this.productService.hasFlavors(flavorsData.name); const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
if (hasFlavors) { if (hasFlavors) {
return errorResponse('分类已存在'); return errorResponse('口味已存在');
} }
let data = await this.productService.createFlavors(flavorsData); let data = await this.productService.createFlavors(flavorsData);
return successResponse(data); return successResponse(data);
@ -301,7 +299,7 @@ export class ProductController {
try { try {
const hasFlavors = await this.productService.hasFlavors(flavorsData.name); const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
if (hasFlavors) { if (hasFlavors) {
return errorResponse('分类已存在'); return errorResponse('口味已存在');
} }
const data = this.productService.updateFlavors(id, flavorsData); const data = this.productService.updateFlavors(id, flavorsData);
return successResponse(data); return successResponse(data);
@ -317,7 +315,7 @@ export class ProductController {
async deleteFlavors(@Param('id') id: number) { async deleteFlavors(@Param('id') id: number) {
try { try {
const hasProducts = await this.productService.hasProductsInFlavors(id); const hasProducts = await this.productService.hasProductsInFlavors(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除'); if (hasProducts) throw new Error('该口味下有商品,无法删除');
const data = await this.productService.deleteFlavors(id); const data = await this.productService.deleteFlavors(id);
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {
@ -359,7 +357,7 @@ export class ProductController {
strengthData.name strengthData.name
); );
if (hasStrength) { if (hasStrength) {
return errorResponse('分类已存在'); return errorResponse('规格已存在');
} }
let data = await this.productService.createStrength(strengthData); let data = await this.productService.createStrength(strengthData);
return successResponse(data); return successResponse(data);
@ -379,7 +377,7 @@ export class ProductController {
strengthData.name strengthData.name
); );
if (hasStrength) { if (hasStrength) {
return errorResponse('分类已存在'); return errorResponse('规格已存在');
} }
const data = this.productService.updateStrength(id, strengthData); const data = this.productService.updateStrength(id, strengthData);
return successResponse(data); return successResponse(data);
@ -395,7 +393,7 @@ export class ProductController {
async deleteStrength(@Param('id') id: number) { async deleteStrength(@Param('id') id: number) {
try { try {
const hasProducts = await this.productService.hasProductsInStrength(id); const hasProducts = await this.productService.hasProductsInStrength(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除'); if (hasProducts) throw new Error('该规格下有商品,无法删除');
const data = await this.productService.deleteStrength(id); const data = await this.productService.deleteStrength(id);
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {

87
src/db/datasource.ts Normal file
View File

@ -0,0 +1,87 @@
import { DataSource, DataSourceOptions } from 'typeorm';
import { SeederOptions } from 'typeorm-extension';
import { Product } from '../entity/product.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_coupon.entity';
import { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity';
import { OrderRefundItem } from '../entity/order_refund_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 { 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';
import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity';
const options: DataSourceOptions & SeederOptions = {
type: 'mysql',
host: 'localhost',
port: 23306,
username: 'root',
password: '12345678',
database: 'inventory',
synchronize: false,
logging: true,
entities: [
Product,
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,
Dict,
DictItem,
],
migrations: ['src/migration/*.ts'],
seeds: ['src/db/seeds/**/*.ts'],
};
export default new DataSource(options);

View File

@ -1,65 +0,0 @@
/**
* @description
* @author ZKS
* @date 2025-11-27
*/
// 真实数据
export const flavorsData: { id: number; title: string; name: string }[] = [
{ id: 1, title: 'Bellini Mini', name: 'Bellini Mini' },
{ id: 2, title: 'Max Polarmint', name: 'Max Polarmint' },
{ id: 3, title: 'Blueberry', name: 'Blueberry' },
{ id: 4, title: 'Citrus', name: 'Citrus' },
{ id: 5, title: 'Wintergreen', name: 'Wintergreen' },
{ id: 6, title: 'COOL MINT', name: 'COOL MINT' },
{ id: 7, title: 'JUICY PEACH', name: 'JUICY PEACH' },
{ id: 8, title: 'ORANGE', name: 'ORANGE' },
{ id: 9, title: 'PEPPERMINT', name: 'PEPPERMINT' },
{ id: 10, title: 'SPEARMINT', name: 'SPEARMINT' },
{ id: 11, title: 'STRAWBERRY', name: 'STRAWBERRY' },
{ id: 12, title: 'WATERMELON', name: 'WATERMELON' },
{ id: 13, title: 'COFFEE', name: 'COFFEE' },
{ id: 14, title: 'LEMONADE', name: 'LEMONADE' },
{ id: 15, title: 'apple mint', name: 'apple mint' },
{ id: 16, title: 'PEACH', name: 'PEACH' },
{ id: 17, title: 'Mango', name: 'Mango' },
{ id: 18, title: 'ICE WINTERGREEN', name: 'ICE WINTERGREEN' },
{ id: 19, title: 'Pink Lemonade', name: 'Pink Lemonade' },
{ id: 20, title: 'Blackcherry', name: 'Blackcherry' },
{ id: 21, title: 'fresh mint', name: 'fresh mint' },
{ id: 22, title: 'Strawberry Lychee', name: 'Strawberry Lychee' },
{ id: 23, title: 'Passion Fruit', name: 'Passion Fruit' },
{ id: 24, title: 'Banana lce', name: 'Banana lce' },
{ id: 25, title: 'Bubblegum', name: 'Bubblegum' },
{ id: 26, title: 'Mango lce', name: 'Mango lce' },
{ id: 27, title: 'Grape lce', name: 'Grape lce' },
{ id: 28, title: 'Peach', name: 'Peach' },
];
export const brandsData: { id: number; title: string; name: string }[] = [
{ id: 1, title: 'Yoone', name: 'YOONE' },
{ id: 2, title: 'White Fox', name: 'WHITE_FOX' },
{ id: 3, title: 'ZYN', name: 'ZYN' },
{ id: 4, title: 'Zonnic', name: 'ZONNIC' },
{ id: 5, title: 'Zolt', name: 'ZOLT' },
{ id: 6, title: 'Velo', name: 'VELO' },
{ id: 7, title: 'Lucy', name: 'LUCY' },
{ id: 8, title: 'EGP', name: 'EGP' },
{ id: 9, title: 'Bridge', name: 'BRIDGE' },
{ id: 10, title: 'ZEX', name: 'ZEX' },
{ id: 11, title: 'Sesh', name: 'Sesh' },
{ id: 12, title: 'Pablo', name: 'Pablo' },
];
export const strengthsData: { id: number; title: string; name: string }[] = [
{ id: 1, title: '3MG', name: '3MG' },
{ id: 2, title: '9MG', name: '9MG' },
{ id: 3, title: '2MG', name: '2MG' },
{ id: 4, title: '4MG', name: '4MG' },
{ id: 5, title: '12MG', name: '12MG' },
{ id: 6, title: '18MG', name: '18MG' },
{ id: 7, title: '6MG', name: '6MG' },
{ id: 8, title: '16.5MG', name: '16.5MG' },
{ id: 9, title: '6.5MG', name: '6.5MG' },
{ id: 10, title: '30MG', name: '30MG' },
];

View File

@ -1,44 +0,0 @@
import { createConnection } from 'typeorm';
import { Dict } from '../../entity/dict.entity';
import { DictItem } from '../../entity/dict_item.entity';
import { brandsData, flavorsData, strengthsData } from './dict';
async function seed() {
const connection = await createConnection();
// 创建字典
const brandDict = await connection.manager.save(Dict, { name: '品牌', unique_key: 'brand' });
const flavorDict = await connection.manager.save(Dict, { name: '口味', unique_key: 'flavor' });
const strengthDict = await connection.manager.save(Dict, { name: '规格', unique_key: 'strength' });
// 植入品牌数据
for (const brand of brandsData) {
await connection.manager.save(DictItem, {
name: brand.name,
unique_key: brand.unique_key,
dict_id: brandDict.id,
});
}
// 植入口味数据
for (const flavor of flavorsData) {
await connection.manager.save(DictItem, {
name: flavor.name,
unique_key: flavor.unique_key,
dict_id: flavorDict.id,
});
}
// 植入规格数据
for (const strength of strengthsData) {
await connection.manager.save(DictItem, {
name: strength.name,
unique_key: strength.unique_key,
dict_id: strengthDict.id,
});
}
await connection.close();
}
seed().catch(error => console.error(error));

113
src/db/seeds/dict.seeder.ts Normal file
View File

@ -0,0 +1,113 @@
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { Dict } from '../../entity/dict.entity';
import { DictItem } from '../../entity/dict_item.entity';
export default class DictSeeder implements Seeder {
public async run(
dataSource: DataSource,
factoryManager: SeederFactoryManager
): Promise<any> {
const dictRepository = dataSource.getRepository(Dict);
const dictItemRepository = dataSource.getRepository(DictItem);
const flavorsData = [
{ id: 1, title: 'Bellini Mini', name: 'Bellini Mini' },
{ id: 2, title: 'Max Polarmint', name: 'Max Polarmint' },
{ id: 3, title: 'Blueberry', name: 'Blueberry' },
{ id: 4, title: 'Citrus', name: 'Citrus' },
{ id: 5, title: 'Wintergreen', name: 'Wintergreen' },
{ id: 6, title: 'COOL MINT', name: 'COOL MINT' },
{ id: 7, title: 'JUICY PEACH', name: 'JUICY PEACH' },
{ id: 8, title: 'ORANGE', name: 'ORANGE' },
{ id: 9, title: 'PEPPERMINT', name: 'PEPPERMINT' },
{ id: 10, title: 'SPEARMINT', name: 'SPEARMINT' },
{ id: 11, title: 'STRAWBERRY', name: 'STRAWBERRY' },
{ id: 12, title: 'WATERMELON', name: 'WATERMELON' },
{ id: 13, title: 'COFFEE', name: 'COFFEE' },
{ id: 14, title: 'LEMONADE', name: 'LEMONADE' },
{ id: 15, title: 'apple mint', name: 'apple mint' },
{ id: 16, title: 'PEACH', name: 'PEACH' },
{ id: 17, title: 'Mango', name: 'Mango' },
{ id: 18, title: 'ICE WINTERGREEN', name: 'ICE WINTERGREEN' },
{ id: 19, title: 'Pink Lemonade', name: 'Pink Lemonade' },
{ id: 20, title: 'Blackcherry', name: 'Blackcherry' },
{ id: 21, title: 'fresh mint', name: 'fresh mint' },
{ id: 22, title: 'Strawberry Lychee', name: 'Strawberry Lychee' },
{ id: 23, title: 'Passion Fruit', name: 'Passion Fruit' },
{ id: 24, title: 'Banana lce', name: 'Banana lce' },
{ id: 25, title: 'Bubblegum', name: 'Bubblegum' },
{ id: 26, title: 'Mango lce', name: 'Mango lce' },
{ id: 27, title: 'Grape lce', name: 'Grape lce' },
];
const brandsData = [
{ id: 1, title: 'Yoone', name: 'YOONE' },
{ id: 2, title: 'White Fox', name: 'WHITE_FOX' },
{ id: 3, title: 'ZYN', name: 'ZYN' },
{ id: 4, title: 'Zonnic', name: 'ZONNIC' },
{ id: 5, title: 'Zolt', name: 'ZOLT' },
{ id: 6, title: 'Velo', name: 'VELO' },
{ id: 7, title: 'Lucy', name: 'LUCY' },
{ id: 8, title: 'EGP', name: 'EGP' },
{ id: 9, title: 'Bridge', name: 'BRIDGE' },
{ id: 10, title: 'ZEX', name: 'ZEX' },
{ id: 11, title: 'Sesh', name: 'Sesh' },
{ id: 12, title: 'Pablo', name: 'Pablo' },
];
const strengthsData = [
{ id: 1, title: '3MG', name: '3MG' },
{ id: 2, title: '9MG', name: '9MG' },
{ id: 3, title: '2MG', name: '2MG' },
{ id: 4, title: '4MG', name: '4MG' },
{ id: 5, title: '12MG', name: '12MG' },
{ id: 6, title: '18MG', name: '18MG' },
{ id: 7, title: '6MG', name: '6MG' },
{ id: 8, title: '16.5MG', name: '16.5MG' },
{ id: 9, title: '6.5MG', name: '6.5MG' },
{ id: 10, title: '30MG', name: '30MG' },
];
// 在插入新数据前,清空旧数据
await dictItemRepository.query('DELETE FROM `dict_item`');
await dictRepository.query('DELETE FROM `dict`');
// 重置自增 ID
await dictItemRepository.query('ALTER TABLE `dict_item` AUTO_INCREMENT = 1');
await dictRepository.query('ALTER TABLE `dict` AUTO_INCREMENT = 1');
const brandDict = await dictRepository.save({ title: '品牌', name: 'brand' });
const flavorDict = await dictRepository.save({ title: '口味', name: 'flavor' });
const strengthDict = await dictRepository.save({ title: '强度', name: 'strength' });
// 遍历品牌数据
for (const brand of brandsData) {
// 保存字典项,并关联到品牌字典
await dictItemRepository.save({
title: brand.title,
name: brand.name,
dict: brandDict,
});
}
// 遍历口味数据
for (const flavor of flavorsData) {
// 保存字典项,并关联到口味字典
await dictItemRepository.save({
title: flavor.title,
name: flavor.name,
dict: flavorDict,
});
}
// 遍历强度数据
for (const strength of strengthsData) {
// 保存字典项,并关联到强度字典
await dictItemRepository.save({
title: strength.title,
name: strength.name,
dict: strengthDict,
});
}
}
}

View File

@ -1,6 +1,16 @@
import { ApiProperty } from '@midwayjs/swagger'; import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate'; import { Rule, RuleType } from '@midwayjs/validate';
class DictItemDTO {
@ApiProperty({ description: '显示名称', required: false })
@Rule(RuleType.string())
title?: string;
@ApiProperty({ description: '唯一标识', required: true })
@Rule(RuleType.string().required())
name: string;
}
/** /**
* DTO * DTO
*/ */
@ -17,17 +27,17 @@ export class CreateProductDTO {
@Rule(RuleType.string()) @Rule(RuleType.string())
description: string; description: string;
@ApiProperty({ example: '1', description: '分类 ID' }) @ApiProperty({ description: '品牌', type: DictItemDTO })
@Rule(RuleType.number()) @Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() }))
categoryId: number; brand: DictItemDTO;
@ApiProperty() @ApiProperty({ description: '规格', type: DictItemDTO })
@Rule(RuleType.number()) @Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() }))
strengthId: number; strength: DictItemDTO;
@ApiProperty() @ApiProperty({ description: '口味', type: DictItemDTO })
@Rule(RuleType.number()) @Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() }))
flavorsId: number; flavor: DictItemDTO;
@ApiProperty() @ApiProperty()
@Rule(RuleType.string()) @Rule(RuleType.string())
@ -59,36 +69,41 @@ export class QueryProductDTO {
@Rule(RuleType.string()) @Rule(RuleType.string())
name: string; name: string;
@ApiProperty({ example: '1', description: '分类 ID' }) @ApiProperty({ example: '1', description: '品牌 ID' })
@Rule(RuleType.string()) @Rule(RuleType.string())
categoryId: number; brandId: number;
} }
/** /**
* DTO * DTO
*/ */
export class CreateCategoryDTO { export class CreateBrandDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true }) @ApiProperty({ example: 'ZYN', description: '品牌名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' })) @Rule(RuleType.string().required().empty({ message: '品牌名称不能为空' }))
name: string; title: string;
@ApiProperty({ example: 'ZYN', description: '品牌唯一标识', required: true })
@Rule(RuleType.string().required().empty({ message: 'key不能为空' })) @Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string; name: string;
} }
/** /**
* DTO * DTO
*/ */
export class UpdateCategoryDTO { export class UpdateBrandDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' }) @ApiProperty({ example: 'ZYN', description: '品牌名称' })
@Rule(RuleType.string())
title: string;
@ApiProperty({ example: 'ZYN', description: '品牌唯一标识' })
@Rule(RuleType.string()) @Rule(RuleType.string())
name: string; name: string;
} }
/** /**
* DTO * DTO
*/ */
export class QueryCategoryDTO { export class QueryBrandDTO {
@ApiProperty({ example: '1', description: '页码' }) @ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number()) @Rule(RuleType.number())
current: number; // 页码 current: number; // 页码
@ -98,21 +113,30 @@ export class QueryCategoryDTO {
pageSize: number; // 每页大小 pageSize: number; // 每页大小
@ApiProperty({ example: 'ZYN', description: '关键字' }) @ApiProperty({ example: 'ZYN', description: '关键字' })
@Rule(RuleType.string()) @Rule(RuleType.string().required())
name: string; // 搜索关键字(支持模糊查询) name: string; // 搜索关键字(支持模糊查询)
} }
export class CreateFlavorsDTO { export class CreateFlavorsDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true }) @ApiProperty({ example: 'WINTERGREEN', description: '口味名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' })) @Rule(RuleType.string().required().empty({ message: '口味名称不能为空' }))
name: string; title: string;
@ApiProperty({
example: 'WINTERGREEN',
description: '口味唯一标识',
required: true,
})
@Rule(RuleType.string().required().empty({ message: 'key不能为空' })) @Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string; name: string;
} }
export class UpdateFlavorsDTO { export class UpdateFlavorsDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' }) @ApiProperty({ example: 'WINTERGREEN', description: '口味名称' })
@Rule(RuleType.string())
title: string;
@ApiProperty({ example: 'WINTERGREEN', description: '口味唯一标识' })
@Rule(RuleType.string()) @Rule(RuleType.string())
name: string; name: string;
} }
@ -132,16 +156,21 @@ export class QueryFlavorsDTO {
} }
export class CreateStrengthDTO { export class CreateStrengthDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true }) @ApiProperty({ example: '6MG', description: '规格名称', required: false })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' })) @Rule(RuleType.string())
name: string; title?: string;
@ApiProperty({ example: '6MG', description: '规格唯一标识', required: true })
@Rule(RuleType.string().required().empty({ message: 'key不能为空' })) @Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string; name: string;
} }
export class UpdateStrengthDTO { export class UpdateStrengthDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' }) @ApiProperty({ example: '6MG', description: '规格名称' })
@Rule(RuleType.string())
title: string;
@ApiProperty({ example: '6MG', description: '规格唯一标识' })
@Rule(RuleType.string()) @Rule(RuleType.string())
name: string; name: string;
} }
@ -155,7 +184,7 @@ export class QueryStrengthDTO {
@Rule(RuleType.number()) @Rule(RuleType.number())
pageSize: number; // 每页大小 pageSize: number; // 每页大小
@ApiProperty({ example: 'ZYN', description: '关键字' }) @ApiProperty({ example: 'YOONE', description: '关键字' })
@Rule(RuleType.string()) @Rule(RuleType.string())
name: string; // 搜索关键字(支持模糊查询) name: string; // 搜索关键字(支持模糊查询)
} }

View File

@ -1,5 +1,4 @@
import { ApiProperty } from '@midwayjs/swagger'; import { ApiProperty } from '@midwayjs/swagger';
import { Category } from '../entity/category.entity';
import { Order } from '../entity/order.entity'; import { Order } from '../entity/order.entity';
import { Product } from '../entity/product.entity'; import { Product } from '../entity/product.entity';
import { StockPoint } from '../entity/stock_point.entity'; import { StockPoint } from '../entity/stock_point.entity';
@ -18,12 +17,11 @@ import { Service } from '../entity/service.entity';
import { RateDTO } from './freightcom.dto'; import { RateDTO } from './freightcom.dto';
import { ShippingAddress } from '../entity/shipping_address.entity'; import { ShippingAddress } from '../entity/shipping_address.entity';
import { OrderItem } from '../entity/order_item.entity'; import { OrderItem } from '../entity/order_item.entity';
import { OrderRefundItem } from '../entity/order_retund_item.entity'; import { OrderRefundItem } from '../entity/order_refund_item.entity';
import { OrderNote } from '../entity/order_note.entity'; import { OrderNote } from '../entity/order_note.entity';
import { PaymentMethodDTO } from './logistics.dto'; import { PaymentMethodDTO } from './logistics.dto';
import { Flavors } from '../entity/flavors.entity';
import { Strength } from '../entity/strength.entity';
import { Subscription } from '../entity/subscription.entity'; import { Subscription } from '../entity/subscription.entity';
import { Dict } from '../entity/dict.entity';
export class BooleanRes extends SuccessWrapper(Boolean) {} export class BooleanRes extends SuccessWrapper(Boolean) {}
//网站配置返回数据 //网站配置返回数据
@ -35,18 +33,38 @@ export class ProductListRes extends SuccessWrapper(ProductPaginatedResponse) {}
//产品返回数据 //产品返回数据
export class ProductRes extends SuccessWrapper(Product) {} export class ProductRes extends SuccessWrapper(Product) {}
export class ProductsRes extends SuccessArrayWrapper(Product) {} export class ProductsRes extends SuccessArrayWrapper(Product) {}
//产品分类返分页数据 //产品品牌返分页数据
export class CategoryPaginatedResponse extends PaginatedWrapper(Category) {} export class BrandPaginatedResponse extends PaginatedWrapper(Dict) {}
export class FlavorsPaginatedResponse extends PaginatedWrapper(Flavors) {} //产品品牌返分页返回数据
export class StrengthPaginatedResponse extends PaginatedWrapper(Strength) {} export class ProductBrandListRes extends SuccessWrapper(
//产品分类返分页返回数据 BrandPaginatedResponse
export class ProductCatListRes extends SuccessWrapper(
CategoryPaginatedResponse
) {} ) {}
//产品分类返所有数据 //产品品牌返所有数据
export class ProductCatAllRes extends SuccessArrayWrapper(Category) {} export class ProductBrandAllRes extends SuccessArrayWrapper(Dict) {}
//产品分类返回数据 //产品品牌返回数据
export class ProductCatRes extends SuccessWrapper(Category) {} export class ProductBrandRes extends SuccessWrapper(Dict) {}
//产品口味返分页数据
export class FlavorsPaginatedResponse extends PaginatedWrapper(Dict) {}
//产品口味返分页返回数据
export class ProductFlavorsListRes extends SuccessWrapper(
FlavorsPaginatedResponse
) {}
//产品口味返所有数据
export class ProductFlavorsAllRes extends SuccessArrayWrapper(Dict) {}
//产品口味返回数据
export class ProductFlavorsRes extends SuccessWrapper(Dict) {}
//产品规格返分页数据
export class StrengthPaginatedResponse extends PaginatedWrapper(Dict) {}
//产品规格返分页返回数据
export class ProductStrengthListRes extends SuccessWrapper(
StrengthPaginatedResponse
) {}
//产品规格返所有数据
export class ProductStrengthAllRes extends SuccessArrayWrapper(Dict) {}
//产品规格返回数据
export class ProductStrengthRes extends SuccessWrapper(Dict) {}
//产品分页数据 //产品分页数据
export class WpProductPaginatedResponse extends PaginatedWrapper( export class WpProductPaginatedResponse extends PaginatedWrapper(

View File

@ -1,53 +0,0 @@
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;
}

39
src/entity/dict.entity.ts Normal file
View File

@ -0,0 +1,39 @@
/**
* @description
* @author ZKS
* @date 2025-11-27
*/
import { DictItem } from './dict_item.entity';
import {
Column,
CreateDateColumn,
Entity,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity()
export class Dict {
// 主键
@PrimaryGeneratedColumn()
id: number;
@Column({comment: '字典显示名称'})
title: string;
// 字典名称
@Column({ unique: true, comment: '字典名称' })
name: string;
// 字典项
@OneToMany(() => DictItem, item => item.dict)
items: DictItem[];
// 创建时间
@CreateDateColumn()
createdAt: Date;
// 更新时间
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,43 @@
/**
* @description
* @author ZKS
* @date 2025-11-27
*/
import { Dict } from './dict.entity';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity()
export class DictItem {
// 主键
@PrimaryGeneratedColumn()
id: number;
// 字典项名称
@Column({ comment: '字典项显示名称' })
title: string;
// 唯一标识
@Column({ unique: true, comment: '字典唯一标识名称' })
name: string;
// 属于哪个字典
@ManyToOne(() => Dict, dict => dict.items)
@JoinColumn({ name: 'dict_id' })
dict: Dict;
// 创建时间
@CreateDateColumn()
createdAt: Date;
// 更新时间
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -1,43 +0,0 @@
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;
}

View File

@ -35,9 +35,9 @@ export class Product {
@Column({ nullable: true }) @Column({ nullable: true })
description?: string; description?: string;
@ApiProperty({ example: '1', description: '分类 ID', type: 'number' }) @ApiProperty({ example: '1', description: '品牌 ID', type: 'number' })
@Column() @Column()
categoryId: number; brandId: number;
@ApiProperty({ description: '口味ID' }) @ApiProperty({ description: '口味ID' })
@Column() @Column()

View File

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

View File

@ -1,43 +0,0 @@
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

@ -12,8 +12,8 @@ import { WpProduct } from '../entity/wp_product.entity';
import { Product } from '../entity/product.entity'; import { Product } from '../entity/product.entity';
import { OrderFee } from '../entity/order_fee.entity'; import { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity'; import { OrderRefund } from '../entity/order_refund.entity';
import { OrderRefundItem } from '../entity/order_retund_item.entity'; import { OrderRefundItem } from '../entity/order_refund_item.entity';
import { OrderCoupon } from '../entity/order_copon.entity'; import { OrderCoupon } from '../entity/order_coupon.entity';
import { OrderShipping } from '../entity/order_shipping.entity'; import { OrderShipping } from '../entity/order_shipping.entity';
import { Shipment } from '../entity/shipment.entity'; import { Shipment } from '../entity/shipment.entity';
import { Customer } from '../entity/customer.entity'; import { Customer } from '../entity/customer.entity';

View File

@ -1,21 +1,20 @@
import { Provide } from '@midwayjs/core'; import { Inject, Provide } from '@midwayjs/core';
import { In, Like, Not, Repository } from 'typeorm'; import { In, Like, Not, Repository } from 'typeorm';
import { Product } from '../entity/product.entity'; import { Product } from '../entity/product.entity';
import { Category } from '../entity/category.entity';
import { paginate } from '../utils/paginate.util'; import { paginate } from '../utils/paginate.util';
import { PaginationParams } from '../interface'; import { PaginationParams } from '../interface';
import { import {
CreateCategoryDTO, CreateBrandDTO,
CreateFlavorsDTO, CreateFlavorsDTO,
CreateProductDTO, CreateProductDTO,
CreateStrengthDTO, CreateStrengthDTO,
UpdateCategoryDTO, UpdateBrandDTO,
UpdateFlavorsDTO, UpdateFlavorsDTO,
UpdateProductDTO, UpdateProductDTO,
UpdateStrengthDTO, UpdateStrengthDTO,
} from '../dto/product.dto'; } from '../dto/product.dto';
import { import {
CategoryPaginatedResponse, BrandPaginatedResponse,
FlavorsPaginatedResponse, FlavorsPaginatedResponse,
ProductPaginatedResponse, ProductPaginatedResponse,
StrengthPaginatedResponse, StrengthPaginatedResponse,
@ -23,22 +22,23 @@ import {
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { WpProduct } from '../entity/wp_product.entity'; import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity'; import { Variation } from '../entity/variation.entity';
import { Strength } from '../entity/strength.entity'; import { Dict } from '../entity/dict.entity';
import { Flavors } from '../entity/flavors.entity'; import { DictItem } from '../entity/dict_item.entity';
import { Context } from '@midwayjs/koa';
@Provide() @Provide()
export class ProductService { export class ProductService {
@Inject()
ctx: Context;
@InjectEntityModel(Product) @InjectEntityModel(Product)
productModel: Repository<Product>; productModel: Repository<Product>;
@InjectEntityModel(Category) @InjectEntityModel(Dict)
categoryModel: Repository<Category>; dictModel: Repository<Dict>;
@InjectEntityModel(Strength) @InjectEntityModel(DictItem)
strengthModel: Repository<Strength>; dictItemModel: Repository<DictItem>;
@InjectEntityModel(Flavors)
flavorsModel: Repository<Flavors>;
@InjectEntityModel(WpProduct) @InjectEntityModel(WpProduct)
wpProductModel: Repository<WpProduct>; wpProductModel: Repository<WpProduct>;
@ -111,15 +111,42 @@ export class ProductService {
async getProductList( async getProductList(
pagination: PaginationParams, pagination: PaginationParams,
name?: string, name?: string,
categoryId?: number brandId?: number
): Promise<ProductPaginatedResponse> { ): Promise<ProductPaginatedResponse> {
const nameFilter = name ? name.split(' ').filter(Boolean) : []; const nameFilter = name ? name.split(' ').filter(Boolean) : [];
// 查询品牌、口味、规格字典
const brandDict = await this.dictModel.findOne({
where: { name: 'brand' },
});
const flavorDict = await this.dictModel.findOne({
where: { name: 'flavor' },
});
const strengthDict = await this.dictModel.findOne({
where: { name: 'strength' },
});
// 构建查询
const qb = this.productModel const qb = this.productModel
.createQueryBuilder('product') .createQueryBuilder('product')
.leftJoin(Category, 'category', 'category.id = product.categoryId') .leftJoin(
.leftJoin(Strength, 'strength', 'strength.id = product.strengthId') DictItem,
.leftJoin(Flavors, 'flavors', 'flavors.id = product.flavorsId') 'brand',
'brand.id = product.brandId AND brand.dict_id = :brandDictId',
{ brandDictId: brandDict?.id }
)
.leftJoin(
DictItem,
'flavor',
'flavor.id = product.flavorsId AND flavor.dict_id = :flavorDictId',
{ flavorDictId: flavorDict?.id }
)
.leftJoin(
DictItem,
'strength',
'strength.id = product.strengthId AND strength.dict_id = :strengthDictId',
{ strengthDictId: strengthDict?.id }
)
.select([ .select([
'product.id as id', 'product.id as id',
'product.name as name', 'product.name as name',
@ -129,9 +156,9 @@ export class ProductService {
'product.sku as sku', 'product.sku as sku',
'product.createdAt as createdAt', 'product.createdAt as createdAt',
'product.updatedAt as updatedAt', 'product.updatedAt as updatedAt',
'category.name AS categoryName', 'brand.title AS brandName',
'strength.name AS strengthName', 'flavor.title AS flavorsName',
'flavors.name AS flavorsName', 'strength.title AS strengthName',
]); ]);
// 模糊搜索 name支持多个关键词 // 模糊搜索 name支持多个关键词
@ -141,9 +168,9 @@ export class ProductService {
}); });
}); });
// 分类过滤 // 品牌过滤
if (categoryId) { if (brandId) {
qb.andWhere('product.categoryId = :categoryId', { categoryId }); qb.andWhere('product.brandId = :brandId', { brandId });
} }
// 分页 // 分页
@ -162,35 +189,79 @@ export class ProductService {
}; };
} }
async getOrCreateDictItem(
dictName: string,
itemTitle: string,
itemName?: string
): Promise<DictItem> {
// 查找字典
const dict = await this.dictModel.findOne({ where: { name: dictName } });
if (!dict) {
throw new Error(`字典 '${dictName}' 不存在`);
}
// 查找字典项
let item = await this.dictItemModel.findOne({
where: { title: itemTitle, dict_id: dict.id },
});
// 如果字典项不存在,则创建
if (!item) {
item = new DictItem();
item.title = itemTitle;
item.name = itemName || itemTitle; // 如果没有提供 name则使用 title
item.dict_id = dict.id;
await this.dictItemModel.save(item);
}
return item;
}
async createProduct(createProductDTO: CreateProductDTO): Promise<Product> { async createProduct(createProductDTO: CreateProductDTO): Promise<Product> {
const { name, description, categoryId, strengthId, flavorsId, humidity } = const { name, description, brand, flavor, strength, humidity } =
createProductDTO; createProductDTO;
// 获取或创建品牌、口味、规格
const brandItem = await this.getOrCreateDictItem(
'brand',
brand.title,
brand.name
);
const flavorItem = await this.getOrCreateDictItem(
'flavor',
flavor.title,
flavor.name
);
const strengthItem = await this.getOrCreateDictItem(
'strength',
strength.title,
strength.name
);
// 检查产品是否已存在
const isExit = await this.productModel.findOne({ const isExit = await this.productModel.findOne({
where: { where: {
categoryId, brandId: brandItem.id,
strengthId, flavorsId: flavorItem.id,
flavorsId, strengthId: strengthItem.id,
humidity, humidity,
}, },
}); });
if (isExit) throw new Error('产品已存在'); if (isExit) throw new Error('产品已存在');
// 创建新产品实例
const product = new Product(); const product = new Product();
product.name = name; product.name = name;
product.description = description; product.description = description;
product.categoryId = categoryId; product.brandId = brandItem.id;
product.strengthId = strengthId; product.flavorsId = flavorItem.id;
product.flavorsId = flavorsId; product.strengthId = strengthItem.id;
product.humidity = humidity; product.humidity = humidity;
const categoryKey = (
await this.categoryModel.findOne({ where: { id: categoryId } }) // 生成 SKU
).unique_key; product.sku = `${brandItem.name}-${flavorItem.name}-${strengthItem.name}-${humidity}`;
const strengthKey = (
await this.strengthModel.findOne({ where: { id: strengthId } }) // 保存产品
).unique_key;
const flavorsKey = (
await this.flavorsModel.findOne({ where: { id: flavorsId } })
).unique_key;
product.sku = `${categoryKey}-${flavorsKey}-${strengthKey}-${humidity}`;
return await this.productModel.save(product); return await this.productModel.save(product);
} }
@ -257,67 +328,126 @@ export class ProductService {
return result.affected > 0; // `affected` 表示删除的行数 return result.affected > 0; // `affected` 表示删除的行数
} }
async hasProductsInCategory(categoryId: number): Promise<boolean> { async hasProductsInBrand(brandId: number): Promise<boolean> {
// 检查是否有产品属于该品牌
const count = await this.productModel.count({ const count = await this.productModel.count({
where: { categoryId }, where: { brandId },
}); });
return count > 0; return count > 0;
} }
async hasCategory(name: string, id?: string): Promise<boolean> { async hasBrand(title: string, id?: number): Promise<boolean> {
const where: any = { name }; // 查找 'brand' 字典
const brandDict = await this.dictModel.findOne({
where: { name: 'brand' },
});
// 如果字典不存在,则品牌不存在
if (!brandDict) {
return false;
}
// 设置查询条件
const where: any = { title, dict_id: brandDict.id };
if (id) where.id = Not(id); if (id) where.id = Not(id);
const count = await this.categoryModel.count({
// 统计数量
const count = await this.dictItemModel.count({
where, where,
}); });
return count > 0; return count > 0;
} }
async getCategoryList( async getBrandList(
pagination: PaginationParams, pagination: PaginationParams,
name?: string title?: string
): Promise<CategoryPaginatedResponse> { ): Promise<BrandPaginatedResponse> {
const where: any = {}; // 查找 'brand' 字典
if (name) { const brandDict = await this.dictModel.findOne({
where.name = Like(`%${name}%`); where: { name: 'brand' },
} });
return await paginate(this.categoryModel, { pagination, where });
// 如果字典不存在,则返回空
if (!brandDict) {
return {
items: [],
total: 0,
...pagination,
};
} }
async getCategoryAll(): Promise<CategoryPaginatedResponse> { // 设置查询条件
return await this.categoryModel.find(); const where: any = { dict_id: brandDict.id };
if (title) {
where.title = Like(`%${title}%`);
} }
async createCategory( // 分页查询
createCategoryDTO: CreateCategoryDTO return await paginate(this.dictItemModel, { pagination, where });
): Promise<Category> {
const { name, unique_key } = createCategoryDTO;
const category = new Category();
category.name = name;
category.unique_key = unique_key;
return await this.categoryModel.save(category);
} }
async updateCategory(id: number, updateCategory: UpdateCategoryDTO) { async getBrandAll(): Promise<BrandPaginatedResponse> {
// 确认产品是否存在 // 查找 'brand' 字典
const category = await this.categoryModel.findOneBy({ id }); const brandDict = await this.dictModel.findOne({
if (!category) { where: { name: 'brand' },
throw new Error(`产品分类 ID ${id} 不存在`); });
}
// 更新产品 // 如果字典不存在,则返回空数组
await this.categoryModel.update(id, updateCategory); if (!brandDict) {
// 返回更新后的产品 return [];
return await this.categoryModel.findOneBy({ id });
} }
async deleteCategory(id: number): Promise<boolean> { // 返回所有品牌
// 检查产品是否存在 return this.dictItemModel.find({ where: { dict_id: brandDict.id } });
const category = await this.categoryModel.findOneBy({ id });
if (!category) {
throw new Error(`产品分类 ID ${id} 不存在`);
} }
// 删除产品
const result = await this.categoryModel.delete(id); async createBrand(createBrandDTO: CreateBrandDTO): Promise<DictItem> {
const { title, name } = createBrandDTO;
// 查找 'brand' 字典
const brandDict = await this.dictModel.findOne({
where: { name: 'brand' },
});
// 如果字典不存在,则抛出错误
if (!brandDict) {
throw new Error('品牌字典不存在');
}
// 创建新的品牌实例
const brand = new DictItem();
brand.title = title;
brand.name = name;
brand.dict_id = brandDict.id;
// 保存到数据库
return await this.dictItemModel.save(brand);
}
async updateBrand(id: number, updateBrand: UpdateBrandDTO) {
// 确认品牌是否存在
const brand = await this.dictItemModel.findOneBy({ id });
if (!brand) {
throw new Error(`品牌 ID ${id} 不存在`);
}
// 更新品牌
await this.dictItemModel.update(id, updateBrand);
// 返回更新后的品牌
return await this.dictItemModel.findOneBy({ id });
}
async deleteBrand(id: number): Promise<boolean> {
// 检查品牌是否存在
const brand = await this.dictItemModel.findOneBy({ id });
if (!brand) {
throw new Error(`品牌 ID ${id} 不存在`);
}
// 删除品牌
const result = await this.dictItemModel.delete(id);
return result.affected > 0; // `affected` 表示删除的行数 return result.affected > 0; // `affected` 表示删除的行数
} }
@ -328,58 +458,82 @@ export class ProductService {
return count > 0; return count > 0;
} }
async hasFlavors(name: string, id?: string): Promise<boolean> { async hasFlavors(title: string, id?: string): Promise<boolean> {
const where: any = { name }; const flavorsDict = await this.dictModel.findOne({
where: { name: 'flavor' },
});
if (!flavorsDict) {
return false;
}
const where: any = { title, dict_id: flavorsDict.id };
if (id) where.id = Not(id); if (id) where.id = Not(id);
const count = await this.flavorsModel.count({ const count = await this.dictItemModel.count({
where, where,
}); });
return count > 0; return count > 0;
} }
async getFlavorsList( async getFlavorsList(
pagination: PaginationParams, pagination: PaginationParams,
name?: string title?: string
): Promise<FlavorsPaginatedResponse> { ): Promise<FlavorsPaginatedResponse> {
const where: any = {}; const flavorsDict = await this.dictModel.findOne({
if (name) { where: { name: 'flavor' },
where.name = Like(`%${name}%`); });
if (!flavorsDict) {
return {
items: [],
total: 0,
...pagination,
};
} }
return await paginate(this.flavorsModel, { pagination, where }); const where: any = { dict_id: flavorsDict.id };
if (title) {
where.title = Like(`%${title}%`);
}
return await paginate(this.dictItemModel, { pagination, where });
} }
async getFlavorsAll(): Promise<FlavorsPaginatedResponse> { async getFlavorsAll(): Promise<FlavorsPaginatedResponse> {
return await this.flavorsModel.find(); const flavorsDict = await this.dictModel.findOne({
where: { name: 'flavor' },
});
if (!flavorsDict) {
return [];
}
return this.dictItemModel.find({ where: { dict_id: flavorsDict.id } });
} }
async createFlavors(createFlavorsDTO: CreateFlavorsDTO): Promise<Flavors> { async createFlavors(createFlavorsDTO: CreateFlavorsDTO): Promise<DictItem> {
const { name, unique_key } = createFlavorsDTO; const { title, name } = createFlavorsDTO;
const flavors = new Flavors(); const flavorsDict = await this.dictModel.findOne({
where: { name: 'flavor' },
});
if (!flavorsDict) {
throw new Error('口味字典不存在');
}
const flavors = new DictItem();
flavors.title = title;
flavors.name = name; flavors.name = name;
flavors.unique_key = unique_key; flavors.dict_id = flavorsDict.id;
return await this.flavorsModel.save(flavors); return await this.dictItemModel.save(flavors);
} }
async updateFlavors(id: number, updateFlavors: UpdateFlavorsDTO) { async updateFlavors(id: number, updateFlavors: UpdateFlavorsDTO) {
// 确认产品是否存在 const flavors = await this.dictItemModel.findOneBy({ id });
const flavors = await this.flavorsModel.findOneBy({ id });
if (!flavors) { if (!flavors) {
throw new Error(`口味 ID ${id} 不存在`); throw new Error(`口味 ID ${id} 不存在`);
} }
// 更新产品 await this.dictItemModel.update(id, updateFlavors);
await this.flavorsModel.update(id, updateFlavors); return await this.dictItemModel.findOneBy({ id });
// 返回更新后的产品
return await this.flavorsModel.findOneBy({ id });
} }
async deleteFlavors(id: number): Promise<boolean> { async deleteFlavors(id: number): Promise<boolean> {
// 检查产品是否存在 const flavors = await this.dictItemModel.findOneBy({ id });
const flavors = await this.flavorsModel.findOneBy({ id });
if (!flavors) { if (!flavors) {
throw new Error(`口味 ID ${id} 不存在`); throw new Error(`口味 ID ${id} 不存在`);
} }
// 删除产品 const result = await this.dictItemModel.delete(id);
const result = await this.flavorsModel.delete(id); return result.affected > 0;
return result.affected > 0; // `affected` 表示删除的行数
} }
async hasProductsInStrength(strengthId: number): Promise<boolean> { async hasProductsInStrength(strengthId: number): Promise<boolean> {
const count = await this.productModel.count({ const count = await this.productModel.count({
@ -388,60 +542,82 @@ export class ProductService {
return count > 0; return count > 0;
} }
async hasStrength(name: string, id?: string): Promise<boolean> { async hasStrength(title: string, id?: string): Promise<boolean> {
const where: any = { name }; const strengthDict = await this.dictModel.findOne({
where: { name: 'strength' },
});
if (!strengthDict) {
return false;
}
const where: any = { title, dict_id: strengthDict.id };
if (id) where.id = Not(id); if (id) where.id = Not(id);
const count = await this.strengthModel.count({ const count = await this.dictItemModel.count({
where, where,
}); });
return count > 0; return count > 0;
} }
async getStrengthList( async getStrengthList(
pagination: PaginationParams, pagination: PaginationParams,
name?: string title?: string
): Promise<StrengthPaginatedResponse> { ): Promise<StrengthPaginatedResponse> {
const where: any = {}; const strengthDict = await this.dictModel.findOne({
if (name) { where: { name: 'strength' },
where.name = Like(`%${name}%`); });
if (!strengthDict) {
return {
items: [],
total: 0,
...pagination,
};
} }
return await paginate(this.strengthModel, { pagination, where }); const where: any = { dict_id: strengthDict.id };
if (title) {
where.title = Like(`%${title}%`);
}
return await paginate(this.dictItemModel, { pagination, where });
} }
async getStrengthAll(): Promise<StrengthPaginatedResponse> { async getStrengthAll(): Promise<StrengthPaginatedResponse> {
return await this.strengthModel.find(); const strengthDict = await this.dictModel.findOne({
where: { name: 'strength' },
});
if (!strengthDict) {
return [];
}
return this.dictItemModel.find({ where: { dict_id: strengthDict.id } });
} }
async createStrength( async createStrength(createStrengthDTO: CreateStrengthDTO): Promise<DictItem> {
createStrengthDTO: CreateStrengthDTO const { title, name } = createStrengthDTO;
): Promise<Strength> { const strengthDict = await this.dictModel.findOne({
const { name, unique_key } = createStrengthDTO; where: { name: 'strength' },
const strength = new Strength(); });
if (!strengthDict) {
throw new Error('规格字典不存在');
}
const strength = new DictItem();
strength.title = title;
strength.name = name; strength.name = name;
strength.unique_key = unique_key; strength.dict_id = strengthDict.id;
return await this.strengthModel.save(strength); return await this.dictItemModel.save(strength);
} }
async updateStrength(id: number, updateStrength: UpdateStrengthDTO) { async updateStrength(id: number, updateStrength: UpdateStrengthDTO) {
// 确认产品是否存在 const strength = await this.dictItemModel.findOneBy({ id });
const strength = await this.strengthModel.findOneBy({ id });
if (!strength) { if (!strength) {
throw new Error(`口味 ID ${id} 不存在`); throw new Error(`规格 ID ${id} 不存在`);
} }
// 更新产品 await this.dictItemModel.update(id, updateStrength);
await this.strengthModel.update(id, updateStrength); return await this.dictItemModel.findOneBy({ id });
// 返回更新后的产品
return await this.strengthModel.findOneBy({ id });
} }
async deleteStrength(id: number): Promise<boolean> { async deleteStrength(id: number): Promise<boolean> {
// 检查产品是否存在 const strength = await this.dictItemModel.findOneBy({ id });
const strength = await this.strengthModel.findOneBy({ id });
if (!strength) { if (!strength) {
throw new Error(`口味 ID ${id} 不存在`); throw new Error(`规格 ID ${id} 不存在`);
} }
// 删除产品 const result = await this.dictItemModel.delete(id);
const result = await this.flavorsModel.delete(id); return result.affected > 0;
return result.affected > 0; // `affected` 表示删除的行数
} }
async batchSetSku(skus: { productId: number; sku: string }[]) { async batchSetSku(skus: { productId: number; sku: string }[]) {