fix: route Fiesta-tenant orders through queue.workolik.com to Fiesta's own backend

jupiter and Fiesta are separate applications (separate repos, separate
binaries) that happen to share one Postgres instance. The previous fix
(661a08d) repointed queue.workolik.com's createorder from jupiter's v1 to
jupiter's v3 handler to stop items being silently dropped - that worked
because they share a database, but it was never the right target: jupiter
has no item-required guard, no atomic order-number allocation, no
stock-insufficient check, because none of that was ever written for
jupiter. Fiesta's own CreateOrderv3 already has all of it.

Added explicit tenant-based routing in worker.py: FIESTA_TENANT_IDS (seeded
with 1147/R mart and 1135/Suriya Store, the two confirmed so far) forces
createorder for those tenants to FIESTA_BASE_URL instead of jupiter's
mapping. Explicit allowlist rather than a DB heuristic, since jupiter and
Fiesta share one `tenants` table with no single column that cleanly
separates the two populations (checked: tenanttype/moduleid/categoryid/
configid are all inconsistent across the tenants that are known to belong
to each app). Non-Fiesta tenants keep going to jupiter's v3 endpoint
(661a08d), unaffected.

Verified live through the real queue.workolik.com path:
- Zero-item order (tenant 1147): worker log shows "Routing tenant 1147
  createorder to Fiesta backend", Fiesta correctly returns 400 "Order must
  contain at least one item", no phantom order created.
- Order with items (tenant 1147): itemcount=1, detail_count=1, product 7076
  stock ledger moved 25->24, then restored to 25 on cancel.

Expand FIESTA_TENANT_IDS as more Fiesta tenants are identified - there's no
programmatic way to auto-detect them from the shared tenants table.
This commit is contained in:
Suriya
2026-07-29 18:15:24 +05:30
parent 661a08d0b7
commit 58448f5faa
2 changed files with 50 additions and 7 deletions

View File

@@ -48,6 +48,20 @@ data:
# waiting on a slow-but-legitimate response, causing a duplicate forward.
ACK_WAIT_SECONDS = int(os.getenv("ACK_WAIT_SECONDS", "60"))
# Fiesta is a *separate application* (separate repo, separate binary,
# separate business line - jupiter and Fiesta only happen to share one
# Postgres instance). Its tenants' orders need Fiesta's own CreateOrderv3
# (item validation, atomic order numbers, stock-insufficient checks) -
# jupiter has no equivalent logic and was never meant to process this
# tenant population. jupiter and Fiesta share one `tenants` table with
# no single clean column to tell them apart, so this is an explicit
# allowlist rather than a heuristic. Expand FIESTA_TENANT_IDS as more
# Fiesta tenants are identified (2026-07-29).
FIESTA_BASE_URL = os.getenv("FIESTA_BASE_URL", "http://fiesta.nearle")
FIESTA_TENANT_IDS = set(
int(t) for t in os.getenv("FIESTA_TENANT_IDS", "").split(",") if t.strip()
)
# Endpoint mapping is still useful for constructing the target URL
ENDPOINT_MAPPING = {
"/live/api/v1/deliveries/createdeliveries": f"{BASE_URL}/live/api/v1/deliveries/createdeliveries",
@@ -56,13 +70,13 @@ data:
"/live/api/v2/deliveries/createdeliverylog": f"{BASE_URL}/live/api/v2/deliveries/createdeliverylog",
"/live/api/v2/partners/createbreaklog": f"{BASE_URL}/live/api/v2/partners/createbreaklog",
"/live/api/v2/partners/updatebreaklog": f"{BASE_URL}/live/api/v2/partners/updatebreaklog",
# v1 CreateOrder only ever writes the order header - it never loops
# over "items", so orderdetails/productstocks are never touched.
# CreateOrderv3 does ("for _, item := range data.Items"), and the
# loop is a no-op when Items is empty, so tenants who never send
# items (their whole order history is header-only, e.g. 916/908)
# behave identically. Tenants who do send items now get them
# persisted instead of silently dropped (2026-07-29).
# Default target for non-Fiesta tenants (jupiter). v1 CreateOrder
# only ever writes the order header - it never loops over "items".
# CreateOrderv3 does, and the loop is a no-op when Items is empty,
# so jupiter-native tenants who never send items (e.g. 916/908)
# behave identically either way. Fiesta tenants are redirected to
# their own backend below, in forward_to_external - this mapping
# is only the fallback for everyone else.
"/live/api/v1/mob/orders/createorder": f"{BASE_URL}/live/api/v3/orders/createorder",
"/live/api/v1/web/products/create": f"{BASE_URL}/live/api/v1/products/create",
"/live/api/v1/mob/customers/login": f"{BASE_URL}/live/api/v1/customers/login",
@@ -105,6 +119,27 @@ data:
return "DROP", None
data_to_forward = payload["data"]
# Fiesta-tenant orders go to Fiesta's own backend instead of jupiter
# - see FIESTA_TENANT_IDS above. tenantid may arrive nested under
# "orders" (mobile app shape) or flat (direct/API-tool shape); this
# is only used to pick the target, Fiesta's own handler does its own
# (more thorough) parsing of the actual body.
if endpoint == "/live/api/v1/mob/orders/createorder" and FIESTA_TENANT_IDS:
tenantid = None
if isinstance(data_to_forward, dict):
orders_obj = data_to_forward.get("orders")
if isinstance(orders_obj, dict) and "tenantid" in orders_obj:
tenantid = orders_obj.get("tenantid")
elif "tenantid" in data_to_forward:
tenantid = data_to_forward.get("tenantid")
try:
tenantid = int(tenantid) if tenantid is not None else None
except (TypeError, ValueError):
tenantid = None
if tenantid in FIESTA_TENANT_IDS:
external_url = f"{FIESTA_BASE_URL}{endpoint}"
print(f"?? Routing tenant {tenantid} createorder to Fiesta backend")
# Payload Normalization for logs
if endpoint == "/live/api/v2/deliveries/createdeliverylog":
if isinstance(data_to_forward, dict):