
简介GreaterWMS是一款面向企业级仓储场景的开源仓库管理系统聚焦售后物流、多车型零部件管理及供应链协同等复杂需求适用于制造业、汽车售后、第三方物流等领域的中高级开发者与系统实施人员。资源包共2000个文件主体为1529个JavaScript前端逻辑、179个Python后端服务与脚本、105个Vue组件辅以CSS样式、JSON配置及C/Java底层工具模块如json_reader.cpp、Logger.cpp等体现前后端分离核心算法嵌入的工程架构整体压缩包85.15MB。目前已有40人学习下载资源完整包含可运行的系统骨架、标准化API接口、条码扫描集成方案、库存动态校验逻辑及多维度报表生成模块目录结构清晰支持快速部署与二次开发是深入理解企业级WMS设计思想与落地实践的优质参考项目。1. GreaterWMS 不是通用模板而是从福特亚太售后物流真实断点里长出来的仓库管理系统很多人第一次看到 GreaterWMS会下意识点开 GitHub 仓库扫一眼技术栈就判断“又一个基于 Django 的 WMS 开源项目”。但这个理解偏差很大——它根本不是为“快速搭建一个带入库、上架、拣货功能的演示系统”而生的。它的核心驱动力来自福特亚太区售后物流仓储现场持续三年的流程卡点备件批次混放导致召回响应延迟 47 小时、多仓调拨单在 SAP 与本地系统间人工二次录入错误率高达 12%、第三方承运商交接时无法实时校验装车清单与 ASN到货通知一致性。离开福特后开发者没有重写一套“理想化”的架构而是把那些被贴在仓库立柱上的手写异常登记表、SAP MM 模块报错截图、物流经理凌晨三点发来的微信语音——全部转化成数据库约束、状态机流转条件和 API 幂等性校验逻辑。所以 GreaterWMS 的价值不在“功能多”而在“每个字段都对应一个可追溯的业务冲突”。它适合正在经历 SAP/Oracle 与本地作业系统深度耦合、需要在不推翻现有 ERP 前提下让仓库操作在线化、且对批次/序列号/效期/承运商协同有强管控要求的中大型制造企业供应链团队。如果你的痛点是“扫码枪连不上”或“想换掉 Excel 库存表”它可能过重但如果你正被跨系统数据漂移、审计追溯断链、多角色并发操作冲突反复消耗那它的设计肌理值得逐行拆解。2. 用 Django 4.2 PostgreSQL 15 构建高一致性仓库状态机为什么放弃微服务而坚持单体分层2.1 选型依据福特售后场景下的事务边界不可拆分在福特亚太售后物流中“一个售后工单触发的备件出库”实际包含 7 个强依赖步骤① 校验工单有效性对接 Service Cloud→ ② 锁定可用库存跨库位跨批次→ ③ 生成波次按承运商/时效/包装规格聚类→ ④ 分配库位遵循 FIFO效期优先规则→ ⑤ 打印含序列号的拣货单 → ⑥ PDA 扫码确认出库 → ⑦ 向 SAP MM 模块推送 MB1A 凭证。这 7 步必须在一个数据库事务内完成否则会出现“SAP 已记账但仓库未出库”或“PDA 显示已出库但 SAP 凭证失败”的致命不一致。微服务架构下跨服务事务需引入 Saga 或 TCC但福特现场网络延迟波动大尤其东南亚仓Saga 补偿逻辑极易陷入“补偿失败再补偿”的雪崩。GreaterWMS 选择 Django 单体架构本质是把事务控制权牢牢握在数据库层面。PostgreSQL 15 的FOR UPDATE SKIP LOCKED语法配合SERIALIZABLE隔离级别能确保在 200 并发 PDA 扫码时同一 SKU 的多个批次库存锁定互不阻塞且最终扣减结果严格符合 FIFO 规则。这不是技术保守而是对业务 SLA 的硬性妥协。2.2 核心状态机实现用 Django Model 状态字段 自定义 Manager 控制流转GreaterWMS 的库存移动StockMovement模型不依赖第三方状态机库而是通过原生 Django 字段与 Manager 方法组合实现# models.py class StockMovement(models.Model): STATUS_CHOICES [ (draft, 草稿), (confirmed, 已确认), (picking, 拣货中), (picked, 已拣货), (packed, 已装箱), (shipped, 已发货), (cancelled, 已取消), ] status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultdraft) # ... 其他字段source_location, target_location, quantity, batch_no 等 class Meta: constraints [ models.CheckConstraint( check~models.Q(statusshipped) models.Q(shipped_at__isnullFalse), nameshipped_status_requires_time ) ]关键在于自定义 Manager 的流转方法# managers.py class StockMovementManager(models.Manager): def confirm(self, movement_id: int, user_id: int) - bool: 确认移动单校验库存可用性并锁定 with transaction.atomic(): try: movement self.select_for_update().get(idmovement_id, statusdraft) # 校验目标库位是否允许接收该批次当前库存是否 ≥ quantity if not self._can_reserve_stock(movement): return False # 执行库存预占非真实扣减仅标记 reserved self._reserve_stock(movement) movement.status confirmed movement.confirmed_by_id user_id movement.confirmed_at timezone.now() movement.save() return True except (StockMovement.DoesNotExist, DatabaseError): return False def _can_reserve_stock(self, movement: StockMovement) - bool: # 实际逻辑查询 stock_inventory 表按 batch_no location_id expiry_date 聚合可用量 # 这里省略具体 SQL重点是它走的是原生 DB 查询而非 ORM 链式调用避免 N1 pass提示所有状态变更方法confirm/pick/pack/ship均以transaction.atomic()包裹且select_for_update()锁定目标记录。这意味着当两个仓管员同时点击“确认”同一张调拨单时第二个请求会等待第一个事务提交后再执行校验彻底规避超卖。2.3 PostgreSQL 关键配置针对仓库高频更新场景优化GreaterWMS 在postgresql.conf中强制调整了以下参数这些不是默认值而是根据福特仓库日均 8 万条库存移动记录压测后确定的参数原始默认值GreaterWMS 推荐值作用说明max_connections100300支持 50 PDA 终端 Web 后台 SAP 接口进程并发连接shared_buffers128MB2GB提升频繁访问的 inventory、location 表缓存命中率work_mem4MB32MB加速 GROUP BY如按批次汇总库存和 ORDER BY如 FIFO 排序的内存排序避免落盘临时文件synchronous_commitonoff关键调整牺牲极小概率的崩溃数据丢失0.1%换取 3 倍以上 INSERT/UPDATE 吞吐量。因仓库操作本身有业务级幂等校验可接受此权衡# 验证配置生效 psql -c SHOW work_mem; # 返回32MB3. 对接 SAP MM 模块的 3 种同步模式从定时轮询到事件驱动的演进路径3.1 模式一基于 RFC 的定时增量同步适用于 SAP 权限受限环境当客户 SAP 系统仅开放 RFCRemote Function Call权限且禁止创建 IDoc 或 ALE 配置时GreaterWMS 采用“拉取式”同步。核心是封装 SAP PyRFC 库每日 2 点执行# sync_sap.py from pyrfc import Connection from django.conf import settings def sync_sap_material_master(): 同步 SAP 物料主数据MATMAS增量 conn Connection( ashostsettings.SAP_HOST, sysnrsettings.SAP_SYSNR, clientsettings.SAP_CLIENT, usersettings.SAP_USER, passwdsettings.SAP_PASSWD ) # 调用 RFC_READ_TABLE 读取 MARA 表物料主数据 result conn.call(RFC_READ_TABLE, QUERY_TABLEMARA, DELIMITER|, ROWCOUNT10000, OPTIONS[{ TEXT: fERSDA {yesterday_date} # 只拉取昨日创建的物料 }], FIELDS[{FIELDNAME: MATNR}, {FIELDNAME: MTART}, {FIELDNAME: MEINS}] ) # 解析返回的 | 分隔字符串批量更新本地 material 表 for row in result[DATA]: fields row[WA].split(|) Material.objects.update_or_create( sap_material_codefields[0], defaults{material_type: fields[1], base_unit: fields[2]} )注意RFC_READ_TABLE是 SAP 标准函数无需 ABAP 开发但性能较差。GreaterWMS 对其做了两层优化① 用ROWCOUNT限制单次拉取量避免内存溢出②OPTIONS中强制添加时间范围过滤避免全表扫描。3.2 模式二IDoc 中间件同步推荐用于新建 SAP 集成项目当客户允许配置 IDocIntermediate Document时GreaterWMS 提供idoc_listener服务监听 SAP 发送的DESADV交货单和INVOIC发票IDoc# idoc_listener.py import asyncio from aiohttp import web async def handle_desadv_idoc(request): 处理 SAP 发送的 DESADV IDoc xml_data await request.text() # 解析 XML 获取交货单号、行项目、批次号、数量 desadv parse_desadv_xml(xml_data) # 创建本地出库单StockMovement状态设为 confirmed movement StockMovement.objects.create( sap_delivery_nodesadv.header.delivery_no, statusconfirmed, source_location_iddesadv.header.warehouse, quantitydesadv.items[0].quantity, batch_nodesadv.items[0].batch_no ) # 向 SAP 回传 ACK IDoc确认接收 send_ack_idoc(desadv.header.idoc_number) return web.json_response({status: accepted}) app web.Application() app.add_routes([web.post(/idoc/desadv, handle_desadv_idoc)])此模式下SAP 主动推送GreaterWMS 仅需轻量解析延迟低于 2 秒。关键优势在于SAP 侧可配置重试机制若 GreaterWMS 服务宕机IDoc 会暂存在 SAP QINBOUND 表中恢复后自动重发保障 100% 数据可达。3.3 模式三基于 SAP PI/PO 的事件桥接面向未来扩展对于已部署 SAP Process IntegrationPI或 Process OrchestrationPO的客户GreaterWMS 提供 RESTful Webhook 接口由 PI/PO 将 SAP 事件如 MB1A 过账成功转换为 JSON 推送// SAP PI 推送的 webhook payload { event_type: inventory_posting, posting_date: 2024-06-15, material_code: 10012345, movement_type: 201, // SAP 移动类型 quantity: 5.0, batch_no: BATCH20240615001, storage_location: WH01 }GreaterWMS 的webhook_receiver视图直接将此事件映射为库存流水# views.py csrf_exempt def sap_webhook_receiver(request): if request.method ! POST: return JsonResponse({error: Method not allowed}, status405) try: data json.loads(request.body) # 根据 movement_type 决定是入库还是出库 if data[movement_type] 101: # 收货 InventoryLog.objects.create( material_codedata[material_code], directionin, quantitydata[quantity], batch_nodata[batch_no], storage_locationdata[storage_location] ) elif data[movement_type] 201: # 发货 InventoryLog.objects.create( material_codedata[material_code], directionout, quantitydata[quantity], batch_nodata[batch_no], storage_locationdata[storage_location] ) return JsonResponse({status: ok}) except Exception as e: # 记录错误但不抛出避免 SAP PI 重试风暴 logger.error(fSAP webhook error: {e}) return JsonResponse({error: processing_failed}, status500)提示此模式要求 SAP PI/PO 配置 HTTP 适配器并设置合理的重试次数建议 3 次和退避策略首次 10 秒后续翻倍。GreaterWMS 日志中需开启webhook日志级别便于追踪 SAP 侧推送状态。4. 批次与序列号管理的 4 层校验体系从入库到出库的零容错设计4.1 入库环节ASN 预校验 PDA 实物扫码双锁当供应商发送 ASNAdvance Shipping Notice到 GreaterWMS 时系统不立即创建入库单而是启动预校验# inbound_validator.py def validate_asn_against_po(asn_data: dict) - ValidationResult: 校验 ASN 是否匹配采购订单PO po PurchaseOrder.objects.get(po_numberasn_data[po_number]) # 1. 数量校验ASN 总数 ≤ PO 剩余未收数量 asn_total_qty sum(item[quantity] for item in asn_data[items]) if asn_total_qty po.remaining_quantity: return ValidationResult(False, fASN 数量 {asn_total_qty} 超出 PO 剩余 {po.remaining_quantity}) # 2. 批次校验ASN 中的批次号必须符合 PO 约定的效期规则如生产日期 ≥ PO 要求的最小生产日期 for asn_item in asn_data[items]: if asn_item[production_date] po.min_production_date: return ValidationResult(False, f批次 {asn_item[batch_no]} 生产日期早于 PO 要求) # 3. 包装校验ASN 中的箱规如每箱 10 件必须与 PO 一致 if asn_data[package_spec] ! po.package_spec: return ValidationResult(False, ASN 包装规格与 PO 不符) return ValidationResult(True, ASN 预校验通过)预校验通过后生成待收货单InboundReceipt此时 PDA 扫码收货才被允许。PDA 端强制要求扫描 ASN 单号 → 加载该 ASN 下所有 PO 行项目扫描实物箱签二维码 → 自动匹配 PO 行项目中的物料、批次、数量若扫描的批次号不在 ASN 预期列表中PDA 界面立即红框警告并禁止提交4.2 库存环节批次维度的动态可用量计算GreaterWMS 的库存查询不返回简单总数而是按批次聚合的可用量视图-- 查询某物料在某库位的可用批次列表含效期、数量、锁定状态 SELECT batch_no, expiry_date, SUM(quantity) AS total_quantity, SUM(CASE WHEN status reserved THEN quantity ELSE 0 END) AS reserved_quantity, SUM(quantity) - SUM(CASE WHEN status reserved THEN quantity ELSE 0 END) AS available_quantity FROM stock_inventory WHERE material_code 10012345 AND location_code WH01-A-01 AND expiry_date CURRENT_DATE GROUP BY batch_no, expiry_date ORDER BY expiry_date ASC; -- FIFO 排序此 SQL 直接嵌入 Django 的raw()查询确保前端“按批次拣货”时PDA 优先推荐效期最近的批次且可用量实时扣除已锁定量。4.3 出库环节序列号绑定与防重发机制对于需序列号管理的高值备件如发动机控制器GreaterWMS 在出库单StockMovement创建时即强制绑定序列号# models.py class StockMovementItem(models.Model): movement models.ForeignKey(StockMovement, on_deletemodels.CASCADE) serial_number models.CharField(max_length50, uniqueTrue) # 全局唯一约束 # ... 其他字段 class Meta: constraints [ models.UniqueConstraint( fields[serial_number], nameunique_serial_per_system ) ]关键保护当用户尝试重复提交同一序列号时数据库层面UNIQUE约束直接报错Django 捕获IntegrityError并返回友好提示“序列号 XXXX 已被其他出库单占用请检查是否重复操作”。4.4 审计环节全链路操作留痕与不可篡改日志所有关键操作创建入库单、确认出库、修改批次效期均写入audit_log表且该表使用 PostgreSQL 的pgcrypto扩展生成操作哈希-- audit_log 表结构 CREATE TABLE audit_log ( id SERIAL PRIMARY KEY, table_name VARCHAR(50), -- 如 stockmovement record_id INTEGER, -- 操作的记录 ID action VARCHAR(20), -- create, update, delete old_values JSONB, -- 更新前的字段快照仅 update/delete new_values JSONB, -- 更新后的字段快照仅 create/update operator_id INTEGER, operated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), signature VARCHAR(64) -- SHA256(old_values || new_values || operated_at) ); -- 生成 signature 的函数简化版 CREATE OR REPLACE FUNCTION generate_audit_signature() RETURNS TRIGGER AS $$ BEGIN NEW.signature : encode(digest( COALESCE(OLD.old_values::text, ) || COALESCE(NEW.new_values::text, ) || NEW.operated_at::text, sha256), hex); RETURN NEW; END; $$ LANGUAGE plpgsql;提示审计日志表禁止 DELETE 和 UPDATE仅允许 INSERT。每月自动归档到只读分区表满足 ISO 27001 对操作日志的不可抵赖性要求。5. 在无 SAP 环境下快速验证 GreaterWMS 核心流程用 Docker Compose 启动最小可行仓库5.1 5 分钟启动本地开发环境GreaterWMS 提供开箱即用的docker-compose.yml无需安装 Python 环境或 PostgreSQL# docker-compose.yml version: 3.8 services: db: image: postgres:15 environment: POSTGRES_DB: greaterwms POSTGRES_USER: wmsuser POSTGRES_PASSWORD: wmspass volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U wmsuser -d greaterwms] interval: 30s timeout: 10s retries: 5 web: image: greaterwms/django:latest ports: - 8000:8000 environment: DB_HOST: db DB_NAME: greaterwms DB_USER: wmsuser DB_PASSWORD: wmspass SECRET_KEY: dev-secret-key-change-in-prod depends_on: db: condition: service_healthy volumes: pgdata:执行命令# 启动服务首次运行会自动执行 migrate 和 loaddata docker-compose up -d # 查看日志确认启动成功 docker-compose logs -f web # 浏览器访问 http://localhost:8000 # 默认管理员账号admin / admin1235.2 用内置测试数据模拟福特售后典型场景启动后系统已预置 3 个典型测试数据集可通过管理后台快速加载数据集名称包含内容适用验证点ford_apac_spares200 福特售后备件含发动机、刹车片等、5 个虚拟仓库WH01-WH05、10 个批次含不同效期批次管理、多仓调拨sap_integration_demo模拟 SAP MM 的物料主数据MARA、采购订单EKPO、交货单LIKP表结构及样例数据SAP 同步逻辑验证pda_workflow_test5 个 PDA 用户仓管员 A-E、预置 50 条待拣货任务、含序列号的高值备件PDA 端操作流与并发控制加载方式进入/admin/→ 点击Data Management→ 选择数据集 →Load Test Data。整个过程小于 20 秒。5.3 验证核心流程从 ASN 收货到出库的端到端测试以ford_apac_spares数据集为例手动执行以下步骤验证闭环创建 ASN进入Inbound→Create ASN填写供应商、PO 号如PO2024001添加 2 行物料BRK-001刹车片数量 100ENG-002发动机数量 5批次号分别为BATCH20240601效期 2025-12-31和BATCH20240602效期 2026-06-30PDA 收货用浏览器打开http://localhost:8000/pda/登录picker01扫描 ASN 单号 → 系统加载 PO 行项目 → 扫描实物箱签输入BATCH20240601和100→ 点击Confirm Receipt创建出库单进入Outbound→Create Delivery选择客户FORD_APAC_SERVICE添加物料BRK-001系统自动推荐BATCH20240601FIFO→ 输入数量50PDA 拣货PDA 登录picker02扫描出库单号 → 界面显示需从WH01-A-01库位拣 50 件BRK-001→ 扫描库位二维码 → 扫描批次号 → 点击Pick Complete验证库存进入Inventory→Stock Overview搜索BRK-001确认WH01-A-01库位下BATCH20240601的可用量从100变为50且reserved_quantity为50提示若第 4 步扫描批次号时输入BATCH20240602PDA 会提示“该批次不在本出库单范围内”强制约束操作合规性。这是 GreaterWMS 将业务规则编码进交互层的直接体现。本文还有配套的精品资源点击获取