Job 1788150466600

Command: python -c "import base64;exec(base64.b64decode('ZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCnA9UGF0aChyJ0M6XFVzZXJzXGFzaFxEb2N1bWVudHNcU2hvcGVlQXBpXGFwcFxtYWluLnB5JykKbGluZXM9cC5yZWFkX3RleHQoZW5jb2Rpbmc9J3V0Zi04JyxlcnJvcnM9J3JlcGxhY2UnKS5zcGxpdGxpbmVzKCkKaGl0cz1baSBmb3IgaSx4IGluIGVudW1lcmF0ZShsaW5lcykgaWYgJy9hcGkvb3BlcmF0aW9ucy8nIGluIHggb3IgJ3JlcXVpcmVfb3BlcmF0aW9uc19kZXZpY2UnIGluIHggb3IgJ2lzX3ByaW1hcnknIGluIHhdCnNlZW49c2V0KCkKZm9yIGkgaW4gaGl0czoKICAgIGE9bWF4KDAsaS0xNSk7IGI9bWluKGxlbihsaW5lcyksaSs1NSkKICAgIGlmIGFueShhPj14IGFuZCBiPD15IGZvciB4LHkgaW4gc2Vlbik6IGNvbnRpbnVlCiAgICBzZWVuLmFkZCgoYSxiKSkKICAgIHByaW50KGYnLS0tIHthKzF9LXtifSAtLS0nKQogICAgZm9yIGogaW4gcmFuZ2UoYSxiKTogcHJpbnQoZid7aisxfToge2xpbmVzW2pdWzo1MDBdfScpCg=='))"
Directory: projects
Status: SUCCESS
Exit code: 0

Cancel Job Rerun Command Refresh

