跳至內容

QMS + SQC 系統架構與開發範例

QMS 專案架構與目錄結構


專案概述

本品質管理系統(QMS)基於 Pear Admin Flask 框架,採用 RBAC 架構與插件模式,實現進貨檢驗、製程巡檢、出貨檢驗、不良品管理、來料異常處理、供應商品質管理、量測儀器管理、品質異常與8D改善、統計報表與分析、製程站設定、ERP/MES 串接及 QR Code 掃碼功能。系統符合 ISO 9001 標準,支援資料追溯與審計。

 功能模組說明:

  1. 進貨檢驗(IQC):檢驗來料,支援 QR Code 掃描與 ERP 串接。
  2. 製程巡檢(IPQC):工站巡檢,與 MES 工單串接,支援 QR Code 掃描。
  3. 出貨檢驗(OQC):檢查成品,支援 QR Code 掃描與 ERP 串接。
  4. 不良品管理(NG品處理):記錄不良品,與 ERP 成本系統連結。
  5. 來料異常處理(MRB):處理來料不合格,生成退貨單。
  6. 供應商品質管理(SPC + 稽核):管理供應商評比與 SPC 分析。
  7. 量測儀器管理(含校正):管理儀器校正與狀態。
  8. 品質異常與8D改善:執行8D流程,追蹤異常改善。
  9. 統計報表與分析:生成多維度報表,整合 ERP/MES 資料。
  10. 製程站設定:配置工站與檢查項目,與 MES 工單整合。
  11. ERP/MES 串接:交換工單、物料、檢驗結果。
  12. QR Code 掃碼進表單:掃描 QR Code 跳轉表單並自動填充資料。

🛠 功能細節說明

1. 進貨檢驗(IQC)

  • 對供應商來料進行檢驗(含尺寸、外觀、功能等)
  • 建立檢驗標準及抽樣方式(如 AQL)
  • 檢驗記錄電子化、可上傳照片
  • 可追蹤供應商的來料良率與不良原因
  • 檢驗結果自動轉入供應商評比系統

2. 製程巡檢(IPQC)

  • 生產過程中之品質點檢與巡檢作業
  • 定時提醒檢查項目(與工單連動)
  • 可設定不同工站的檢查項目與標準
  • 巡檢紀錄可統一管理、生成報表
  • 異常可即時通報,與8D系統串接

3. 出貨檢驗(OQC)

  • 成品出貨前檢查(外觀、功能、包裝)
  • 客戶驗收標準建檔與比對
  • 可根據不同客戶設定不同的檢驗程序
  • 不良品記錄與追蹤(客訴來源分析)

4. 不良品管理(NG品處理)

  • 生產中或檢驗中不良品分類記錄
  • 可標記不良原因、責任歸屬
  • 提供報廢、返工、讓步接受等處置選項
  • 與成本系統連結,計算品質損失成本

5. 來料異常處理(MRB)

  • 來料檢驗不合格時,啟動 MRB 流程
  • 判定責任歸屬:供應商、物流、內部
  • 提出處理方式(退貨、重工、報廢)
  • 可生成通知單、退貨單、臨時檢驗單等

6. 供應商品質管理(SPC + 稽核)

  • 建立供應商資料庫與評分機制
  • 每月/季/年供應商品質評比
  • 稽核排程管理(稽核表、紀錄、缺失追蹤)
  • SPC 分析供應商交貨品質穩定性

7. 量測儀器管理(含校正)

  • 儀器建檔(型號、使用單位、精度等)
  • 校正週期提醒與紀錄
  • 可上傳校正報告或外部證書
  • 儀器狀態管理(可用、維修中、報廢)

8. 品質異常與 8D 改善

  • 建立異常單(來源:製程、客訴、內稽)
  • 啟動8D問題解決流程(D1~D8)
  • 責任人指派與改善進度追蹤
  • 結案需上傳佐證文件與預防措施

9. 統計報表與分析

  • 可根據不同維度(時間、產品、客戶)分析:
    • 不良率趨勢
    • 客訴數據
    • 各站品質表現
    • 供應商品質走勢
  • 圖表自動生成(折線圖、長條圖、派圖)

✅ 各模組額外欄位與操作

📦 IQC 模組(進貨檢驗)

補充欄位:

  • 檢驗依據標準:如 ASTM D4169、MIL-STD-105
  • 驗收等級:如 N=125,AQL=0.65
  • 檢驗人/複驗人:簽核軌跡
  • 評等紀錄:供應商歷史績效

操作補充:

  • 異常件自動進入 MRB 流程模組
  • 可設定特定物料免檢或全檢規則

🛠 IPQC 模組(製程檢驗)

補充欄位:

  • 檢驗頻率設定(如每1hr/每10pcs)
  • 基準樣標記/圖片參照
  • 巡檢人員班別、簽名欄

操作補充:

  • 支援現場平板或工站終端快速錄入
  • 巡檢異常自動觸發 CAPA 事件

🚚 OQC 模組(出貨檢驗)

補充欄位:

  • 最終品項審核表(包含包裝、貼標)
  • 出貨預警(如近90日出貨異常率超標提醒)

操作補充:

  • 客戶規格比對提醒
  • 自動生成帶簽章的電子驗收報告

