| @@ -111,7 +111,8 @@ def try_printer_connection(printer_name: str, sett: dict) -> bool: | |||
| FONT_SIZE = 16 | |||
| FONT_SIZE_BUTTONS = 15 | |||
| FONT_SIZE_QTY = 12 # smaller for 數量 under batch no. | |||
| FONT_SIZE_ITEM = 20 # item code and item name (larger for readability) | |||
| FONT_SIZE_ITEM_CODE = 20 # item code (larger for readability) | |||
| FONT_SIZE_ITEM_NAME = 26 # item name (bigger than item code) | |||
| FONT_FAMILY = "Microsoft JhengHei UI" # Traditional Chinese; fallback to TkDefaultFont | |||
| # Column widths: item code own column; item name at least double, wraps in its column | |||
| ITEM_CODE_WRAP = 140 # item code column width (long codes wrap under code only) | |||
| @@ -134,6 +135,7 @@ PRINTER_CHECK_MS = 60 * 1000 # 1 minute when printer OK | |||
| PRINTER_RETRY_MS = 30 * 1000 # 30 seconds when printer failed | |||
| PRINTER_SOCKET_TIMEOUT = 3 | |||
| DATAFLEX_SEND_TIMEOUT = 10 # seconds when sending ZPL to DataFlex | |||
| DATE_AUTO_RESET_SEC = 5 * 60 # 5 minutes: if no manual date change, auto-set to today | |||
| def _zpl_escape(s: str) -> str: | |||
| @@ -180,9 +182,9 @@ def generate_zpl_dataflex( | |||
| ^PW700 | |||
| ^LL500 | |||
| ^PO N | |||
| ^FO10,188 | |||
| ^FO10,20 | |||
| ^BQR,4,7^FDQA,{batch_no}^FS | |||
| ^FO170,188 | |||
| ^FO170,20 | |||
| ^A@R,72,72,{font_regular}^FD{desc}^FS | |||
| ^FO0,260 | |||
| ^A@R,72,72,{font_regular}^FD{batch_esc}^FS | |||
| @@ -216,8 +218,9 @@ def format_qty(val) -> str: | |||
| def batch_no(year: int, job_order_id: int) -> str: | |||
| """Batch no.: B + 4-digit year + jobOrderId zero-padded to 6 digits.""" | |||
| return f"B{year}{job_order_id:06d}" | |||
| """Batch no.: B + 2-digit year + jobOrderId zero-padded to 6 digits.""" | |||
| short_year = year % 100 | |||
| return f"B{short_year:02d}{job_order_id:06d}" | |||
| def get_font(size: int = FONT_SIZE, bold: bool = False) -> tuple: | |||
| @@ -380,6 +383,15 @@ def main() -> None: | |||
| status_frame.configure(bg=BG_STATUS_ERROR) | |||
| status_lbl.configure(text="連接不到服務器", bg=BG_STATUS_ERROR, fg=FG_STATUS_ERROR) | |||
| def set_status_message(msg: str, is_error: bool = False): | |||
| """Show a temporary message on the status bar.""" | |||
| if is_error: | |||
| status_frame.configure(bg=BG_STATUS_ERROR) | |||
| status_lbl.configure(text=msg, bg=BG_STATUS_ERROR, fg=FG_STATUS_ERROR) | |||
| else: | |||
| status_frame.configure(bg=BG_STATUS_OK) | |||
| status_lbl.configure(text=msg, bg=BG_STATUS_OK, fg=FG_STATUS_OK) | |||
| # Top: left [前一天] [date] [後一天] | right [printer dropdown] | |||
| top = tk.Frame(root, padx=12, pady=12, bg=BG_TOP) | |||
| top.pack(fill=tk.X) | |||
| @@ -387,23 +399,31 @@ def main() -> None: | |||
| date_var = tk.StringVar(value=date.today().isoformat()) | |||
| printer_options = ["打袋機 DataFlex", "標簽機", "激光機"] | |||
| printer_var = tk.StringVar(value=printer_options[0]) | |||
| last_manual_date_change_ref = [time.time()] # track when user last changed date manually | |||
| def mark_manual_date_change(): | |||
| last_manual_date_change_ref[0] = time.time() | |||
| def go_prev_day() -> None: | |||
| try: | |||
| d = date.fromisoformat(date_var.get().strip()) | |||
| date_var.set((d - timedelta(days=1)).isoformat()) | |||
| mark_manual_date_change() | |||
| load_job_orders(from_user_date_change=True) | |||
| except ValueError: | |||
| date_var.set(date.today().isoformat()) | |||
| mark_manual_date_change() | |||
| load_job_orders(from_user_date_change=True) | |||
| def go_next_day() -> None: | |||
| try: | |||
| d = date.fromisoformat(date_var.get().strip()) | |||
| date_var.set((d + timedelta(days=1)).isoformat()) | |||
| mark_manual_date_change() | |||
| load_job_orders(from_user_date_change=True) | |||
| except ValueError: | |||
| date_var.set(date.today().isoformat()) | |||
| mark_manual_date_change() | |||
| load_job_orders(from_user_date_change=True) | |||
| # 前一天 (previous day) with left arrow icon | |||
| @@ -420,6 +440,11 @@ def main() -> None: | |||
| ) | |||
| date_entry.pack(side=tk.LEFT, padx=(0, 8), ipady=4) | |||
| # Track manual typing in date field as user date change | |||
| def on_date_entry_key(event): | |||
| mark_manual_date_change() | |||
| date_entry.bind("<Key>", on_date_entry_key) | |||
| # 後一天 (next day) with right arrow icon | |||
| btn_next = ttk.Button(top, text="後一天 ▶", command=go_next_day) | |||
| btn_next.pack(side=tk.LEFT, padx=(0, 8)) | |||
| @@ -643,7 +668,7 @@ def main() -> None: | |||
| code_lbl = tk.Label( | |||
| row, | |||
| text=item_code, | |||
| font=get_font(FONT_SIZE_ITEM), | |||
| font=get_font(FONT_SIZE_ITEM_CODE), | |||
| bg=BG_ROW, | |||
| fg="black", | |||
| wraplength=ITEM_CODE_WRAP, | |||
| @@ -652,11 +677,11 @@ def main() -> None: | |||
| ) | |||
| code_lbl.pack(side=tk.LEFT, anchor=tk.NW, padx=(12, 8)) | |||
| # Column 3: item name only, same bigger font, at least double width, wraps under its own column | |||
| # Column 3: item name only, bigger font, at least double width, wraps under its own column | |||
| name_lbl = tk.Label( | |||
| row, | |||
| text=item_name or "—", | |||
| font=get_font(FONT_SIZE_ITEM), | |||
| font=get_font(FONT_SIZE_ITEM_NAME), | |||
| bg=BG_ROW, | |||
| fg="black", | |||
| wraplength=ITEM_NAME_WRAP, | |||
| @@ -693,13 +718,13 @@ def main() -> None: | |||
| if i < n - 1: | |||
| time.sleep(2) | |||
| msg = f"已送出列印:批次 {b} x {n} 張" if count != -1 else f"已送出列印:批次 {b} x {n} 張 (連續)" | |||
| messagebox.showinfo("打袋機", msg) | |||
| set_status_message(msg, is_error=False) | |||
| except ConnectionRefusedError: | |||
| messagebox.showerror("打袋機", f"無法連線至 {ip}:{port},請確認印表機已開機且 IP 正確。") | |||
| set_status_message(f"無法連線至 {ip}:{port},請確認印表機已開機且 IP 正確。", is_error=True) | |||
| except socket.timeout: | |||
| messagebox.showerror("打袋機", f"連線逾時 ({ip}:{port}),請檢查網路與連接埠。") | |||
| set_status_message(f"連線逾時 ({ip}:{port}),請檢查網路與連接埠。", is_error=True) | |||
| except OSError as err: | |||
| messagebox.showerror("打袋機", f"列印失敗:{err}") | |||
| set_status_message(f"列印失敗:{err}", is_error=True) | |||
| elif printer_var.get() == "標簽機": | |||
| count = ask_label_count(root) | |||
| if count is not None: | |||
| @@ -707,7 +732,7 @@ def main() -> None: | |||
| msg = "已選擇連續列印標簽" | |||
| else: | |||
| msg = f"將列印 {count} 張標簽" | |||
| messagebox.showinfo("標簽機", msg) | |||
| set_status_message(msg, is_error=False) | |||
| on_job_order_click(j, b) | |||
| for w in (row, left, batch_lbl, code_lbl, name_lbl): | |||
| @@ -730,6 +755,13 @@ def main() -> None: | |||
| if after_id_ref[0] is not None: | |||
| root.after_cancel(after_id_ref[0]) | |||
| after_id_ref[0] = None | |||
| # Auto-reset date to today if user hasn't manually changed it recently (for 24x7 use) | |||
| if not from_user_date_change: | |||
| elapsed = time.time() - last_manual_date_change_ref[0] | |||
| today_str = date.today().isoformat() | |||
| if elapsed > DATE_AUTO_RESET_SEC and date_var.get().strip() != today_str: | |||
| date_var.set(today_str) | |||
| from_user_date_change = True # treat as date change to reset selection/scroll | |||
| date_str = date_var.get().strip() | |||
| try: | |||
| plan_start = date.fromisoformat(date_str) | |||