Commit graph

93 commits

Author SHA1 Message Date
50e69d985c Merge pull request 'FIX/promotion-rollback-to-credit' (#9) from FIX/promotion-rollback-to-credit into master
Reviewed-on: #9
2026-09-05 09:54:36 -04:00
db2f0afcd5 Merge branch 'FIX/promotion-rollback-to-credit' of https://git.addwin.ir/addwin/promotions into FIX/promotion-rollback-to-credit
# Conflicts:
#	apps/promotions/admin.py
#	apps/promotions/models.py
#	apps/promotions/views_application.py
2026-09-01 15:10:25 +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
18875af772 FEATURE(promotions): reversible promotion payouts to the advertising transit wallet
When the advertising service discards what a promotion paid for (e.g. a
captured billboard deleted while still pending approval), the promotion
money sitting in the advertising transit wallet needs to go back to the
promotions credit wallet. The promotion itself stays consumed -- only the
money is returned -- and only payouts that landed in the transit wallet
are reversible (a payout straight to the user's wallet is the user's).

- Promotion.rolled_back_at + rollback_to_credit(): row-locked, CAS-stamped,
  idempotent transfer transit -> credit for SUCCESS/transit-destined payouts.
- PromotionRollback model: keyed (user, event_label) -- all the advertising
  side knows. Handles the async race (payout runs in a Celery task, so the
  Promotion may not exist yet): Recipient.promote() checks for an unsettled
  request before paying (suppresses the payout) and after (reverses a
  payout that landed mid-request).
- POST .../event/<event_label>/rollback/ -> {status: reversed|deferred|nothing, amount}.
- migration 0011 (hand-written; verified via makemigrations --dry-run + check).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 14:47:05 +03:30
161664cee9 Merge pull request 'RENAME(promotions): WALLET_PROMOTIONS_TRANSIT -> WALLET_PROMOTIONS_CREDIT' (#8) from feature/wallet-changes into master
Reviewed-on: #8
2026-08-31 11:25:04 -04:00
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
f68af8a8af Merge pull request 'feature/wallet-changes' (#7) from feature/wallet-changes into master
Reviewed-on: #7
2026-08-25 10:28:43 -04:00
d3800584b5 Merge branch 'master' into feature/wallet-changes
# Conflicts:
#	README.md
#	apps/promotions/models.py
#	main/settings.py
2026-08-25 17:49:45 +03:30
5c10096a5f Merge pull request 'feat(notifications): accept optional click_url in notifications_push_user' (#5) from feature/notification-client-update into master
Reviewed-on: #5
2026-08-25 09:50:53 -04:00
059de2914d Merge pull request 'FEATURE(wallets): rename WALLET_RIAL/WALLET_REWARD and add WALLET_PROMOTION' (#4) from feature/wallet-refactor into master
Reviewed-on: #4
2026-08-25 09:50:35 -04:00
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
c7af6bb14b feat(notifications): accept optional click_url in notifications_push_user
Passes through to the notifications service, which embeds it as the
Gotify tap destination when set. Optional — no existing call site
changes, most notifications stay non-clickable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 09:36:24 +03:30
92bea29816 FEATURE(wallets): rename WALLET_RIAL/WALLET_REWARD and add WALLET_PROMOTION
WALLET_RIAL -> WALLET_RIAL_DEPOSIT, WALLET_REWARD -> WALLET_USER_BILLBOARD_VISIT_INCOME
to match the naming used for the same wallet-service UUIDs in the advertising/settlement
repos. Also introduces WALLET_PROMOTION as this service's own pool and routes
Promotion.promote()'s payer_wallet through it instead of reusing the payee's
WALLET_REWARD value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 14:50:42 +03:30
079c795065 Merge pull request 'FIX(promotions): resolve user UUID before saving application events' (#3) from feature/debug-v2 into master
Reviewed-on: #3
2026-08-18 07:05:14 -04:00
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
1a63acee32 Merge pull request 'FIX(promotions): default event status promotion_amount to 0' (#2) from feature/allow-users into master
Reviewed-on: #2
2026-08-03 11:04:17 -04:00
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
c2a0c2f1d8 Merge pull request 'feature/allow-users' (#1) from feature/allow-users into master
Reviewed-on: #1
2026-08-03 09:24:13 -04:00
0e95fab85f DOCS: add service brief for promotions
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-03 16:18:32 +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
ab348d5134 Merge pull request 'from FEATURE/refactore-application-views into master' (#2) from FEATURE/refactore-application-views into master
Reviewed-on: https://git.mindplus-dev.ir/gooyal/promotions/pulls/2
2026-07-11 06:58:18 -04:00
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
81e3e7776d update libs 2026-02-22 12:08:32 +03:30
Sayyid Hamid Mahdavi
7a55488a17 csrf 2026-02-21 17:10:59 +03:30
Sayyid Hamid Mahdavi
d4e77dbdf8 csrf 2026-02-15 12:24:21 +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
3443cba7bd logger 2025-12-03 18:32:23 +03:30
Sayyid Hamid Mahdavi
d67f4f6069 get notification base url from env 2025-12-03 18:14:15 +03:30
Sayyid Hamid Mahdavi
1ccc31ad23 get notification base url from env 2025-12-03 13:57:16 +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