🔍 CAPA 模組(8D+矯正預防)

補充欄位:

  • 客戶/稽核單位名稱(來源明確化)
  • CAPA 類型(立即處置/長期改善)
  • 改善結果驗證欄(含文件上傳/簽核)

操作補充:

  • 可設定多階簽核流程(如 品保→工程→品保主管→結案)
  • 系統自動提醒改善進度,逾期紅字標示

📊 Analysis 模組(統計分析)

補充欄位/功能:

  • 報表版本控制與簽核機制
  • 報表出處標記(如 ISO/TS 規範出處)
  • 支援 CPK、PPK 自動化計算與圖示

操作補充:

  • 圖表導出可含公司 Logo 與稽核用標準語言
  • 支援客製化 KPI 儀表板(如良率、異常比例)

📷 Scan_input 模組(掃碼錄入)

補充欄位:

  • 設備序號綁定欄(追蹤量測設備用)
  • 操作者 RFID/指紋登入紀錄欄(符合 21 CFR Part 11)

操作補充:

  • 自動紀錄掃碼→輸入→提交的完整操作軌跡
  • 與實驗室儀器串接(如量測工具的自動資料回填)

📌 系統整體 ISO 相容性建議

標準項目建議對應做法
ISO 9001:2015 7.5 文件控制每張表單皆保留歷史版本與編修紀錄,自動打流水號
ISO 9001:2015 8.7 不合格處理IQC/OQC/IPQC 異常皆能觸發 CAPA 與 MRB 流程
ISO 17025 7.11 記錄保存每筆檢驗或掃碼紀錄須含簽名、時間戳記與追蹤碼
21 CFR Part 11(如適用醫療)符合電子簽名、操作軌跡不可竄改、帳號分權控管



目錄結構

