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/validate": "^3.20.2",
"@woocommerce/woocommerce-rest-api": "^1.0.2",
"axios": "^1.7.9",
"axios": "^1.13.2",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"dayjs": "^1.11.13",
"mysql2": "^3.11.5",
"nodemailer": "^7.0.5",
"npm-check-updates": "^19.1.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"
},
"engines": {
@ -36,10 +39,13 @@
"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",
"lint": "mwtsc check",
"lint:fix": "mwtsc fix",
"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": {
"type": "git",
@ -51,6 +57,7 @@
"@midwayjs/mock": "^3.20.11",
"cross-env": "^10.1.0",
"mwtsc": "^1.15.2",
"tsx": "^4.20.6",
"typescript": "^5.9.3"
}
}

View File

@ -65,12 +65,18 @@ importers:
nodemailer:
specifier: ^7.0.5
version: 7.0.10
npm-check-updates:
specifier: ^19.1.2
version: 19.1.2
swagger-ui-dist:
specifier: ^5.18.2
version: 5.30.2
typeorm:
specifier: ^0.3.20
version: 0.3.27(mysql2@3.15.3)(reflect-metadata@0.2.2)
xlsx:
specifier: ^0.18.5
version: 0.18.5
xml2js:
specifier: ^0.6.2
version: 0.6.2
@ -84,6 +90,9 @@ importers:
mwtsc:
specifier: ^1.15.2
version: 1.15.2
tsx:
specifier: ^4.20.6
version: 4.20.6
typescript:
specifier: ^5.9.3
version: 5.9.3
@ -97,6 +106,162 @@ packages:
'@epic-web/invariant@1.0.0':
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':
resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
@ -314,6 +479,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
adler-32@1.3.1:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@ -415,6 +584,10 @@ packages:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'}
cfb@1.2.2:
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
engines: {node: '>=0.8'}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@ -446,6 +619,10 @@ packages:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
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:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -488,6 +665,11 @@ packages:
core-util-is@1.0.3:
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:
resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
@ -606,6 +788,11 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
esbuild@0.25.12:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'}
hasBin: true
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@ -651,6 +838,10 @@ packages:
formidable@2.1.5:
resolution: {integrity: sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==}
frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'}
fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
@ -961,6 +1152,11 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
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:
resolution: {integrity: sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==}
@ -1160,6 +1356,10 @@ packages:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'}
ssf@0.11.2:
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
engines: {node: '>=0.8'}
statuses@1.5.0:
resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
engines: {node: '>= 0.6'}
@ -1228,6 +1428,11 @@ packages:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
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:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
@ -1327,6 +1532,14 @@ packages:
engines: {node: '>= 8'}
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:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@ -1338,6 +1551,11 @@ packages:
wrappy@1.0.2:
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:
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
engines: {node: '>=4.0.0'}
@ -1373,6 +1591,84 @@ snapshots:
'@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/hoek@9.3.0': {}
@ -1645,6 +1941,8 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
adler-32@1.3.1: {}
ansi-regex@5.0.1: {}
ansi-regex@6.2.2: {}
@ -1735,6 +2033,11 @@ snapshots:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0
cfb@1.2.2:
dependencies:
adler-32: 1.3.1
crc-32: 1.2.2
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
@ -1779,6 +2082,8 @@ snapshots:
co@4.6.0: {}
codepage@1.15.0: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@ -1812,6 +2117,8 @@ snapshots:
core-util-is@1.0.3: {}
crc-32@1.2.2: {}
create-hash@1.2.0:
dependencies:
cipher-base: 1.0.7
@ -1919,6 +2226,35 @@ snapshots:
has-tostringtag: 1.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: {}
escape-html@1.0.3: {}
@ -1967,6 +2303,8 @@ snapshots:
once: 1.4.0
qs: 6.14.0
frac@1.1.2: {}
fresh@0.5.2: {}
fsevents@2.3.3:
@ -2313,6 +2651,8 @@ snapshots:
normalize-path@3.0.0: {}
npm-check-updates@19.1.2: {}
oauth-1.0a@2.2.6: {}
object-inspect@1.13.4: {}
@ -2494,6 +2834,10 @@ snapshots:
sqlstring@2.3.3: {}
ssf@0.11.2:
dependencies:
frac: 1.1.2
statuses@1.5.0: {}
statuses@2.0.1: {}
@ -2582,6 +2926,13 @@ snapshots:
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:
dependencies:
media-typer: 0.3.0
@ -2647,6 +2998,10 @@ snapshots:
dependencies:
isexe: 2.0.0
wmf@1.0.2: {}
word@0.3.0: {}
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@ -2661,6 +3016,16 @@ snapshots:
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:
dependencies:
sax: 1.4.3

