
先聊聊我最近的感受。很多中小型制造企業和貿易公司在庫存管理上其實一直處于“半手工”狀態采購靠Excel登記銷售開單靠聊天記錄倉庫盤點靠人肉數數。一開始訂單量小還沒感覺等業務跑起來之后庫存賬面和實物對不上、訂單超賣、采購重復下單等問題就會接連出現。企業想直接上SAP、Oracle這類商業ERP成本太高實施周期也長。這時候開源免費的ERP庫存管理系統就成了一個性價比很高的選擇。市面上確實有不少成熟的開源項目也有不少團隊選擇基于開源框架二次開發一套自己的庫存管理系統。這篇文章我就從開源ERP庫存管理系統的選型思路、核心概念、數據庫設計、接口實現到常見問題和工程化建議做一次完整的拆解幫助你從零搭建一套可用的庫存管理系統或者幫助你評估現有開源項目的落地方式。文章會包含大量可復用的SQL、Java和Vue代碼重點講清楚庫存系統的核心閉環入庫、出庫、庫存流水、庫存余額以及并發扣減庫存時的安全問題。不管你是準備學習ERP開發還是要在企業內部推動開源ERP落地這篇文章都值得收藏備用。1. 開源ERP庫存管理系統是什么解決什么問題1.1 什么是ERP庫存管理系統ERPEnterprise Resource Planning是企業資源計劃系統的簡稱它覆蓋財務、采購、銷售、生產、庫存、人力資源等模塊。而庫存管理系統通常指其中負責物料管理和庫存核算的部分它要回答幾個核心問題倉庫里現在有什么物料每個物料的數量是多少這些物料分布在哪些倉庫庫存的變化是由哪張業務單據觸發的當前的可用庫存是否能滿足一張銷售訂單所以庫存管理系統并不是簡單的“進銷存登記表”。它必須把采購入庫、銷售出庫、生產領料、退貨、盤點、調撥這些業務動作統一建模形成一條完整的數據鏈路。1.2 開源ERP與傳統商業ERP的區別對比維度開源ERP商業ERP軟件許可費用免費或低成本按模塊、用戶數、年費計費源代碼通常開放可二次開發閉源定制依賴原廠實施方式自研團隊或第三方實施原廠或授權顧問實施擴展能力可靈活修改業務邏輯受限于平臺能力技術門檻較高需要懂開發和運維較低配置為主典型代表Odoo、ERPNext、Apache OFBizSAP、Oracle EBS、用友、金蝶選擇開源ERP庫存管理系統并不意味著“免費所以湊合”。相反你需要具備一定的技術能力去掌控它。很多企業選擇開源方案核心訴求是三點源代碼可控業務邏輯可以按需改造。無License授權壓力尤其是對預算敏感的成長型企業。技術棧公開透明不綁定特定廠商。1.3 常見的開源ERP項目概覽項目名稱技術棧適用場景許可證OdooPython PostgreSQL中小型制造、貿易、電商LGPLERPNextPython Frappe框架中小企業全模塊管理GPLApache OFBizJava Tomcat大型企業復雜業務Apache 2.0華夏ERP基于SpringBootJava Vue MySQL國內中小企業進銷存GPL若依RuoYiJava Vue適合二次開發后臺管理系統MIT需要說明的是開源項目的許可證非常關鍵。如果企業計劃對系統做閉源二次發行那么GPL類協議會有較嚴格的約束如果是內部使用不對外發行通常影響較小。做技術選型時一定要去對應開源倉庫確認License原文不能只看社區傳聞。1.4 開源庫存管理系統適合哪些場景年營收在幾千萬到幾億之間、還在用Excel管理庫存的中小制造企業。有技術團隊、希望自己掌控核心業務系統的成長型公司。高校、實驗室、非營利組織預算有限但又需要規范流程管理。個人開發者學習ERP業務邏輯接觸真實進銷存場景。軟件公司希望基于成熟開源框架快速交付客戶項目。2. 開源ERP庫存管理系統的技術選型與架構設計2.1 技術棧選型建議在實際項目中庫存管理系統通常作為整個ERP系統中的子模塊出現。如果從零開始搭建一套輕量級庫存管理系統我比較推薦下面的技術組合技術層次推薦方案說明前端框架Vue 3 Element Plus組件生態豐富適合中后臺系統后端框架Spring Boot 2.x / 3.xJava生態成熟事務與并發控制能力強數據庫MySQL 8.0使用廣泛運維成本低權限框架Spring Security JWT實現登錄認證與接口權限構建工具Maven / npm標準工程構建方式部署方式Docker Compose便于本地環境與生產環境保持一致這里的版本號只是推薦具體需要結合你的項目要求來定。Spring Boot 3.x基于Jakarta EE與Spring Boot 2.x在某些依賴上不兼容MySQL 8.0支持窗口函數和更好的鎖機制但如果你已有的生產環境是5.7也不必強行升級。本文側重講解庫存管理系統的核心設計代碼示例會盡量保持通用。2.2 整體架構圖文字版Vue 3 Element Plus | | HTTP/JSON v Spring Boot 后端服務 |--- 認證模塊 (JWT) |--- 商品管理模塊 |--- 倉庫管理模塊 |--- 入庫單模塊 |--- 出庫單模塊 |--- 庫存流水模塊 |--- 庫存余額模塊 | | JDBC v MySQL 數據庫 |--- sys_user 用戶表 |--- bas_product 商品表 |--- bas_warehouse 倉庫表 |--- stk_inbound 入庫單表 |--- stk_inbound_item 入庫明細表 |--- stk_outbound 出庫單表 |--- stk_outbound_item 出庫明細表 |--- stk_stock_flow 庫存流水表 |--- stk_stock_balance 庫存余額表從整體架構可以看出庫存管理系統的核心是“單據 流水 余額”三層模型。業務操作不直接修改庫存數量而是通過生成入庫單或出庫單同時記錄庫存流水最后更新庫存余額。2.3 核心業務閉環庫存系統的完整閉環可以拆成下面幾步用戶創建入庫單填寫供應商、收貨倉庫、商品和數量。入庫單審核通過后系統生成入庫庫存流水。系統更新對應倉庫、對應商品的庫存余額。用戶創建出庫單填寫客戶、發貨倉庫、商品和數量。出庫單審核時系統檢查可用庫存是否充足。庫存充足則扣減庫存不足則提示庫存不足。財務或管理人員通過報表查看庫存變動、庫存價值、出入庫趨勢。這個閉環看起來簡單但實際開發中并發扣減庫存、事務一致性、冪等性等問題非常容易出現。3. 數據庫表設計庫存系統的基石3.1 數據模型設計原則在設計庫存管理系統數據庫時至少要遵循以下原則單據與明細分離主表存單據頭信息明細表存商品明細避免單表字段過多。數量與金額精度控制庫存數量使用DECIMAL不使用浮點類型避免精度丟失。庫存流水只增不改流水表通過流水號唯一約束一旦寫入不允許修改和刪除只能通過沖銷單糾正。庫存余額必須有版本號或鎖機制用于處理并發更新防止超賣。所有業務表都要帶創建人、創建時間、更新人、更新時間和邏輯刪除標記。3.2 商品表和倉庫表-- 商品表 CREATE TABLE bas_product ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, product_code VARCHAR(50) NOT NULL COMMENT 商品編碼, product_name VARCHAR(100) NOT NULL COMMENT 商品名稱, category_id BIGINT COMMENT 分類ID, unit VARCHAR(20) DEFAULT 件 COMMENT 單位, spec VARCHAR(100) COMMENT 規格型號, status TINYINT DEFAULT 1 COMMENT 狀態1啟用0停用, deleted TINYINT DEFAULT 0 COMMENT 邏輯刪除0否1是, create_by VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_by VARCHAR(50), update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_product_code (product_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; -- 倉庫表 CREATE TABLE bas_warehouse ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, warehouse_code VARCHAR(50) NOT NULL COMMENT 倉庫編碼, warehouse_name VARCHAR(100) NOT NULL COMMENT 倉庫名稱, address VARCHAR(255) COMMENT 倉庫地址, manager VARCHAR(50) COMMENT 負責人, status TINYINT DEFAULT 1 COMMENT 狀態1啟用0停用, deleted TINYINT DEFAULT 0 COMMENT 邏輯刪除, create_by VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_by VARCHAR(50), update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_warehouse_code (warehouse_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT倉庫表;3.3 入庫單和入庫明細表-- 入庫單主表 CREATE TABLE stk_inbound ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, inbound_no VARCHAR(50) NOT NULL COMMENT 入庫單號, inbound_type TINYINT COMMENT 入庫類型1采購入庫2生產入庫3退貨入庫4盤點入庫, supplier_id BIGINT COMMENT 供應商ID, warehouse_id BIGINT NOT NULL COMMENT 收貨倉庫ID, inbound_date DATE NOT NULL COMMENT 入庫日期, status TINYINT DEFAULT 0 COMMENT 狀態0草稿1已審核2已作廢, remark VARCHAR(500) COMMENT 備注, deleted TINYINT DEFAULT 0, create_by VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_by VARCHAR(50), update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_inbound_no (inbound_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入庫單主表; -- 入庫明細表 CREATE TABLE stk_inbound_item ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, inbound_id BIGINT NOT NULL COMMENT 入庫單主表ID, product_id BIGINT NOT NULL COMMENT 商品ID, quantity DECIMAL(18,3) NOT NULL COMMENT 入庫數量, price DECIMAL(18,2) DEFAULT 0 COMMENT 入庫單價, amount DECIMAL(18,2) DEFAULT 0 COMMENT 金額, remark VARCHAR(200) COMMENT 明細備注, KEY idx_inbound_id (inbound_id), KEY idx_product_id (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入庫明細表;3.4 出庫單和出庫明細表出庫表和入庫表結構非常相似這里可以單獨建表也可以設計成一張通用庫存單據表。對于初學者來說分表更容易理解。如果業務擴展后單據類型增多再考慮將公共字段抽取為通用單據表。-- 出庫單主表 CREATE TABLE stk_outbound ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, outbound_no VARCHAR(50) NOT NULL COMMENT 出庫單號, outbound_type TINYINT COMMENT 出庫類型1銷售出庫2生產領料3報損出庫4盤虧出庫, customer_id BIGINT COMMENT 客戶ID, warehouse_id BIGINT NOT NULL COMMENT 發貨倉庫ID, outbound_date DATE NOT NULL COMMENT 出庫日期, status TINYINT DEFAULT 0 COMMENT 狀態0草稿1已審核2已作廢, remark VARCHAR(500) COMMENT 備注, deleted TINYINT DEFAULT 0, create_by VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_by VARCHAR(50), update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_outbound_no (outbound_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT出庫單主表; -- 出庫明細表 CREATE TABLE stk_outbound_item ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, outbound_id BIGINT NOT NULL COMMENT 出庫單主表ID, product_id BIGINT NOT NULL COMMENT 商品ID, quantity DECIMAL(18,3) NOT NULL COMMENT 出庫數量, price DECIMAL(18,2) DEFAULT 0 COMMENT 出庫單價, amount DECIMAL(18,2) DEFAULT 0 COMMENT 金額, remark VARCHAR(200) COMMENT 明細備注, KEY idx_outbound_id (outbound_id), KEY idx_product_id (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT出庫明細表;3.5 庫存流水表和庫存余額表庫存流水表和庫存余額表是整個庫存系統的核心。簡單來說流水表記錄了每一次庫存變化余額表保存當前庫存數量。查詢報表時可以先看余額表要追溯時可以查流水表。-- 庫存流水表 CREATE TABLE stk_stock_flow ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, flow_no VARCHAR(50) NOT NULL COMMENT 流水號, product_id BIGINT NOT NULL COMMENT 商品ID, warehouse_id BIGINT NOT NULL COMMENT 倉庫ID, change_type TINYINT COMMENT 變動類型1入庫2出庫3盤點調整4調撥出5調撥入, source_no VARCHAR(50) COMMENT 來源單據號, source_type TINYINT COMMENT 來源單據類型1入庫單2出庫單3盤點單4調撥單, quantity_change DECIMAL(18,3) NOT NULL COMMENT 變動數量入庫為正出庫為負, before_quantity DECIMAL(18,3) DEFAULT 0 COMMENT 變動前庫存, after_quantity DECIMAL(18,3) DEFAULT 0 COMMENT 變動后庫存, create_by VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_flow_no (flow_no), KEY idx_product_warehouse (product_id, warehouse_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT庫存流水表; -- 庫存余額表 CREATE TABLE stk_stock_balance ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, product_id BIGINT NOT NULL COMMENT 商品ID, warehouse_id BIGINT NOT NULL COMMENT 倉庫ID, stock_quantity DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT 庫存數量, locked_quantity DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT 鎖定數量, available_quantity DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT 可用數量, version INT NOT NULL DEFAULT 0 COMMENT 樂觀鎖版本號, create_by VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_by VARCHAR(50), update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_product_warehouse (product_id, warehouse_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT庫存余額表;這里引入了一個“鎖定數量”字段。這個概念在電商庫存系統中用得非常多。當用戶提交訂單但還沒付款時可以先占用一部分庫存避免其他訂單繼續超賣。在普通ERP庫存系統中如果業務流程沒有“預占”環節可以暫時不用這個字段但保留它能為以后的復雜業務留出擴展空間。3.6 為什么需要 before_quantity 和 after_quantity在設計庫存流水表時有人會問既然有了 quantity_change為什么還要存 before_quantity 和 after_quantity原因很簡單便于對賬和排查。如果某一天發現庫存余額不平可以通過流水表快速看到每筆操作發生前后的庫存快照。特別是在多人同時操作的情況下光靠變動數量無法還原當時的庫存上下文。保留這兩個字段相當于給每次庫存變動記錄了系統日志。4. 后端核心實現Spring Boot 庫存服務4.1 項目基礎結構假設我們使用Spring Boot MyBatis-Plus來構建后端服務項目結構如下erp-stock-system ├── pom.xml └── src └── main ├── java │ └── com │ └── example │ └── erp │ ├── ErpApplication.java │ ├── common │ │ ├── Result.java │ │ └── BusinessException.java │ ├── controller │ │ └── StockController.java │ ├── entity │ │ ├── StockBalance.java │ │ ├── StockFlow.java │ │ ├── Inbound.java │ │ └── Outbound.java │ ├── mapper │ │ ├── StockBalanceMapper.java │ │ ├── StockFlowMapper.java │ │ ├── InboundMapper.java │ │ └── OutboundMapper.java │ └── service │ ├── InboundService.java │ └── OutboundService.java └── resources ├── application.yml └── mapper4.2 Maven 依賴配置dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies4.3 庫存扣減的核心邏輯庫存扣減是庫存系統里最容易出Bug的地方。下面用一個出庫場景來演示核心代碼。注意這里的示例重點是事務和并發控制。首先定義庫存余額實體Data TableName(stk_stock_balance) public class StockBalance { TableId(type IdType.AUTO) private Long id; private Long productId; private Long warehouseId; private BigDecimal stockQuantity; private BigDecimal lockedQuantity; private BigDecimal availableQuantity; Version private Integer version; }然后在Service中實現出庫審核邏輯Service public class OutboundService { Resource private StockBalanceMapper stockBalanceMapper; Resource private StockFlowMapper stockFlowMapper; Transactional(rollbackFor Exception.class) public void auditOutbound(Outbound outbound, ListOutboundItem items) { for (OutboundItem item : items) { // 1. 查詢庫存余額 StockBalance balance stockBalanceMapper.selectByProductAndWarehouse( item.getProductId(), outbound.getWarehouseId()); if (balance null) { throw new BusinessException(庫存余額不存在請先初始化庫存); } // 2. 校驗可用庫存 if (balance.getAvailableQuantity().compareTo(item.getQuantity()) 0) { throw new BusinessException(商品庫存不足); } // 3. 使用樂觀鎖更新庫存 int rows stockBalanceMapper.deductStock( balance.getId(), item.getQuantity(), balance.getVersion()); if (rows 0) { throw new BusinessException(庫存扣減失敗請重試); } // 4. 寫入庫存流水 StockFlow flow new StockFlow(); flow.setFlowNo(generateFlowNo()); flow.setProductId(item.getProductId()); flow.setWarehouseId(outbound.getWarehouseId()); flow.setChangeType(2); flow.setSourceNo(outbound.getOutboundNo()); flow.setSourceType(2); flow.setQuantityChange(item.getQuantity().negate()); flow.setBeforeQuantity(balance.getStockQuantity()); flow.setAfterQuantity(balance.getStockQuantity().subtract(item.getQuantity())); flow.setCreateBy(outbound.getUpdateBy()); stockFlowMapper.insert(flow); } // 5. 更新出庫單狀態為已審核 outbound.setStatus(1); outboundMapper.updateById(outbound); } }對應的Mapper SQL如下update iddeductStock UPDATE stk_stock_balance SET stock_quantity stock_quantity - #{quantity}, available_quantity available_quantity - #{quantity}, version version 1 WHERE id #{id} AND version #{version} AND available_quantity gt; #{quantity} /update這段SQL的關鍵點在于UPDATE語句中同時帶上了version條件并且通過available_quantity大于等于待扣數量來做額外的庫存約束。在數據庫層面這種寫法可以防止并發場景下兩個請求同時扣減同一份庫存。4.4 入庫審核的核心邏輯入庫相對簡單核心是生成流水并累加庫存Transactional(rollbackFor Exception.class) public void auditInbound(Inbound inbound, ListInboundItem items) { for (InboundItem item : items) { StockBalance balance stockBalanceMapper.selectByProductAndWarehouse( item.getProductId(), inbound.getWarehouseId()); if (balance null) { // 初始化一條庫存余額記錄 balance new StockBalance(); balance.setProductId(item.getProductId()); balance.setWarehouseId(inbound.getWarehouseId()); balance.setStockQuantity(BigDecimal.ZERO); balance.setLockedQuantity(BigDecimal.ZERO); balance.setAvailableQuantity(BigDecimal.ZERO); stockBalanceMapper.insert(balance); } // 增加庫存 int rows stockBalanceMapper.addStock( balance.getId(), item.getQuantity(), balance.getVersion()); if (rows 0) { throw new BusinessException(庫存更新失敗請重試); } // 寫入流水 StockFlow flow new StockFlow(); flow.setFlowNo(generateFlowNo()); flow.setProductId(item.getProductId()); flow.setWarehouseId(inbound.getWarehouseId()); flow.setChangeType(1); flow.setSourceNo(inbound.getInboundNo()); flow.setSourceType(1); flow.setQuantityChange(item.getQuantity()); flow.setBeforeQuantity(balance.getStockQuantity()); flow.setAfterQuantity(balance.getStockQuantity().add(item.getQuantity())); flow.setCreateBy(inbound.getUpdateBy()); stockFlowMapper.insert(flow); } inbound.setStatus(1); inboundMapper.updateById(inbound); }這里有幾個容易被忽略的點新增庫存余額記錄時要注意并發插入。如果在極短時間內同時收到兩條入庫請求且庫存余額記錄不存在可能會插入兩條重復記錄。解決方式有兩種一是利用數據庫唯一索引uk_product_warehouse兜底二是先查詢如果不存在則通過INSERT IGNORE或ON DUPLICATE KEY UPDATE方式處理。流水號建議使用類似“年月日隨機數”或“日期序列”的格式。這里可以使用Redis自增序列也可以直接用數據庫的ID生成器但一定要保證唯一。4.5 事務與并發控制方案選擇在庫存扣減場景中常見的并發控制方案有三種方案實現方式優點缺點樂觀鎖使用version字段更新時校驗版本號簡單、性能好沖突時需要重試悲觀鎖SELECT ... FOR UPDATE鎖定記錄實現直觀不會重試并發高時鎖競爭激烈數據庫約束使用條件更新 WHERE available_quantity quantity數據庫層面兜底需要配合樂觀鎖或事務使用實際項目中我比較推薦“樂觀鎖 條件更新”的組合方式。先用樂觀鎖保證版本一致性再用數據庫條件更新作為最終約束這樣既不會因為鎖等待導致系統卡頓又能防止超賣。5. 前端頁面Vue 3 Element Plus 快速搭建5.1 前端項目結構前端部分使用Vue 3 Vite Element Plus項目結構如下erp-web ├── index.html ├── package.json ├── vite.config.js └── src ├── main.js ├── App.vue ├── api │ └── stock.js └── views ├── dashboard.vue ├── inbound.vue ├── outbound.vue └── stock.vue5.2 庫存余額展示頁面下面是一個簡單的庫存余額列表頁面template div classstock-page el-card el-form :inlinetrue el-form-item label商品編碼 el-input v-modelqueryParam.productCode placeholder請輸入商品編碼 clearable / /el-form-item el-form-item label倉庫 el-select v-modelqueryParam.warehouseId placeholder請選擇倉庫 clearable el-option v-foritem in warehouseList :keyitem.id :labelitem.warehouseName :valueitem.id / /el-select /el-form-item el-form-item el-button typeprimary clickloadStockList查詢/el-button el-button clickresetQuery重置/el-button /el-form-item /el-form /el-card el-card stylemargin-top: 16px el-table :datastockList border stripe el-table-column propproductCode label商品編碼 min-width120 / el-table-column propproductName label商品名稱 min-width150 / el-table-column propwarehouseName label倉庫名稱 min-width120 / el-table-column propstockQuantity label庫存數量 min-width100 alignright / el-table-column proplockedQuantity label鎖定數量 min-width100 alignright / el-table-column propavailableQuantity label可用數量 min-width100 alignright / /el-table /el-card /div /template script setup import { ref, onMounted } from vue import { getStockList } from /api/stock const queryParam ref({ productCode: , warehouseId: null }) const stockList ref([]) const warehouseList ref([]) async function loadStockList() { const res await getStockList(queryParam.value) stockList.value res.data.records } function resetQuery() { queryParam.value { productCode: , warehouseId: null } loadStockList() } onMounted(() { loadStockList() }) /script這個頁面的作用是展示當前庫存余額讓管理者可以快速看到某個倉庫中所有商品的數量分布。實際系統中還可以加入庫存預警閾值當可用庫存低于某個下限時在列表里用紅色標識提示需要補貨。5.3 前端接口封裝import request from /utils/request export function getStockList(params) { return request({ url: /api/stock/list, method: get, params }) } export function auditInbound(data) { return request({ url: /api/inbound/audit, method: post, data }) } export function auditOutbound(data) { return request({ url: /api/outbound/audit, method: post, data }) }前端做好數據展示和交互即可真正的業務規則必須放在后端服務中。尤其是庫存扣減、事務、權限校驗這類邏輯如果放在前端任何人都可以通過瀏覽器控制臺繞過。6. 開源許可證與二次開發注意事項6.1 許可證怎么選很多開發者在Gitee或GitHub上創建開源項目時都會糾結許可證怎么選。這里給出一個簡單的選擇建議許可證特點適合場景MIT寬松允許商用允許閉源個人項目、內部系統、希望被廣泛使用Apache 2.0寬松附帶專利授權條款企業級開源項目GPL傳染性衍生代碼需開源希望生態保持開源LGPL修改庫文件才需要開源被其他項目以庫形式引用MPL文件級別傳染混合開源開發模型如果你在開發開源ERP庫存管理系統需要明確一個問題允許別人拿去商用而不開源嗎如果允許建議MIT或Apache 2.0如果希望商業公司在使用后把增強功能回饋社區可以選擇GPL。6.2 二次開發時的注意事項保留上游項目版權聲明不要刪除License文件。修改代碼時盡量以模塊化方式擴展避免在核心代碼上大面積改動。如果是基于GPL項目二次開發對外分發時需要注意源代碼開放義務。如果只是企業內部使用不分發給第三方GPL和MIT的影響相對可控但也要咨詢法務。7. 常見問題與排查思路7.1 常見問題匯總表問題現象常見原因解決思路庫存扣減后變成負數未使用樂觀鎖或條件更新并發扣減在UPDATE語句中增加可用庫存判斷使用version同一商品出現多條庫存余額記錄并發插入庫存余額唯一索引失效創建唯一索引插入時使用ON DUPLICATE KEY UPDATE頁面顯示庫存和實際盤點不一致手工修改數據庫導致流水斷裂禁止手工改庫存通過盤點單調整超賣訂單提交和庫存扣減不是同一事務將訂單創建與庫存扣減放在同一事務中流水號重復使用時間戳或簡單隨機數生成流水號使用Redis自增或數據庫序列報表查詢慢庫存流水表全表掃描為product_id、warehouse_id、create_time建立聯合索引Decimal類型計算出現精度問題使用double或float存儲金額金額和數量統一使用DECIMAL7.2 復盤一個典型的庫存變負事故假設系統上線后運營反饋某個商品庫存從“3件”變成了“-1件”。排查思路如下查詢該商品的庫存流水表看看最近有哪些出入庫記錄。找出導致庫存變負的那條流水確認來源單據。檢查該單據審核時后端是否執行了“可用庫存是否充足”的判斷。如果判斷邏輯存在再看是否是并發導致的兩個請求同時讀到庫存為3同時扣2理論上最終結果應該是1但由于沒有版本號控制兩條SQL都成功執行結果變成-1。修復方式為庫存余額表增加version字段修改UPDATE語句增加available_quantity quantity條件。這個案例說明庫存系統不是“能增能減”就行必須把并發控制放到數據庫層而不是依賴應用層的判斷。8. 最佳實踐與工程建議8.1 單據驅動禁止直接改庫存很多初學ERP開發的朋友會犯一個錯誤用戶點擊“庫存調整”直接寫一條UPDATE語句把庫存數量改了。這樣做表面上很快實際是在給系統埋雷。正確的做法是引入“盤點單”。盤點的本質是“賬面數量 vs 實盤數量”差異部分通過盤點單生成盤盈或盤虧流水最終調整庫存余額。這樣做的好處是所有庫存變動都可追溯每筆變更都能找到對應的業務單據。8.2 庫存流水只增不改庫存流水表記錄了企業所有庫存變動的歷史是一份極其重要的審計數據。設計上應該做到流水表不提供修改和刪除接口。即使對應的入庫單或出庫單作廢流水表仍然保留。作廢單據時需要生成一張反向沖銷的流水而不是物理刪除原來的流水。這樣設計雖然會增加一些開發量但長期來看系統的數據可信度會高很多。8.3 永遠在事務中操作庫存所有涉及庫存余額變化的操作都必須放在數據庫事務中。Spring中可以通過Transactional注解實現。事務的隔離級別建議設置為默認的數據庫隔離級別不要輕易提高。另外事務中盡量縮短業務邏輯的執行時間不要在事務內調用第三方HTTP接口、發送短信郵件、等待MQ消息等耗時操作。否則會導致數據庫連接長時間占用系統吞吐量下降。8.4 權限與安全邊界庫存管理系統涉及企業的核心資產數據安全設計不能放松接口必須做登錄認證不能裸奔。權限控制建議做到按鈕級別不只是頁面級別。審核入庫單和創建入庫單建議由不同角色承擔形成簡單復核機制。所有寫操作都要記錄操作日志包括操作人、操作時間、IP、操作內容。不要在前端頁面暴露全量庫存導出接口或者至少加入權限校驗和數據量限制防止數據被批量拉取。8.5 數據備份與灰度發布生產環境數據庫必須配置自動備份策略。庫存數據一旦丟失或錯亂恢復成本極高。建議每周至少做一次全量備份每天做一次增量備份備份文件要存儲到獨立存儲空間。系統升級時先在測試環境完整跑一遍出入庫流程確認無問題后再發布到生產。如果條件允許可以采用灰度發布方式先讓倉庫部門的少量賬號使用新版本驗證穩定后再全員切換。8.6 性能優化要點庫存流水表數據量會隨著業務增長快速膨脹建議按季度或按年做分區。常用查詢條件要建立聯合索引例如(product_id, warehouse_id, create_time)。庫存余額表的查詢非常頻繁盡量通過組合條件唯一命中記錄避免全表掃描。報表類查詢不要直接在業務庫執行可以通過定時任務把數據同步到統計庫或者使用讀從庫。9. 總結與學習路線寫到這里開源ERP庫存管理系統的核心內容已經拆解得比較完整了。如果你是從零開始學習ERP開發建議按下面的路線繼續深入先理解“單據 流水 余額”三層模型這是整個庫存系統的靈魂。自己動手建一套MySQL表結構手工插入幾條入庫單、出庫單數據查看庫存余額變化。學習Spring Boot事務管理和MyBatis-Plus的樂觀鎖插件寫一個簡單的庫存扣減接口。完善單據審核流程、庫存盤點、庫存預警、調撥單逐步擴展系統能力。閱讀優秀開源項目的源碼比如Odoo庫存模塊、ERPNext的Stock模塊學習它們的業務抽象方式。最后嘗試把庫存模塊與采購、銷售、財務模塊打通理解ERP系統各模塊之間的關聯。開源ERP庫存管理系統的優勢在于你可以邊看源碼邊學習遇到不滿足的業務需求還能自己改。但這也意味著你需要投入時間和精力不能把自己當成普通的軟件使用者。如果你所在的企業正好需要一個庫存管理系統不妨先從本文提到的數據模型和核心接口入手搭一個最小可運行版本再根據實際業務去迭代。我在文章里分享的SQL和Java代碼均是可以直接參考的核心片段。實際項目里你還需要補充用戶認證、菜單權限、操作日志、報表統計等功能。如果遇到庫存扣減并發問題優先檢查你們的庫存余額表是否使用了樂觀鎖以及UPDATE語句是否帶有庫存數量約束條件——大部分問題都出在這兩個地方。如果你準備把某個開源ERP項目引入公司也不要急著部署。先讓運維和開發團隊把技術棧、部署方式、License條款、二次開發成本都評估清楚。庫存系統是業務系統的核心一旦跑了錯誤的數據業務方對系統的信任度會大打折扣。希望這篇文章能幫你少走一些彎路。