qms-project/
├── application/                  # 核心應用資料夾(不得直接修改)
│   ├── __init__.py              # 應用初始化
│   ├── utils/                   # 共用函式庫
│   │   ├── __init__.py
│   │   ├── rbac.py             # RBAC 存取控制工具
│   │   ├── data_processor.py   # 資料驗證與處理工具
│   │   ├── report_generator.py # 報表生成工具
│   │   ├── file_handler.py     # 檔案上傳與管理工具
│   │   ├── qr_code.py          # QR Code 掃描與解析工具
│   │   ├── erp_mes_api.py      # ERP/MES API 串接工具
│   ├── config.py                # 系統配置(資料庫、密鑰、API 端點等)
├── plugs/                       # 插件資料夾
│   ├── qms_auth/                # 認證插件
│   │   ├── __init__.py
│   │   ├── routes.py           # 認證路由
│   │   ├── templates/          # 認證模板
│   │   │   ├── login.html
│   │   │   ├── register.html
│   │   │   ├── unauthorized.html
│   │   ├── models/             # 認證模型
│   │   │   ├── user.py
│   │   │   ├── role.py
│   │   │   ├── permission.py
│   ├── qms_iqc/                 # 進貨檢驗插件
│   │   ├── __init__.py
│   │   ├── routes.py           # IQC 路由(含 QR Code 掃描)
│   │   ├── templates/          # IQC 模板
│   │   │   ├── iqc_entry.html
│   │   │   ├── iqc_view.html
│   │   │   ├── qr_scan.html
│   │   ├── models/             # IQC 模型
│   │   │   ├── iqc_record.py
│   │   ├── static/             # IQC 專屬靜態檔案
│   │   │   ├── js/iqc_qr_scan.js
│   │   │   ├── css/iqc.css
│   ├── qms_ipqc/                # 製程巡檢插件
│   │   ├── __init__.py
│   │   ├── routes.py           # IPQC 路由(含 QR Code 掃描)
│   │   ├── templates/          # IPQC 模板
│   │   │   ├── ipqc_entry.html
│   │   │   ├── ipqc_view.html
│   │   │   ├── qr_scan.html
│   │   ├── models/             # IPQC 模型
│   │   │   ├── ipqc_record.py
│   │   ├── static/             # IPQC 專屬靜態檔案
│   │   │   ├── js/ipqc_qr_scan.js
│   │   │   ├── css/ipqc.css
│   ├── qms_oqc/                 # 出貨檢驗插件
│   │   ├── __init__.py
│   │   ├── routes.py           # OQC 路由(含 QR Code 掃描)
│   │   ├── templates/          # OQC 模板
│   │   │   ├── oqc_entry.html
│   │   │   ├── oqc_view.html
│   │   │   ├── qr_scan.html
│   │   ├── models/             # OQC 模型
│   │   │   ├── oqc_record.py
│   │   ├── static/             # OQC 專屬靜態檔案
│   │   │   ├── js/oqc_qr_scan.js
│   │   │   ├── css/oqc.css
│   ├── qms_ng/                  # 不良品管理插件
│   │   ├── __init__.py
│   │   ├── routes.py           # NG品路由
│   │   ├── templates/          # NG品模板
│   │   │   ├── ng_entry.html
│   │   │   ├── ng_view.html
│   │   ├── models/             # NG品模型
│   │   │   ├── ng_record.py
│   │   ├── static/             # NG品專屬靜態檔案
│   │   │   ├── css/ng.css
│   ├── qms_mrb/                 # 來料異常處理插件
│   │   ├── __init__.py
│   │   ├── routes.py           # MRB 路由
│   │   ├── templates/          # MRB 模板
│   │   │   ├── mrb_entry.html
│   │   │   ├── mrb_view.html
│   │   ├── models/             # MRB 模型
│   │   │   ├── mrb_record.py
│   │   ├── static/             # MRB 專屬靜態檔案
│   │   │   ├── css/mrb.css
│   ├── qms_supplier/            # 供應商品質管理插件
│   │   ├── __init__.py
│   │   ├── routes.py           # 供應商路由
│   │   ├── templates/          # 供應商模板
│   │   │   ├── supplier_profile.html
│   │   │   ├── audit_schedule.html
│   │   │   ├── spc_analysis.html
│   │   ├── models/             # 供應商模型
│   │   │   ├── supplier.py
│   │   │   ├── audit_record.py
│   │   ├── static/             # 供應商專屬靜態檔案
│   │   │   ├── css/supplier.css
│   ├── qms_instrument/          # 量測儀器管理插件
│   │   ├── __init__.py
│   │   ├── routes.py           # 儀器管理路由
│   │   ├── templates/          # 儀器管理模板
│   │   │   ├── instrument_entry.html
│   │   │   ├── instrument_view.html
│   │   ├── models/             # 儀器管理模型
│   │   │   ├── instrument.py
│   │   │   ├── calibration_record.py
│   │   ├── static/             # 儀器管理專屬靜態檔案
│   │   │   ├── css/instrument.css
│   ├── qms_8d/                  # 品質異常與8D改善插件
│   │   ├── __init__.py
│   │   ├── routes.py           # 8D 路由
│   │   ├── templates/          # 8D 模板
│   │   │   ├── 8d_entry.html
│   │   │   ├── 8d_view.html
│   │   ├── models/             # 8D 模型
│   │   │   ├── 8d_record.py
│   │   ├── static/             # 8D 專屬靜態檔案
│   │   │   ├── css/8d.css
│   ├── qms_report/              # 統計報表與分析插件
│   │   ├── __init__.py
│   │   ├── routes.py           # 報表路由
│   │   ├── templates/          # 報表模板
│   │   │   ├── report_view.html
│   │   ├── models/             # 報表模型
│   │   │   ├── report.py
│   │   ├── static/             # 報表專屬靜態檔案
│   │   │   ├── css/report.css
│   ├── qms_station/             # 製程站設定插件
│   │   ├── __init__.py
│   │   ├── routes.py           # 製程站路由
│   │   ├── templates/          # 製程站模板
│   │   │   ├── station_entry.html
│   │   │   ├── station_view.html
│   │   ├── models/             # 製程站模型
│   │   │   ├── station.py
│   │   ├── static/             # 製程站專屬靜態檔案
│   │   │   ├── css/station.css
│   ├── qms_admin/               # 管理員插件
│   │   ├── __init__.py
│   │   ├── routes.py           # 管理員路由
│   │   ├── templates/          # 管理員模板
│   │   │   ├── dashboard.html
│   │   │   ├── user_management.html
│   │   ├── static/             # 管理員專屬靜態檔案
│   │   │   ├── css/admin.css
├── migrations/                  # 資料庫遷移腳本
├── static/                      # 共用靜態檔案
│   ├── css/
│   │   ├── tailwind.min.css    # Tailwind CSS
│   ├── js/
│   │   ├── jsQR.min.js        # QR Code 掃描庫
├── tests/                       # 測試檔案
│   ├── test_auth.py
│   ├── test_iqc.py
│   ├── test_ipqc.py
│   ├── test_oqc.py
│   ├── test_ng.py
│   ├── test_mrb.py
│   ├── test_supplier.py
│   ├── test_instrument.py
│   ├── test_8d.py
│   ├── test_report.py
│   ├── test_station.py
├── requirements.txt             # 專案依賴
├── run.py                       # 應用啟動入口
├── README.md                    # 專案說明文件

角色與權限

RBAC 模型定義以下角色及其權限:

  1. 管理員(Admin)
    • 權限:all_*(完整存取)。
    • 功能:管理使用者、角色、權限、製程站設定、ERP/MES API 配置,查看所有報表。
  2. 品質經理(Quality Manager)
    • 權限:iqc_view、iqc_edit、ipqc_view、ipqc_edit、oqc_view、oqc_edit、ng_view、ng_edit、mrb_view、mrb_edit、8d_view、8d_edit、report_view。
    • 功能:管理品質資料與報表,無製程站編輯或 API 配置權限。
  3. 操作員(Operator)
    • 權限:iqc_edit、ipqc_edit、oqc_edit。
    • 功能:輸入品質資料(支援 QR Code 掃描),查看分配的製程站資訊。
  4. 供應商管理員(Supplier Manager)
    • 權限:supplier_view、supplier_edit、audit_view、audit_edit。
    • 功能:管理供應商資料與稽核,查看 SPC 報表。
  5. 製程工程師(Process Engineer)
    • 權限:station_view、station_edit。
    • 功能:管理製程站設定,與 MES 工單同步。