View File

@ -1,6 +1,5 @@
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';
@ -11,10 +10,10 @@ 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 { 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_retund_item.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';
@ -26,14 +25,14 @@ 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';
import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity';
export default {
// use for cookie sign key, should change to your own and keep security
@ -42,9 +41,6 @@ export default {
default: {
entities: [
Product,
Category,
Strength,
Flavors,
WpProduct,
Variation,
User,
@ -76,6 +72,8 @@ export default {
AuthCode,
Subscription,
Site,
Dict,
DictItem,
],
synchronize: true,
logging: false,

View File

@ -13,15 +13,15 @@ import { ProductService } from '../service/product.service';
import { errorResponse, successResponse } from '../utils/response.util';
import {
BatchSetSkuDTO,
CreateCategoryDTO,
CreateBrandDTO,
CreateFlavorsDTO,
CreateProductDTO,
CreateStrengthDTO,
QueryCategoryDTO,
QueryBrandDTO,
QueryFlavorsDTO,
QueryProductDTO,
QueryStrengthDTO,
UpdateCategoryDTO,
UpdateBrandDTO,
UpdateFlavorsDTO,
UpdateProductDTO,
UpdateStrengthDTO,
@ -29,8 +29,8 @@ import {
import { ApiOkResponse } from '@midwayjs/swagger';
import {
BooleanRes,
ProductCatListRes,
ProductCatRes,
ProductBrandListRes,
ProductBrandRes,
ProductListRes,
ProductRes,
ProductsRes,
@ -79,12 +79,12 @@ export class ProductController {
async getProductList(
@Query() query: QueryProductDTO
): Promise<ProductListRes> {
const { current = 1, pageSize = 10, name, categoryId } = query;
const { current = 1, pageSize = 10, name, brandId } = query;
try {
const data = await this.productService.getProductList(
{ current, pageSize },
name,
categoryId
brandId
);
return successResponse(data);
} catch (error) {
@ -138,8 +138,6 @@ export class ProductController {
}
}
@ApiOkResponse({
type: BooleanRes,
})
@ -154,13 +152,13 @@ export class ProductController {
}
@ApiOkResponse({
type: ProductCatListRes,
type: ProductBrandListRes,
})
@Get('/categories')
async getCategories(@Query() query: QueryCategoryDTO) {
@Get('/brands')
async getBrands(@Query() query: QueryBrandDTO) {
const { current = 1, pageSize = 10, name } = query;
try {
let data = await this.productService.getCategoryList(
let data = await this.productService.getBrandList(
{ current, pageSize },
name
);
@ -171,10 +169,10 @@ export class ProductController {
}
@ApiOkResponse()
@Get('/categorieAll')
async getCategorieAll() {
@Get('/brandAll')
async getBrandAll() {
try {
let data = await this.productService.getCategoryAll();
let data = await this.productService.getBrandAll();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
@ -182,18 +180,18 @@ export class ProductController {
}
@ApiOkResponse({
type: ProductCatRes,
type: ProductBrandRes,
})
@Post('/category')
async createCategory(@Body() categoryData: CreateCategoryDTO) {
@Post('/brand')
async createBrand(@Body() brandData: CreateBrandDTO) {
try {
const hasCategory = await this.productService.hasCategory(
categoryData.name
const hasBrand = await this.productService.hasBrand(
brandData.name
);
if (hasCategory) {
return errorResponse('分类已存在');
if (hasBrand) {
return errorResponse('品牌已存在');
}
let data = await this.productService.createCategory(categoryData);
let data = await this.productService.createBrand(brandData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
@ -201,21 +199,21 @@ export class ProductController {
}
@ApiOkResponse({
type: ProductCatRes,
type: ProductBrandRes,
})
@Put('/category/:id')
async updateCategory(
@Put('/brand/:id')
async updateBrand(
@Param('id') id: number,
@Body() categoryData: UpdateCategoryDTO
@Body() brandData: UpdateBrandDTO
) {
try {
const hasCategory = await this.productService.hasCategory(
categoryData.name
const hasBrand = await this.productService.hasBrand(
brandData.name
);
if (hasCategory) {
return errorResponse('分类已存在');
if (hasBrand) {
return errorResponse('品牌已存在');
}
const data = this.productService.updateCategory(id, categoryData);
const data = this.productService.updateBrand(id, brandData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
@ -225,12 +223,12 @@ export class ProductController {
@ApiOkResponse({
type: BooleanRes,
})
@Del('/category/:id')
async deleteCategory(@Param('id') id: number) {
@Del('/brand/:id')
async deleteBrand(@Param('id') id: number) {
try {
const hasProducts = await this.productService.hasProductsInCategory(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除');
const data = await this.productService.deleteCategory(id);
const hasProducts = await this.productService.hasProductsInBrand(id);
if (hasProducts) throw new Error('该品牌下有商品,无法删除');
const data = await this.productService.deleteBrand(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
@ -283,7 +281,7 @@ export class ProductController {
try {
const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
if (hasFlavors) {
return errorResponse('分类已存在');
return errorResponse('口味已存在');
}
let data = await this.productService.createFlavors(flavorsData);
return successResponse(data);
@ -301,7 +299,7 @@ export class ProductController {
try {
const hasFlavors = await this.productService.hasFlavors(flavorsData.name);
if (hasFlavors) {
return errorResponse('分类已存在');
return errorResponse('口味已存在');
}
const data = this.productService.updateFlavors(id, flavorsData);
return successResponse(data);
@ -317,7 +315,7 @@ export class ProductController {
async deleteFlavors(@Param('id') id: number) {
try {
const hasProducts = await this.productService.hasProductsInFlavors(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除');
if (hasProducts) throw new Error('该口味下有商品,无法删除');
const data = await this.productService.deleteFlavors(id);
return successResponse(data);
} catch (error) {
@ -359,7 +357,7 @@ export class ProductController {
strengthData.name
);
if (hasStrength) {
return errorResponse('分类已存在');
return errorResponse('规格已存在');
}
let data = await this.productService.createStrength(strengthData);
return successResponse(data);
@ -379,7 +377,7 @@ export class ProductController {
strengthData.name
);
if (hasStrength) {
return errorResponse('分类已存在');
return errorResponse('规格已存在');
}
const data = this.productService.updateStrength(id, strengthData);
return successResponse(data);
@ -395,7 +393,7 @@ export class ProductController {
async deleteStrength(@Param('id') id: number) {
try {
const hasProducts = await this.productService.hasProductsInStrength(id);
if (hasProducts) throw new Error('该分类下有商品,无法删除');
if (hasProducts) throw new Error('该规格下有商品,无法删除');
const data = await this.productService.deleteStrength(id);
return successResponse(data);
} 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 { 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
*/
@ -17,17 +27,17 @@ export class CreateProductDTO {
@Rule(RuleType.string())
description: string;
@ApiProperty({ example: '1', description: '分类 ID' })
@Rule(RuleType.number())
categoryId: number;
@ApiProperty({ description: '品牌', type: DictItemDTO })
@Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() }))
brand: DictItemDTO;
@ApiProperty()
@Rule(RuleType.number())
strengthId: number;
@ApiProperty({ description: '规格', type: DictItemDTO })
@Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() }))
strength: DictItemDTO;
@ApiProperty()
@Rule(RuleType.number())
flavorsId: number;
@ApiProperty({ description: '口味', type: DictItemDTO })
@Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() }))
flavor: DictItemDTO;
@ApiProperty()
@Rule(RuleType.string())
@ -59,36 +69,41 @@ export class QueryProductDTO {
@Rule(RuleType.string())
name: string;
@ApiProperty({ example: '1', description: '分类 ID' })
@ApiProperty({ example: '1', description: '品牌 ID' })
@Rule(RuleType.string())
categoryId: number;
brandId: number;
}
/**
* DTO
* DTO
*/
export class CreateCategoryDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
name: string;
export class CreateBrandDTO {
@ApiProperty({ example: 'ZYN', description: '品牌名称', required: true })
@Rule(RuleType.string().required().empty({ message: '品牌名称不能为空' }))
title: string;
@ApiProperty({ example: 'ZYN', description: '品牌唯一标识', required: true })
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string;
name: string;
}
/**
* DTO
* DTO
*/
export class UpdateCategoryDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' })
export class UpdateBrandDTO {
@ApiProperty({ example: 'ZYN', description: '品牌名称' })
@Rule(RuleType.string())
title: string;
@ApiProperty({ example: 'ZYN', description: '品牌唯一标识' })
@Rule(RuleType.string())
name: string;
}
/**
* DTO
* DTO
*/
export class QueryCategoryDTO {
export class QueryBrandDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number; // 页码
@ -98,21 +113,30 @@ export class QueryCategoryDTO {
pageSize: number; // 每页大小
@ApiProperty({ example: 'ZYN', description: '关键字' })
@Rule(RuleType.string())
@Rule(RuleType.string().required())
name: string; // 搜索关键字(支持模糊查询)
}
export class CreateFlavorsDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
name: string;
@ApiProperty({ example: 'WINTERGREEN', description: '口味名称', required: true })
@Rule(RuleType.string().required().empty({ message: '口味名称不能为空' }))
title: string;
@ApiProperty({
example: 'WINTERGREEN',
description: '口味唯一标识',
required: true,
})
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string;
name: string;
}
export class UpdateFlavorsDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' })
@ApiProperty({ example: 'WINTERGREEN', description: '口味名称' })
@Rule(RuleType.string())
title: string;
@ApiProperty({ example: 'WINTERGREEN', description: '口味唯一标识' })
@Rule(RuleType.string())
name: string;
}
@ -132,16 +156,21 @@ export class QueryFlavorsDTO {
}
export class CreateStrengthDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称', required: true })
@Rule(RuleType.string().required().empty({ message: '分类名称不能为空' }))
name: string;
@ApiProperty({ example: '6MG', description: '规格名称', required: false })
@Rule(RuleType.string())
title?: string;
@ApiProperty({ example: '6MG', description: '规格唯一标识', required: true })
@Rule(RuleType.string().required().empty({ message: 'key不能为空' }))
unique_key: string;
name: string;
}
export class UpdateStrengthDTO {
@ApiProperty({ example: 'ZYN', description: '分类名称' })
@ApiProperty({ example: '6MG', description: '规格名称' })
@Rule(RuleType.string())
title: string;
@ApiProperty({ example: '6MG', description: '规格唯一标识' })
@Rule(RuleType.string())
name: string;
}
@ -155,7 +184,7 @@ export class QueryStrengthDTO {
@Rule(RuleType.number())
pageSize: number; // 每页大小
@ApiProperty({ example: 'ZYN', description: '关键字' })
@ApiProperty({ example: 'YOONE', description: '关键字' })
@Rule(RuleType.string())
name: string; // 搜索关键字(支持模糊查询)
}

View File

@ -1,5 +1,4 @@
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';
@ -18,12 +17,11 @@ 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 { OrderRefundItem } from '../entity/order_refund_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';
import { Dict } from '../entity/dict.entity';
export class BooleanRes extends SuccessWrapper(Boolean) {}
//网站配置返回数据
@ -35,18 +33,38 @@ 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 BrandPaginatedResponse extends PaginatedWrapper(Dict) {}
//产品品牌返分页返回数据
export class ProductBrandListRes extends SuccessWrapper(
BrandPaginatedResponse
) {}
//产品分类返所有数据
export class ProductCatAllRes extends SuccessArrayWrapper(Category) {}
//产品分类返回数据
export class ProductCatRes extends SuccessWrapper(Category) {}
//产品品牌返所有数据
export class ProductBrandAllRes extends SuccessArrayWrapper(Dict) {}
//产品品牌返回数据
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(

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

@ -22,7 +22,7 @@ export class OrderCoupon {
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Column( )
@Expose()
siteId: string; // 来源站点唯一标识

View File

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

View File

@ -2,27 +2,27 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('site')
export class Site {
@PrimaryGeneratedColumn({ type: 'int' })
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 255, nullable: true })
@Column({ length: 255, nullable: true })
apiUrl: string;
@Column({ type: 'varchar', length: 255, nullable: true })
@Column({ length: 255, nullable: true })
consumerKey: string;
@Column({ type: 'varchar', length: 255, nullable: true })
@Column({ length: 255, nullable: true })
consumerSecret: string;
@Column({ type: 'varchar', length: 255, unique: true })
@Column({ length: 255, unique: true })
siteName: string;
@Column({ type: 'varchar', length: 32, default: 'woocommerce' })
@Column({ length: 32, default: 'woocommerce' })
type: string; // 平台类型woocommerce | shopyy
@Column({ type: 'varchar', length: 64, nullable: true })
@Column({ length: 64, nullable: true })
skuPrefix: string;
@Column({ type: 'tinyint', default: 0 })
isDisabled: number;
@Column({ default: false })
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 { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity';
import { OrderRefundItem } from '../entity/order_retund_item.entity';
import { OrderCoupon } from '../entity/order_copon.entity';
import { OrderRefundItem } from '../entity/order_refund_item.entity';
import { OrderCoupon } from '../entity/order_coupon.entity';
import { OrderShipping } from '../entity/order_shipping.entity';
import { Shipment } from '../entity/shipment.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 { Product } from '../entity/product.entity';
import { Category } from '../entity/category.entity';
import { paginate } from '../utils/paginate.util';
import { PaginationParams } from '../interface';
import {
CreateCategoryDTO,
CreateBrandDTO,
CreateFlavorsDTO,
CreateProductDTO,
CreateStrengthDTO,
UpdateCategoryDTO,
UpdateBrandDTO,
UpdateFlavorsDTO,
UpdateProductDTO,
UpdateStrengthDTO,
} from '../dto/product.dto';
import {
CategoryPaginatedResponse,
BrandPaginatedResponse,
FlavorsPaginatedResponse,
ProductPaginatedResponse,
StrengthPaginatedResponse,
@ -23,22 +22,23 @@ import {
import { InjectEntityModel } from '@midwayjs/typeorm';
import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity';
import { Strength } from '../entity/strength.entity';
import { Flavors } from '../entity/flavors.entity';
import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity';
import { Context } from '@midwayjs/koa';
@Provide()
export class ProductService {
@Inject()
ctx: Context;
@InjectEntityModel(Product)
productModel: Repository<Product>;
@InjectEntityModel(Category)
categoryModel: Repository<Category>;
@InjectEntityModel(Dict)
dictModel: Repository<Dict>;
@InjectEntityModel(Strength)
strengthModel: Repository<Strength>;
@InjectEntityModel(Flavors)
flavorsModel: Repository<Flavors>;
@InjectEntityModel(DictItem)
dictItemModel: Repository<DictItem>;
@InjectEntityModel(WpProduct)
wpProductModel: Repository<WpProduct>;
@ -111,15 +111,42 @@ export class ProductService {
async getProductList(
pagination: PaginationParams,
name?: string,
categoryId?: number
brandId?: number
): Promise<ProductPaginatedResponse> {
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
.createQueryBuilder('product')
.leftJoin(Category, 'category', 'category.id = product.categoryId')
.leftJoin(Strength, 'strength', 'strength.id = product.strengthId')
.leftJoin(Flavors, 'flavors', 'flavors.id = product.flavorsId')
.leftJoin(
DictItem,
'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([
'product.id as id',
'product.name as name',
@ -129,9 +156,9 @@ export class ProductService {
'product.sku as sku',
'product.createdAt as createdAt',
'product.updatedAt as updatedAt',
'category.name AS categoryName',
'strength.name AS strengthName',
'flavors.name AS flavorsName',
'brand.title AS brandName',
'flavor.title AS flavorsName',
'strength.title AS strengthName',
]);
// 模糊搜索 name支持多个关键词
@ -141,9 +168,9 @@ export class ProductService {
});
});
// 分类过滤
if (categoryId) {
qb.andWhere('product.categoryId = :categoryId', { categoryId });
// 品牌过滤
if (brandId) {
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> {
const { name, description, categoryId, strengthId, flavorsId, humidity } =
const { name, description, brand, flavor, strength, humidity } =
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({
where: {
categoryId,
strengthId,
flavorsId,
brandId: brandItem.id,
flavorsId: flavorItem.id,
strengthId: strengthItem.id,
humidity,
},
});
if (isExit) throw new Error('产品已存在');
// 创建新产品实例
const product = new Product();
product.name = name;
product.description = description;
product.categoryId = categoryId;
product.strengthId = strengthId;
product.flavorsId = flavorsId;
product.brandId = brandItem.id;
product.flavorsId = flavorItem.id;
product.strengthId = strengthItem.id;
product.humidity = humidity;
const categoryKey = (
await this.categoryModel.findOne({ where: { id: categoryId } })
).unique_key;
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}`;
// 生成 SKU
product.sku = `${brandItem.name}-${flavorItem.name}-${strengthItem.name}-${humidity}`;
// 保存产品
return await this.productModel.save(product);
}
@ -257,67 +328,126 @@ export class ProductService {
return result.affected > 0; // `affected` 表示删除的行数
}
async hasProductsInCategory(categoryId: number): Promise<boolean> {
async hasProductsInBrand(brandId: number): Promise<boolean> {
// 检查是否有产品属于该品牌
const count = await this.productModel.count({
where: { categoryId },
where: { brandId },
});
return count > 0;
}
async hasCategory(name: string, id?: string): Promise<boolean> {
const where: any = { name };
async hasBrand(title: string, id?: number): Promise<boolean> {
// 查找 '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);
const count = await this.categoryModel.count({
// 统计数量
const count = await this.dictItemModel.count({
where,
});
return count > 0;
}
async getCategoryList(
async getBrandList(
pagination: PaginationParams,
name?: string
): Promise<CategoryPaginatedResponse> {
const where: any = {};
if (name) {
where.name = Like(`%${name}%`);
title?: string
): Promise<BrandPaginatedResponse> {
// 查找 'brand' 字典
const brandDict = await this.dictModel.findOne({
where: { name: 'brand' },
});
// 如果字典不存在,则返回空
if (!brandDict) {
return {
items: [],
total: 0,
...pagination,
};
}
return await paginate(this.categoryModel, { pagination, where });
}
async getCategoryAll(): Promise<CategoryPaginatedResponse> {
return await this.categoryModel.find();
}
async createCategory(
createCategoryDTO: CreateCategoryDTO
): 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) {
// 确认产品是否存在
const category = await this.categoryModel.findOneBy({ id });
if (!category) {
throw new Error(`产品分类 ID ${id} 不存在`);
// 设置查询条件
const where: any = { dict_id: brandDict.id };
if (title) {
where.title = Like(`%${title}%`);
}
// 更新产品
await this.categoryModel.update(id, updateCategory);
// 返回更新后的产品
return await this.categoryModel.findOneBy({ id });
// 分页查询
return await paginate(this.dictItemModel, { pagination, where });
}
async deleteCategory(id: number): Promise<boolean> {
// 检查产品是否存在
const category = await this.categoryModel.findOneBy({ id });
if (!category) {
throw new Error(`产品分类 ID ${id} 不存在`);
async getBrandAll(): Promise<BrandPaginatedResponse> {
// 查找 'brand' 字典
const brandDict = await this.dictModel.findOne({
where: { name: 'brand' },
});
// 如果字典不存在,则返回空数组
if (!brandDict) {
return [];
}
// 删除产品
const result = await this.categoryModel.delete(id);
// 返回所有品牌
return this.dictItemModel.find({ where: { dict_id: brandDict.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` 表示删除的行数
}
@ -328,58 +458,82 @@ export class ProductService {
return count > 0;
}
async hasFlavors(name: string, id?: string): Promise<boolean> {
const where: any = { name };
async hasFlavors(title: string, id?: string): Promise<boolean> {
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);
const count = await this.flavorsModel.count({
const count = await this.dictItemModel.count({
where,
});
return count > 0;
}
async getFlavorsList(
pagination: PaginationParams,
name?: string
title?: string
): Promise<FlavorsPaginatedResponse> {
const where: any = {};
if (name) {
where.name = Like(`%${name}%`);
const flavorsDict = await this.dictModel.findOne({
where: { name: 'flavor' },
});
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> {
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> {
const { name, unique_key } = createFlavorsDTO;
const flavors = new Flavors();
async createFlavors(createFlavorsDTO: CreateFlavorsDTO): Promise<DictItem> {
const { title, name } = createFlavorsDTO;
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.unique_key = unique_key;
return await this.flavorsModel.save(flavors);
flavors.dict_id = flavorsDict.id;
return await this.dictItemModel.save(flavors);
}
async updateFlavors(id: number, updateFlavors: UpdateFlavorsDTO) {
// 确认产品是否存在
const flavors = await this.flavorsModel.findOneBy({ id });
const flavors = await this.dictItemModel.findOneBy({ id });
if (!flavors) {
throw new Error(`口味 ID ${id} 不存在`);
}
// 更新产品
await this.flavorsModel.update(id, updateFlavors);
// 返回更新后的产品
return await this.flavorsModel.findOneBy({ id });
await this.dictItemModel.update(id, updateFlavors);
return await this.dictItemModel.findOneBy({ id });
}
async deleteFlavors(id: number): Promise<boolean> {
// 检查产品是否存在
const flavors = await this.flavorsModel.findOneBy({ id });
const flavors = await this.dictItemModel.findOneBy({ id });
if (!flavors) {
throw new Error(`口味 ID ${id} 不存在`);
}
// 删除产品
const result = await this.flavorsModel.delete(id);
return result.affected > 0; // `affected` 表示删除的行数
const result = await this.dictItemModel.delete(id);
return result.affected > 0;
}
async hasProductsInStrength(strengthId: number): Promise<boolean> {
const count = await this.productModel.count({
@ -388,60 +542,82 @@ export class ProductService {
return count > 0;
}
async hasStrength(name: string, id?: string): Promise<boolean> {
const where: any = { name };
async hasStrength(title: string, id?: string): Promise<boolean> {
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);
const count = await this.strengthModel.count({
const count = await this.dictItemModel.count({
where,
});
return count > 0;
}
async getStrengthList(
pagination: PaginationParams,
name?: string
title?: string
): Promise<StrengthPaginatedResponse> {
const where: any = {};
if (name) {
where.name = Like(`%${name}%`);
const strengthDict = await this.dictModel.findOne({
where: { name: 'strength' },
});
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> {
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(
createStrengthDTO: CreateStrengthDTO
): Promise<Strength> {
const { name, unique_key } = createStrengthDTO;
const strength = new Strength();
async createStrength(createStrengthDTO: CreateStrengthDTO): Promise<DictItem> {
const { title, name } = createStrengthDTO;
const strengthDict = await this.dictModel.findOne({
where: { name: 'strength' },
});
if (!strengthDict) {
throw new Error('规格字典不存在');
}
const strength = new DictItem();
strength.title = title;
strength.name = name;
strength.unique_key = unique_key;
return await this.strengthModel.save(strength);
strength.dict_id = strengthDict.id;
return await this.dictItemModel.save(strength);
}
async updateStrength(id: number, updateStrength: UpdateStrengthDTO) {
// 确认产品是否存在
const strength = await this.strengthModel.findOneBy({ id });
const strength = await this.dictItemModel.findOneBy({ id });
if (!strength) {
throw new Error(`口味 ID ${id} 不存在`);
throw new Error(`规格 ID ${id} 不存在`);
}
// 更新产品
await this.strengthModel.update(id, updateStrength);
// 返回更新后的产品
return await this.strengthModel.findOneBy({ id });
await this.dictItemModel.update(id, updateStrength);
return await this.dictItemModel.findOneBy({ id });
}
async deleteStrength(id: number): Promise<boolean> {
// 检查产品是否存在
const strength = await this.strengthModel.findOneBy({ id });
const strength = await this.dictItemModel.findOneBy({ id });
if (!strength) {
throw new Error(`口味 ID ${id} 不存在`);
throw new Error(`规格 ID ${id} 不存在`);
}
// 删除产品
const result = await this.flavorsModel.delete(id);
return result.affected > 0; // `affected` 表示删除的行数
const result = await this.dictItemModel.delete(id);
return result.affected > 0;
}
async batchSetSku(skus: { productId: number; sku: string }[]) {