| @@ -0,0 +1,9 @@ | |||||
| { | |||||
| "api_ip": "10.10.0.81", | |||||
| "api_port": "8090", | |||||
| "dabag_ip": "192.168.17.27", | |||||
| "dabag_port": "3008", | |||||
| "laser_ip": "192.168.17.10", | |||||
| "laser_port": "45678", | |||||
| "label_com": "TSC TTP-246M Pro" | |||||
| } | |||||
| @@ -0,0 +1,5 @@ | |||||
| # Python dependencies for FPSMS backend integration | |||||
| requests>=2.28.0 | |||||
| pyserial>=3.5 | |||||
| Pillow>=9.0.0 | |||||
| qrcode[pil]>=7.0 | |||||
| @@ -1,54 +0,0 @@ | |||||
| # Python scripts for FPSMS backend | |||||
| This folder holds Python programs that integrate with the FPSMS backend (e.g. calling the public `/py` API). | |||||
| ## Setup | |||||
| ```bash | |||||
| cd python | |||||
| pip install -r requirements.txt | |||||
| ``` | |||||
| ## Configuration | |||||
| Set the backend base URL (optional, default below): | |||||
| - Environment: `FPSMS_BASE_URL` (default: `http://localhost:8090/api` — includes context path `/api`) | |||||
| - Or edit the default in each script. | |||||
| ## Scripts | |||||
| | Script | Description | | |||||
| |--------|-------------| | |||||
| | `Bag1.py` | **GUI**: date selector (default today) and job orders as buttons; click shows "Clicked on Job Order code XXXX item xxxx". Run: `python Bag1.py` | | |||||
| | `fetch_job_orders.py` | CLI: fetches job orders by plan date from `GET /py/job-orders` | | |||||
| | `label_zpl.py` | ZPL label generator (90° rotated, UTF-8 Chinese, QR). `generate_zpl(batch_no, item_code, chinese_desc)`, `send_zpl(zpl, host, port)`. Run: `python label_zpl.py` to print one test label. | | |||||
| ## Building Bag1 as a standalone .exe | |||||
| To distribute Bag1 to customer PCs **without giving them source code or requiring Python**: | |||||
| 1. On your development PC (with Python installed), open a terminal in the `python` folder. | |||||
| 2. Install build dependencies: | |||||
| ```bash | |||||
| pip install -r requirements-build.txt | |||||
| ``` | |||||
| 3. Run the build script: | |||||
| ```bash | |||||
| build_exe.bat | |||||
| ``` | |||||
| Or run PyInstaller directly: | |||||
| ```bash | |||||
| pyinstaller --onefile --windowed --name Bag1 Bag1.py | |||||
| ``` | |||||
| 4. The executable is created at `dist\Bag1.exe`. Copy **only** `Bag1.exe` to the customer computer and run it; no Python or source code is needed. The app will save its settings (`bag1_settings.json`) in the same folder as the exe. | |||||
| ## Adding new scripts | |||||
| Add new `.py` files here and list them in this README. Use `requirements.txt` for any new dependencies. | |||||
| @@ -1,20 +0,0 @@ | |||||
| @echo off | |||||
| REM Build Bag1.exe (single file, no console window). | |||||
| REM Run from this folder: build_exe.bat | |||||
| REM Requires: pip install -r requirements-build.txt | |||||
| set SCRIPT=Bag1.py | |||||
| set NAME=Bag1 | |||||
| if not exist "%SCRIPT%" ( | |||||
| echo Error: %SCRIPT% not found. Run from the python folder. | |||||
| exit /b 1 | |||||
| ) | |||||
| pip install -r requirements-build.txt | |||||
| pyinstaller --onefile --windowed --name "%NAME%" "%SCRIPT%" | |||||
| echo. | |||||
| echo Done. Exe is in: dist\%NAME%.exe | |||||
| echo Copy dist\%NAME%.exe to the customer PC and run it (no Python needed). | |||||
| pause | |||||
| @@ -1,80 +0,0 @@ | |||||
| #!/usr/bin/env python3 | |||||
| """ | |||||
| Fetch job orders from FPSMS backend by plan date. | |||||
| Uses the public API GET /py/job-orders (no login required). | |||||
| Usage: | |||||
| python fetch_job_orders.py # today | |||||
| python fetch_job_orders.py 2026-02-24 # specific date | |||||
| python fetch_job_orders.py --date 2026-02-24 | |||||
| """ | |||||
| import argparse | |||||
| import os | |||||
| import sys | |||||
| from datetime import date | |||||
| from typing import List, Optional | |||||
| import requests | |||||
| DEFAULT_BASE_URL = os.environ.get("FPSMS_BASE_URL", "http://localhost:8090/api") | |||||
| def fetch_job_orders(base_url: str, plan_start: Optional[date]) -> List[dict]: | |||||
| """Call GET /py/job-orders and return the JSON list.""" | |||||
| url = f"{base_url.rstrip('/')}/py/job-orders" | |||||
| params = {} | |||||
| if plan_start is not None: | |||||
| params["planStart"] = plan_start.isoformat() | |||||
| resp = requests.get(url, params=params, timeout=30) | |||||
| resp.raise_for_status() | |||||
| return resp.json() | |||||
| def main() -> None: | |||||
| parser = argparse.ArgumentParser(description="Fetch job orders from FPSMS by plan date") | |||||
| parser.add_argument( | |||||
| "date", | |||||
| nargs="?", | |||||
| type=str, | |||||
| default=None, | |||||
| help="Plan date (yyyy-MM-dd). Default: today", | |||||
| ) | |||||
| parser.add_argument( | |||||
| "--date", | |||||
| dest="date_alt", | |||||
| type=str, | |||||
| default=None, | |||||
| help="Plan date (yyyy-MM-dd)", | |||||
| ) | |||||
| parser.add_argument( | |||||
| "--base-url", | |||||
| type=str, | |||||
| default=DEFAULT_BASE_URL, | |||||
| help=f"Backend base URL (default: {DEFAULT_BASE_URL})", | |||||
| ) | |||||
| args = parser.parse_args() | |||||
| plan_str = args.date_alt or args.date | |||||
| if plan_str: | |||||
| try: | |||||
| plan_start = date.fromisoformat(plan_str) | |||||
| except ValueError: | |||||
| print(f"Invalid date: {plan_str}. Use yyyy-MM-dd.", file=sys.stderr) | |||||
| sys.exit(1) | |||||
| else: | |||||
| plan_start = date.today() | |||||
| try: | |||||
| data = fetch_job_orders(args.base_url, plan_start) | |||||
| except requests.RequestException as e: | |||||
| print(f"Request failed: {e}", file=sys.stderr) | |||||
| sys.exit(1) | |||||
| print(f"Job orders for planStart={plan_start} ({len(data)} items)") | |||||
| for jo in data: | |||||
| print(f" id={jo.get('id')} code={jo.get('code')} itemCode={jo.get('itemCode')} itemName={jo.get('itemName')} reqQty={jo.get('reqQty')}") | |||||
| if __name__ == "__main__": | |||||
| main() | |||||
| @@ -1,97 +0,0 @@ | |||||
| #!/usr/bin/env python3 | |||||
| """ | |||||
| ZPL label generator and TCP send for Zebra label printer (標簽機). | |||||
| - Rotated 90° clockwise for ~53 mm media width | |||||
| - UTF-8 (^CI28) for Chinese | |||||
| - Large fonts, QR code mag 6 | |||||
| Standalone: python label_zpl.py | |||||
| Or import generate_zpl() / send_zpl() and call from Bag1. | |||||
| """ | |||||
| import socket | |||||
| # Default printer (override via args or Bag1 settings) | |||||
| DEFAULT_PRINTER_IP = "192.168.17.27" | |||||
| DEFAULT_PRINTER_PORT = 3008 | |||||
| SOCKET_TIMEOUT = 10 | |||||
| def generate_zpl( | |||||
| batch_no: str, | |||||
| item_code: str = "PP2238-02", | |||||
| chinese_desc: str = "(餐廳用)凍咖啡底P+10(0.91L包)", | |||||
| ) -> str: | |||||
| """ | |||||
| Generates ZPL label: | |||||
| - Rotated 90° clockwise to fit ~53 mm media width | |||||
| - UTF-8 mode (^CI28) for correct Chinese display | |||||
| - Larger fonts | |||||
| - Bigger QR code (mag 6) | |||||
| """ | |||||
| return f"""^XA | |||||
| ^PW420 ^# Fits ~53 mm width (~420 dots @ 203 dpi) | |||||
| ^LL780 ^# Taller label after rotation + bigger elements | |||||
| ^PO N ^# Normal — change to ^POI if upside-down | |||||
| ^CI28 ^# Enable UTF-8 / Unicode for Chinese (critical fix for boxes) | |||||
| ^FO70,70 | |||||
| ^A@R,60,60,E:SIMSUN.FNT^FD{chinese_desc}^FS | |||||
| ^# Very large Chinese text, rotated | |||||
| ^FO220,70 | |||||
| ^A0R,50,50^FD{item_code}^FS | |||||
| ^# Larger item code | |||||
| ^FO310,70 | |||||
| ^A0R,45,45^FDBatch: {batch_no}^FS | |||||
| ^# Larger batch text | |||||
| ^FO150,420 | |||||
| ^BQN,2,6^FDQA,{batch_no}^FS | |||||
| ^# Bigger QR code (magnification 6), lower position for space | |||||
| ^XZ""" | |||||
| def send_zpl( | |||||
| zpl: str, | |||||
| host: str = DEFAULT_PRINTER_IP, | |||||
| port: int = DEFAULT_PRINTER_PORT, | |||||
| timeout: float = SOCKET_TIMEOUT, | |||||
| ) -> None: | |||||
| """Send ZPL to printer over TCP. Raises on connection/send error.""" | |||||
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||||
| sock.settimeout(timeout) | |||||
| sock.connect((host, port)) | |||||
| sock.sendall(zpl.encode("utf-8")) | |||||
| sock.close() | |||||
| # ──────────────────────────────────────────────── | |||||
| # Example usage — prints one label | |||||
| # ──────────────────────────────────────────────── | |||||
| if __name__ == "__main__": | |||||
| test_batch = "2025121209" | |||||
| zpl = generate_zpl(test_batch) | |||||
| print("Sending ZPL (90° rotated, UTF-8 Chinese, bigger QR):") | |||||
| print("-" * 90) | |||||
| print(zpl) | |||||
| print("-" * 90) | |||||
| try: | |||||
| send_zpl(zpl) | |||||
| print("Label sent successfully!") | |||||
| print("→ Check Chinese — should show real characters (not 口口 or symbols)") | |||||
| print("→ QR is now bigger (mag 6) — test scan with phone") | |||||
| print("→ If upside-down: edit ^PO N → ^POI") | |||||
| print("→ If still boxes: SimSun font may be missing — reinstall via Zebra Setup Utilities") | |||||
| except ConnectionRefusedError: | |||||
| print(f"Cannot connect to {DEFAULT_PRINTER_IP}:{DEFAULT_PRINTER_PORT} — printer off or wrong IP?") | |||||
| except socket.timeout: | |||||
| print("Connection timeout — check printer/network/port") | |||||
| except Exception as e: | |||||
| print(f"Error: {e}") | |||||
| @@ -1,4 +0,0 @@ | |||||
| # Install with: pip install -r requirements-build.txt | |||||
| # Used only for building the .exe (PyInstaller). | |||||
| -r requirements.txt | |||||
| pyinstaller>=6.0.0 | |||||