ISO 合規性

  • 客戶導向:ERP/MES 串接確保資料與客戶需求一致。
  • 流程導向:模組化插件與 QR Code 掃描優化流程。
  • 持續改進:8D流程與審計日誌支援改進。
  • 證據決策:多維度報表與資料追溯。

🔸 IQC/IPQC/OQC 檢驗模組

資料表:inspection_form

欄位類型說明
idUUID主鍵
form_typeENUMiqc/ipqc/oqc
work_order_noVARCHAR工單編號
product_codeVARCHAR產品型號
equipment_idVARCHAR儀器來源
inspector_idUUID使用者ID
statusENUMdraft/confirmed/verified
created_atDATETIME建立時間
updated_atDATETIME最後更新時間

資料表:inspection_item

欄位類型說明
idUUID主鍵
form_idUUID外鍵(對應 inspection_form)
item_codeVARCHAR測項代碼
measured_valueFLOAT實際值
usl/lsl/targetFLOAT規格上下限與中心值
resultENUMOK/NG
sourceENUMmanual/machine

操作流程:

  1. 使用者選擇工單與產品型號,系統載入預設檢驗項目。
  2. 儀器自動回填測值或由人工錄入。
  3. 每筆數據自動比較 LSL/USL 判定 OK/NG。
  4. 表單提交後,鎖定欄位,進入 SPC 統計流程。

🔸 CAPA 模組

資料表:capa_record

欄位類型說明
idUUID主鍵
source_typeENUMcomplaint/audit/inspection
issue_summaryTEXT缺失說明
root_causeTEXT原因分析
action_planTEXT改善對策
verification_noteTEXT改善驗證說明
file_pathVARCHAR附加文件
statusENUMnew/in_progress/completed/overdue
created_byUUID建立者
deadlineDATE回覆期限

操作流程:

  1. 系統或使用者建立 CAPA 表單。
  2. 負責人填寫原因與對策,主管驗證改善成效。
  3. 系統根據 deadline 計算是否逾期。
  4. 完成後列入改善紀錄供未來稽核追蹤。

🔸 SPC 模組

資料表:spc_entry

欄位類型說明
idUUID主鍵
form_idUUID對應檢驗表單
item_codeVARCHAR檢驗項目代碼
measured_valueFLOAT測值
batch_noVARCHAR批次號
timestampDATETIME測量時間

資料表:spc_result

欄位類型說明
idUUID主鍵
item_codeVARCHAR測項代碼
cpFLOAT製程能力
cpkFLOAT製程能力(考慮偏移)
avgFLOAT平均值
stddevFLOAT標準差
lsl/uslFLOAT規格上下限
updated_atDATETIME更新時間

操作流程:

  1. 每筆檢驗數據(SPC Entry)即時寫入資料庫。
  2. 定期或依表單完成觸發計算 SPC 統計結果。
  3. 結果供圖表展示與異常報表匯出。

🔸 稽核模組(Audit)

資料表:audit_record

欄位類型說明
idUUID主鍵
audit_typeENUMinternal/client/third_party
itemsJSON檢查項目與對應結果
auditor_idUUID稽核員
resultENUMpass/fail/partial
linked_capa_idUUID不符合時關聯 CAPA
report_pathVARCHARPDF 匯出報告

🔸 掃碼模組

資料表:scan_log

欄位類型說明
idUUID主鍵
codeVARCHAR條碼或 QRCode 資料
form_idUUID關聯表單
scanned_byUUID操作者
scanned_atDATETIME掃描時間
matched_fieldsJSON自動填入之表單欄位與值


共用函式庫

RBAC 工具(application/utils/rbac.py)

from functools import wraps
from flask import session, redirect, url_for, current_app
from plugs.qms_auth.models.user import User
from plugs.qms_auth.models.role import Role
from typing import Callable

def requires_permission(permission: str) -> Callable:
    """檢查使用者是否具有指定權限
    
    Args:
        permission (str): 權限名稱,例如 'iqc_edit'
    
    Returns:
        Callable: 裝飾器函式
    """
    def decorator(f: Callable) -> Callable:
        @wraps(f)
        def decorated_function(*args, **kwargs):
            user_id = session.get('user_id')
            if not user_id:
                current_app.logger.warning("未登入使用者嘗試存取受限路由")
                return redirect(url_for('qms_auth.login'))
            user = User.query.get(user_id)
            role = Role.query.get(user.role_id)
            if permission not in role.permissions:
                current_app.logger.warning(f"使用者 {user_id} 無權限 {permission}")
                return redirect(url_for('qms_auth.unauthorized'))
            return f(*args, **kwargs)
        return decorated_function
    return decorator

資料處理工具(application/utils/data_processor.py)

from typing import Optional

def validate_quality_data(value: str) -> Optional[float]:
    """驗證品質資料格式
    
    Args:
        value (str): 輸入資料,例如 '1.5' 或 '3/4'
    
    Returns:
        Optional[float]: 驗證後的數值,或 None(無效)
    """
    try:
        if isinstance(value, str) and '/' in value:
            num, denom = map(int, value.split('/'))
            return num / denom
        return float(value)
    except (ValueError, ZeroDivisionError):
        return None

QR Code 處理工具(application/utils/qr_code.py)

import json
from typing import Optional, Dict
from flask import current_app

