chore: one-off data fix — paid-but-stuck offers + orphan cart_offers
Two ACCEPTED offers on shop.unturf.com were paid via PayPal but never transitioned ACCEPTED → PAID because the cart-drain TypeError 502'd the complete-checkout flow before mark_paid could fire (fixed byef91469+31e06ff+6e44027). The /u/offers page still lists them as Accepted with active Pay $X buttons that would re-charge. Migration 73c5cb973915: 1. Flips the two specific offer IDs to PAID, stamps paid_timestamp, writes a synthetic OFFER_EVENT_PAY entry in the offer-event log so the History panel reflects reality. 2. Sweeps every cart_offer row whose offer is now in PAID state. These are orphans from pre-6e44027 drained carts — the symptom is an empty cart still rendering the green "Offer accepted" banner with a $X.XX total in the navbar even though the offer is settled. 3. Same sweep for cart_auction → SETTLED auctions, for parity. The flip-to-PAID is guarded — it only runs on offers still in ACCEPTED state, so a re-run is a no-op. The cart_offer / cart_auction sweep joins to terminal-state offers/auctions, so it's idempotent.
This commit is contained in:
parent
5dbbe697b6
commit
1075004419
1 changed files with 141 additions and 0 deletions
|
|
@ -0,0 +1,141 @@
|
|||
"""one-off: mark stuck-ACCEPTED offers PAID + drop orphan cart_offers
|
||||
|
||||
Revision ID: 73c5cb973915
|
||||
Revises: 882d68db47fa
|
||||
Create Date: 2026-05-15 09:18:46.600361
|
||||
|
||||
One-shot data fix for shop.unturf.com:
|
||||
|
||||
Two offers were paid via PayPal but never transitioned ACCEPTED → PAID
|
||||
because of the cart.remove_product() TypeError that 502'd the
|
||||
complete-checkout flow before mark_paid could fire:
|
||||
|
||||
ff6a2ae64fd711f1979d02dfe05770ee $6.00 countered to $6 then accepted
|
||||
dd3ef09f4e5e11f1adea02dfe05770ee $21.00 buyer accepted seller's open
|
||||
|
||||
Mark them PAID, stamp paid_timestamp = now_ms, write a synthetic
|
||||
OFFER_EVENT_PAY (event_type=6, actor=None=system, message="one-off
|
||||
data fix — paid via PayPal before mark_paid wiring landed").
|
||||
|
||||
Then sweep every cart_offer / cart_auction row whose linked offer or
|
||||
auction is in a terminal-PAID state and the row is still alive. These
|
||||
are orphans from carts that got drained but kept the association row
|
||||
alive (pre-6e44027). Symptom: empty cart still rendering the green
|
||||
"Offer accepted" banner with the agreed-price total in the navbar.
|
||||
|
||||
Safe to re-run: the offer-flip is no-op if state != ACCEPTED, the
|
||||
cart_offer sweep is by-join-to-PAID so already-deleted rows simply
|
||||
don't appear.
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import uuid as _uuid
|
||||
|
||||
|
||||
revision = "73c5cb973915"
|
||||
down_revision = "882d68db47fa"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
# OFFER_STATE enum values, from make_post_sell/models/offer.py
|
||||
OFFER_STATE_ACCEPTED = 1
|
||||
OFFER_STATE_PAID = 6
|
||||
OFFER_EVENT_PAY = 6
|
||||
|
||||
# Auction state value from make_post_sell/models/auction.py
|
||||
AUCTION_STATE_SETTLED = 4
|
||||
|
||||
|
||||
# The two offers fox flagged on shop.unturf.com /u/offers.
|
||||
STUCK_OFFER_IDS = [
|
||||
"ff6a2ae64fd711f1979d02dfe05770ee",
|
||||
"dd3ef09f4e5e11f1adea02dfe05770ee",
|
||||
]
|
||||
|
||||
|
||||
def _ms_now():
|
||||
import time
|
||||
return int(time.time() * 1000)
|
||||
|
||||
|
||||
def upgrade():
|
||||
conn = op.get_bind()
|
||||
now_ms = _ms_now()
|
||||
|
||||
# 1. Flip the two stuck offers to PAID + stamp paid_timestamp.
|
||||
for offer_id in STUCK_OFFER_IDS:
|
||||
result = conn.execute(
|
||||
sa.text(
|
||||
"SELECT state, current_amount_in_cents "
|
||||
"FROM mps_offer WHERE id = :id"
|
||||
),
|
||||
{"id": offer_id},
|
||||
).fetchone()
|
||||
if result is None:
|
||||
continue
|
||||
state, current_amount = result
|
||||
if state != OFFER_STATE_ACCEPTED:
|
||||
# Already moved past ACCEPTED — leave alone.
|
||||
continue
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"UPDATE mps_offer "
|
||||
"SET state = :paid, "
|
||||
" paid_timestamp = :ts, "
|
||||
" last_action_timestamp = :ts "
|
||||
"WHERE id = :id"
|
||||
),
|
||||
{"paid": OFFER_STATE_PAID, "ts": now_ms, "id": offer_id},
|
||||
)
|
||||
# Synthetic PAY event so the History panel reflects reality.
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"INSERT INTO mps_offer_event "
|
||||
"(id, offer_id, event_type, actor_user_id, "
|
||||
" amount_in_cents, message, created_timestamp) "
|
||||
"VALUES (:id, :offer_id, :event_type, NULL, "
|
||||
" :amount, :msg, :ts)"
|
||||
),
|
||||
{
|
||||
"id": _uuid.uuid1().hex,
|
||||
"offer_id": offer_id,
|
||||
"event_type": OFFER_EVENT_PAY,
|
||||
"amount": current_amount,
|
||||
"msg": (
|
||||
"one-off data fix — paid via PayPal before "
|
||||
"mark_paid wiring landed (commits ef91469, 6e44027)"
|
||||
),
|
||||
"ts": now_ms,
|
||||
},
|
||||
)
|
||||
|
||||
# 2. Drop every cart_offer row whose offer is now in PAID state —
|
||||
# these are orphans from carts that got drained pre-6e44027 but
|
||||
# kept the association alive. They cause empty carts to still
|
||||
# render the negotiated total in the navbar.
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"DELETE FROM mps_cart_offer "
|
||||
"WHERE offer_id IN ("
|
||||
" SELECT id FROM mps_offer WHERE state = :paid"
|
||||
")"
|
||||
),
|
||||
{"paid": OFFER_STATE_PAID},
|
||||
)
|
||||
|
||||
# 3. Same sweep for cart_auction → SETTLED auctions.
|
||||
conn.execute(
|
||||
sa.text(
|
||||
"DELETE FROM mps_cart_auction "
|
||||
"WHERE auction_id IN ("
|
||||
" SELECT id FROM mps_auction WHERE state = :settled"
|
||||
")"
|
||||
),
|
||||
{"settled": AUCTION_STATE_SETTLED},
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
# Not reversible — we don't track which cart_offers we deleted.
|
||||
pass
|
||||
Loading…
Add table
Add a link
Reference in a new issue