Commit graph

63 commits

Author SHA1 Message Date
8300f09003 fix(promotion): fix promotion not valid condition
fix promotion not valid condition
2026-09-14 16:21:26 +03:30
ea070ad293 FEATURE(promotions): wallet-transaction ledger + reversible payouts
The promotions service had no record of the wallet transfers it makes --
the Promotion row's state was the only trace. Add PromotionTransaction, a
ledger row per transfer (mirrors advertising's AdPayment / escrow's
EscrowWalletPayment): PAYOUT (promotions credit -> recipient wallet) and
ROLLBACK (advertising transit -> promotions credit).

- Promotion.promote() now drives its payout through a PromotionTransaction
  PAYOUT row (.execute() does the submit/verify dance) instead of an
  inline, unrecorded wallet call.
- rollback_promotion_payout(user, event_label): reverses a payout that
  landed in the advertising transit wallet, back to the promotions credit
  wallet, when the advertising side discards what it paid for (e.g. a
  captured billboard deleted while pending approval). The Promotion stays
  consumed -- only the money moves; payouts straight to the user's wallet
  are not reversible. Idempotent; returns reversed | deferred | nothing.
- The ROLLBACK row doubles as the async-race marker: when the request
  arrives before the Celery payout task has run, a ROLLBACK row is
  recorded and Recipient.promote() suppresses (or, if it raced, reverses)
  the payout. Replaces the separate PromotionRollback table from the first
  cut of this change.
- POST .../application/<user>/event/<event_label>/rollback/
- migration 0011 (hand-written; verified via makemigrations --dry-run + check)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 15:07:37 +03:30
8725cdf699 RENAME(promotions): WALLET_PROMOTIONS_TRANSIT -> WALLET_PROMOTIONS_CREDIT
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 18:27:16 +03:30
0fe4a3ed79 FIX(promotions): route payouts by explicit Recipient.wallet_destination, not promotion type
The first attempt at this inferred the payout destination from
PromotionTypeChoices (first_ad_view/capture/first_ad_create), a general
promotion-category field that had never held real values. That coupled two
independent facts together — a promotion's category and where its money
goes aren't the same thing, and every new promotion type would need someone
to remember to also classify it for wallet purposes.

Revert PromotionTypeChoices to empty (left as pre-existing dead scaffolding,
untouched) and add Recipient.wallet_destination instead — a real field that
states the routing decision directly. Recipient.get_wallet_category_uuid()
now branches on it: user_reward (default) resolves to
WALLET_USER_BILLBOARD_VISIT_INCOME, advertising_transit resolves to
WALLET_ADVERTISING_TRANSIT. An explicit Recipient.wallet_uuid still wins
over either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 17:12:01 +03:30
45a64f0013 FEATURE(promotions): route capture/first_ad_create payouts to advertising transit wallet
PromotionTypeChoices was an empty enum, so Recipient.promotion_type could
never hold a real value and every payout without an explicit
Recipient.wallet_uuid fell through to the same default wallet regardless of
what kind of promotion it was.

Give PromotionTypeChoices real values (first_ad_view, capture,
first_ad_create) and branch Recipient.get_wallet_category_uuid() on it:
first_ad_view (or unset) still pays into the user's cash-like reward wallet
(WALLET_USER_BILLBOARD_VISIT_INCOME); capture/first_ad_create are billboard/ad
credit, not a cash reward, so they route to the new WALLET_ADVERTISING_TRANSIT
setting (same wallet-service UUID as the advertising repo's own setting of
that name) instead. An explicit Recipient.wallet_uuid still wins over
promotion_type.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 16:57:44 +03:30
f59b8810d8 FEATURE(wallets): rename WALLET_RIAL/WALLET_REWARD, add WALLET_PROMOTIONS_TRANSIT, wire per-recipient routing
Promotion.promote() passed the same wallet UUID (WALLET_REWARD) as both
payer_wallet and payee_wallet, so every payout was routed out of and into
the same wallet type with no real company-owned pool. Add
WALLET_PROMOTIONS_TRANSIT as the dedicated payer_wallet, and rename
WALLET_RIAL/WALLET_REWARD to WALLET_RIAL_DEPOSIT/WALLET_USER_BILLBOARD_VISIT_INCOME
to match the naming used for the same wallet-service UUIDs in advertising/
settlement/ipg.

Also wires Recipient.get_wallet_category_uuid() (previously dead, and
falling back to an undefined setting) into the deposit call as payee_wallet,
so a Recipient can route its payout to its own wallet_uuid instead of every
payout hardcoding one constant.