def parse_qr_code(qr_data: str) -> Optional[Dict[str, str]]:
    """解析 QR Code 資料
    
    Args:
        qr_data (str): QR Code 內容
    
    Returns:
        Optional[Dict[str, str]]: 解析結果,包含 'type' 和 'id',或 None(無效)
    """
    try:
        data = json.loads(qr_data)
        return {
            'type': data.get('type'),  # 例如 'iqc', 'ipqc', 'oqc'
            'id': data.get('id'),      # 工單或物料 ID
        }
    except json.JSONDecodeError:
        current_app.logger.error(f"QR Code 解析失敗: {qr_data}")
        return None

ERP/MES API 串接工具(application/utils/erp_mes_api.py)

import requests
from flask import current_app
from typing import Optional, Dict, Any

def fetch_work_order(work_order_id: str) -> Optional[Dict[str, Any]]:
    """從 MES 獲取工單資料
    
    Args:
        work_order_id (str): 工單 ID
    
    Returns:
        Optional[Dict[str, Any]]: 工單資料,或 None(失敗)
    """
    try:
        url = f"{current_app.config['MES_API_URL']}/work_orders/{work_order_id}"
        response = requests.get(url, headers={'Authorization': current_app.config['MES_API_KEY']})
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        current_app.logger.error(f"MES API 錯誤: {e}")
        return None

def send_inspection_result(data: Dict[str, Any]) -> bool:
    """將檢驗結果傳送至 ERP
    
    Args:
        data (Dict[str, Any]): 檢驗結果資料
    
    Returns:
        bool: 傳送是否成功
    """
    try:
        url = f"{current_app.config['ERP_API_URL']}/inspection_results"
        response = requests.post(url, json=data, headers={'Authorization': current_app.config['ERP_API_KEY']})
        response.raise_for_status()
        return True
    except requests.RequestException as e:
        current_app.logger.error(f"ERP API 錯誤: {e}")
        return False

報表生成工具(application/utils/report_generator.py)

import matplotlib.pyplot as plt
import numpy as np
from typing import List

def generate_trend_chart(data: List[float], title: str, x_label: str, y_label: str, filename: str) -> None:
    """生成趨勢折線圖
    
    Args:
        data (List[float]): 資料點
        title (str): 圖表標題
        x_label (str): X 軸標籤
        y_label (str): Y 軸標籤
        filename (str): 輸出檔案路徑
    """
    x = np.arange(len(data))
    plt.figure(figsize=(10, 6))
    plt.plot(x, data, 'b-', label=title)
    plt.title(title)
    plt.xlabel(x_label)
    plt.ylabel(y_label)
    plt.grid(True)
    plt.legend()
    plt.savefig(filename)
    plt.close()

檔案處理工具(application/utils/file_handler.py)

import os
from flask import current_app
from typing import Optional

def save_file(file: Any, filename: str) -> Optional[str]:
    """儲存上傳檔案
    
    Args:
        file (Any): 上傳的檔案物件
        filename (str): 檔案名稱
    
    Returns:
        Optional[str]: 檔案儲存路徑,或 None(失敗)
    """
    try:
        upload_folder = current_app.config['UPLOAD_FOLDER']
        os.makedirs(upload_folder, exist_ok=True)
        file_path = os.path.join(upload_folder, filename)
        file.save(file_path)
        return file_path
    except Exception as e:
        current_app.logger.error(f"檔案儲存失敗: {e}")
        return None


插件程式碼實現

以下為各插件的關鍵程式碼,包含後台路由、模型、前台模板與 JavaScript,僅展示代表性模組(IQC、IPQC、Station),其他模組遵循相同規範。

進貨檢驗(IQC)插件

模型(plugs/qms_iqc/models/iqc_record.py)

from application import db
from datetime import datetime
from typing import Optional

class IQCRecord(db.Model):
    """進貨檢驗記錄模型"""
    __tablename__ = 'iqc_records'
    
    id = db.Column(db.Integer, primary_key=True)
    value = db.Column(db.Float, nullable=False)  # 檢驗數值
    material_id = db.Column(db.String(50), nullable=False)  # 物料 ID
    photo_path = db.Column(db.String(255))  # 照片路徑
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    user = db.relationship('User', backref='iqc_records')

路由(plugs/qms_iqc/routes.py)

from flask import Blueprint, render_template, request, redirect, url_for, session
from plugs.qms_iqc.models.iqc_record import IQCRecord
from application.utils.rbac import requires_permission
from application.utils.data_processor import validate_quality_data
from application.utils.qr_code import parse_qr_code
from application.utils.file_handler import save_file
from application.utils.erp_mes_api import fetch_work_order, send_inspection_result
from application import db
from typing import Optional

iqc = Blueprint('qms_iqc', __name__, url_prefix='/iqc')

@iqc.route('/qr_scan', methods=['GET', 'POST'])
@requires_permission('iqc_edit')
def qr_scan() -> str:
    """QR Code 掃描頁面"""
    if request.method == 'POST':
        qr_data = request.form.get('qr_data')
        parsed_data = parse_qr_code(qr_data)
        if parsed_data and parsed_data['type'] == 'iqc':
            session['material_id'] = parsed_data['id']
            return redirect(url_for('qms_iqc.iqc_entry'))
        return render_template('qms_iqc/qr_scan.html', error='無效的 QR Code')
    return render_template('qms_iqc/qr_scan.html')

