new api
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
# 数据库配置
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=your_password
|
||||||
|
DB_NAME=miniprogram
|
||||||
|
|
||||||
|
# AI 配置(阿里云 DashScope)
|
||||||
|
DASHSCOPE_API_KEY=sk-your-api-key
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
PORT=3006
|
||||||
|
NODE_ENV=development
|
||||||
@@ -3,6 +3,7 @@ const Router = require('koa-router')
|
|||||||
const cors = require('@koa/cors')
|
const cors = require('@koa/cors')
|
||||||
const bodyParser = require('koa-bodyparser')
|
const bodyParser = require('koa-bodyparser')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
const app = new Koa()
|
const app = new Koa()
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
@@ -48,6 +49,12 @@ const userRoutes = require('./routes/users')
|
|||||||
const uploadRoutes = require('./routes/upload')
|
const uploadRoutes = require('./routes/upload')
|
||||||
const stockRoutes = require('./routes/stock')
|
const stockRoutes = require('./routes/stock')
|
||||||
const aiRoutes = require('./routes/ai')
|
const aiRoutes = require('./routes/ai')
|
||||||
|
const supplierRoutes = require('./routes/suppliers')
|
||||||
|
const purchaseRoutes = require('./routes/purchases')
|
||||||
|
const pointsGoodsRoutes = require('./routes/points-goods')
|
||||||
|
const statsRoutes = require('./routes/stats')
|
||||||
|
const priceListRoutes = require('./routes/price-list')
|
||||||
|
const pointsLogsRoutes = require('./routes/points-logs')
|
||||||
|
|
||||||
router.use('/api/orders', orderRoutes)
|
router.use('/api/orders', orderRoutes)
|
||||||
router.use('/api/categories', categoryRoutes)
|
router.use('/api/categories', categoryRoutes)
|
||||||
@@ -56,6 +63,12 @@ router.use('/api/users', userRoutes)
|
|||||||
router.use('/api/upload', uploadRoutes)
|
router.use('/api/upload', uploadRoutes)
|
||||||
router.use('/api/stock', stockRoutes)
|
router.use('/api/stock', stockRoutes)
|
||||||
router.use('/api/ai', aiRoutes)
|
router.use('/api/ai', aiRoutes)
|
||||||
|
router.use('/api/suppliers', supplierRoutes)
|
||||||
|
router.use('/api/purchases', purchaseRoutes)
|
||||||
|
router.use('/api/points-goods', pointsGoodsRoutes)
|
||||||
|
router.use('/api/stats', statsRoutes)
|
||||||
|
router.use('/api/price-list', priceListRoutes)
|
||||||
|
router.use('/api/points/logs', pointsLogsRoutes)
|
||||||
|
|
||||||
app.use(router.routes())
|
app.use(router.routes())
|
||||||
app.use(router.allowedMethods())
|
app.use(router.allowedMethods())
|
||||||
|
|||||||
+11
-6
@@ -1,16 +1,21 @@
|
|||||||
const mysql = require('mysql2/promise')
|
const mysql = require('mysql2/promise')
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
host: '110.42.255.239',
|
host: process.env.DB_HOST || '110.42.255.239',
|
||||||
port: 3306,
|
port: parseInt(process.env.DB_PORT || '3306'),
|
||||||
user: 'admin',
|
user: process.env.DB_USER || 'admin',
|
||||||
password: 'Admin@123',
|
password: process.env.DB_PASSWORD || 'Admin@123',
|
||||||
database: 'miniprogram',
|
database: process.env.DB_NAME || 'miniprogram',
|
||||||
waitForConnections: true,
|
waitForConnections: true,
|
||||||
connectionLimit: 10,
|
connectionLimit: 10,
|
||||||
queueLimit: 0
|
queueLimit: 0
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
# 登录服务器,找到 Koa 进程
|
||||||
|
ssh ubuntu@110.42.255.239
|
||||||
|
pm2 restart weixin
|
||||||
|
*/
|
||||||
const pool = mysql.createPool(config)
|
const pool = mysql.createPool(config)
|
||||||
|
|
||||||
async function query(sql, params = []) {
|
async function query(sql, params = []) {
|
||||||
|
|||||||
@@ -82,3 +82,56 @@ CREATE TABLE IF NOT EXISTS `stock` (
|
|||||||
UNIQUE KEY `goods_id` (`goods_id`),
|
UNIQUE KEY `goods_id` (`goods_id`),
|
||||||
CONSTRAINT `stock_ibfk_1` FOREIGN KEY (`goods_id`) REFERENCES `goods` (`id`)
|
CONSTRAINT `stock_ibfk_1` FOREIGN KEY (`goods_id`) REFERENCES `goods` (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存表';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `suppliers` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(200) NOT NULL COMMENT '供应商名称',
|
||||||
|
`contact` varchar(100) DEFAULT '' COMMENT '联系人',
|
||||||
|
`phone` varchar(20) DEFAULT '' COMMENT '联系电话',
|
||||||
|
`address` varchar(500) DEFAULT '' COMMENT '地址',
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商表';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `purchases` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`supplier_id` int(11) DEFAULT NULL COMMENT '供应商ID',
|
||||||
|
`supplier_name` varchar(200) DEFAULT '' COMMENT '供应商名称(冗余)',
|
||||||
|
`total` decimal(10,2) DEFAULT 0.00 COMMENT '采购总金额',
|
||||||
|
`status` tinyint(4) DEFAULT 0 COMMENT '状态 0-待入库 1-已入库',
|
||||||
|
`remarks` text COMMENT '备注',
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `supplier_id` (`supplier_id`),
|
||||||
|
CONSTRAINT `purchases_ibfk_1` FOREIGN KEY (`supplier_id`) REFERENCES `suppliers` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采购单表';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `purchase_items` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`purchase_id` int(11) NOT NULL COMMENT '采购单ID',
|
||||||
|
`goods_id` int(11) NOT NULL COMMENT '商品ID',
|
||||||
|
`goods_name` varchar(200) DEFAULT '' COMMENT '商品名称(冗余)',
|
||||||
|
`quantity` int(11) DEFAULT 0 COMMENT '采购数量',
|
||||||
|
`purchase_price` decimal(10,2) DEFAULT 0.00 COMMENT '采购单价',
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `purchase_id` (`purchase_id`),
|
||||||
|
KEY `goods_id` (`goods_id`),
|
||||||
|
CONSTRAINT `purchase_items_ibfk_1` FOREIGN KEY (`purchase_id`) REFERENCES `purchases` (`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `purchase_items_ibfk_2` FOREIGN KEY (`goods_id`) REFERENCES `goods` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采购单明细表';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `points_goods` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(200) NOT NULL COMMENT '商品名称',
|
||||||
|
`points` int(11) DEFAULT 0 COMMENT '所需积分',
|
||||||
|
`stock` int(11) DEFAULT 0 COMMENT '库存',
|
||||||
|
`image` varchar(500) DEFAULT '' COMMENT '图片',
|
||||||
|
`description` text COMMENT '描述',
|
||||||
|
`is_show` tinyint(4) DEFAULT 1 COMMENT '是否显示',
|
||||||
|
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='积分商品表';
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
const { query } = require('../config/database')
|
||||||
|
|
||||||
|
async function getPointsGoods(ctx) {
|
||||||
|
let sql = 'SELECT * FROM points_goods WHERE 1=1'
|
||||||
|
const params = []
|
||||||
|
|
||||||
|
if (ctx.query.visible === '1') {
|
||||||
|
sql += ' AND is_show = 1'
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += ' ORDER BY points ASC'
|
||||||
|
const goods = await query(sql, params)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
data: goods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPointsGoodsById(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
const goods = await query('SELECT * FROM points_goods WHERE id = ?', [id])
|
||||||
|
|
||||||
|
if (goods.length > 0) {
|
||||||
|
ctx.body = { code: 200, data: goods[0] }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 404, message: '积分商品不存在' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPointsGoods(ctx) {
|
||||||
|
const { name, points, stock, image, description } = ctx.request.body
|
||||||
|
|
||||||
|
if (!name || points === undefined) {
|
||||||
|
ctx.body = { code: 400, message: '请填写商品名称和所需积分' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query(
|
||||||
|
'INSERT INTO points_goods (name, points, stock, image, description) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[name, parseInt(points) || 0, parseInt(stock) || 0, image || '', description || '']
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
message: '添加成功',
|
||||||
|
data: { id: result.insertId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updatePointsGoods(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
const { name, points, stock, image, description } = ctx.request.body
|
||||||
|
|
||||||
|
if (!name || points === undefined) {
|
||||||
|
ctx.body = { code: 400, message: '请填写商品名称和所需积分' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query(
|
||||||
|
'UPDATE points_goods SET name = ?, points = ?, stock = ?, image = ?, description = ? WHERE id = ?',
|
||||||
|
[name, parseInt(points) || 0, parseInt(stock) || 0, image || '', description || '', id]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
ctx.body = { code: 200, message: '更新成功' }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 404, message: '积分商品不存在' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePointsGoods(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
|
||||||
|
const result = await query('DELETE FROM points_goods WHERE id = ?', [id])
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
ctx.body = { code: 200, message: '删除成功' }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 404, message: '积分商品不存在' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exchangePointsGoods(ctx) {
|
||||||
|
const { userId, goodsId, quantity } = ctx.request.body
|
||||||
|
|
||||||
|
if (!userId || !goodsId) {
|
||||||
|
ctx.body = { code: 400, message: '参数不完整' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const qty = quantity || 1
|
||||||
|
|
||||||
|
const users = await query('SELECT * FROM users WHERE id = ? AND status = 1', [userId])
|
||||||
|
if (users.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '用户不存在' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = users[0]
|
||||||
|
|
||||||
|
const goods = await query('SELECT * FROM points_goods WHERE id = ? AND is_show = 1', [goodsId])
|
||||||
|
if (goods.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '积分商品不存在' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const goodsItem = goods[0]
|
||||||
|
|
||||||
|
if (goodsItem.stock < qty) {
|
||||||
|
ctx.body = { code: 400, message: '库存不足' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPoints = goodsItem.points * qty
|
||||||
|
if (user.points < totalPoints) {
|
||||||
|
ctx.body = { code: 400, message: '积分不足' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPoints = user.points - totalPoints
|
||||||
|
await query('UPDATE users SET points = ? WHERE id = ?', [newPoints, userId])
|
||||||
|
await query('UPDATE points_goods SET stock = stock - ? WHERE id = ?', [qty, goodsId])
|
||||||
|
|
||||||
|
await query(
|
||||||
|
'INSERT INTO points_logs (user_id, type, amount, description) VALUES (?, ?, ?, ?)',
|
||||||
|
[userId, 'spend', totalPoints, `兑换「${goodsItem.name}」x${qty}`]
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
message: '兑换成功',
|
||||||
|
data: {
|
||||||
|
remainingPoints: newPoints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getPointsGoods,
|
||||||
|
getPointsGoodsById,
|
||||||
|
createPointsGoods,
|
||||||
|
updatePointsGoods,
|
||||||
|
deletePointsGoods,
|
||||||
|
exchangePointsGoods
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
const { query } = require('../config/database')
|
||||||
|
|
||||||
|
async function getPriceList(ctx) {
|
||||||
|
const orderId = ctx.params.orderId
|
||||||
|
|
||||||
|
const orders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
|
||||||
|
if (orders.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '订单不存在' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = orders[0]
|
||||||
|
|
||||||
|
let items = []
|
||||||
|
try {
|
||||||
|
const cartData = typeof order.cart === 'string' ? JSON.parse(order.cart) : order.cart
|
||||||
|
items = Array.isArray(cartData) ? cartData : (cartData.items || cartData.list || [])
|
||||||
|
} catch (e) {
|
||||||
|
items = []
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
orderNo: order.id,
|
||||||
|
createTime: order.created_at,
|
||||||
|
items: items.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
quantity: item.quantity || item.count || 1,
|
||||||
|
price: item.price || 0,
|
||||||
|
unit: item.unit || '件',
|
||||||
|
subtotal: (item.price || 0) * (item.quantity || item.count || 1)
|
||||||
|
})),
|
||||||
|
total: order.total_price,
|
||||||
|
remark: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getPriceList
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
const { query } = require('../config/database')
|
||||||
|
|
||||||
|
async function getPurchases(ctx) {
|
||||||
|
let sql = `
|
||||||
|
SELECT p.*,
|
||||||
|
(SELECT COUNT(*) FROM purchase_items WHERE purchase_id = p.id) as items_count
|
||||||
|
FROM purchases p
|
||||||
|
WHERE 1=1`
|
||||||
|
const params = []
|
||||||
|
|
||||||
|
if (ctx.query.keyword) {
|
||||||
|
sql += ' AND (p.supplier_name LIKE ?)'
|
||||||
|
params.push(`%${ctx.query.keyword}%`)
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += ' ORDER BY p.created_at DESC'
|
||||||
|
const purchases = await query(sql, params)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
data: purchases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPurchaseById(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
const purchases = await query('SELECT * FROM purchases WHERE id = ?', [id])
|
||||||
|
|
||||||
|
if (purchases.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '采购单不存在' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const purchase = purchases[0]
|
||||||
|
const items = await query(
|
||||||
|
`SELECT pi.*, g.name as goods_name
|
||||||
|
FROM purchase_items pi
|
||||||
|
LEFT JOIN goods g ON pi.goods_id = g.id
|
||||||
|
WHERE pi.purchase_id = ?`,
|
||||||
|
[id]
|
||||||
|
)
|
||||||
|
|
||||||
|
purchase.items = items
|
||||||
|
purchase.items_count = items.length
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
data: purchase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPurchase(ctx) {
|
||||||
|
const { supplier_id, items, remarks } = ctx.request.body
|
||||||
|
|
||||||
|
if (!supplier_id || !items || items.length === 0) {
|
||||||
|
ctx.body = { code: 400, message: '请选择供应商和采购商品' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const suppliers = await query('SELECT * FROM suppliers WHERE id = ?', [supplier_id])
|
||||||
|
if (suppliers.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '供应商不存在' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const supplier = suppliers[0]
|
||||||
|
|
||||||
|
let total = 0
|
||||||
|
for (const item of items) {
|
||||||
|
total += (item.purchase_price || 0) * (item.quantity || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const purchaseResult = await query(
|
||||||
|
'INSERT INTO purchases (supplier_id, supplier_name, total, remarks) VALUES (?, ?, ?, ?)',
|
||||||
|
[supplier_id, supplier.name, total, remarks || '']
|
||||||
|
)
|
||||||
|
|
||||||
|
const purchaseId = purchaseResult.insertId
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const goods = await query('SELECT name FROM goods WHERE id = ?', [item.goods_id])
|
||||||
|
const goodsName = goods.length > 0 ? goods[0].name : ''
|
||||||
|
await query(
|
||||||
|
'INSERT INTO purchase_items (purchase_id, goods_id, goods_name, quantity, purchase_price) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[purchaseId, item.goods_id, goodsName, item.quantity || 0, item.purchase_price || 0]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
message: '采购单创建成功',
|
||||||
|
data: { id: purchaseId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function inboundPurchase(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
const purchases = await query('SELECT * FROM purchases WHERE id = ?', [id])
|
||||||
|
|
||||||
|
if (purchases.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '采购单不存在' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const purchase = purchases[0]
|
||||||
|
if (purchase.status === 1) {
|
||||||
|
ctx.body = { code: 400, message: '该采购单已入库' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = await query('SELECT * FROM purchase_items WHERE purchase_id = ?', [id])
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const existing = await query('SELECT * FROM stock WHERE goods_id = ?', [item.goods_id])
|
||||||
|
if (existing.length > 0) {
|
||||||
|
await query(
|
||||||
|
'UPDATE stock SET quantity = quantity + ? WHERE goods_id = ?',
|
||||||
|
[item.quantity, item.goods_id]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await query(
|
||||||
|
'INSERT INTO stock (goods_id, quantity, warehouse) VALUES (?, ?, ?)',
|
||||||
|
[item.goods_id, item.quantity, '默认仓库']
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await query(
|
||||||
|
'UPDATE goods SET stock = stock + ? WHERE id = ?',
|
||||||
|
[item.quantity, item.goods_id]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await query('UPDATE purchases SET status = 1 WHERE id = ?', [id])
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
message: '入库成功'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getPurchases,
|
||||||
|
getPurchaseById,
|
||||||
|
createPurchase,
|
||||||
|
inboundPurchase
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const { query } = require('../config/database')
|
||||||
|
|
||||||
|
async function getTodayStats(ctx) {
|
||||||
|
const today = new Date()
|
||||||
|
const todayStart = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')} 00:00:00`
|
||||||
|
const todayEnd = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')} 23:59:59`
|
||||||
|
|
||||||
|
const orderResult = await query(
|
||||||
|
'SELECT COUNT(*) as orderCount, COALESCE(SUM(total_price), 0) as totalSales FROM orders WHERE created_at >= ? AND created_at <= ? AND status IN ("paid", "completed")',
|
||||||
|
[todayStart, todayEnd]
|
||||||
|
)
|
||||||
|
|
||||||
|
const customerResult = await query(
|
||||||
|
'SELECT COUNT(DISTINCT user_id) as customerCount FROM orders WHERE created_at >= ? AND created_at <= ?',
|
||||||
|
[todayStart, todayEnd]
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
sales: orderResult[0].totalSales,
|
||||||
|
orders: orderResult[0].orderCount,
|
||||||
|
customers: customerResult[0].customerCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getTodayStats
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
const { query } = require('../config/database')
|
||||||
|
|
||||||
|
async function getSuppliers(ctx) {
|
||||||
|
let sql = 'SELECT * FROM suppliers WHERE 1=1'
|
||||||
|
const params = []
|
||||||
|
|
||||||
|
if (ctx.query.keyword) {
|
||||||
|
sql += ' AND (name LIKE ? OR contact LIKE ? OR phone LIKE ?)'
|
||||||
|
params.push(`%${ctx.query.keyword}%`, `%${ctx.query.keyword}%`, `%${ctx.query.keyword}%`)
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += ' ORDER BY id DESC'
|
||||||
|
const suppliers = await query(sql, params)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
data: suppliers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSupplierById(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
const suppliers = await query('SELECT * FROM suppliers WHERE id = ?', [id])
|
||||||
|
|
||||||
|
if (suppliers.length > 0) {
|
||||||
|
ctx.body = { code: 200, data: suppliers[0] }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 404, message: '供应商不存在' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSupplier(ctx) {
|
||||||
|
const { name, contact, phone, address } = ctx.request.body
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
ctx.body = { code: 400, message: '请输入供应商名称' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query(
|
||||||
|
'INSERT INTO suppliers (name, contact, phone, address) VALUES (?, ?, ?, ?)',
|
||||||
|
[name, contact || '', phone || '', address || '']
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
message: '添加成功',
|
||||||
|
data: { id: result.insertId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSupplier(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
const { name, contact, phone, address } = ctx.request.body
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
ctx.body = { code: 400, message: '请输入供应商名称' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await query(
|
||||||
|
'UPDATE suppliers SET name = ?, contact = ?, phone = ?, address = ? WHERE id = ?',
|
||||||
|
[name, contact || '', phone || '', address || '', id]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
ctx.body = { code: 200, message: '更新成功' }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 404, message: '供应商不存在' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteSupplier(ctx) {
|
||||||
|
const id = parseInt(ctx.params.id)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await query('DELETE FROM suppliers WHERE id = ?', [id])
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
ctx.body = { code: 200, message: '删除成功' }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 404, message: '供应商不存在' }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除供应商失败:', error)
|
||||||
|
ctx.body = { code: 500, message: '删除失败' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getSuppliers,
|
||||||
|
getSupplierById,
|
||||||
|
createSupplier,
|
||||||
|
updateSupplier,
|
||||||
|
deleteSupplier
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@koa/cors": "^4.0.0",
|
"@koa/cors": "^4.0.0",
|
||||||
"@koa/multer": "^4.0.0",
|
"@koa/multer": "^4.0.0",
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
"koa": "^2.13.4",
|
"koa": "^2.13.4",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.3.0",
|
||||||
"koa-router": "^10.1.1",
|
"koa-router": "^10.1.1",
|
||||||
|
|||||||
Generated
+9
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@koa/multer':
|
'@koa/multer':
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0(koa@2.16.4)(multer@2.1.1)
|
version: 4.0.0(koa@2.16.4)(multer@2.1.1)
|
||||||
|
dotenv:
|
||||||
|
specifier: ^17.4.2
|
||||||
|
version: 17.4.2
|
||||||
koa:
|
koa:
|
||||||
specifier: ^2.13.4
|
specifier: ^2.13.4
|
||||||
version: 2.16.4
|
version: 2.16.4
|
||||||
@@ -155,6 +158,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
||||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
|
||||||
|
dotenv@17.4.2:
|
||||||
|
resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -583,6 +590,8 @@ snapshots:
|
|||||||
|
|
||||||
destroy@1.2.0: {}
|
destroy@1.2.0: {}
|
||||||
|
|
||||||
|
dotenv@17.4.2: {}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
|||||||
+2
-1
@@ -2,10 +2,11 @@ const Router = require('koa-router');
|
|||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const { query } = require('../config/database');
|
const { query } = require('../config/database');
|
||||||
const { toRelativeUrl } = require('../utils/image-url');
|
const { toRelativeUrl } = require('../utils/image-url');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
const AI_API_KEY = 'sk-7f5d6f370f824f2ab76480c01bb00d40';
|
const AI_API_KEY = process.env.DASHSCOPE_API_KEY;
|
||||||
const AI_API_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions';
|
const AI_API_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions';
|
||||||
// 2026-05-24 21:31:40
|
// 2026-05-24 21:31:40
|
||||||
router.post('/generate-product', async (ctx) => {
|
router.post('/generate-product', async (ctx) => {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
const Router = require('koa-router')
|
||||||
|
const pointsGoodsController = require('../controllers/points-goods')
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router.get('/', pointsGoodsController.getPointsGoods)
|
||||||
|
router.get('/:id', pointsGoodsController.getPointsGoodsById)
|
||||||
|
router.post('/', pointsGoodsController.createPointsGoods)
|
||||||
|
router.post('/exchange', pointsGoodsController.exchangePointsGoods)
|
||||||
|
router.put('/:id', pointsGoodsController.updatePointsGoods)
|
||||||
|
router.delete('/:id', pointsGoodsController.deletePointsGoods)
|
||||||
|
|
||||||
|
module.exports = router.routes()
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const Router = require('koa-router')
|
||||||
|
const { query } = require('../config/database')
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router.get('/:userId', async (ctx) => {
|
||||||
|
const userId = parseInt(ctx.params.userId)
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
ctx.body = { code: 400, message: '请指定用户ID' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const logs = await query(
|
||||||
|
'SELECT * FROM points_logs WHERE user_id = ? ORDER BY created_at DESC',
|
||||||
|
[userId]
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
data: logs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router.routes()
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
const Router = require('koa-router')
|
||||||
|
const priceListController = require('../controllers/price-list')
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router.get('/:orderId', priceListController.getPriceList)
|
||||||
|
|
||||||
|
module.exports = router.routes()
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const Router = require('koa-router')
|
||||||
|
const purchaseController = require('../controllers/purchases')
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router.get('/', purchaseController.getPurchases)
|
||||||
|
router.get('/:id', purchaseController.getPurchaseById)
|
||||||
|
router.post('/', purchaseController.createPurchase)
|
||||||
|
router.post('/:id/inbound', purchaseController.inboundPurchase)
|
||||||
|
|
||||||
|
module.exports = router.routes()
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
const Router = require('koa-router')
|
||||||
|
const statsController = require('../controllers/stats')
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router.get('/today', statsController.getTodayStats)
|
||||||
|
|
||||||
|
module.exports = router.routes()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const Router = require('koa-router')
|
||||||
|
const supplierController = require('../controllers/suppliers')
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router.get('/', supplierController.getSuppliers)
|
||||||
|
router.get('/:id', supplierController.getSupplierById)
|
||||||
|
router.post('/', supplierController.createSupplier)
|
||||||
|
router.put('/:id', supplierController.updateSupplier)
|
||||||
|
router.delete('/:id', supplierController.deleteSupplier)
|
||||||
|
|
||||||
|
module.exports = router.routes()
|
||||||
@@ -97,6 +97,68 @@ async function run() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Inserting mock suppliers...')
|
||||||
|
|
||||||
|
const suppliers = [
|
||||||
|
{ name: '鲜果源供应链', contact: '王经理', phone: '13800001001', address: '广州市白云区江南批发市场A区' },
|
||||||
|
{ name: '旺旺食品总代理', contact: '李经理', phone: '13800001002', address: '广州市天河区中山大道88号' },
|
||||||
|
{ name: '百事饮品华南分公司', contact: '陈经理', phone: '13800001003', address: '广州市番禺区南村镇兴业大道' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const s of suppliers) {
|
||||||
|
await query(
|
||||||
|
'INSERT INTO suppliers (name, contact, phone, address) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE name=VALUES(name)',
|
||||||
|
[s.name, s.contact, s.phone, s.address]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Inserting mock purchases...')
|
||||||
|
|
||||||
|
const purchases = [
|
||||||
|
{ supplier_name: '鲜果源供应链', total: 2560.00, status: 1, remarks: '周常补货' },
|
||||||
|
{ supplier_name: '旺旺食品总代理', total: 1800.50, status: 0, remarks: '' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const p of purchases) {
|
||||||
|
const supplier = await query('SELECT id FROM suppliers WHERE name = ?', [p.supplier_name])
|
||||||
|
const supplierId = supplier.length > 0 ? supplier[0].id : null
|
||||||
|
const purchaseResult = await query(
|
||||||
|
'INSERT INTO purchases (supplier_id, supplier_name, total, status, remarks) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[supplierId, p.supplier_name, p.total, p.status, p.remarks || '']
|
||||||
|
)
|
||||||
|
|
||||||
|
if (p.supplier_name === '鲜果源供应链') {
|
||||||
|
await query(
|
||||||
|
'INSERT INTO purchase_items (purchase_id, goods_id, goods_name, quantity, purchase_price) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[purchaseResult.insertId, 1, '红富士苹果', 50, 10.00]
|
||||||
|
)
|
||||||
|
await query(
|
||||||
|
'INSERT INTO purchase_items (purchase_id, goods_id, goods_name, quantity, purchase_price) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[purchaseResult.insertId, 3, '进口车厘子', 20, 45.00]
|
||||||
|
)
|
||||||
|
} else if (p.supplier_name === '旺旺食品总代理') {
|
||||||
|
await query(
|
||||||
|
'INSERT INTO purchase_items (purchase_id, goods_id, goods_name, quantity, purchase_price) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[purchaseResult.insertId, 12, '卫龙辣条', 100, 3.80]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Inserting mock points goods...')
|
||||||
|
|
||||||
|
const pointsGoods = [
|
||||||
|
{ name: '定制帆布袋', points: 200, stock: 50, image: '', description: '环保帆布袋' },
|
||||||
|
{ name: '玻璃水杯', points: 500, stock: 30, image: '', description: '350ml 双层玻璃杯' },
|
||||||
|
{ name: '50元优惠券', points: 1000, stock: 20, image: '', description: '满100可用' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const g of pointsGoods) {
|
||||||
|
await query(
|
||||||
|
'INSERT INTO points_goods (name, points, stock, image, description) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[g.name, g.points, g.stock, g.image, g.description]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Inserting mock points logs...')
|
console.log('Inserting mock points logs...')
|
||||||
|
|
||||||
const pointsLogs = [
|
const pointsLogs = [
|
||||||
@@ -124,12 +186,19 @@ async function run() {
|
|||||||
const stockCount = await query('SELECT COUNT(*) as count FROM stock')
|
const stockCount = await query('SELECT COUNT(*) as count FROM stock')
|
||||||
const logsCount = await query('SELECT COUNT(*) as count FROM points_logs')
|
const logsCount = await query('SELECT COUNT(*) as count FROM points_logs')
|
||||||
|
|
||||||
|
const suppliersCount = await query('SELECT COUNT(*) as count FROM suppliers')
|
||||||
|
const purchasesCount = await query('SELECT COUNT(*) as count FROM purchases')
|
||||||
|
const pointsGoodsCount = await query('SELECT COUNT(*) as count FROM points_goods')
|
||||||
|
|
||||||
console.log(`分类: ${categories[0]?.count || 0} 条`)
|
console.log(`分类: ${categories[0]?.count || 0} 条`)
|
||||||
console.log(`商品: ${goods[0]?.count || 0} 条`)
|
console.log(`商品: ${goods[0]?.count || 0} 条`)
|
||||||
console.log(`用户: ${users[0]?.count || 0} 条`)
|
console.log(`用户: ${users[0]?.count || 0} 条`)
|
||||||
console.log(`订单: ${ordersCount[0]?.count || 0} 条`)
|
console.log(`订单: ${ordersCount[0]?.count || 0} 条`)
|
||||||
console.log(`库存: ${stockCount[0]?.count || 0} 条`)
|
console.log(`库存: ${stockCount[0]?.count || 0} 条`)
|
||||||
console.log(`积分记录: ${logsCount[0]?.count || 0} 条`)
|
console.log(`积分记录: ${logsCount[0]?.count || 0} 条`)
|
||||||
|
console.log(`供应商: ${suppliersCount[0]?.count || 0} 条`)
|
||||||
|
console.log(`采购单: ${purchasesCount[0]?.count || 0} 条`)
|
||||||
|
console.log(`积分商品: ${pointsGoodsCount[0]?.count || 0} 条`)
|
||||||
|
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user