diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4c7f8a8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# 🎨 editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..8d20e22 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": "./node_modules/mwts/", + "ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"], + "env": { + "jest": true + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c492891 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +logs/ +npm-debug.log +yarn-error.log +node_modules/ +coverage/ +dist/ +.idea/ +run/ +.DS_Store +*.sw* +*.un~ +.tsbuildinfo +.tsbuildinfo.* +yarn.lock +**/config.prod.ts \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..b964930 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('mwts/.prettierrc.json') +} diff --git a/bootstrap.js b/bootstrap.js new file mode 100644 index 0000000..e0c9aa3 --- /dev/null +++ b/bootstrap.js @@ -0,0 +1,2 @@ +const { Bootstrap } = require('@midwayjs/bootstrap') +Bootstrap.run() diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..c5bd388 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/test/fixtures'], + coveragePathIgnorePatterns: ['/test/'], +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..3310d10 --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "my-midway-project", + "version": "1.0.0", + "description": "", + "private": true, + "dependencies": { + "@midwayjs/bootstrap": "^3.20.0", + "@midwayjs/core": "^3.20.0", + "@midwayjs/cron": "^3.20.0", + "@midwayjs/cross-domain": "^3.20.2", + "@midwayjs/decorator": "^3.20.0", + "@midwayjs/info": "^3.20.2", + "@midwayjs/jwt": "^3.20.2", + "@midwayjs/koa": "^3.20.2", + "@midwayjs/logger": "^3.1.0", + "@midwayjs/swagger": "^3.20.2", + "@midwayjs/typeorm": "^3.20.0", + "@midwayjs/validate": "^3.20.2", + "axios": "^1.7.9", + "bcryptjs": "^2.4.3", + "class-transformer": "^0.5.1", + "dayjs": "^1.11.13", + "mysql2": "^3.11.5", + "swagger-ui-dist": "^5.18.2", + "typeorm": "^0.3.20", + "xml2js": "^0.6.2" + }, + "devDependencies": { + "@midwayjs/mock": "^3.20.0", + "@types/jest": "^29.2.0", + "@types/node": "14", + "cross-env": "^6.0.0", + "jest": "^29.2.2", + "mwts": "^1.3.0", + "mwtsc": "^1.4.0", + "ts-jest": "^29.0.3", + "typescript": "~4.8.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "scripts": { + "start": "cross-env NODE_ENV=prod pm2 start ./bootstrap.js --name yoone -i 4", + "prod": "cross-env NODE_ENV=prod node ./bootstrap.js ", + "dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js", + "test": "cross-env NODE_ENV=unittest jest", + "cov": "jest --coverage", + "lint": "mwts check", + "lint:fix": "mwts fix", + "ci": "npm run cov", + "build": "mwtsc --cleanOutDir" + }, + "repository": { + "type": "git", + "url": "" + }, + "author": "anonymous", + "license": "MIT" +} diff --git a/src/config/config.default.ts b/src/config/config.default.ts new file mode 100644 index 0000000..f4e6d34 --- /dev/null +++ b/src/config/config.default.ts @@ -0,0 +1,101 @@ +import { MidwayConfig } from '@midwayjs/core'; +import { Product } from '../entity/product.entty'; +import { Category } from '../entity/category.entity'; +import { WpProduct } from '../entity/wp_product.entity'; +import { Variation } from '../entity/variation.entity'; +import { User } from '../entity/user.entity'; +import { PurchaseOrder } from '../entity/purchase_order.entity'; +import { PurchaseOrderItem } from '../entity/purchase_order_item.entity'; +import { Stock } from '../entity/stock.entity'; +import { StockPoint } from '../entity/stock_point.entity'; +import { StockRecord } from '../entity/stock_record.entity'; +import { Order } from '../entity/order.entity'; +import { OrderItem } from '../entity/order_item.entity'; +import { OrderCoupon } from '../entity/order_copon.entity'; +import { OrderFee } from '../entity/order_fee.entity'; +import { OrderRefund } from '../entity/order_refund.entity'; +import { OrderRefundItem } from '../entity/order_retund_item.entity'; +import { OrderSale } from '../entity/order_sale.entity'; +import { OrderShipping } from '../entity/order_shipping.entity'; +import { Service } from '../entity/service.entity'; +import { ShippingAddress } from '../entity/shipping_address.entity'; +import { OrderNote } from '../entity/order_note.entity'; +import { OrderShipment } from '../entity/order_shipment.entity'; +import { Shipment } from '../entity/shipment.entity'; +import { ShipmentItem } from '../entity/shipment_item.entity'; +import { Transfer } from '../entity/transfer.entity'; +import { TransferItem } from '../entity/transfer_item.entity'; +import { Strength } from '../entity/strength.entity'; +import { Flavors } from '../entity/flavors.entity'; + +export default { + // use for cookie sign key, should change to your own and keep security + keys: '1733728588817_720', + typeorm: { + default: { + entities: [ + Product, + Category, + Strength, + Flavors, + WpProduct, + Variation, + User, + PurchaseOrder, + PurchaseOrderItem, + Stock, + StockPoint, + StockRecord, + Order, + OrderItem, + OrderCoupon, + OrderFee, + OrderRefund, + OrderRefundItem, + OrderSale, + OrderShipment, + ShipmentItem, + Shipment, + OrderShipping, + Service, + ShippingAddress, + OrderNote, + Transfer, + TransferItem, + ], + synchronize: true, + logging: false, + }, + dataSource: { + default: { + type: 'mysql', + host: 'localhost', + port: 3306, + username: 'root', + password: 'root', + database: 'inventory', + }, + }, + }, + // cors: { + // origin: '*', // 允许所有来源跨域请求 + // allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法 + // allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头 + // credentials: true, // 允许携带凭据(cookies等) + // }, + // jwt: { + // secret: 'YOONE2024!@abc', + // expiresIn: '7d', + // }, + // wpSite: [ + // { + // id: '2', + // wpApiUrl: 'http://localhost:10004', + // consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652', + // consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38', + // siteName: 'Local', + // email: 'tom@yoonevape.com', + // emailPswd: '', + // }, + // ], +} as MidwayConfig; diff --git a/src/config/config.local.ts b/src/config/config.local.ts new file mode 100644 index 0000000..fde05be --- /dev/null +++ b/src/config/config.local.ts @@ -0,0 +1,52 @@ +import { MidwayConfig } from '@midwayjs/core'; +export default { + koa: { + port: 7001, + }, + typeorm: { + dataSource: { + default: { + host: '127.0.0.1', + username: 'root', + password: '123456', + }, + }, + }, + cors: { + origin: '*', // 允许所有来源跨域请求 + allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法 + allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头 + credentials: true, // 允许携带凭据(cookies等) + }, + jwt: { + secret: 'YOONE2024!@abc', + expiresIn: '7d', + }, + wpSite: [ + { + id: '-1', + siteName: 'Admin', + email: 'tom@yoonevape.com', + }, + { + id: '2', + wpApiUrl: 'http://localhost:10004', + consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652', + consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38', + siteName: 'Local', + email: 'tom@yoonevape.com', + emailPswd: 'lulin91.', + }, + ], + freightcom: { + url: 'https://customer-external-api.ssd-test.freightcom.com', + token: '6zGj1qPTL1jIkbLmgaiYc6SwHUIXJ2t25htUF8uuFYiCg8ILCY6xnBEbvrX1p79L', + }, + canadaPost: { + url: 'https://ct.soa-gw.canadapost.ca', + username: '65d23d3a75d7baf7', + password: '56443bb98b68dfdd60f52e', + customerNumber: '0006122480', + contractId: '0044168528', + }, +} as MidwayConfig; diff --git a/src/config/config.unittest.ts b/src/config/config.unittest.ts new file mode 100644 index 0000000..423dfcd --- /dev/null +++ b/src/config/config.unittest.ts @@ -0,0 +1,7 @@ +import { MidwayConfig } from '@midwayjs/core'; + +export default { + koa: { + port: null, + }, +} as MidwayConfig; diff --git a/src/configuration.ts b/src/configuration.ts new file mode 100644 index 0000000..8205c4b --- /dev/null +++ b/src/configuration.ts @@ -0,0 +1,78 @@ +import { + Configuration, + App, + Inject, + MidwayDecoratorService, +} from '@midwayjs/core'; +import * as koa from '@midwayjs/koa'; +import * as validate from '@midwayjs/validate'; +import * as info from '@midwayjs/info'; +import * as orm from '@midwayjs/typeorm'; +import { join } from 'path'; +// import { DefaultErrorFilter } from './filter/default.filter'; +// import { NotFoundFilter } from './filter/notfound.filter'; +import { ReportMiddleware } from './middleware/report.middleware'; +import * as swagger from '@midwayjs/swagger'; +import * as crossDomain from '@midwayjs/cross-domain'; +import * as cron from '@midwayjs/cron'; +import * as jwt from '@midwayjs/jwt'; +import { USER_KEY } from './decorator/user.decorator'; +import { AuthMiddleware } from './middleware/auth.middleware'; + +@Configuration({ + imports: [ + koa, + validate, + { + component: info, + enabledEnvironment: ['local', 'prod'], + }, + orm, + swagger, + crossDomain, + cron, + jwt, + ], + importConfigs: [join(__dirname, './config')], +}) +export class MainConfiguration { + @App('koa') + app: koa.Application; + + @Inject() + decoratorService: MidwayDecoratorService; + + @Inject() + jwtService: jwt.JwtService; // 注入 JwtService 实例 + + async onReady() { + // add middleware + this.app.useMiddleware([ReportMiddleware, AuthMiddleware]); + // add filter + // this.app.useFilter([NotFoundFilter, DefaultErrorFilter]); + + this.decoratorService.registerParameterHandler( + USER_KEY, + async ( + options + ): Promise<{ + id: number; + userName: string; + }> => { + const ctx = options.originArgs[0]; + const token = ctx.headers['authorization']?.split(' ')[1]; + const config = ctx.app.getConfig('jwt'); // 动态获取配置项 + if (!token) { + ctx.throw(401, 'Token not found'); + } + + try { + const decoded: any = this.jwtService.verify(token, config.secret); // 替换为你的密钥 + return decoded; + } catch (error) { + ctx.throw(401, 'Invalid token'); + } + } + ); + } +} diff --git a/src/controller/api.controller.ts b/src/controller/api.controller.ts new file mode 100644 index 0000000..cb10a70 --- /dev/null +++ b/src/controller/api.controller.ts @@ -0,0 +1,8 @@ +import { Inject, Controller } from '@midwayjs/core'; +import { Context } from '@midwayjs/koa'; + +@Controller('/') +export class APIController { + @Inject() + ctx: Context; +} diff --git a/src/controller/logistics.controller.ts b/src/controller/logistics.controller.ts new file mode 100644 index 0000000..4e2435b --- /dev/null +++ b/src/controller/logistics.controller.ts @@ -0,0 +1,227 @@ +import { + Inject, + Controller, + Post, + Body, + Get, + Put, + Param, + Del, + Query, +} from '@midwayjs/core'; +import { Context } from '@midwayjs/koa'; +import { ApiOkResponse } from '@midwayjs/swagger'; +import { + BooleanRes, + RateLitRes, + ServiceListRes, + ShippingAddressListRes, +} from '../dto/reponse.dto'; +import { FreightcomService } from '../service/freightcom.service'; +import { errorResponse, successResponse } from '../utils/response.util'; +import { LogisticsService } from '../service/logistics.service'; +import { ShippingDetailsDTO } from '../dto/freightcom.dto'; +import { ShippingAddress } from '../entity/shipping_address.entity'; +import { QueryServiceDTO, ShipmentBookDTO } from '../dto/logistics.dto'; +import { User } from '../decorator/user.decorator'; + +@Controller('/logistics') +export class LogisticsController { + @Inject() + ctx: Context; + + @Inject() + freightcomService: FreightcomService; + + @Inject() + logisticsService: LogisticsService; + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/syncServices') + async syncServices() { + try { + await this.freightcomService.syncServices(); + return successResponse(true); + } catch (error) { + return errorResponse('同步失败'); + } + } + + @ApiOkResponse({ + description: '服务商列表', + type: ServiceListRes, + }) + @Get('/getServiceList') + async getServiceList( + @Query() + param: QueryServiceDTO + ) { + try { + const data = await this.logisticsService.getServiceList(param); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/toggleActive') + async toggleActive(@Body() body: { id: string; isActive: boolean }) { + try { + await this.logisticsService.toggleServiceActive(body.id, body.isActive); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ + type: RateLitRes, + }) + @Post('/getRateList') + async getRateList(@Body() details: ShippingDetailsDTO) { + try { + const rates = await this.logisticsService.getRateList(details); + return successResponse(rates); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/createShippingAddress') + async createShippingAddress(@Body() shippingAddress: ShippingAddress) { + try { + await this.logisticsService.createShippingAddress(shippingAddress); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Put('/updateShippingAddress/:id') + async updateShippingAddress( + @Body() shippingAddress: ShippingAddress, + @Param('id') id: number + ) { + try { + await this.logisticsService.updateShippingAddress(id, shippingAddress); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || '更新失败'); + } + } + + @ApiOkResponse({ + type: ShippingAddressListRes, + }) + @Get('/getShippingAddressList') + async getShippingAddressList() { + try { + const data = await this.logisticsService.getShippingAddressList(); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Del('/delShippingAddress/:id') + async delShippingAddress(@Param('id') id: number) { + try { + const boolen = await this.logisticsService.delShippingAddress(id); + return successResponse(boolen); + } catch (error) { + return errorResponse(error?.message || '删除失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/createShipment/:orderId') + async createShipment( + @Param('orderId') orderId: number, + @Body() data: ShipmentBookDTO, + @User() user + ) { + try { + await this.logisticsService.createShipment(orderId, data, user.id); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Post('/getPaymentMethods') + async getpaymentmethods() { + try { + const data = await this.freightcomService.getPaymentMethods(); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Del('/shipment/:id') + async delShipment(@Param('id') id: string, @User() user) { + try { + const data = await this.logisticsService.delShipment(id, user.id); + return successResponse(data); + } catch (error) { + console.log(error); + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/getTrackingNumber') + async getTrackingNumber(@Query('number') number: string) { + try { + return successResponse( + await this.logisticsService.getTrackingNumber(number) + ); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/getListByTrackingId') + async getListByTrackingId(@Query('shipment_id') shipment_id: string) { + try { + return successResponse( + await this.logisticsService.getListByTrackingId(shipment_id) + ); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ + description: '物流列表', + }) + @Get('/list') + async getList( + @Query() + param: Record + ) { + try { + const data = await this.logisticsService.getList(param); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } +} diff --git a/src/controller/order.controller.ts b/src/controller/order.controller.ts new file mode 100644 index 0000000..1c09f3e --- /dev/null +++ b/src/controller/order.controller.ts @@ -0,0 +1,197 @@ +import { + Body, + Controller, + Del, + Get, + Inject, + Param, + Post, + Put, + Query, +} from '@midwayjs/core'; +import { ApiOkResponse } from '@midwayjs/swagger'; +import { + BooleanRes, + OrderDetailRes, + OrderListRes, + OrderSaleListRes, +} from '../dto/reponse.dto'; +import { OrderService } from '../service/order.service'; +import { errorResponse, successResponse } from '../utils/response.util'; +import { + CreateOrderNoteDTO, + QueryOrderDTO, + QueryOrderSalesDTO, +} from '../dto/order.dto'; +import { User } from '../decorator/user.decorator'; +import { ErpOrderStatus } from '../enums/base.enum'; + +@Controller('/order') +export class OrderController { + @Inject() + orderService: OrderService; + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/syncOrder/:siteId') + async syncOrder(@Param('siteId') siteId: string) { + try { + await this.orderService.syncOrders(siteId); + return successResponse(true); + } catch (error) { + console.log(error); + return errorResponse('同步失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/syncOrder/:siteId/order/:orderId') + async syncOrderById( + @Param('siteId') siteId: string, + @Param('orderId') orderId: string + ) { + try { + await this.orderService.syncOrderById(siteId, orderId); + return successResponse(true); + } catch (error) { + console.log(error); + return errorResponse('同步失败'); + } + } + + @ApiOkResponse({ + type: OrderListRes, + }) + @Get('/getOrders') + async getOrders( + @Query() + param: QueryOrderDTO + ) { + try { + const count = await this.orderService.getOrderStatus(param); + const data = await this.orderService.getOrders(param); + return successResponse({ + ...data, + count, + }); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ + type: OrderSaleListRes, + }) + @Get('/getOrderSales') + async getOrderSales(@Query() param: QueryOrderSalesDTO) { + try { + if (param.isSource) + return successResponse(await this.orderService.getOrderItems(param)); + return successResponse(await this.orderService.getOrderSales(param)); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ + type: OrderDetailRes, + }) + @Get('/:orderId') + async getOrderDetail(@Param('orderId') orderId: number) { + try { + return successResponse(await this.orderService.getOrderDetail(orderId)); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Del('/:id') + async delOrder(@Param('id') id: number) { + try { + return successResponse(await this.orderService.delOrder(id)); + } catch (error) { + return errorResponse(error?.message || '删除失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/createNote') + async createNote(@Body() data: CreateOrderNoteDTO, @User() user) { + try { + return successResponse(await this.orderService.createNote(user.id, data)); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Post('/getOrderByNumber') + async getOrderByNumber(@Body('number') number: string) { + try { + return successResponse(await this.orderService.getOrderByNumber(number)); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Post('/order/cancel/:id') + async cancelOrder(@Param('id') id: number) { + try { + return successResponse(await this.orderService.cancelOrder(id)); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Post('/order/refund/:id') + async refundOrder(@Param('id') id: number) { + try { + return successResponse(await this.orderService.refundOrder(id)); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Post('/order/completed/:id') + async completedOrder(@Param('id') id: number) { + try { + return successResponse(await this.orderService.completedOrder(id)); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Put('/order/status/:id') + async changeStatus( + @Param('id') id: number, + @Body('status') status: ErpOrderStatus + ) { + try { + return successResponse(await this.orderService.changeStatus(id, status)); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Post('/order/create') + async createOrder(@Body() data: Record) { + try { + return successResponse(await this.orderService.createOrder(data)); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } +} diff --git a/src/controller/product.controller.ts b/src/controller/product.controller.ts new file mode 100644 index 0000000..7e85072 --- /dev/null +++ b/src/controller/product.controller.ts @@ -0,0 +1,387 @@ +import { + Inject, + Post, + Put, + Get, + Body, + Param, + Del, + Query, + Controller, +} from '@midwayjs/core'; +import { ProductService } from '../service/product.service'; +import { errorResponse, successResponse } from '../utils/response.util'; +import { + BatchSetSkuDTO, + CreateCategoryDTO, + CreateFlavorsDTO, + CreateProductDTO, + CreateStrengthDTO, + QueryCategoryDTO, + QueryFlavorsDTO, + QueryProductDTO, + QueryStrengthDTO, + UpdateCategoryDTO, + UpdateFlavorsDTO, + UpdateProductDTO, + UpdateStrengthDTO, +} from '../dto/product.dto'; +import { ApiOkResponse } from '@midwayjs/swagger'; +import { + BooleanRes, + ProductCatListRes, + ProductCatRes, + ProductListRes, + ProductRes, + ProductsRes, +} from '../dto/reponse.dto'; + +@Controller('/product') +export class ProductController { + @Inject() + productService: ProductService; + ProductRes; + + @ApiOkResponse({ + description: '通过name搜索产品', + type: ProductsRes, + }) + @Get('/search') + async searchProducts(@Query('name') name: string) { + try { + // 调用服务获取产品数据 + const products = await this.productService.findProductsByName(name); + return successResponse(products); + } catch (error) { + return errorResponse(error.message || '获取数据失败'); + } + } + + @ApiOkResponse({ + type: ProductRes, + }) + @Get('/sku/:sku') + async productBySku(@Param('sku') sku: string) { + try { + // 调用服务获取产品数据 + const product = await this.productService.findProductBySku(sku); + return successResponse(product); + } catch (error) { + return errorResponse(error.message || '获取数据失败'); + } + } + + @ApiOkResponse({ + description: '成功返回产品列表', + type: ProductListRes, + }) + @Get('/list') + async getProductList( + @Query() query: QueryProductDTO + ): Promise { + const { current = 1, pageSize = 10, name, categoryId } = query; + try { + const data = await this.productService.getProductList( + { current, pageSize }, + name, + categoryId + ); + return successResponse(data); + } catch (error) { + console.log(error); + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: ProductRes, + }) + @Post('/') + async createProduct(@Body() productData: CreateProductDTO) { + try { + const data = this.productService.createProduct(productData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: ProductRes, + }) + @Put('/:id') + async updateProduct( + @Param('id') id: number, + @Body() productData: UpdateProductDTO + ) { + try { + const data = this.productService.updateProduct(id, productData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Del('/:id') + async deleteProduct(@Param('id') id: number) { + try { + const data = await this.productService.deleteProduct(id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: ProductCatListRes, + }) + @Get('/categories') + async getCategories(@Query() query: QueryCategoryDTO) { + const { current = 1, pageSize = 10, name } = query; + try { + let data = await this.productService.getCategoryList( + { current, pageSize }, + name + ); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Get('/categorieAll') + async getCategorieAll() { + try { + let data = await this.productService.getCategoryAll(); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: ProductCatRes, + }) + @Post('/category') + async createCategory(@Body() categoryData: CreateCategoryDTO) { + try { + const hasCategory = await this.productService.hasCategory( + categoryData.name + ); + if (hasCategory) { + return errorResponse('分类已存在'); + } + let data = await this.productService.createCategory(categoryData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: ProductCatRes, + }) + @Put('/category/:id') + async updateCategory( + @Param('id') id: number, + @Body() categoryData: UpdateCategoryDTO + ) { + try { + const hasCategory = await this.productService.hasCategory( + categoryData.name + ); + if (hasCategory) { + return errorResponse('分类已存在'); + } + const data = this.productService.updateCategory(id, categoryData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Del('/category/:id') + async deleteCategory(@Param('id') id: number) { + try { + const hasProducts = await this.productService.hasProductsInCategory(id); + if (hasProducts) throw new Error('该分类下有商品,无法删除'); + const data = await this.productService.deleteCategory(id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @Post('/batchSetSku') + @ApiOkResponse({ + description: '批量设置 sku 的响应结果', + type: BooleanRes, + }) + async batchSetSku(@Body() body: BatchSetSkuDTO) { + try { + const result = await this.productService.batchSetSku(body.skus); + return successResponse(result, '批量设置 sku 成功'); + } catch (error) { + return errorResponse(error.message, 400); + } + } + + @ApiOkResponse() + @Get('/flavorsAll') + async getFlavorsAll() { + try { + let data = await this.productService.getFlavorsAll(); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Get('/flavors') + async getFlavors(@Query() query: QueryFlavorsDTO) { + const { current = 1, pageSize = 10, name } = query; + try { + let data = await this.productService.getFlavorsList( + { current, pageSize }, + name + ); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Post('/flavors') + async createFlavors(@Body() flavorsData: CreateFlavorsDTO) { + try { + const hasFlavors = await this.productService.hasFlavors(flavorsData.name); + if (hasFlavors) { + return errorResponse('分类已存在'); + } + let data = await this.productService.createFlavors(flavorsData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Put('/flavors/:id') + async updateFlavors( + @Param('id') id: number, + @Body() flavorsData: UpdateFlavorsDTO + ) { + try { + const hasFlavors = await this.productService.hasFlavors(flavorsData.name); + if (hasFlavors) { + return errorResponse('分类已存在'); + } + const data = this.productService.updateFlavors(id, flavorsData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Del('/flavors/:id') + async deleteFlavors(@Param('id') id: number) { + try { + const hasProducts = await this.productService.hasProductsInFlavors(id); + if (hasProducts) throw new Error('该分类下有商品,无法删除'); + const data = await this.productService.deleteFlavors(id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Get('/strengthAll') + async getStrengthAll() { + try { + let data = await this.productService.getStrengthAll(); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Get('/strength') + async getStrength(@Query() query: QueryStrengthDTO) { + const { current = 1, pageSize = 10, name } = query; + try { + let data = await this.productService.getStrengthList( + { current, pageSize }, + name + ); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Post('/strength') + async createStrength(@Body() strengthData: CreateStrengthDTO) { + try { + const hasStrength = await this.productService.hasStrength( + strengthData.name + ); + if (hasStrength) { + return errorResponse('分类已存在'); + } + let data = await this.productService.createStrength(strengthData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse() + @Put('/strength/:id') + async updateStrength( + @Param('id') id: number, + @Body() strengthData: UpdateStrengthDTO + ) { + try { + const hasStrength = await this.productService.hasStrength( + strengthData.name + ); + if (hasStrength) { + return errorResponse('分类已存在'); + } + const data = this.productService.updateStrength(id, strengthData); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Del('/strength/:id') + async deleteStrength(@Param('id') id: number) { + try { + const hasProducts = await this.productService.hasProductsInStrength(id); + if (hasProducts) throw new Error('该分类下有商品,无法删除'); + const data = await this.productService.deleteStrength(id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || error); + } + } +} diff --git a/src/controller/site.controller.ts b/src/controller/site.controller.ts new file mode 100644 index 0000000..c84c7c2 --- /dev/null +++ b/src/controller/site.controller.ts @@ -0,0 +1,25 @@ +import { Config, Controller, Get } from '@midwayjs/core'; +import { ApiOkResponse } from '@midwayjs/swagger'; +import { WpSitesResponse } from '../dto/reponse.dto'; +import { successResponse } from '../utils/response.util'; +import { WpSite } from '../interface'; + +@Controller('/site') +export class SiteController { + @Config('wpSite') + sites: WpSite[]; + + @ApiOkResponse({ + description: '关联网站', + type: WpSitesResponse, + }) + @Get('/all') + async all() { + return successResponse( + this.sites.map(v => ({ + id: v.id, + siteName: v.siteName, + })) + ); + } +} diff --git a/src/controller/statistics.controller.ts b/src/controller/statistics.controller.ts new file mode 100644 index 0000000..1e41bbf --- /dev/null +++ b/src/controller/statistics.controller.ts @@ -0,0 +1,79 @@ +import { Body, Controller, Inject, Post } from '@midwayjs/core'; +import { StatisticsService } from '../service/statistics.service'; +import { OrderStatisticsParams } from '../dto/statistics.dto'; +import { errorResponse, successResponse } from '../utils/response.util'; +import { ApiOkResponse } from '@midwayjs/swagger'; + +@Controller('/statistics') +export class StatisticsController { + @Inject() + statisticsService: StatisticsService; + + @ApiOkResponse() + @Post('/order') + async getOrderStatistics(@Body() params: OrderStatisticsParams) { + try { + return successResponse( + await this.statisticsService.getOrderStatistics(params) + ); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/orderByDate') + async getOrderByDate(@Body('date') date: string) { + try { + return successResponse(await this.statisticsService.getOrderByDate(date)); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/orderByEmail') + async getOrderByEmail(@Body('email') email: string) { + try { + return successResponse( + await this.statisticsService.getOrderByEmail(email) + ); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/getCustomerOrders') + async getCustomerOrders(@Body('month') month) { + try { + return successResponse( + await this.statisticsService.getCustomerOrders(month) + ); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/stockForecast') + async stockForecast(@Body() params) { + try { + return successResponse( + await this.statisticsService.stockForecast(params) + ); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse() + @Post('/restocking') + async restocking(@Body() params) { + try { + return successResponse(await this.statisticsService.restocking(params)); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } +} diff --git a/src/controller/stock.controller.ts b/src/controller/stock.controller.ts new file mode 100644 index 0000000..9db7768 --- /dev/null +++ b/src/controller/stock.controller.ts @@ -0,0 +1,262 @@ +import { + Controller, + Post, + Get, + Body, + Inject, + Del, + Param, + Query, + Put, +} from '@midwayjs/core'; +import { StockService } from '../service/stock.service'; +import { errorResponse, successResponse } from '../utils/response.util'; +import { + CreatePurchaseOrderDTO, + CreateStockPointDTO, + QueryPointDTO, + QueryPurchaseOrderDTO, + QueryStockDTO, + QueryStockRecordDTO, + UpdatePurchaseOrderDTO, + UpdateStockDTO, + UpdateStockPointDTO, +} from '../dto/stock.dto'; +import { ApiOkResponse } from '@midwayjs/swagger'; +import { + BooleanRes, + PurchaseOrderListRes, + StockListRes, + StockPointAllRespone, + StockPointListRes, + StockRecordListRes, +} from '../dto/reponse.dto'; +import { User } from '../decorator/user.decorator'; + +@Controller('/stock') +export class StockController { + @Inject() + private readonly stockService: StockService; + + @ApiOkResponse({ type: BooleanRes, description: '创建库存点' }) + @Post('/stock-point') + async createStockPoint(@Body() body: CreateStockPointDTO) { + try { + const data = await this.stockService.createStockPoint(body); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取库存列表失败'); + } + } + + @ApiOkResponse({ type: BooleanRes, description: '创建库存点' }) + @Put('/stock-point/:id') + async updateStockPoint( + @Param('id') id: number, + @Body() body: UpdateStockPointDTO + ) { + try { + const data = await this.stockService.updateStockPoint(id, body); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取库存列表失败'); + } + } + + @ApiOkResponse({ type: StockPointListRes, description: '获取库存点列表' }) + @Get('/stock-point') + async getStockPoints(@Query() query: QueryPointDTO) { + try { + const data = await this.stockService.getStockPoints(query); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取库存列表失败'); + } + } + + @ApiOkResponse({ type: StockPointAllRespone, description: '获取所有库存' }) + @Get('/stock-point/all') + async getAllStockPoints() { + try { + const data = await this.stockService.getAllStockPoints(); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取失败'); + } + } + + @ApiOkResponse({ type: BooleanRes, description: '删除库存点' }) + @Del('/stock-point/:id') + async delStockPoints(@Param('id') id: number) { + try { + await this.stockService.delStockPoints(id); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || '删除库存点失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Post('/purchase-order') + async createPurchaseOrder(@Body() body: CreatePurchaseOrderDTO) { + try { + const data = await this.stockService.createPurchaseOrder(body); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Put('/purchase-order/:id') + async updatePurchaseOrder( + @Param('id') id: number, + @Body() body: UpdatePurchaseOrderDTO + ) { + try { + const data = await this.stockService.updatePurchaseOrder(id, body); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '更新失败'); + } + } + + @ApiOkResponse({ type: PurchaseOrderListRes, description: '获取采购列表' }) + @Get('/purchase-order') + async getPurchaseOrders(@Query() query: QueryPurchaseOrderDTO) { + try { + const data = await this.stockService.getPurchaseOrders(query); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取库存记录列表失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Del('/purchase-order/:id') + async delPurchaseOrder(@Param('id') id: number) { + try { + const data = await this.stockService.delPurchaseOrder(id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '删除失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Post('/purchase-order/:id') + async receivePurchaseOrder(@Param('id') id: number, @User() user) { + try { + const data = await this.stockService.receivePurchaseOrder(id, user.id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '更新失败'); + } + } + + @ApiOkResponse({ type: StockListRes, description: '获取库存列表' }) + @Get('/') + async getStocks(@Query() query: QueryStockDTO) { + try { + const data = await this.stockService.getStocks(query); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取库存列表失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + description: '更新库存(入库、出库、调整)', + }) + @Post('/update') + async updateStock(@Body() body: UpdateStockDTO) { + try { + await this.stockService.updateStock(body); + return successResponse(true); + } catch (error) { + return errorResponse(error?.message || '更新库存失败'); + } + } + + @ApiOkResponse({ type: StockRecordListRes, description: '获取库存记录列表' }) + @Get('/records') + async getStockRecords(@Query() query: QueryStockRecordDTO) { + try { + const data = await this.stockService.getStockRecords(query); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取库存记录列表失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Post('/transfer') + async createTransfer(@Body() body: Record, @User() user) { + try { + const data = await this.stockService.createTransfer(body, user.id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '创建失败'); + } + } + + @ApiOkResponse() + @Get('/transfer') + async getTransfers(@Query() query: Record) { + try { + const data = await this.stockService.getTransfers(query); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '获取调拨列表失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Post('/cancelTransfer/:id') + async cancelTransfer(@Param('id') id: number, @User() user) { + try { + const data = await this.stockService.cancelTransfer(id, user.id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '删除失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Post('/receiveTransfer/:id') + async receiveTransfer(@Param('id') id: number, @User() user) { + try { + const data = await this.stockService.receiveTransfer(id, user.id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '更新失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Post('/lostTransfer/:id') + async lostTransfer(@Param('id') id: number) { + try { + const data = await this.stockService.lostTransfer(id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '更新失败'); + } + } + + @ApiOkResponse({ type: BooleanRes }) + @Put('/receiveTransfer/:id') + async updateTransfer( + @Param('id') id: number, + @Body() body: Record, + @User() user + ) { + try { + const data = await this.stockService.updateTransfer(id, body, user.id); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '更新失败'); + } + } +} diff --git a/src/controller/user.controller.ts b/src/controller/user.controller.ts new file mode 100644 index 0000000..9bce0d5 --- /dev/null +++ b/src/controller/user.controller.ts @@ -0,0 +1,72 @@ +// src/controller/user.controller.ts +import { Controller, Post, Get, Body, Query } from '@midwayjs/core'; +import { Inject } from '@midwayjs/decorator'; +import { UserService } from '../service/user.service'; +import { errorResponse, successResponse } from '../utils/response.util'; +import { ApiOkResponse } from '@midwayjs/swagger'; +import { BooleanRes, LoginRes } from '../dto/reponse.dto'; +import { LoginDTO } from '../dto/user.dto'; +import { User } from '../decorator/user.decorator'; + +@Controller('/user') +export class UserController { + @Inject() + userService: UserService; + + @ApiOkResponse({ + type: LoginRes, + }) + @Post('/login') + async login(@Body() body: LoginDTO) { + const { username, password } = body; + try { + const result = await this.userService.login(username, password); + return successResponse(result, '登录成功'); + } catch (error) { + return errorResponse(error?.message || '登录失败'); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/logout') + async logout() { + // 可选:在这里处理服务端缓存的 token 或 session + + return successResponse(true); + } + + @Post('/add') + async addUser(@Body() body: { username: string; password: string }) { + const { username, password } = body; + try { + await this.userService.addUser(username, password); + return successResponse(true); + } catch (error) { + console.log(error); + return errorResponse('添加用户失败'); + } + } + + @Get('/list') + async listUsers(@Query() query: { current: number; pageSize: number }) { + const { current = 1, pageSize = 10 } = query; + return successResponse(await this.userService.listUsers(current, pageSize)); + } + + @Post('/toggleActive') + async toggleActive(@Body() body: { userId: number; isActive: boolean }) { + return this.userService.toggleUserActive(body.userId, body.isActive); + } + + @ApiOkResponse() + @Get() + async getUser(@User() user) { + try { + return successResponse(await this.userService.getUser(user.id)); + } catch (error) { + return errorResponse('获取失败'); + } + } +} diff --git a/src/controller/webhook.controller.ts b/src/controller/webhook.controller.ts new file mode 100644 index 0000000..1374678 --- /dev/null +++ b/src/controller/webhook.controller.ts @@ -0,0 +1,138 @@ +import { Config, HttpStatus, Inject } from '@midwayjs/core'; +import { + Controller, + Post, + Body, + Headers, + Get, + Query, +} from '@midwayjs/decorator'; +import { Context } from '@midwayjs/koa'; +import * as crypto from 'crypto'; +import { WpProductService } from '../service/wp_product.service'; +import { WPService } from '../service/wp.service'; +import { OrderService } from '../service/order.service'; +import { WpSite } from '../interface'; + +@Controller('/webhook') +export class WebhookController { + private secret = 'YOONE24kd$kjcdjflddd'; + + @Inject() + private readonly wpProductService: WpProductService; + + @Inject() + private readonly wpApiService: WPService; + + @Inject() + private readonly orderService: OrderService; + + @Inject() + ctx: Context; + + @Config('wpSite') + sites: WpSite[]; + + @Get('/') + async test() { + return 'webhook'; + } + + @Post('/woocommerce') + async handleWooWebhook( + @Body() body: any, + @Query('siteId') siteId: string, + @Headers() header: any + ) { + const signature = header['x-wc-webhook-signature']; + const topic = header['x-wc-webhook-topic']; + const source = header['x-wc-webhook-source']; + let site = this.sites.find(item => item.id === siteId); + + if (!site || !source.includes(site.wpApiUrl)) { + console.log('domain not match'); + return { + code: HttpStatus.BAD_REQUEST, + success: false, + message: 'domain not match', + }; + } + + if (!signature) { + return { + code: HttpStatus.BAD_REQUEST, + success: false, + message: 'Signature missing', + }; + } + const rawBody = this.ctx.request.rawBody; + const hash = crypto + .createHmac('sha256', this.secret) + .update(rawBody) + .digest('base64'); + try { + if (hash === signature) { + switch (topic) { + case 'product.created': + case 'product.updated': + const site = await this.wpProductService.geSite(siteId); + // 变体更新 + if (body.type === 'variation') { + const variation = await this.wpApiService.getVariation( + site, + body.parent_id, + body.id + ); + this.wpProductService.syncVariation( + siteId, + body.parent_id, + variation + ); + break; + } + const variations = + body.type === 'variable' + ? await this.wpApiService.getVariations(site, body.id) + : []; + await this.wpProductService.syncProductAndVariations( + site.id, + body, + variations + ); + break; + case 'product.deleted': + await this.wpProductService.delWpProduct(site.id, body.id); + break; + case 'order.created': + case 'order.updated': + await this.orderService.syncSingleOrder(siteId, body); + break; + case 'order.deleted': + break; + case 'customer.created': + break; + case 'customer.updated': + break; + case 'customer.deleted': + break; + default: + console.log('Unhandled event:', body.event); + } + + return { + code: 200, + success: true, + message: 'Webhook processed successfully', + }; + } else { + return { + code: 403, + success: false, + message: 'Webhook verification failed', + }; + } + } catch (error) { + console.log(error); + } + } +} diff --git a/src/controller/wp_product.controller.ts b/src/controller/wp_product.controller.ts new file mode 100644 index 0000000..04d64bf --- /dev/null +++ b/src/controller/wp_product.controller.ts @@ -0,0 +1,161 @@ +import { + Controller, + Param, + Post, + Inject, + Get, + Query, + Put, + Body, +} from '@midwayjs/core'; +import { WpProductService } from '../service/wp_product.service'; +import { errorResponse, successResponse } from '../utils/response.util'; +import { ApiOkResponse } from '@midwayjs/swagger'; +import { BooleanRes, WpProductListRes } from '../dto/reponse.dto'; +import { + QueryWpProductDTO, + SetConstitutionDTO, + UpdateVariationDTO, + UpdateWpProductDTO, +} from '../dto/wp_product.dto'; +import { WPService } from '../service/wp.service'; + +@Controller('/wp_product') +export class WpProductController { + @Inject() + private readonly wpProductService: WpProductService; + + @Inject() + private readonly wpApiService: WPService; + + @ApiOkResponse({ + type: BooleanRes, + }) + @Post('/sync/:siteId') + async syncProducts(@Param('siteId') siteId: string) { + try { + await this.wpProductService.syncSite(siteId); + return successResponse(true); + } catch (error) { + console.log(error); + return errorResponse('同步失败'); + } + } + + @ApiOkResponse({ + type: WpProductListRes, + }) + @Get('/list') + async getWpProducts(@Query() query: QueryWpProductDTO) { + try { + const data = await this.wpProductService.getProductList(query); + return successResponse(data); + } catch (error) { + return errorResponse(error.message); + } + } + + @ApiOkResponse({ + type: BooleanRes, + }) + @Put('/:id/constitution') + async setConstitution( + @Param('id') id: number, + @Body() + body: SetConstitutionDTO + ) { + const { isProduct, constitution } = body; + try { + await this.wpProductService.setConstitution(id, isProduct, constitution); + return successResponse(true); + } catch (error) { + return errorResponse(error.message); + } + } + + /** + * 更新产品接口 + * @param productId 产品 ID + * @param body 更新数据 + */ + @ApiOkResponse({ + type: BooleanRes, + }) + @Put('/siteId/:siteId/products/:productId') + async updateProduct( + @Param('siteId') siteId: string, + @Param('productId') productId: string, + @Body() body: UpdateWpProductDTO + ) { + try { + const isDuplicate = await this.wpProductService.isSkuDuplicate( + body.sku, + siteId, + productId + ); + if (isDuplicate) { + return errorResponse('SKU已存在'); + } + const site = await this.wpProductService.geSite(siteId); + const result = await this.wpApiService.updateProduct( + site, + productId, + body + ); + if (result) { + this.wpProductService.updateWpProduct(siteId, productId, body); + return successResponse(result, '产品更新成功'); + } + return errorResponse('产品更新失败'); + } catch (error) { + console.error('更新产品失败:', error); + return errorResponse(error.message || '产品更新失败'); + } + } + + /** + * 更新变体接口 + * @param productId 产品 ID + * @param variationId 变体 ID + * @param body 更新数据 + */ + @Put('/siteId/:siteId/products/:productId/variations/:variationId') + async updateVariation( + @Param('siteId') siteId: string, + @Param('productId') productId: string, + @Param('variationId') variationId: string, + @Body() body: UpdateVariationDTO + ) { + try { + const isDuplicate = await this.wpProductService.isSkuDuplicate( + body.sku, + siteId, + productId, + variationId + ); + if (isDuplicate) { + return errorResponse('SKU已存在'); + } + const site = await this.wpProductService.geSite(siteId); + const result = await this.wpApiService.updateVariation( + site, + productId, + variationId, + body + ); + if (result) { + this.wpProductService.updateWpProductVaritation( + siteId, + productId, + variationId, + body + ); + return successResponse(result, '产品变体更新成功'); + } + return errorResponse('变体更新失败'); + } catch (error) { + console.error('更新变体失败:', error); + return errorResponse(error.message || '产品变体更新失败'); + } + } +} diff --git a/src/decorator/user.decorator.ts b/src/decorator/user.decorator.ts new file mode 100644 index 0000000..fa88fb7 --- /dev/null +++ b/src/decorator/user.decorator.ts @@ -0,0 +1,8 @@ +import { createCustomParamDecorator } from '@midwayjs/core'; + +export const USER_KEY = 'USER_KEY'; + +// 定义装饰器 +export function User(): ParameterDecorator { + return createCustomParamDecorator(USER_KEY, {}); +} diff --git a/src/dto/freightcom.dto.ts b/src/dto/freightcom.dto.ts new file mode 100644 index 0000000..81ee8cb --- /dev/null +++ b/src/dto/freightcom.dto.ts @@ -0,0 +1,303 @@ +import { ApiProperty } from '@midwayjs/swagger'; +import { Rule, RuleType } from '@midwayjs/validate'; + +// 定义包装类型的联合类型 +export type PackagingType = + // | PackagingPallet + PackagingPackage; +// | PackagingCourierPak +// | PackagingEnvelope; + +// 定义包装类型的枚举,用于 API 文档描述 +export enum PackagingTypeEnum { + Pallet = 'pallet', + Package = 'package', + CourierPak = 'courier-pak', + Envelope = 'envelope', +} + +export class Address { + @ApiProperty() + @Rule(RuleType.string()) + address_line_1: string; + + @ApiProperty() + @Rule(RuleType.string()) + city: string; + + @ApiProperty() + @Rule(RuleType.string()) + region: string; + + @ApiProperty() + @Rule(RuleType.string()) + country: string; + + @ApiProperty() + @Rule(RuleType.string()) + postal_code: string; +} + +export class PhoneNumber { + @ApiProperty() + @Rule(RuleType.string()) + number: string; + + @ApiProperty() + @Rule(RuleType.string()) + extension: string; +} + +export class Location { + @ApiProperty() + @Rule(RuleType.string()) + name: string; + + @ApiProperty({ type: Address }) + @Rule(RuleType.object
()) + address: Address; + + @ApiProperty({ type: PhoneNumber }) + @Rule(RuleType.object()) + phone_number: PhoneNumber; + + @ApiProperty() + @Rule(RuleType.array()) + email_addresses: string[]; + + contact_name?: string; +} + +export class Time { + @ApiProperty() + @Rule(RuleType.string()) + hour: string; + + @ApiProperty() + @Rule(RuleType.string()) + minute: string; +} + +export enum SignatureRequirementEnum { + NOTREQUIRED = 'not-required', + REQUIRED = 'required', + ADULTREQUIRED = 'adult-required', +} + +export class Destination extends Location { + @ApiProperty({ type: Time }) + @Rule(RuleType.object