@iqc.route('/entry', methods=['GET', 'POST'])
@requires_permission('iqc_edit')
def iqc_entry() -> str:
    """進貨檢驗資料輸入"""
    material_id = session.get('material_id')
    if not material_id:
        return redirect(url_for('qms_iqc.qr_scan'))
    
    if request.method == 'POST':
        value = request.form.get('value')
        validated_value = validate_quality_data(value)
        if validated_value is None:
            return render_template('qms_iqc/iqc_entry.html', error='無效的資料格式', material_id=material_id)
        
        photo = request.files.get('photo')
        photo_path = None
        if photo:
            photo_path = save_file(photo, photo.filename)
        
        record = IQCRecord(
            value=validated_value,
            material_id=material_id,
            photo_path=photo_path,
            user_id=session['user_id']
        )
        db.session.add(record)
        db.session.commit()
        
        # 回傳檢驗結果至 ERP
        send_inspection_result({
            'material_id': material_id,
            'value': validated_value,
            'user_id': session['user_id']
        })
        
        return redirect(url_for('qms_iqc.iqc_view'))
    return render_template('qms_iqc/iqc_entry.html', material_id=material_id)

@iqc.route('/view')
@requires_permission('iqc_view')
def iqc_view() -> str:
    """進貨檢驗資料查看"""
    records = IQCRecord.query.all()
    return render_template('qms_iqc/iqc_view.html', records=records)

模板(plugs/qms_iqc/templates/iqc_entry.html)

{% extends "base.html" %}
{% block content %}
<h1>進貨檢驗資料輸入</h1>
{% if error %}
    <p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST" enctype="multipart/form-data">
    <label for="material_id">物料 ID</label>
    <input type="text" id="material_id" name="material_id" value="{{ material_id }}" readonly aria-label="物料 ID">
    <label for="value">檢驗數值</label>
    <input type="text" id="value" name="value" required aria-label="檢驗數值">
    <label for="photo">上傳照片</label>
    <input type="file" id="photo" name="photo" accept="image/*" aria-label="上傳照片">
    <button type="submit">提交</button>
</form>
{% endblock %}

QR 掃描模板(plugs/qms_iqc/templates/qr_scan.html)

{% extends "base.html" %}
{% block content %}
<h1>掃描 QR Code</h1>
{% if error %}
    <p style="color: red;">{{ error }}</p>
{% endif %}
<video id="video" width="300" height="200"></video>
<canvas id="canvas" style="display: none;"></canvas>
<form id="qr_form" method="POST" action="{{ url_for('qms_iqc.qr_scan') }}">
    <input type="hidden" name="qr_data" id="qr_data">
</form>
<script src="{{ url_for('static', filename='js/jsQR.min.js') }}"></script>
<script src="{{ url_for('qms_iqc.static', filename='js/iqc_qr_scan.js') }}"></script>
{% endblock %}

JavaScript(plugs/qms_iqc/static/js/iqc_qr_scan.js)

document.addEventListener('DOMContentLoaded', () => {
    const video = document.getElementById('video');
    const canvas = document.getElementById('canvas');
    const context = canvas.getContext('2d');

    navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
        .then(stream => {
            video.srcObject = stream;
            video.play();
            scanQRCode();
        })
        .catch(err => {
            console.error('相機存取失敗:', err);
            alert('無法存取相機,請檢查權限');
        });

    function scanQRCode() {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        context.drawImage(video, 0, 0, canvas.width, canvas.height);
        const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
        const code = jsQR(imageData.data, imageData.width, imageData.height);
        if (code) {
            document.getElementById('qr_data').value = code.data;
            document.getElementById('qr_form').submit();
        } else {
            requestAnimationFrame(scanQRCode);
        }
    }
});

CSS(plugs/qms_iqc/static/css/iqc.css)

form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    max-width: 400px;
    margin: 0 auto;
}

input, button {
    padding: 0.5rem;
    font-size: 1rem;
}

button {
    background-color: #2563eb;
    color: white;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #1e40af;
}

製程巡檢(IPQC)插件

模型(plugs/qms_ipqc/models/ipqc_record.py)

from application import db
from datetime import datetime
from typing import Optional

class IPQCRecord(db.Model):
    """製程巡檢記錄模型"""
    __tablename__ = 'ipqc_records'
    
    id = db.Column(db.Integer, primary_key=True)
    value = db.Column(db.Float, nullable=False)  # 檢驗數值
    station_id = db.Column(db.Integer, db.ForeignKey('stations.id'), nullable=False)
    work_order_id = db.Column(db.String(50), nullable=False)  # 工單 ID
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    station = db.relationship('Station', backref='ipqc_records')
    user = db.relationship('User', backref='ipqc_records')

路由(plugs/qms_ipqc/routes.py)

from flask import Blueprint, render_template, request, redirect, url_for, session
from plugs.qms_ipqc.models.ipqc_record import IPQCRecord
from plugs.qms_station.models.station import Station
from application.utils.rbac import requires_permission
from application.utils.data_processor import validate_quality_data
from application.utils.qr_code import parse_qr_code
from application.utils.erp_mes_api import fetch_work_order, send_inspection_result
from application import db
from typing import Optional