Adds .env.example (none existed before) and docs/wallet_refactor.md
documenting the before/after and required .env changes per deploy target.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 13:27:49 +03:30
1536016f14 FIX(promotions): resolve user UUID before saving application events
ApplicationEventViewSet.perform_create() was passing the full request.user
model instance into Event.user (a plain UUIDField), which raises on every
real call. Use the same _resolve_user() -> user.uuid pattern already used by
status(), and raise UnprocessableEntity on save_event failure to match the
v1 endpoint's error-handling convention.
2026-08-18 14:26:14 +03:30
54de7fdb48 PERF(promotions): collapse allowed-user check into a single query
Fold the restricted-recipient access check into the recipients
queryset itself via Q(public OR restricted-and-allowed), joining
against AllowedUser instead of running a separate lookup query.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 18:21:23 +03:30
33aa0ed1b4 PERF(promotions): batch allowed-user lookup in get_configured_promotion_amount
Checking each recipient via Recipient.is_user_allowed() inside the
loop issued one AllowedUser query per restricted recipient (N+1).
Fetch all matching AllowedUser rows for the plan's recipients in a
single query up front instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 17:55:42 +03:30
ac37f15bea FIX(promotions): default event status promotion_amount to 0
get_event_status_for_user returned None whenever the event was
already processed, no plan matched, or the user lacked access to the
recipient. Callers now always get a concrete amount: the real payout
if accessible, 0 otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 17:33:59 +03:30
c4ff4cf4cb FEATURE(promotions): restrict recipients to allowed users
Add Recipient.access_type (restricted/public) and an AllowedUser
table linking users to the recipients they may be paid through.
Restricted recipients are now checked against AllowedUser both when
computing a promotion payout and in the event-status endpoints that
report eligibility before submission.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 16:11:33 +03:30
3cef84a346 FEATURE(promotions): adding promotion is used status for user
adding promotion is used status for user
2026-07-26 15:25:49 +03:30
Sayyid Hamid Mahdavi
a8b10b79a5 user availability test 2026-07-08 12:05:42 +03:30
Sayyid Hamid Mahdavi
1eca11e4a4 better exceptions 2026-07-07 17:24:32 +03:30
Sayyid Hamid Mahdavi
e7db91d1da get user from accounts 2026-07-07 16:42:34 +03:30
Sayyid Hamid Mahdavi
3320ff93e3 temp 2026-07-07 12:10:19 +03:30
Sayyid Hamid Mahdavi
a7964ce0ab application views 2026-06-26 14:58:36 +03:30
Sayyid Hamid Mahdavi
83582f25aa application views 2026-06-26 13:56:22 +03:30
Sayyid Hamid Mahdavi
30ccca8efc get promotion amount from another promotion 2026-06-25 11:52:04 +03:30
Sayyid Hamid Mahdavi
7038ada31e additional data in event 2026-02-24 16:19:08 +03:30
Sayyid Hamid Mahdavi
8cdcd7b1c2 basic serial promotion 2026-02-24 13:27:33 +03:30
Sayyid Hamid Mahdavi
2de2f5adc3 access without scope 2026-02-22 16:24:49 +03:30
Sayyid Hamid Mahdavi
4830602d50 get plan details 2026-02-22 14:03:52 +03:30
Sayyid Hamid Mahdavi
61f3c8a920 get plan details 2026-02-22 13:59:48 +03:30
Sayyid Hamid Mahdavi
87fdae51c5 better query 2025-12-13 13:19:53 +03:30
Sayyid Hamid Mahdavi
74bd3e4ed9 percentage promotion 2025-12-06 12:29:49 +03:30
Sayyid Hamid Mahdavi
be838d1d19 push 2025-12-03 13:49:39 +03:30
Sayyid Hamid Mahdavi
308faff858 some bugfix 2025-12-03 12:08:16 +03:30
Sayyid Hamid Mahdavi
7eff9739fd some cleanup 2025-12-03 10:52:57 +03:30
Sayyid Hamid Mahdavi
89768fa02e index for jsonfield 2025-11-15 14:50:18 +03:30
Sayyid Hamid Mahdavi
faab09595c create promotion outside of atomic 2025-11-13 10:23:15 +03:30
Sayyid Hamid Mahdavi
2a428fbb5d bugfix 2025-11-12 20:04:27 +03:30
Sayyid Hamid Mahdavi
6570be7b20 bugfix 2025-11-12 16:01:28 +03:30
Sayyid Hamid Mahdavi
7982878a1d bugfix 2025-11-12 15:34:38 +03:30
Sayyid Hamid Mahdavi
419fe24c57 new wallet system 2025-11-11 18:57:22 +03:30
Sayyid Hamid Mahdavi
68cea34add promotion get promoted 2025-11-08 20:16:33 +03:30
Sayyid Hamid Mahdavi
2c7d7808b3 promotion get promoted 2025-11-08 20:16:26 +03:30
Sayyid Hamid Mahdavi
ee1d1e6e66 promotion get promoted 2025-11-08 19:57:43 +03:30
Sayyid Hamid Mahdavi
2a6971f106 3 type of promotion test pass 2025-11-08 15:34:22 +03:30
Sayyid Hamid Mahdavi
aa0011d615 3 type of promotion test pass 2025-11-08 13:49:56 +03:30
Sayyid Hamid Mahdavi
450aadd6da first test pass 2025-11-06 14:11:00 +03:30
Sayyid Hamid Mahdavi
c6ad3cb82d delme 2025-11-02 18:08:47 +03:30
Sayyid Hamid Mahdavi
61cb8aaf5a delme 2025-10-30 15:55:58 +03:30
Sayyid Hamid Mahdavi
ca904081c3 bugfix 2025-10-19 12:41:03 +03:30
Sayyid Hamid Mahdavi
26c7e765a3 promotion with new method works 2025-10-16 19:05:13 +03:30
Sayyid Hamid Mahdavi
e30005021d last commit 2025-10-14 20:49:31 +03:30
Sayyid Hamid Mahdavi
491670200c Merge remote-tracking branch 'origin/master' 2025-10-12 21:03:02 +03:30
Sayyid Hamid Mahdavi
a5854390cb recipient skeleton 2025-10-12 21:02:48 +03:30
Sayyid Hamid Mahdavi
6595408f63 correct urls 2025-09-27 15:14:46 +03:30
Sayyid Hamid Mahdavi
32f94178cd event api 2025-09-20 16:50:10 +03:30