diff --git a/make_post_sell/scripts/alembic/versions/73c5cb973915_one_off_mark_stuck_accepted_offers_paid_.py b/make_post_sell/scripts/alembic/versions/73c5cb973915_one_off_mark_stuck_accepted_offers_paid_.py new file mode 100644 index 0000000..2c78d61 --- /dev/null +++ b/make_post_sell/scripts/alembic/versions/73c5cb973915_one_off_mark_stuck_accepted_offers_paid_.py @@ -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