--- 222-291 ---
222:     if x_device_token:
223:         token_hash = hashlib.sha256(x_device_token.encode("utf-8")).hexdigest()
224:         selected_shop = int(x_shop_id or settings.shop_id or 0)
225:         device = db.authenticate_scanner_device(token_hash, selected_shop)
226:         if not device:
227:             device = db.authenticate_operations_device(token_hash, selected_shop)
228:         if device:
229:             return
230:     if settings.admin_token and x_admin_token and hmac.compare_digest(x_admin_token, settings.admin_token):
231:         return
232:     if _read_session_cookie(request.cookies.get(SESSION_COOKIE)):
233:         return
234:     raise HTTPException(401, "Credential scanner tidak sah")
235: 
236: 
237: def require_operations_device(
238:     x_device_token: str = Header(default="", alias="X-Device-Token"),
239:     x_shop_id: int | None = Header(default=None, alias="X-Shop-Id"),
240: ) -> dict[str, Any]:
241:     shop_id = int(x_shop_id or 0)
242:     if not x_device_token or shop_id <= 0:
243:         raise HTTPException(401, "Credential Shopee Operations tidak lengkap")
244:     device = db.authenticate_operations_device(hashlib.sha256(x_device_token.encode("utf-8")).hexdigest(), shop_id)
245:     if not device:
246:         raise HTTPException(401, "Device Shopee Operations tidak sah")
247:     return device
248: 
249: class OrderSyncRequest(BaseModel):
250:     from_date: date
251:     to_date: date
252:     include_details: bool = True
253:     include_escrow: bool = True
254:     include_logistics_preview: bool = False
255: 
256: class ProductSyncRequest(BaseModel):
257:     statuses: list[str] = Field(default_factory=lambda: ["NORMAL","UNLIST","BANNED"])
258: 
259: 
260: class ProductCostRequest(BaseModel):
261:     item_id: int = Field(gt=0)
262:     size_ml: Literal[10, 35]
263:     unit_cost: float = Field(ge=0)
264:     effective_from: date
265:     currency: str = Field(default="MYR", min_length=3, max_length=3)
266: 
267: 
268: class UniversalCostRequest(BaseModel):
269:     cost_10ml: float = Field(ge=0)
270:     cost_35ml: float = Field(ge=0)
271:     effective_from: date
272:     currency: str = Field(default="MYR", min_length=3, max_length=3)
273: 
274: 
275: class FinanceSyncRequest(BaseModel):
276:     lookback_days: int = Field(default=45, ge=1, le=3650)
277: 
278: 
279: class LlmContextRequest(BaseModel):
280:     query: str = Field(min_length=1, max_length=2000)
281:     product_hint: str = Field(default="", max_length=500)
282:     order_sn: str = Field(default="", max_length=80)
283:     limit: int = Field(default=5, ge=1, le=5)
284: 
285: class ShopUpsertRequest(BaseModel):
286:     shop_id: int
287:     name: str = ""
288: 
289: 
290: class PerfumeRegistryRequest(BaseModel):
291:     id: int | None = None
--- 919-988 ---
919: 
920: def _operations_apk_storage_path(version_name: str) -> Path:
921:     safe = re.sub(r"[^A-Za-z0-9._-]", "_", version_name)
922:     return settings.data_dir / "downloads" / f"ShopeeOperations-{safe}.apk"
923: 
924: 
925: def _load_operations_version() -> dict[str, Any]:
926:     if not _OPERATIONS_VERSION_FILE.is_file():
927:         return {"version_code": 0, "version_name": "", "apk_size": 0, "apk_sha256": "", "changelog": "", "updated_at": ""}
928:     try:
929:         return json.loads(_OPERATIONS_VERSION_FILE.read_text(encoding="utf-8"))
930:     except Exception:
931:         return {"version_code": 0, "version_name": "", "apk_size": 0, "apk_sha256": "", "changelog": "", "updated_at": ""}
932: 
933: 
934: @app.post("/api/operations/app-upload", dependencies=[Depends(require_admin)])
935: async def api_operations_app_upload(
936:     apk: UploadFile = File(...), version_name: str = Form(...), version_code: int = Form(...), changelog: str = Form(default=""),
937: ):
938:     content = await apk.read()
939:     if not content:
940:         raise HTTPException(422, "APK kosong")
941:     path = _operations_apk_storage_path(version_name)
942:     path.parent.mkdir(parents=True, exist_ok=True)
943:     path.write_bytes(content)
944:     version = {
945:         "version_code": int(version_code), "version_name": version_name, "apk_size": len(content),
946:         "apk_sha256": hashlib.sha256(content).hexdigest(), "changelog": changelog,
947:         "updated_at": datetime.now(safe_zoneinfo(settings.timezone)).isoformat(),
948:     }
949:     _OPERATIONS_VERSION_FILE.write_text(json.dumps(version, indent=2, ensure_ascii=False), encoding="utf-8")
950:     for old in settings.data_dir.glob("downloads/ShopeeOperations-*.apk"):
951:         if old.resolve() != path.resolve():
952:             old.unlink(missing_ok=True)
953:     return {"ok": True, **version}
954: 
955: 
956: @app.get("/api/operations/app-version")
957: def api_operations_app_version():
958:     return {"ok": True, "app": _load_operations_version()}
959: 
960: 
961: @app.get("/downloads/ShopeeOperations.apk")
962: def operations_apk_download_latest():
963:     version = _load_operations_version()
964:     name = str(version.get("version_name") or "")
965:     path = _operations_apk_storage_path(name)
966:     if not name or not path.is_file():
967:         raise HTTPException(404, "APK Shopee Operations belum dimuat naik")
968:     return RedirectResponse(f"/downloads/ShopeeOperations-{name}.apk", headers={"Cache-Control": "no-store"})
969: 
970: 
971: @app.get("/downloads/ShopeeOperations-{version_name}.apk")
972: def operations_apk_download_versioned(version_name: str):
973:     version = _load_operations_version()
974:     if str(version.get("version_name") or "") != version_name:
975:         raise HTTPException(404, "Versi APK Shopee Operations tidak dijumpai")
976:     path = _operations_apk_storage_path(version_name)
977:     if not path.is_file():
978:         raise HTTPException(404, "Fail APK Shopee Operations tidak dijumpai")
979:     return FileResponse(path, media_type="application/vnd.android.package-archive", filename=f"ShopeeOperations-{version_name}.apk",
980:                         headers={"Cache-Control": "public, max-age=86400", "X-App-Version-Code": str(version.get("version_code", 0))})
981: 
982: 
983: @app.post("/api/scanner/app-upload", dependencies=[Depends(require_admin)])
984: async def api_scanner_app_upload(
985:     apk: UploadFile = File(...),
986:     version_name: str = Form(...),
987:     version_code: int = Form(...),
988:     changelog: str = Form(default=""),
--- 941-1010 ---
941:     path = _operations_apk_storage_path(version_name)
942:     path.parent.mkdir(parents=True, exist_ok=True)
943:     path.write_bytes(content)
944:     version = {
945:         "version_code": int(version_code), "version_name": version_name, "apk_size": len(content),
946:         "apk_sha256": hashlib.sha256(content).hexdigest(), "changelog": changelog,
947:         "updated_at": datetime.now(safe_zoneinfo(settings.timezone)).isoformat(),
948:     }
949:     _OPERATIONS_VERSION_FILE.write_text(json.dumps(version, indent=2, ensure_ascii=False), encoding="utf-8")
950:     for old in settings.data_dir.glob("downloads/ShopeeOperations-*.apk"):
951:         if old.resolve() != path.resolve():
952:             old.unlink(missing_ok=True)
953:     return {"ok": True, **version}
954: 
955: 
956: @app.get("/api/operations/app-version")
957: def api_operations_app_version():
958:     return {"ok": True, "app": _load_operations_version()}
959: 
960: 
961: @app.get("/downloads/ShopeeOperations.apk")
962: def operations_apk_download_latest():
963:     version = _load_operations_version()
964:     name = str(version.get("version_name") or "")
965:     path = _operations_apk_storage_path(name)
966:     if not name or not path.is_file():
967:         raise HTTPException(404, "APK Shopee Operations belum dimuat naik")
968:     return RedirectResponse(f"/downloads/ShopeeOperations-{name}.apk", headers={"Cache-Control": "no-store"})
969: 
970: 
971: @app.get("/downloads/ShopeeOperations-{version_name}.apk")
972: def operations_apk_download_versioned(version_name: str):
973:     version = _load_operations_version()
974:     if str(version.get("version_name") or "") != version_name:
975:         raise HTTPException(404, "Versi APK Shopee Operations tidak dijumpai")
976:     path = _operations_apk_storage_path(version_name)
977:     if not path.is_file():
978:         raise HTTPException(404, "Fail APK Shopee Operations tidak dijumpai")
979:     return FileResponse(path, media_type="application/vnd.android.package-archive", filename=f"ShopeeOperations-{version_name}.apk",
980:                         headers={"Cache-Control": "public, max-age=86400", "X-App-Version-Code": str(version.get("version_code", 0))})
981: 
982: 
983: @app.post("/api/scanner/app-upload", dependencies=[Depends(require_admin)])
984: async def api_scanner_app_upload(
985:     apk: UploadFile = File(...),
986:     version_name: str = Form(...),
987:     version_code: int = Form(...),
988:     changelog: str = Form(default=""),
989:     force_update: bool = Form(default=False),
990: ):
991:     content = await apk.read()
992:     if not content:
993:         raise HTTPException(422, "APK kosong")
994:     apk_sha256 = hashlib.sha256(content).hexdigest()
995:     apk_path = _apk_storage_path(version_name)
996:     apk_path.parent.mkdir(parents=True, exist_ok=True)
997:     apk_path.write_bytes(content)
998:     _tz = safe_zoneinfo(settings.timezone)
999:     version = {
1000:         "version_code": version_code,
1001:         "version_name": version_name,
1002:         "apk_size": len(content),
1003:         "apk_sha256": apk_sha256,
1004:         "changelog": changelog,
1005:         "force_update": force_update,
1006:         "updated_at": datetime.now(_tz).isoformat(),
1007:     }
1008:     _save_app_version(version)
1009:     old_apks = sorted(settings.data_dir.glob("downloads/LabelScanStock-*.apk"))
1010:     for old in old_apks:
--- 3158-3227 ---
3158:         shop_id=shop_id,
3159:         device_id=req.device_id,
3160:         token_sha256=hashlib.sha256(device_token.encode("utf-8")).hexdigest(),
3161:         device_name=req.device_name,
3162:         app_version=req.app_version,
3163:     )
3164:     db.log_audit(
3165:         actor=f"scanner:{req.device_id}",
3166:         action="scanner_device_enrolled",
3167:         target=req.device_id,
3168:         details={"shop_id": shop_id, "app_version": req.app_version},
3169:     )
3170:     return {"ok": True, "shop_id": shop_id, "device_id": req.device_id, "device_token": device_token}
3171: 
3172: 
3173: @app.post("/api/operations/enroll", dependencies=[Depends(require_admin)])
3174: def api_operations_enroll(req: OperationsEnrollmentRequest):
3175:     valid_shop_ids = {int(item.get("shop_id") or 0) for item in _shop_records()}
3176:     if req.shop_id not in valid_shop_ids:
3177:         raise HTTPException(404, "Shop belum didaftarkan pada server")
3178:     device_token = secrets.token_urlsafe(32)
3179:     device = db.enroll_operations_device(
3180:         req.shop_id,
3181:         req.device_id,
3182:         hashlib.sha256(device_token.encode("utf-8")).hexdigest(),
3183:         req.device_name,
3184:         req.app_version,
3185:     )
3186:     db.log_audit(
3187:         actor=f"operations:{req.device_id}", action="operations_device_enrolled",
3188:         target=req.device_id, details={"shop_id": req.shop_id, "app_version": req.app_version},
3189:     )
3190:     return {"ok": True, "shop_id": req.shop_id, "device": device, "device_token": device_token}
3191: 
3192: 
3193: @app.get("/api/operations/status")
3194: def api_operations_status(device: dict[str, Any] = Depends(require_operations_device)):
3195:     shop_id = int(device["shop_id"])
3196:     alert_settings = db.operations_alert_settings(shop_id)
3197:     return {
3198:         "ok": True,
3199:         "shop_id": shop_id,
3200:         "device_id": device["device_id"],
3201:         "is_primary": str(alert_settings.get("primary_device_id") or "") == str(device["device_id"]),
3202:         "alert_settings": alert_settings,
3203:     }
3204: 
3205: 
3206: @app.put("/api/operations/primary-device")
3207: def api_operations_primary_device(
3208:     req: OperationsPrimaryRequest,
3209:     device: dict[str, Any] = Depends(require_operations_device),
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
--- 3178-3247 ---
3178:     device_token = secrets.token_urlsafe(32)
3179:     device = db.enroll_operations_device(
3180:         req.shop_id,
3181:         req.device_id,
3182:         hashlib.sha256(device_token.encode("utf-8")).hexdigest(),
3183:         req.device_name,
3184:         req.app_version,
3185:     )
3186:     db.log_audit(
3187:         actor=f"operations:{req.device_id}", action="operations_device_enrolled",
3188:         target=req.device_id, details={"shop_id": req.shop_id, "app_version": req.app_version},
3189:     )
3190:     return {"ok": True, "shop_id": req.shop_id, "device": device, "device_token": device_token}
3191: 
3192: 
3193: @app.get("/api/operations/status")
3194: def api_operations_status(device: dict[str, Any] = Depends(require_operations_device)):
3195:     shop_id = int(device["shop_id"])
3196:     alert_settings = db.operations_alert_settings(shop_id)
3197:     return {
3198:         "ok": True,
3199:         "shop_id": shop_id,
3200:         "device_id": device["device_id"],
3201:         "is_primary": str(alert_settings.get("primary_device_id") or "") == str(device["device_id"]),
3202:         "alert_settings": alert_settings,
3203:     }
3204: 
3205: 
3206: @app.put("/api/operations/primary-device")
3207: def api_operations_primary_device(
3208:     req: OperationsPrimaryRequest,
3209:     device: dict[str, Any] = Depends(require_operations_device),
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
--- 3179-3248 ---
3179:     device = db.enroll_operations_device(
3180:         req.shop_id,
3181:         req.device_id,
3182:         hashlib.sha256(device_token.encode("utf-8")).hexdigest(),
3183:         req.device_name,
3184:         req.app_version,
3185:     )
3186:     db.log_audit(
3187:         actor=f"operations:{req.device_id}", action="operations_device_enrolled",
3188:         target=req.device_id, details={"shop_id": req.shop_id, "app_version": req.app_version},
3189:     )
3190:     return {"ok": True, "shop_id": req.shop_id, "device": device, "device_token": device_token}
3191: 
3192: 
3193: @app.get("/api/operations/status")
3194: def api_operations_status(device: dict[str, Any] = Depends(require_operations_device)):
3195:     shop_id = int(device["shop_id"])
3196:     alert_settings = db.operations_alert_settings(shop_id)
3197:     return {
3198:         "ok": True,
3199:         "shop_id": shop_id,
3200:         "device_id": device["device_id"],
3201:         "is_primary": str(alert_settings.get("primary_device_id") or "") == str(device["device_id"]),
3202:         "alert_settings": alert_settings,
3203:     }
3204: 
3205: 
3206: @app.put("/api/operations/primary-device")
3207: def api_operations_primary_device(
3208:     req: OperationsPrimaryRequest,
3209:     device: dict[str, Any] = Depends(require_operations_device),
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
--- 3186-3255 ---
3186:     db.log_audit(
3187:         actor=f"operations:{req.device_id}", action="operations_device_enrolled",
3188:         target=req.device_id, details={"shop_id": req.shop_id, "app_version": req.app_version},
3189:     )
3190:     return {"ok": True, "shop_id": req.shop_id, "device": device, "device_token": device_token}
3191: 
3192: 
3193: @app.get("/api/operations/status")
3194: def api_operations_status(device: dict[str, Any] = Depends(require_operations_device)):
3195:     shop_id = int(device["shop_id"])
3196:     alert_settings = db.operations_alert_settings(shop_id)
3197:     return {
3198:         "ok": True,
3199:         "shop_id": shop_id,
3200:         "device_id": device["device_id"],
3201:         "is_primary": str(alert_settings.get("primary_device_id") or "") == str(device["device_id"]),
3202:         "alert_settings": alert_settings,
3203:     }
3204: 
3205: 
3206: @app.put("/api/operations/primary-device")
3207: def api_operations_primary_device(
3208:     req: OperationsPrimaryRequest,
3209:     device: dict[str, Any] = Depends(require_operations_device),
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
--- 3191-3260 ---
3191: 
3192: 
3193: @app.get("/api/operations/status")
3194: def api_operations_status(device: dict[str, Any] = Depends(require_operations_device)):
3195:     shop_id = int(device["shop_id"])
3196:     alert_settings = db.operations_alert_settings(shop_id)
3197:     return {
3198:         "ok": True,
3199:         "shop_id": shop_id,
3200:         "device_id": device["device_id"],
3201:         "is_primary": str(alert_settings.get("primary_device_id") or "") == str(device["device_id"]),
3202:         "alert_settings": alert_settings,
3203:     }
3204: 
3205: 
3206: @app.put("/api/operations/primary-device")
3207: def api_operations_primary_device(
3208:     req: OperationsPrimaryRequest,
3209:     device: dict[str, Any] = Depends(require_operations_device),
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
--- 3194-3263 ---
3194: def api_operations_status(device: dict[str, Any] = Depends(require_operations_device)):
3195:     shop_id = int(device["shop_id"])
3196:     alert_settings = db.operations_alert_settings(shop_id)
3197:     return {
3198:         "ok": True,
3199:         "shop_id": shop_id,
3200:         "device_id": device["device_id"],
3201:         "is_primary": str(alert_settings.get("primary_device_id") or "") == str(device["device_id"]),
3202:         "alert_settings": alert_settings,
3203:     }
3204: 
3205: 
3206: @app.put("/api/operations/primary-device")
3207: def api_operations_primary_device(
3208:     req: OperationsPrimaryRequest,
3209:     device: dict[str, Any] = Depends(require_operations_device),
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
--- 3209-3278 ---
3209:     device: dict[str, Any] = Depends(require_operations_device),
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
3264:         target=req.order_sn, details={"shop_id": shop_id},
3265:     )
3266:     return {"ok": True, "acknowledgement": acknowledgement}
3267: 
3268: 
3269: def _scanner_catalog_revision(shop_id: int) -> str:
3270:     """Revision for registry/inventory entities copied to the phone."""
3271:     import hashlib as _hl
3272:     h = _hl.sha256()
3273:     with db.connect() as conn:
3274:         registry = conn.execute(
3275:             "SELECT COALESCE(MAX(updated_at),'0'), COUNT(*) FROM perfume_name_registry WHERE shop_id IN (0, ?)",
3276:             (shop_id,),
3277:         ).fetchone()
3278:         h.update(f"registry:{registry[0]}:{registry[1]}".encode())
--- 3210-3279 ---
3210: ):
3211:     if not req.confirmed:
3212:         raise HTTPException(409, "Confirmation diperlukan untuk menukar telefon utama")
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
3264:         target=req.order_sn, details={"shop_id": shop_id},
3265:     )
3266:     return {"ok": True, "acknowledgement": acknowledgement}
3267: 
3268: 
3269: def _scanner_catalog_revision(shop_id: int) -> str:
3270:     """Revision for registry/inventory entities copied to the phone."""
3271:     import hashlib as _hl
3272:     h = _hl.sha256()
3273:     with db.connect() as conn:
3274:         registry = conn.execute(
3275:             "SELECT COALESCE(MAX(updated_at),'0'), COUNT(*) FROM perfume_name_registry WHERE shop_id IN (0, ?)",
3276:             (shop_id,),
3277:         ).fetchone()
3278:         h.update(f"registry:{registry[0]}:{registry[1]}".encode())
3279:         canonical = conn.execute(
--- 3213-3282 ---
3213:     try:
3214:         alert_settings = db.set_operations_primary_device(int(device["shop_id"]), str(device["device_id"]))
3215:     except ValueError as exc:
3216:         raise HTTPException(422, str(exc)) from exc
3217:     db.log_audit(
3218:         actor=f"operations:{device['device_id']}", action="instant_alert_primary_changed",
3219:         target=str(device["shop_id"]), details={"primary_device_id": device["device_id"]},
3220:     )
3221:     return {"ok": True, "alert_settings": alert_settings}
3222: 
3223: 
3224: @app.get("/api/operations/instant-delivery/feed")
3225: def api_operations_instant_delivery_feed(device: dict[str, Any] = Depends(require_operations_device)):
3226:     shop_id = int(device["shop_id"])
3227:     alert_settings = db.operations_alert_settings(shop_id)
3228:     is_primary = str(alert_settings.get("primary_device_id") or "") == str(device["device_id"])
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
3264:         target=req.order_sn, details={"shop_id": shop_id},
3265:     )
3266:     return {"ok": True, "acknowledgement": acknowledgement}
3267: 
3268: 
3269: def _scanner_catalog_revision(shop_id: int) -> str:
3270:     """Revision for registry/inventory entities copied to the phone."""
3271:     import hashlib as _hl
3272:     h = _hl.sha256()
3273:     with db.connect() as conn:
3274:         registry = conn.execute(
3275:             "SELECT COALESCE(MAX(updated_at),'0'), COUNT(*) FROM perfume_name_registry WHERE shop_id IN (0, ?)",
3276:             (shop_id,),
3277:         ).fetchone()
3278:         h.update(f"registry:{registry[0]}:{registry[1]}".encode())
3279:         canonical = conn.execute(
3280:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scents WHERE status='ACTIVE'"
3281:         ).fetchone()
3282:         canonical_alias = conn.execute(
--- 3229-3298 ---
3229:     overview = db.get_ready_to_ship_overview()
3230:     orders = overview.get("instant_delivery_orders") or []
3231:     acknowledgements = db.instant_delivery_acknowledgements(shop_id, [str(row.get("order_sn") or "") for row in orders])
3232:     payload_orders = []
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
3264:         target=req.order_sn, details={"shop_id": shop_id},
3265:     )
3266:     return {"ok": True, "acknowledgement": acknowledgement}
3267: 
3268: 
3269: def _scanner_catalog_revision(shop_id: int) -> str:
3270:     """Revision for registry/inventory entities copied to the phone."""
3271:     import hashlib as _hl
3272:     h = _hl.sha256()
3273:     with db.connect() as conn:
3274:         registry = conn.execute(
3275:             "SELECT COALESCE(MAX(updated_at),'0'), COUNT(*) FROM perfume_name_registry WHERE shop_id IN (0, ?)",
3276:             (shop_id,),
3277:         ).fetchone()
3278:         h.update(f"registry:{registry[0]}:{registry[1]}".encode())
3279:         canonical = conn.execute(
3280:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scents WHERE status='ACTIVE'"
3281:         ).fetchone()
3282:         canonical_alias = conn.execute(
3283:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scent_aliases"
3284:         ).fetchone()
3285:         h.update(
3286:             f"canonical:{canonical[0]}:{canonical[1]}:{canonical_alias[0]}:{canonical_alias[1]}".encode()
3287:         )
3288:         for table, timestamp_column in (
3289:             ("canonical_stickers", "updated_at"),
3290:             ("products", "synced_at"),
3291:             ("product_variations", "synced_at"),
3292:             ("shop_product_mappings", "updated_at"),
3293:         ):
3294:             row = conn.execute(
3295:                 f"SELECT COALESCE(MAX({timestamp_column}),'0'),COUNT(*) FROM {table} WHERE shop_id=?",
3296:                 (shop_id,),
3297:             ).fetchone()
3298:             h.update(f"{table}:{row[0]}:{row[1]}".encode())
--- 3233-3302 ---
3233:     for row in orders:
3234:         item = dict(row)
3235:         acknowledgement = acknowledgements.get(str(item.get("order_sn") or ""))
3236:         item["acknowledged"] = bool(acknowledgement)
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
3264:         target=req.order_sn, details={"shop_id": shop_id},
3265:     )
3266:     return {"ok": True, "acknowledgement": acknowledgement}
3267: 
3268: 
3269: def _scanner_catalog_revision(shop_id: int) -> str:
3270:     """Revision for registry/inventory entities copied to the phone."""
3271:     import hashlib as _hl
3272:     h = _hl.sha256()
3273:     with db.connect() as conn:
3274:         registry = conn.execute(
3275:             "SELECT COALESCE(MAX(updated_at),'0'), COUNT(*) FROM perfume_name_registry WHERE shop_id IN (0, ?)",
3276:             (shop_id,),
3277:         ).fetchone()
3278:         h.update(f"registry:{registry[0]}:{registry[1]}".encode())
3279:         canonical = conn.execute(
3280:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scents WHERE status='ACTIVE'"
3281:         ).fetchone()
3282:         canonical_alias = conn.execute(
3283:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scent_aliases"
3284:         ).fetchone()
3285:         h.update(
3286:             f"canonical:{canonical[0]}:{canonical[1]}:{canonical_alias[0]}:{canonical_alias[1]}".encode()
3287:         )
3288:         for table, timestamp_column in (
3289:             ("canonical_stickers", "updated_at"),
3290:             ("products", "synced_at"),
3291:             ("product_variations", "synced_at"),
3292:             ("shop_product_mappings", "updated_at"),
3293:         ):
3294:             row = conn.execute(
3295:                 f"SELECT COALESCE(MAX({timestamp_column}),'0'),COUNT(*) FROM {table} WHERE shop_id=?",
3296:                 (shop_id,),
3297:             ).fetchone()
3298:             h.update(f"{table}:{row[0]}:{row[1]}".encode())
3299:         for table in (
3300:             "perfume_aliases",
3301:             "canonical_products",
3302:             "canonical_aliases",
--- 3237-3306 ---
3237:         item["acknowledged_at"] = acknowledgement.get("acknowledged_at") if acknowledgement else None
3238:         payload_orders.append(item)
3239:     return {
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
3264:         target=req.order_sn, details={"shop_id": shop_id},
3265:     )
3266:     return {"ok": True, "acknowledgement": acknowledgement}
3267: 
3268: 
3269: def _scanner_catalog_revision(shop_id: int) -> str:
3270:     """Revision for registry/inventory entities copied to the phone."""
3271:     import hashlib as _hl
3272:     h = _hl.sha256()
3273:     with db.connect() as conn:
3274:         registry = conn.execute(
3275:             "SELECT COALESCE(MAX(updated_at),'0'), COUNT(*) FROM perfume_name_registry WHERE shop_id IN (0, ?)",
3276:             (shop_id,),
3277:         ).fetchone()
3278:         h.update(f"registry:{registry[0]}:{registry[1]}".encode())
3279:         canonical = conn.execute(
3280:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scents WHERE status='ACTIVE'"
3281:         ).fetchone()
3282:         canonical_alias = conn.execute(
3283:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scent_aliases"
3284:         ).fetchone()
3285:         h.update(
3286:             f"canonical:{canonical[0]}:{canonical[1]}:{canonical_alias[0]}:{canonical_alias[1]}".encode()
3287:         )
3288:         for table, timestamp_column in (
3289:             ("canonical_stickers", "updated_at"),
3290:             ("products", "synced_at"),
3291:             ("product_variations", "synced_at"),
3292:             ("shop_product_mappings", "updated_at"),
3293:         ):
3294:             row = conn.execute(
3295:                 f"SELECT COALESCE(MAX({timestamp_column}),'0'),COUNT(*) FROM {table} WHERE shop_id=?",
3296:                 (shop_id,),
3297:             ).fetchone()
3298:             h.update(f"{table}:{row[0]}:{row[1]}".encode())
3299:         for table in (
3300:             "perfume_aliases",
3301:             "canonical_products",
3302:             "canonical_aliases",
3303:             "perfume_storage",
3304:         ):
3305:             row = conn.execute(
3306:                 f"SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM {table}"
--- 3240-3309 ---
3240:         "ok": True,
3241:         "server_time": datetime.now(timezone.utc).isoformat(),
3242:         "shop_id": shop_id,
3243:         "device_id": device["device_id"],
3244:         "is_primary": is_primary,
3245:         "poll_after_seconds": int(alert_settings.get("poll_seconds") or 30),
3246:         "repeat_after_seconds": int(alert_settings.get("repeat_seconds") or 300),
3247:         "count": len(payload_orders),
3248:         "orders": payload_orders if is_primary else [],
3249:     }
3250: 
3251: 
3252: @app.post("/api/operations/instant-delivery/ack")
3253: def api_operations_instant_delivery_ack(
3254:     req: OperationsAckRequest,
3255:     device: dict[str, Any] = Depends(require_operations_device),
3256: ):
3257:     shop_id = int(device["shop_id"])
3258:     alert_settings = db.operations_alert_settings(shop_id)
3259:     if str(alert_settings.get("primary_device_id") or "") != str(device["device_id"]):
3260:         raise HTTPException(403, "Hanya telefon utama boleh mengesahkan Instant Delivery")
3261:     acknowledgement = db.acknowledge_instant_delivery(shop_id, req.order_sn, str(device["device_id"]))
3262:     db.log_audit(
3263:         actor=f"operations:{device['device_id']}", action="instant_delivery_ready_acknowledged",
3264:         target=req.order_sn, details={"shop_id": shop_id},
3265:     )
3266:     return {"ok": True, "acknowledgement": acknowledgement}
3267: 
3268: 
3269: def _scanner_catalog_revision(shop_id: int) -> str:
3270:     """Revision for registry/inventory entities copied to the phone."""
3271:     import hashlib as _hl
3272:     h = _hl.sha256()
3273:     with db.connect() as conn:
3274:         registry = conn.execute(
3275:             "SELECT COALESCE(MAX(updated_at),'0'), COUNT(*) FROM perfume_name_registry WHERE shop_id IN (0, ?)",
3276:             (shop_id,),
3277:         ).fetchone()
3278:         h.update(f"registry:{registry[0]}:{registry[1]}".encode())
3279:         canonical = conn.execute(
3280:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scents WHERE status='ACTIVE'"
3281:         ).fetchone()
3282:         canonical_alias = conn.execute(
3283:             "SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM canonical_scent_aliases"
3284:         ).fetchone()
3285:         h.update(
3286:             f"canonical:{canonical[0]}:{canonical[1]}:{canonical_alias[0]}:{canonical_alias[1]}".encode()
3287:         )
3288:         for table, timestamp_column in (
3289:             ("canonical_stickers", "updated_at"),
3290:             ("products", "synced_at"),
3291:             ("product_variations", "synced_at"),
3292:             ("shop_product_mappings", "updated_at"),
3293:         ):
3294:             row = conn.execute(
3295:                 f"SELECT COALESCE(MAX({timestamp_column}),'0'),COUNT(*) FROM {table} WHERE shop_id=?",
3296:                 (shop_id,),
3297:             ).fetchone()
3298:             h.update(f"{table}:{row[0]}:{row[1]}".encode())
3299:         for table in (
3300:             "perfume_aliases",
3301:             "canonical_products",
3302:             "canonical_aliases",
3303:             "perfume_storage",
3304:         ):
3305:             row = conn.execute(
3306:                 f"SELECT COALESCE(MAX(updated_at),'0'),COUNT(*) FROM {table}"
3307:             ).fetchone()
3308:             h.update(f"{table}:{row[0]}:{row[1]}".encode())
3309:         for table in (