☰
生产管理系统源代码实战指南:设备对接、状态机与产线部署
2026/9/26 20:12:17 网站建设 项目流程

简介:本资源是一套面向制造型企业信息化建设的生产管理系统源代码,适用于具备ASP开发基础、熟悉数据库设计与制造业业务流程的中高级开发者,用于学习生产计划、物料需求、库存管控等核心模块的工程实现逻辑。压缩包共165个文件,主体为97个ASP服务端页面(如product_in_store.asp、cailiao_out_store.asp等),辅以16个JS交互脚本、9个CSS样式文件及25个GIF图标资源,配合MDB数据库文件与EXE安装引导程序,整体体积仅1.33MB,结构紧凑、便于本地部署与二次开发。已有2002人学习下载,资源完整覆盖MRP计算、订单全周期跟踪、质量检验流程、多角色权限控制及生产报表生成等关键功能模块,代码注释清晰、模块划分明确,特别适合用于理解传统制造业信息系统架构设计与ASP技术栈在工业场景中的落地实践。

1. 生产管理系统源代码:不是拿来就能跑的“万能模板”,而是要亲手焊进产线节奏里的控制中枢

你手头拿到一份标着“生产管理系统源代码”的压缩包,解压后看到几十个 Python 文件、SQL 脚本、Vue 组件和 Dockerfile——但一运行就报ModuleNotFoundError: No module named 'erp_core',数据库连不上,登录页空白,API 返回 500。这不是你代码能力不行,而是绝大多数公开流传的“生产管理系统源代码”根本不是开箱即用的成品系统,而是某家工厂在特定产线、特定 ERP 接口协议、特定设备通信协议(如 Modbus TCP / OPC UA)下,用三年时间边跑边改出来的业务逻辑快照。它不解决“怎么建系统”,而是记录“我们厂怎么让工单流进 CNC、让质检数据回写 MES、让仓库扫码自动扣料”。适合的人群很明确:正在从 Excel 手工排产转向轻量级自研系统的中小制造企业技术负责人、懂 PLC 通讯又会写后端的产线工程师、或是被甲方逼着三天内拿出可演示原型的外包团队。它不能替代 SAP 或用友 U9,但能让你绕过采购周期、跳过定制开发报价单,在真实产线数据流里验证“工单状态变更是否触发了 AGV 调度指令”这种具体问题。本文不讲理论架构图,只拆解:怎么从源码包里识别出真正可用的模块、如何用最小代价把数据库和设备接口接活、哪些文件改错一行就会导致整条产线报工失败。


2. 源码结构解剖:先认出“心脏”“神经”和“手脚”,再动刀

拿到一个标称“生产管理系统源代码”的压缩包,第一件事不是 pip install,而是用 tree 命令或 VS Code 的文件树快速扫描目录骨架。真实可用的生产系统源码,绝不会是“src/ + docs/ + test/”这种教科书式结构,而必然暴露出与物理产线强耦合的痕迹。我一般会先盯死三个位置:设备驱动层、工单状态机定义、以及数据库迁移脚本。它们才是系统能否落地的生死线。

2.1 从drivers/目录识别设备协议兼容性:别被“支持 Modbus”骗了

真正的生产系统源码里,drivers/(或device_adapters/、plc_connectors/)目录下必然存在具体厂商+型号的硬编码适配器。比如:

$ ls drivers/ fanuc_robodrill_v3.py # Fanuc Robodrill 机床,v3 是指适配其 2022 年固件版本 siemens_s7_1200_opcua.py # Siemens S7-1200 PLC,通过 OPC UA 协议读取 DB100.DBX0.0 状态位 mitsubishi_q_series_modbus.py # Mitsubishi Q 系列 PLC,Modbus TCP 地址映射表硬编码在 __init__ 中

注意:如果drivers/下只有modbus_base.py、opcua_client.py这类泛化抽象类,没有具体厂商型号实现,说明这是一套教学 Demo 或未完成品。真实产线里,同一品牌不同型号 PLC 的寄存器地址、心跳包格式、错误码定义都不同,必须逐个适配。

关键看siemens_s7_1200_opcua.py里的连接参数:

# drivers/siemens_s7_1200_opcua.py class S71200OPCUAAdapter: def __init__(self, host="192.168.1.10", port=4840, node_id="ns=2;s=::Program:PLC_PRG.GLOBAL_STATUS", # OPC UA 节点路径,指向实际 PLC 程序变量 timeout=3.0): self.client = Client(f"opc.tcp://{host}:{port}") self.node_id = node_id # 这个 node_id 必须和你现场 PLC 的 TIA Portal 项目中变量的 OPC UA 属性完全一致 self.timeout = timeout

参数说明:

  • host/port:PLC 的 IP 和 OPC UA 端口(S7-1200 默认 4840,但有些工厂防火墙会改);
  • node_id:这是最易翻车的点——它不是通用路径,而是你在 TIA Portal 里右键变量 → “Properties” → “OPC UA Server” → “Node ID” 里复制出来的完整字符串。漏掉ns=2;s=或大小写错一个字母,连接就静默失败;
  • timeout:产线设备响应慢,设成 0.5 秒会导致频繁超时重连,我一般设为 2.5~3.0 秒。

2.2 在models/work_order.py里定位状态流转引擎:工单不是 CRUD,是带约束的有限状态机

生产系统的核心不是增删改查,而是工单(Work Order)在“新建→派工→首件检验→加工中→完工报工→质检→入库”这条链路上的状态跃迁。真实源码里,这个逻辑不会散落在 Controller 里,而会集中在models/work_order.py的WorkflowState类中:

# models/work_order.py class WorkOrderStatus(Enum): DRAFT = "draft" # 草稿,可编辑 ASSIGNED = "assigned" # 已派工,锁定 BOM 和工艺路线 FIRST_PIECE = "first_piece" # 首件检验中,禁止开工 IN_PROGRESS = "in_progress" # 加工中,设备状态必须为 "RUNNING" COMPLETED = "completed" # 完工报工,需校验实际工时 vs 计划工时 QC_PENDING = "qc_pending" # 待质检,触发 QC 工单生成 QC_PASSED = "qc_passed" # 质检通过,可入库 QC_FAILED = "qc_failed" # 质检失败,触发返工流程 class WorkOrder(BaseModel): status: WorkOrderStatus = Field(default=WorkOrderStatus.DRAFT) @validator('status', always=True) def validate_status_transition(cls, v, values): old_status = values.get('status') # 状态跃迁规则:禁止跳步,禁止倒退 valid_transitions = { WorkOrderStatus.DRAFT: [WorkOrderStatus.ASSIGNED], WorkOrderStatus.ASSIGNED: [WorkOrderStatus.FIRST_PIECE], WorkOrderStatus.FIRST_PIECE: [WorkOrderStatus.IN_PROGRESS, WorkOrderStatus.QC_FAILED], WorkOrderStatus.IN_PROGRESS: [WorkOrderStatus.COMPLETED], WorkOrderStatus.COMPLETED: [WorkOrderStatus.QC_PENDING], WorkOrderStatus.QC_PENDING: [WorkOrderStatus.QC_PASSED, WorkOrderStatus.QC_FAILED], } if old_status and v not in valid_transitions.get(old_status, []): raise ValueError(f"Invalid status transition: {old_status} → {v}") return v

为什么这个比 UI 更重要?
因为所有前端按钮(“开始加工”、“报工完成”、“提交质检”)背后,最终调用的都是work_order.status = WorkOrderStatus.IN_PROGRESS这样的赋值。如果源码里没这段校验,或者规则写错了(比如允许QC_FAILED直接跳到QC_PASSED),你的系统就会出现“质检没过就允许入库”这种致命逻辑漏洞。我见过三次因状态机缺失导致的批量报废事故——不是代码崩了,是业务规则没锁死。

2.3 用migrations/目录反推数据库真实结构:别信 README 里的 ER 图

很多源码包的README.md会画一张漂亮的实体关系图(ERD),写着“支持 MySQL/PostgreSQL”,但实际migrations/目录下的 SQL 脚本才暴露真相。打开migrations/0001_initial.sql:

-- migrations/0001_initial.sql CREATE TABLE work_orders ( id SERIAL PRIMARY KEY, order_no VARCHAR(32) NOT NULL UNIQUE, -- 工单号,如 "WO-2024-08765" part_no VARCHAR(64) NOT NULL, -- 物料编码,关联 BOM 表 qty INTEGER NOT NULL CHECK (qty > 0), -- 计划数量 status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','assigned','first_piece','in_progress','completed','qc_pending','qc_passed','qc_failed')), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 关键:这张表没有外键! -- 真实产线系统为避免级联删除导致工单丢失,宁可手动维护一致性 CREATE TABLE work_order_operations ( id SERIAL PRIMARY KEY, work_order_id INTEGER NOT NULL, -- 手动关联,非 FOREIGN KEY operation_seq INTEGER NOT NULL, -- 工序序号,如 10, 20, 30 machine_code VARCHAR(32), -- 设备编码,如 "CNC-01" operator_id INTEGER, -- 操作员 ID,可能为空(自动化工序) start_time TIMESTAMP WITH TIME ZONE, end_time TIMESTAMP WITH TIME ZONE, actual_time_seconds INTEGER -- 实际耗时秒数,用于分析设备 OEE );

参数说明:

  • CHECK (status IN (...)):用数据库层面约束代替应用层枚举校验,更可靠;
  • work_order_operations.work_order_id没有FOREIGN KEY:这是血泪经验——产线系统绝不允许因 BOM 表误删导致工单记录级联消失,所有关联靠应用层逻辑保证;
  • actual_time_seconds:这个字段直接决定 OEE(设备综合效率)计算精度,必须由设备驱动层实时写入,不能靠人工填报。

3. 本地环境启动:用 Docker Compose 绕过 90% 的依赖地狱

生产系统源码最常翻车的环节,就是环境搭建。Python 版本冲突、Node.js 版本错位、PostgreSQL 扩展缺失(如pg_trgm用于模糊搜索)、Redis 密码不匹配……这些琐碎问题能卡住新手三天。我的做法是:放弃 pip install 和 npm install,直接用 Docker Compose 启动最小闭环环境。只要源码包里有docker-compose.yml,就优先走这条路。

3.1 修改docker-compose.yml的三处必调参数:IP、密码、时区

标准docker-compose.yml通常长这样,但必须改这三处才能连上真实设备:

# docker-compose.yml version: '3.8' services: web: build: . ports: - "8000:8000" environment: - DATABASE_URL=postgresql://prod_user:prod_pass@db:5432/prod_db - REDIS_URL=redis://:redis_pass@redis:6379/0 - PLC_HOST=192.168.1.10 # ← 必须改成你现场 PLC 的真实 IP! - PLC_PORT=4840 # ← 对应 PLC 的 OPC UA 或 Modbus 端口 depends_on: - db - redis db: image: postgres:14 environment: - POSTGRES_DB=prod_db - POSTGRES_USER=prod_user - POSTGRES_PASSWORD=prod_pass - TZ=Asia/Shanghai # ← 必须加!否则数据库时间戳和产线日志对不上 volumes: - ./postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine command: redis-server --requirepass redis_pass # ← 密码必须和 web service 的 REDIS_URL 一致

关键修改点说明:

  • PLC_HOST:这是整个系统能否和设备对话的生命线。填错 IP,drivers/里的所有连接代码都会超时;
  • TZ=Asia/Shanghai:产线系统对时间敏感。没设时区,PostgreSQL 里NOW()返回 UTC 时间,而你的设备日志是北京时间,报工时间差 8 小时,OEE 分析全乱;
  • redis-server --requirepass:Redis 密码必须和REDIS_URL中的密码严格一致,且redis:7-alpine镜像默认不启用密码,必须加--requirepass参数,否则连接被拒绝。

3.2 用make migrate执行数据库迁移:别用 Django manage.py

如果源码基于 Django,你会看到manage.py,但别急着python manage.py migrate。真实生产系统源码的迁移脚本往往独立于 Django ORM,因为要精确控制字段类型(如TIMESTAMP WITH TIME ZONE)和索引策略(如CREATE INDEX CONCURRENTLY避免锁表)。正确做法是:

# 进入容器执行迁移 docker-compose run --rm web bash -c "cd /app && make migrate"

Makefile里定义了安全迁移流程:

# Makefile migrate: @echo "Running database migrations..." @psql -h db -U prod_user -d prod_db -f migrations/0001_initial.sql @psql -h db -U prod_user -d prod_db -f migrations/0002_add_indexes.sql @echo "Migrations completed."

为什么不用manage.py migrate?
因为migrations/0002_add_indexes.sql里有:

-- migrations/0002_add_indexes.sql CREATE INDEX CONCURRENTLY idx_work_orders_status ON work_orders(status); CREATE INDEX CONCURRENTLY idx_work_order_ops_wo_id ON work_order_operations(work_order_id);

CONCURRENTLY关键字能让索引创建不锁表——这对正在运行的产线系统至关重要。Django 的makemigrations生成不了这个。

3.3 启动后验证设备连通性:用 curl 测试驱动健康检查端点

服务起来后,别急着打开浏览器。先验证设备层是否打通:

# 查看 web 容器日志,确认无连接错误 docker-compose logs -f web # 用 curl 调用健康检查 API(真实系统必有此端点) curl -X GET http://localhost:8000/api/v1/health/plc # 返回 {"status": "ok", "plc_connected": true, "last_read_at": "2024-06-15T09:23:45+08:00"} # 如果返回 {"plc_connected": false},立刻看日志: docker-compose logs web | grep -i "opc ua\|modbus\|connection refused"

玄学排查技巧:

  • 如果日志显示Connection refused,90% 是PLC_HOST填错,或 PLC 防火墙没开对应端口;
  • 如果日志卡在Connecting to opc.tcp://...不动,大概率是node_id格式错误,或 PLC 的 OPC UA 服务器没启用;
  • last_read_at时间戳如果停在 5 分钟前,说明驱动心跳包没收到响应,检查 PLC 是否断电或网线松动。

4. 避坑指南:那些让产线工程师凌晨三点还在重启服务的 4 个致命细节

生产系统源码的坑,不在语法错误,而在与物理世界的咬合缝隙里。以下是我踩过的、导致整条产线停摆的真实问题,按发生频率排序:

4.1 现象:工单状态卡在IN_PROGRESS,但设备实际已停机

原因:驱动层read_machine_status()函数没处理 PLC 返回的“暂停”状态码,只识别RUNNING和STOPPED,把PAUSED当作RUNNING上报。
解决:打开drivers/fanuc_robodrill_v3.py,找到状态读取函数,补全状态映射:

# 原代码(错误) if plc_value == 1: return "RUNNING" elif plc_value == 0: return "STOPPED" # 正确写法(补全 PAUSED) if plc_value == 1: return "RUNNING" elif plc_value == 0: return "STOPPED" elif plc_value == 2: # Fanuc Robodrill 文档定义:2=PAUSED return "PAUSED" # 系统据此将工单状态转为 "paused",触发人工干预

4.2 现象:报工数量总是比实际少 1 件

原因:work_order_operations表的actual_time_seconds字段定义为INTEGER,但驱动层传入的是浮点数(如32.7秒),PostgreSQL 自动截断为32,导致累计工时误差累积。
解决:修改迁移脚本,将字段类型改为NUMERIC(10,2):

-- migrations/0003_fix_time_precision.sql ALTER TABLE work_order_operations ALTER COLUMN actual_time_seconds TYPE NUMERIC(10,2) USING actual_time_seconds::NUMERIC;

4.3 现象:质检结果无法提交,API 返回422 Unprocessable Entity

原因:前端传来的 JSON 里qc_result字段是字符串"PASS",但后端 Pydantic 模型定义为bool,强制转换失败。
解决:在schemas/qc.py中放宽类型校验:

# schemas/qc.py class QCReportCreate(BaseModel): work_order_id: int qc_result: Literal["PASS", "FAIL", "REWORK"] # ← 改为枚举,而非 bool remarks: str = ""

4.4 现象:系统运行 24 小时后内存暴涨至 95%,容器被 OOM Killer 杀死

原因:drivers/目录下某个 Modbus 驱动使用了全局缓存字典存储设备历史数据,但没设置 TTL,数据无限累积。
解决:定位到drivers/mitsubishi_q_series_modbus.py,添加 LRU 缓存控制:

from functools import lru_cache # 原代码(危险) _cache = {} # 改为带容量限制的缓存 @lru_cache(maxsize=1000) # 最多缓存 1000 条设备读数 def read_register_cached(plc_ip: str, address: int) -> int: return _read_raw_modbus(plc_ip, address)

5. 数据注入实战:用真实工单和设备日志喂活你的系统

源码跑起来只是起点,真正让它产生价值,是注入符合你产线节奏的数据。不要用 Faker 生成假数据——那只会让你的 OEE 报表看起来很美,却和车间大屏对不上。我坚持用三步法:导出现场数据 → 清洗映射字段 → 批量导入。

5.1 从 Excel 工单模板提取核心字段:只保留 7 个必填项

你手头肯定有一份 Excel 工单模板,把它转成 CSV,只保留以下 7 列(其他列全删):

order_nopart_noqtydue_datebom_versionrouting_versionpriority
WO-2024-08765A1002-B1202024-06-20v2.1v3.0HIGH

为什么只这 7 个?

  • order_no:唯一主键,系统所有关联都靠它;
  • part_no:必须和bom_items.part_no完全一致,否则 BOM 展开失败;
  • qty:计划数量,驱动物料需求计算;
  • due_date:交付截止日,影响 APS 排程;
  • bom_version&routing_version:版本号必须存在于boms和routings表中,否则工单创建失败;
  • priority:用于动态调整工单在队列中的位置,HIGH/MEDIUM/LOW 三档。

5.2 用 Python 脚本清洗并注入:避开 ORM,直连 PostgreSQL

别用 Django 的bulk_create,它太慢且容易触发唯一约束冲突。直接用psycopg2批量插入:

# scripts/import_work_orders.py import csv import psycopg2 from psycopg2.extras import execute_batch conn = psycopg2.connect( host="localhost", port=5432, dbname="prod_db", user="prod_user", password="prod_pass" ) cursor = conn.cursor() with open("work_orders_clean.csv", "r", encoding="utf-8") as f: reader = csv.DictReader(f) rows = [] for row in reader: # 字段映射:Excel 列名 → 数据库字段名 rows.append(( row["order_no"], row["part_no"], int(row["qty"]), row["due_date"], row["bom_version"], row["routing_version"], row["priority"].upper() # 统一转大写 )) # 批量插入,1000 行/批 execute_batch( cursor, "INSERT INTO work_orders (order_no, part_no, qty, due_date, bom_version, routing_version, priority) VALUES (%s, %s, %s, %s, %s, %s, %s)", rows, page_size=1000 ) conn.commit() print(f"Imported {len(rows)} work orders")

参数说明:

  • page_size=1000:太大易 OOM,太小效率低,1000 是平衡点;
  • row["priority"].upper():确保数据库CHECK (priority IN ('HIGH','MEDIUM','LOW'))约束通过;
  • int(row["qty"]):强制转整型,避免字符串'120'插入INTEGER字段时报错。

5.3 伪造设备日志:用scripts/generate_plc_logs.py模拟真实节奏

为了让系统“活”起来,需要模拟设备每 5 秒上报一次状态。真实 PLC 日志包含时间戳、设备编码、状态码、当前工序号:

# scripts/generate_plc_logs.py import time import random from datetime import datetime, timedelta # 模拟 3 台设备 machines = ["CNC-01", "CNC-02", "GRINDER-03"] statuses = ["RUNNING", "PAUSED", "STOPPED", "ALARM"] start_time = datetime.now() - timedelta(hours=1) for i in range(720): # 1 小时 * 60 分钟 * 2 次/分钟 = 720 条 ts = start_time + timedelta(seconds=i*5) machine = random.choice(machines) status = random.choices(statuses, weights=[0.7, 0.1, 0.15, 0.05])[0] # RUNNING 占 70% operation_seq = random.randint(10, 50) if status == "RUNNING" else None print(f"{ts.isoformat()},{machine},{status},{operation_seq or ''}") time.sleep(0.01) # 控制输出节奏

保存为plc_logs.csv,再用psql导入:

# 创建日志表(如果不存在) psql -d prod_db -c " CREATE TABLE IF NOT EXISTS plc_logs ( id SERIAL PRIMARY KEY, timestamp TIMESTAMP WITH TIME ZONE NOT NULL, machine_code VARCHAR(32) NOT NULL, status VARCHAR(20) NOT NULL, operation_seq INTEGER );" # 批量导入 psql -d prod_db -c "\COPY plc_logs FROM 'plc_logs.csv' WITH (FORMAT CSV, HEADER FALSE)"

为什么这步不可跳过?
因为work_order_operations.start_time和end_time字段,正是从plc_logs表里status='RUNNING'的连续时间段计算出来的。没日志,工单永远卡在ASSIGNED,状态机转不动。


6. 产线级调试技巧:用数据库视图当“黑匣子”,实时监控状态机卡点

系统上线后,最怕的不是崩溃,而是“看起来正常,但状态没流转”。这时候,别翻日志,直接查数据库——我给自己写的 3 个救命视图,放在views/目录下,每次产线报障,我第一件事就是连上 psql 执行:

6.1 视图 1:vw_stuck_work_orders—— 找出卡在某个状态超过 30 分钟的工单

-- views/vw_stuck_work_orders.sql CREATE OR REPLACE VIEW vw_stuck_work_orders AS SELECT wo.id, wo.order_no, wo.status, wo.updated_at, EXTRACT(EPOCH FROM (NOW() AT TIME ZONE 'Asia/Shanghai' - wo.updated_at)) / 60 AS minutes_since_update, wop.machine_code, wop.start_time, wop.end_time FROM work_orders wo LEFT JOIN work_order_operations wop ON wo.id = wop.work_order_id WHERE wo.status IN ('assigned', 'first_piece', 'in_progress', 'qc_pending') AND wo.updated_at < NOW() AT TIME ZONE 'Asia/Shanghai' - INTERVAL '30 minutes' ORDER BY minutes_since_update DESC;

用法:

SELECT * FROM vw_stuck_work_orders LIMIT 10; -- 输出示例: -- id | order_no | status | updated_at | minutes_since_update | machine_code | start_time | end_time -- 42 | WO-2024-08765 | in_progress | 2024-06-15 08:30:22 | 42.5 | CNC-01 | 08:25:10 | <NULL>

解读:end_time为空,说明 CNC-01 开工后没上报“加工完成”,立刻去查该设备的plc_logs表最后 10 条记录,看是否有status='STOPPED'但系统没捕获。

6.2 视图 2:vw_device_health—— 监控所有设备心跳包存活率

-- views/vw_device_health.sql CREATE OR REPLACE VIEW vw_device_health AS WITH last_logs AS ( SELECT machine_code, MAX(timestamp) as last_seen, COUNT(*) as log_count_last_hour FROM plc_logs WHERE timestamp > NOW() AT TIME ZONE 'Asia/Shanghai' - INTERVAL '1 hour' GROUP BY machine_code ) SELECT machine_code, last_seen, log_count_last_hour, CASE WHEN NOW() AT TIME ZONE 'Asia/Shanghai' - last_seen > INTERVAL '1 minute' THEN 'DOWN' ELSE 'UP' END as status, ROUND(100.0 * log_count_last_hour / 120, 1) as uptime_percent -- 理论应上报 120 次/小时(每 30 秒 1 次) FROM last_logs ORDER BY status, last_seen;

用法:

SELECT * FROM vw_device_health; -- 如果 CNC-01 的 uptime_percent = 0.0,说明驱动进程挂了,立刻 `docker-compose restart web`

6.3 视图 3:vw_oee_breakdown—— 实时计算设备综合效率(OEE)

-- views/vw_oee_breakdown.sql CREATE OR REPLACE VIEW vw_oee_breakdown AS SELECT wop.machine_code, COUNT(*) as total_cycles, SUM(CASE WHEN wop.actual_time_seconds > 0 THEN 1 ELSE 0 END) as good_cycles, ROUND(AVG(wop.actual_time_seconds), 2) as avg_cycle_time_sec, ROUND( 100.0 * SUM(CASE WHEN wop.actual_time_seconds > 0 THEN 1 ELSE 0 END) / COUNT(*), 2 ) as quality_rate_percent, ROUND( 100.0 * SUM(wop.actual_time_seconds) / NULLIF(SUM(EXTRACT(EPOCH FROM (wop.end_time - wop.start_time))), 0), 2 ) as performance_rate_percent FROM work_order_operations wop WHERE wop.start_time IS NOT NULL AND wop.end_time IS NOT NULL AND wop.actual_time_seconds IS NOT NULL GROUP BY wop.machine_code;

用法:

SELECT * FROM vw_oee_breakdown; -- 如果 CNC-01 的 performance_rate_percent = 35.2,远低于行业基准 85%,说明设备频繁启停或空转,立刻查 `plc_logs` 里 `status='PAUSED'` 的密集时段。

这些视图不是为了炫技,而是把数据库变成你的“产线驾驶舱”。我不信前端报表,只信SELECT * FROM vw_stuck_work_orders的结果——它不会撒谎,也不会被缓存误导。每次新部署一套源码,我都在psql里建好这三个视图,然后泡杯茶,盯着屏幕等第一个minutes_since_update > 30的工单冒出来。它出现的那一刻,我就知道,这套代码真的开始呼吸了。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询