ipqc = Blueprint('qms_ipqc', __name__, url_prefix='/ipqc')

@ipqc.route('/qr_scan', methods=['GET', 'POST'])
@requires_permission('ipqc_edit')
def qr_scan() -> str:
    """QR Code 掃描頁面"""
    if request.method == 'POST':
        qr_data = request.form.get('qr_data')
        parsed_data = parse_qr_code(qr_data)
        if parsed_data and parsed_data['type'] == 'ipqc':
            session['work_order_id'] = parsed_data['id']
            return redirect(url_for('qms_ipqc.ipqc_entry'))
        return render_template('qms_ipqc/qr_scan.html', error='無效的 QR Code')
    return render_template('qms_ipqc/qr_scan.html')

@ipqc.route('/entry', methods=['GET', 'POST'])
@requires_permission('ipqc_edit')
 tandard: str) -> str:
    """製程巡檢資料輸入"""
    work_order_id = session.get('work_order_id')
    if not work_order_id:
        return redirect(url_for('qms_ipqc.qr_scan'))
    
    work_order = fetch_work_order(work_order_id)
    if not work_order:
        return render_template('qms_ipqc/ipqc_entry.html', error='無效的工單 ID')
    
    stations = Station.query.filter_by(work_order_id=work_order_id).all()
    
    if request.method == 'POST':
        value = request.form.get('value')
        station_id = request.form.get('station_id')
        validated_value = validate_quality_data(value)
        if validated_value is None:
            return render_template('qms_ipqc/ipqc_entry.html', error='無效的資料格式', stations=stations)
        
        record = IPQCRecord(
            value=validated_value,
            station_id=station_id,
            work_order_id=work_order_id,
            user_id=session['user_id']
        )
        db.session.add(record)
        db.session.commit()
        
        # 回傳檢驗結果至 MES
        send_inspection_result({
            'work_order_id': work_order_id,
            'station_id': station_id,
            'value': validated_value,
            'user_id': session['user_id']
        })
        
        return redirect(url_for('qms_ipqc.ipqc_view'))
    return render_template('qms_ipqc/ipqc_entry.html', stations=stations)

@ipqc.route('/view')
@requires_permission('ipqc_view')
def ipqc_view() -> str:
    """製程巡檢資料查看"""
    records = IPQCRecord.query.all()
    return render_template('qms_ipqc/ipqc_view.html', records=records)

模板(plugs/qms_ipqc/templates/ipqc_entry.html)

{% extends "base.html" %}
{% block content %}
<h1>製程巡檢資料輸入</h1>
{% if error %}
    <p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST">
    <label for="work_order_id">工單 ID</label>
    <input type="text" id="work_order_id" name="work_order_id" value="{{ session.work_order_id }}" readonly aria-label="工單 ID">
    <label for="station_id">工站</label>
    <select id="station_id" name="station_id" required aria-label="選擇工站">
        {% for station in stations %}
            <option value="{{ station.id }}">{{ station.name }}</option>
        {% endfor %}
    </select>
    <label for="value">檢驗數值</label>
    <input type="text" id="value" name="value" required aria-label="檢驗數值">
    <button type="submit">提交</button>
</form>
{% endblock %}

JavaScript(plugs/qms_ipqc/static/js/ipqc_qr_scan.js)

document.addEventListener('DOMContentLoaded', () => {
    const video = document.getElementById('video');
    const canvas = document.getElementById('canvas');
    const context = canvas.getContext('2d');

    navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
        .then(stream => {
            video.srcObject = stream;
            video.play();
            scanQRCode();
        })
        .catch(err => {
            console.error('相機存取失敗:', err);
            alert('無法存取相機,請檢查權限');
        });

    function scanQRCode() {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        context.drawImage(video, 0, 0, canvas.width, canvas.height);
        const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
        const code = jsQR(imageData.data, imageData.width, imageData.height);
        if (code) {
            document.getElementById('qr_data').value = code.data;
            document.getElementById('qr_form').submit();
        } else {
            requestAnimationFrame(scanQRCode);
        }
    }
});

CSS(plugs/qms_ipqc/static/css/ipqc.css)

form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    max-width: 400px;
    margin: 0 auto;
}

select, input, button {
    padding: 0.5rem;
    font-size: 1rem;
}

button {
    background-color: #2563eb;
    color: white;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #1e40af;
}

製程站設定插件

模型(plugs/qms_station/models/station.py)

from application import db
from datetime import datetime
from typing import Optional

class Station(db.Model):
    """製程站模型"""
    __tablename__ = 'stations'
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)  # 工站名稱
    sequence = db.Column(db.Integer, nullable=False)  # 工站序號
    work_order_id = db.Column(db.String(50), nullable=False)  # 工單 ID
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

路由(plugs/qms_station/routes.py)

from flask import Blueprint, render_template, request, redirect, url_for
from plugs.qms_station.models.station import Station
from application.utils.rbac import requires_permission
from application.utils.erp_mes_api import fetch_work_order
from application import db
from typing import Optional

station = Blueprint('qms_station', __name__, url_prefix='/station')

