智慧水务管理系统 - 精河县供水工程综合管理平台

inventory-backend.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. require('dotenv').config();
  2. const express = require('express');
  3. const cors = require('cors');
  4. const fs = require('fs');
  5. const path = require('path');
  6. const app = express();
  7. const PORT = process.env.PORT || 3003;
  8. app.use(cors());
  9. app.use(express.json());
  10. // 数据文件路径
  11. const dbPath = path.join(__dirname, '..', '..', 'data', 'inventory-data.json');
  12. function loadDB() {
  13. try {
  14. if (fs.existsSync(dbPath)) {
  15. return JSON.parse(fs.readFileSync(dbPath, 'utf-8'));
  16. }
  17. } catch (e) {
  18. console.error('加载数据库失败:', e.message);
  19. }
  20. return { products: [], stockRecords: [], platforms: [] };
  21. }
  22. function saveDB(data) {
  23. try {
  24. const dir = path.dirname(dbPath);
  25. if (!fs.existsSync(dir)) {
  26. fs.mkdirSync(dir, { recursive: true });
  27. }
  28. fs.writeFileSync(dbPath, JSON.stringify(data, null, 2), 'utf-8');
  29. console.log('💾 数据已保存:', dbPath);
  30. } catch (e) {
  31. console.error('保存数据库失败:', e.message);
  32. }
  33. }
  34. // 初始化
  35. let db = loadDB();
  36. console.log('📊 数据文件:', dbPath);
  37. console.log('📦 商品数:', db.products.length);
  38. // 健康检查
  39. app.get('/api/health', (req, res) => {
  40. res.json({ status: 'ok', service: 'Inventory API', products: db.products.length });
  41. });
  42. // ============ 商品管理 ============
  43. // 获取商品列表
  44. app.get('/api/products', (req, res) => {
  45. try {
  46. const { status, keyword } = req.query;
  47. let products = db.products;
  48. if (status) {
  49. products = products.filter(p => p.status === status);
  50. }
  51. if (keyword) {
  52. const k = keyword.toLowerCase();
  53. products = products.filter(p =>
  54. p.name.toLowerCase().includes(k) ||
  55. p.sku.toLowerCase().includes(k)
  56. );
  57. }
  58. products.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
  59. res.json({ success: true, data: products });
  60. } catch (error) {
  61. res.status(500).json({ success: false, error: error.message });
  62. }
  63. });
  64. // 获取商品详情
  65. app.get('/api/products/:id', (req, res) => {
  66. try {
  67. const product = db.products.find(p => p.id == req.params.id);
  68. if (!product) {
  69. return res.status(404).json({ success: false, error: '商品不存在' });
  70. }
  71. res.json({ success: true, data: product });
  72. } catch (error) {
  73. res.status(500).json({ success: false, error: error.message });
  74. }
  75. });
  76. // 创建商品
  77. app.post('/api/products', (req, res) => {
  78. try {
  79. const { name, sku, category, price, cost, minStock, description } = req.body;
  80. if (!name || !sku) {
  81. return res.status(400).json({ success: false, error: '商品名称和 SKU 必填' });
  82. }
  83. const product = {
  84. id: Date.now(),
  85. name,
  86. sku,
  87. category: category || '',
  88. price: parseFloat(price) || 0,
  89. cost: parseFloat(cost) || 0,
  90. minStock: parseInt(minStock) || 10,
  91. description: description || '',
  92. status: 'active',
  93. totalStock: 0,
  94. created_at: new Date().toISOString(),
  95. updated_at: new Date().toISOString()
  96. };
  97. db.products.push(product);
  98. saveDB(db);
  99. res.json({ success: true, data: { id: product.id }, message: '商品创建成功' });
  100. } catch (error) {
  101. res.status(500).json({ success: false, error: error.message });
  102. }
  103. });
  104. // 更新商品
  105. app.put('/api/products/:id', (req, res) => {
  106. try {
  107. const index = db.products.findIndex(p => p.id == req.params.id);
  108. if (index === -1) {
  109. return res.status(404).json({ success: false, error: '商品不存在' });
  110. }
  111. const { name, sku, category, price, cost, minStock, description, status } = req.body;
  112. db.products[index] = {
  113. ...db.products[index],
  114. name, sku, category, price, cost, minStock, description, status,
  115. updated_at: new Date().toISOString()
  116. };
  117. saveDB(db);
  118. res.json({ success: true, message: '商品更新成功' });
  119. } catch (error) {
  120. res.status(500).json({ success: false, error: error.message });
  121. }
  122. });
  123. // 删除商品
  124. app.delete('/api/products/:id', (req, res) => {
  125. try {
  126. const index = db.products.findIndex(p => p.id == req.params.id);
  127. if (index === -1) {
  128. return res.status(404).json({ success: false, error: '商品不存在' });
  129. }
  130. db.products.splice(index, 1);
  131. db.stockRecords = db.stockRecords.filter(r => r.product_id != req.params.id);
  132. saveDB(db);
  133. res.json({ success: true, message: '商品删除成功' });
  134. } catch (error) {
  135. res.status(500).json({ success: false, error: error.message });
  136. }
  137. });
  138. // ============ 库存管理 ============
  139. // 获取库存记录
  140. app.get('/api/stock-records', (req, res) => {
  141. try {
  142. const { product_id, type, platform } = req.query;
  143. let records = db.stockRecords;
  144. if (product_id) {
  145. records = records.filter(r => r.product_id == product_id);
  146. }
  147. if (type) {
  148. records = records.filter(r => r.type === type);
  149. }
  150. if (platform) {
  151. records = records.filter(r => r.platform === platform);
  152. }
  153. records.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
  154. res.json({ success: true, data: records.slice(0, 100) });
  155. } catch (error) {
  156. res.status(500).json({ success: false, error: error.message });
  157. }
  158. });
  159. // 添加入库
  160. app.post('/api/stock-in', (req, res) => {
  161. try {
  162. const { product_id, quantity, platform, note } = req.body;
  163. if (!product_id || !quantity) {
  164. return res.status(400).json({ success: false, error: '商品 ID 和数量必填' });
  165. }
  166. const product = db.products.find(p => p.id == product_id);
  167. if (!product) {
  168. return res.status(404).json({ success: false, error: '商品不存在' });
  169. }
  170. // 添加入库记录
  171. const record = {
  172. id: Date.now(),
  173. product_id,
  174. type: 'in',
  175. quantity: parseInt(quantity),
  176. platform: platform || '总仓',
  177. note: note || '',
  178. created_at: new Date().toISOString()
  179. };
  180. db.stockRecords.push(record);
  181. // 更新商品总库存
  182. const platformStock = db.stockRecords
  183. .filter(r => r.product_id == product_id && r.platform === record.platform)
  184. .reduce((sum, r) => sum + (r.type === 'in' ? r.quantity : -r.quantity), 0);
  185. product.totalStock = db.stockRecords
  186. .filter(r => r.product_id == product_id)
  187. .reduce((sum, r) => sum + (r.type === 'in' ? r.quantity : -r.quantity), 0);
  188. product.updated_at = new Date().toISOString();
  189. saveDB(db);
  190. res.json({
  191. success: true,
  192. data: { id: record.id, totalStock: product.totalStock, platformStock },
  193. message: '入库成功'
  194. });
  195. } catch (error) {
  196. res.status(500).json({ success: false, error: error.message });
  197. }
  198. });
  199. // 出库
  200. app.post('/api/stock-out', (req, res) => {
  201. try {
  202. const { product_id, quantity, platform, note } = req.body;
  203. if (!product_id || !quantity) {
  204. return res.status(400).json({ success: false, error: '商品 ID 和数量必填' });
  205. }
  206. const product = db.products.find(p => p.id == product_id);
  207. if (!product) {
  208. return res.status(404).json({ success: false, error: '商品不存在' });
  209. }
  210. // 检查库存是否充足
  211. const currentStock = db.stockRecords
  212. .filter(r => r.product_id == product_id)
  213. .reduce((sum, r) => sum + (r.type === 'in' ? r.quantity : -r.quantity), 0);
  214. if (currentStock < quantity) {
  215. return res.status(400).json({ success: false, error: '库存不足' });
  216. }
  217. // 添加出库记录
  218. const record = {
  219. id: Date.now(),
  220. product_id,
  221. type: 'out',
  222. quantity: parseInt(quantity),
  223. platform: platform || '总仓',
  224. note: note || '',
  225. created_at: new Date().toISOString()
  226. };
  227. db.stockRecords.push(record);
  228. // 更新商品总库存
  229. product.totalStock = currentStock - quantity;
  230. product.updated_at = new Date().toISOString();
  231. saveDB(db);
  232. res.json({
  233. success: true,
  234. data: { id: record.id, totalStock: product.totalStock },
  235. message: '出库成功'
  236. });
  237. } catch (error) {
  238. res.status(500).json({ success: false, error: error.message });
  239. }
  240. });
  241. // ============ 平台库存同步 ============
  242. // 获取平台库存
  243. app.get('/api/platform-stock/:product_id', (req, res) => {
  244. try {
  245. const product = db.products.find(p => p.id == req.params.product_id);
  246. if (!product) {
  247. return res.status(404).json({ success: false, error: '商品不存在' });
  248. }
  249. const platforms = ['淘宝', '京东', '拼多多', '抖音', '总仓'];
  250. const stockByPlatform = {};
  251. platforms.forEach(platform => {
  252. const stock = db.stockRecords
  253. .filter(r => r.product_id == req.params.product_id && r.platform === platform)
  254. .reduce((sum, r) => sum + (r.type === 'in' ? r.quantity : -r.quantity), 0);
  255. stockByPlatform[platform] = stock;
  256. });
  257. res.json({
  258. success: true,
  259. data: {
  260. product_id: product.id,
  261. name: product.name,
  262. sku: product.sku,
  263. totalStock: product.totalStock,
  264. byPlatform: stockByPlatform
  265. }
  266. });
  267. } catch (error) {
  268. res.status(500).json({ success: false, error: error.message });
  269. }
  270. });
  271. // 同步平台库存
  272. app.post('/api/sync-platform/:product_id', (req, res) => {
  273. try {
  274. const { platform, quantity } = req.body;
  275. if (!platform || quantity === undefined) {
  276. return res.status(400).json({ success: false, error: '平台和数量必填' });
  277. }
  278. const product = db.products.find(p => p.id == req.params.product_id);
  279. if (!product) {
  280. return res.status(404).json({ success: false, error: '商品不存在' });
  281. }
  282. // 获取当前该平台库存
  283. const currentPlatformStock = db.stockRecords
  284. .filter(r => r.product_id == req.params.product_id && r.platform === platform)
  285. .reduce((sum, r) => sum + (r.type === 'in' ? r.quantity : -r.quantity), 0);
  286. const diff = quantity - currentPlatformStock;
  287. // 添加调整记录
  288. const record = {
  289. id: Date.now(),
  290. product_id: product.id,
  291. type: diff > 0 ? 'in' : 'out',
  292. quantity: Math.abs(diff),
  293. platform,
  294. note: `平台库存同步 (目标:${quantity}, 当前:${currentPlatformStock})`,
  295. created_at: new Date().toISOString()
  296. };
  297. db.stockRecords.push(record);
  298. // 更新总库存
  299. product.totalStock = db.stockRecords
  300. .filter(r => r.product_id == product.id)
  301. .reduce((sum, r) => sum + (r.type === 'in' ? r.quantity : -r.quantity), 0);
  302. product.updated_at = new Date().toISOString();
  303. saveDB(db);
  304. res.json({
  305. success: true,
  306. data: { totalStock: product.totalStock, platformStock: quantity },
  307. message: `库存同步成功 (调整:${diff > 0 ? '+' : ''}${diff})`
  308. });
  309. } catch (error) {
  310. res.status(500).json({ success: false, error: error.message });
  311. }
  312. });
  313. // ============ 统计数据 ============
  314. app.get('/api/stats', (req, res) => {
  315. try {
  316. const totalProducts = db.products.length;
  317. const activeProducts = db.products.filter(p => p.status === 'active').length;
  318. const lowStock = db.products.filter(p => p.totalStock <= p.minStock).length;
  319. const totalValue = db.products.reduce((sum, p) => sum + (p.cost * p.totalStock), 0);
  320. // 今日出入库
  321. const today = new Date().toDateString();
  322. const todayIn = db.stockRecords
  323. .filter(r => r.type === 'in' && new Date(r.created_at).toDateString() === today)
  324. .reduce((sum, r) => sum + r.quantity, 0);
  325. const todayOut = db.stockRecords
  326. .filter(r => r.type === 'out' && new Date(r.created_at).toDateString() === today)
  327. .reduce((sum, r) => sum + r.quantity, 0);
  328. res.json({
  329. success: true,
  330. data: {
  331. totalProducts,
  332. activeProducts,
  333. lowStock,
  334. totalValue,
  335. todayIn,
  336. todayOut
  337. }
  338. });
  339. } catch (error) {
  340. res.status(500).json({ success: false, error: error.message });
  341. }
  342. });
  343. // 库存预警
  344. app.get('/api/alerts', (req, res) => {
  345. try {
  346. const alerts = db.products
  347. .filter(p => p.totalStock <= p.minStock)
  348. .map(p => ({
  349. product_id: p.id,
  350. name: p.name,
  351. sku: p.sku,
  352. currentStock: p.totalStock,
  353. minStock: p.minStock,
  354. level: p.totalStock === 0 ? 'critical' : 'warning'
  355. }));
  356. res.json({ success: true, data: alerts });
  357. } catch (error) {
  358. res.status(500).json({ success: false, error: error.message });
  359. }
  360. });
  361. app.listen(PORT, () => {
  362. console.log(`🚀 库存管理 API 已启动:http://localhost:${PORT}`);
  363. });