| @@ -111,7 +111,8 @@ def try_printer_connection(printer_name: str, sett: dict) -> bool: | |||||
| FONT_SIZE = 16 | FONT_SIZE = 16 | ||||
| FONT_SIZE_BUTTONS = 15 | FONT_SIZE_BUTTONS = 15 | ||||
| FONT_SIZE_QTY = 12 # smaller for 數量 under batch no. | 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 | 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 | # 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) | 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_RETRY_MS = 30 * 1000 # 30 seconds when printer failed | ||||
| PRINTER_SOCKET_TIMEOUT = 3 | PRINTER_SOCKET_TIMEOUT = 3 | ||||
| DATAFLEX_SEND_TIMEOUT = 10 # seconds when sending ZPL to DataFlex | 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: | def _zpl_escape(s: str) -> str: | ||||
| @@ -180,9 +182,9 @@ def generate_zpl_dataflex( | |||||
| ^PW700 | ^PW700 | ||||
| ^LL500 | ^LL500 | ||||
| ^PO N | ^PO N | ||||
| ^FO10,188 | |||||
| ^FO10,20 | |||||
| ^BQR,4,7^FDQA,{batch_no}^FS | ^BQR,4,7^FDQA,{batch_no}^FS | ||||
| ^FO170,188 | |||||
| ^FO170,20 | |||||
| ^A@R,72,72,{font_regular}^FD{desc}^FS | ^A@R,72,72,{font_regular}^FD{desc}^FS | ||||
| ^FO0,260 | ^FO0,260 | ||||
| ^A@R,72,72,{font_regular}^FD{batch_esc}^FS | ^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: | 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: | 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_frame.configure(bg=BG_STATUS_ERROR) | ||||
| status_lbl.configure(text="連接不到服務器", bg=BG_STATUS_ERROR, fg=FG_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: left [前一天] [date] [後一天] | right [printer dropdown] | ||||
| top = tk.Frame(root, padx=12, pady=12, bg=BG_TOP) | top = tk.Frame(root, padx=12, pady=12, bg=BG_TOP) | ||||
| top.pack(fill=tk.X) | top.pack(fill=tk.X) | ||||
| @@ -387,23 +399,31 @@ def main() -> None: | |||||
| date_var = tk.StringVar(value=date.today().isoformat()) | date_var = tk.StringVar(value=date.today().isoformat()) | ||||
| printer_options = ["打袋機 DataFlex", "標簽機", "激光機"] | printer_options = ["打袋機 DataFlex", "標簽機", "激光機"] | ||||
| printer_var = tk.StringVar(value=printer_options[0]) | 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: | def go_prev_day() -> None: | ||||
| try: | try: | ||||
| d = date.fromisoformat(date_var.get().strip()) | d = date.fromisoformat(date_var.get().strip()) | ||||
| date_var.set((d - timedelta(days=1)).isoformat()) | date_var.set((d - timedelta(days=1)).isoformat()) | ||||
| mark_manual_date_change() | |||||
| load_job_orders(from_user_date_change=True) | load_job_orders(from_user_date_change=True) | ||||
| except ValueError: | except ValueError: | ||||
| date_var.set(date.today().isoformat()) | date_var.set(date.today().isoformat()) | ||||
| mark_manual_date_change() | |||||
| load_job_orders(from_user_date_change=True) | load_job_orders(from_user_date_change=True) | ||||
| def go_next_day() -> None: | def go_next_day() -> None: | ||||
| try: | try: | ||||
| d = date.fromisoformat(date_var.get().strip()) | d = date.fromisoformat(date_var.get().strip()) | ||||
| date_var.set((d + timedelta(days=1)).isoformat()) | date_var.set((d + timedelta(days=1)).isoformat()) | ||||
| mark_manual_date_change() | |||||
| load_job_orders(from_user_date_change=True) | load_job_orders(from_user_date_change=True) | ||||
| except ValueError: | except ValueError: | ||||
| date_var.set(date.today().isoformat()) | date_var.set(date.today().isoformat()) | ||||
| mark_manual_date_change() | |||||
| load_job_orders(from_user_date_change=True) | load_job_orders(from_user_date_change=True) | ||||
| # 前一天 (previous day) with left arrow icon | # 前一天 (previous day) with left arrow icon | ||||
| @@ -420,6 +440,11 @@ def main() -> None: | |||||
| ) | ) | ||||
| date_entry.pack(side=tk.LEFT, padx=(0, 8), ipady=4) | 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 | # 後一天 (next day) with right arrow icon | ||||
| btn_next = ttk.Button(top, text="後一天 ▶", command=go_next_day) | btn_next = ttk.Button(top, text="後一天 ▶", command=go_next_day) | ||||
| btn_next.pack(side=tk.LEFT, padx=(0, 8)) | btn_next.pack(side=tk.LEFT, padx=(0, 8)) | ||||
| @@ -643,7 +668,7 @@ def main() -> None: | |||||
| code_lbl = tk.Label( | code_lbl = tk.Label( | ||||
| row, | row, | ||||
| text=item_code, | text=item_code, | ||||
| font=get_font(FONT_SIZE_ITEM), | |||||
| font=get_font(FONT_SIZE_ITEM_CODE), | |||||
| bg=BG_ROW, | bg=BG_ROW, | ||||
| fg="black", | fg="black", | ||||
| wraplength=ITEM_CODE_WRAP, | wraplength=ITEM_CODE_WRAP, | ||||
| @@ -652,11 +677,11 @@ def main() -> None: | |||||
| ) | ) | ||||
| code_lbl.pack(side=tk.LEFT, anchor=tk.NW, padx=(12, 8)) | 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( | name_lbl = tk.Label( | ||||
| row, | row, | ||||
| text=item_name or "—", | text=item_name or "—", | ||||
| font=get_font(FONT_SIZE_ITEM), | |||||
| font=get_font(FONT_SIZE_ITEM_NAME), | |||||
| bg=BG_ROW, | bg=BG_ROW, | ||||
| fg="black", | fg="black", | ||||
| wraplength=ITEM_NAME_WRAP, | wraplength=ITEM_NAME_WRAP, | ||||
| @@ -693,13 +718,13 @@ def main() -> None: | |||||
| if i < n - 1: | if i < n - 1: | ||||
| time.sleep(2) | time.sleep(2) | ||||
| msg = f"已送出列印:批次 {b} x {n} 張" if count != -1 else f"已送出列印:批次 {b} x {n} 張 (連續)" | 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: | except ConnectionRefusedError: | ||||
| messagebox.showerror("打袋機", f"無法連線至 {ip}:{port},請確認印表機已開機且 IP 正確。") | |||||
| set_status_message(f"無法連線至 {ip}:{port},請確認印表機已開機且 IP 正確。", is_error=True) | |||||
| except socket.timeout: | except socket.timeout: | ||||
| messagebox.showerror("打袋機", f"連線逾時 ({ip}:{port}),請檢查網路與連接埠。") | |||||
| set_status_message(f"連線逾時 ({ip}:{port}),請檢查網路與連接埠。", is_error=True) | |||||
| except OSError as err: | except OSError as err: | ||||
| messagebox.showerror("打袋機", f"列印失敗:{err}") | |||||
| set_status_message(f"列印失敗:{err}", is_error=True) | |||||
| elif printer_var.get() == "標簽機": | elif printer_var.get() == "標簽機": | ||||
| count = ask_label_count(root) | count = ask_label_count(root) | ||||
| if count is not None: | if count is not None: | ||||
| @@ -707,7 +732,7 @@ def main() -> None: | |||||
| msg = "已選擇連續列印標簽" | msg = "已選擇連續列印標簽" | ||||
| else: | else: | ||||
| msg = f"將列印 {count} 張標簽" | msg = f"將列印 {count} 張標簽" | ||||
| messagebox.showinfo("標簽機", msg) | |||||
| set_status_message(msg, is_error=False) | |||||
| on_job_order_click(j, b) | on_job_order_click(j, b) | ||||
| for w in (row, left, batch_lbl, code_lbl, name_lbl): | 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: | if after_id_ref[0] is not None: | ||||
| root.after_cancel(after_id_ref[0]) | root.after_cancel(after_id_ref[0]) | ||||
| after_id_ref[0] = None | 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() | date_str = date_var.get().strip() | ||||
| try: | try: | ||||
| plan_start = date.fromisoformat(date_str) | plan_start = date.fromisoformat(date_str) | ||||