@station.route('/entry', methods=['GET', 'POST'])
@requires_permission('station_edit')
def station_entry() -> str:
    """製程站設定輸入"""
    if request.method == 'POST':
        name = request.form.get('name')
        sequence = request.form.get('sequence')
        work_order_id = request.form.get('work_order_id')
        
        # 從 MES 驗證工單
        work_order = fetch_work_order(work_order_id)
        if not work_order:
            return render_template('qms_station/station_entry.html', error='無效的工單 ID')
        
        station = Station(name=name, sequence=sequence, work_order_id=work_order_id)
        db.session.add(station)
        db.session.commit()
        return redirect(url_for('qms_station.station_view'))
    return render_template('qms_station/station_entry.html')

@station.route('/view')
@requires_permission('station_view')
def station_view() -> str:
    """製程站查看"""
    stations = Station.query.all()
    return render_template('qms_station/station_view.html', stations=stations)

模板(plugs/qms_station/templates/station_entry.html)

{% extends "base.html" %}
{% block content %}
<h1>製程站設定</h1>
{% if error %}
    <p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST">
    <label for="name">工站名稱</label>
    <input type="text" id="name" name="name" required aria-label="工站名稱">
    <label for="sequence">工站序號</label>
    <input type="number" id="sequence" name="sequence" required aria-label="工站序號">
    <label for="work_order_id">工單 ID</label>
    <input type="text" id="work_order_id" name="work_order_id" required aria-label="工單 ID">
    <button type="submit">提交</button>
</form>
{% endblock %}

CSS(plugs/qms_station/static/css/station.css)

form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    max-width: 400px;
    margin: 0 auto;
}

input, button {
    padding: 0.5rem;
    font-size: 1rem;
}

button {
    background-color: #2563eb;
    color: white;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #1e40af;
}

其他模組實現

以下模組遵循相同規範,僅概述關鍵結構:

  1. 出貨檢驗(OQC)
    • 模型:OQCRecord(產品 ID、檢驗數值、客戶標準)。
    • 路由:/oqc/qr_scan、/oqc/entry、/oqc/view。
    • 模板:oqc_entry.html、qr_scan.html。
    • 功能:QR Code 掃描產品 ID,與 ERP 串接客戶標準。
  2. 不良品管理(NG品處理)
    • 模型:NGRecord(不良原因、處置方式)。
    • 路由:/ng/entry、/ng/view。
    • 模板:ng_entry.html。
    • 功能:記錄不良品,與 ERP 成本系統連結。
  3. 來料異常處理(MRB)
    • 模型:MRBRecord(物料 ID、責任歸屬)。
    • 路由:/mrb/entry、/mrb/view。
    • 模板:mrb_entry.html。
    • 功能:生成退貨單,與 ERP 同步。
  4. 供應商品質管理(SPC + 稽核)
    • 模型:Supplier、AuditRecord。
    • 路由:/supplier/profile、/supplier/audit、/supplier/spc。
    • 模板:supplier_profile.html、audit_schedule.html。
    • 功能:SPC 分析,與 ERP 同步供應商資料。
  5. 量測儀器管理(含校正)
    • 模型:Instrument、CalibrationRecord。
    • 路由:/instrument/entry、/instrument/view。
    • 模板:instrument_entry.html。
    • 功能:管理校正週期,支援報告上傳。
  6. 品質異常與8D改善
    • 模型:8DRecord(異常來源、改善進度)。
    • 路由:/8d/entry、/8d/view。
    • 模板:8d_entry.html。
    • 功能:執行8D流程,追蹤改善。
  7. 統計報表與分析
    • 模型:Report(報表資料)。
    • 路由:/report/view。
    • 模板:report_view.html。
    • 功能:生成圖表,整合 ERP/MES 資料。
  8. 管理員插件
    • 路由:/admin/dashboard、/admin/user_management。
    • 模板:dashboard.html、user_management.html。
    • 功能:管理使用者、角色、API 配置。

依賴(requirements.txt)

Flask==2.0.1
Flask-SQLAlchemy==2.5.1
Flask-Login==0.5.0
Flask-Migrate==3.1.0
python-dotenv==0.19.0
gunicorn==20.1.0
matplotlib==3.5.1
numpy==1.21.5
requests==2.26.0

安裝與執行

  1. 複製 Pear Admin Flask 儲存庫:git clone https://gitee.com/pear-admin/pear-admin-flask.git。
  2. 進入專案目錄:cd qms-project。
  3. 安裝依賴:pip install -r requirements.txt。
  4. 下載 QR Code 掃描庫(jsQR):將 jsQR.min.js 放置於 static/js/(從 https://github.com/cozmo/jsQR 下載)。
  5. 初始化資料庫:flask db init && flask db migrate && flask db upgrade。
  6. 配置環境變數(在 application/config.py 中設定 MES_API_URL、ERP_API_URL、MES_API_KEY、ERP_API_KEY、UPLOAD_FOLDER)。
  7. 啟動應用:python run.py。

未來改進

  • 支援多 ERP/MES 系統(如 Oracle NetSuite)。
  • 新增即時品質監控儀表板。
  • 實現多語言支援,符合全球合規性。

評分
0 0

暫時沒有留言。

成為第一個留言的人。