Skip to content

Commit 96a78af

Browse files
committed
fix: complete stars purchase (#4)
1 parent 8778646 commit 96a78af

File tree

14 files changed

+328
-38
lines changed

14 files changed

+328
-38
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ ignore = [
107107
[tool.ruff.lint.per-file-ignores]
108108
"src/ttt/infrastructure/adapters/*" = ["ARG002"]
109109
"src/ttt/presentation/adapters/*" = ["ARG002"]
110-
"src/ttt/presentation/aiogram/*" = ["RUF001"]
110+
"src/ttt/presentation/*" = ["RUF001"]
111111
"tests/*" = ["S101", "PT013", "PLR2004", "D400", "D415"]
112112
"tests/test_tgdb/test_entities/test_horizon.py" = ["D400", "D415"]
113113

src/ttt/application/player/start_stars_purchase_payment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ async def __call__(self, player_id: int, purchase_id: UUID) -> None:
3434

3535
tracking = Tracking()
3636
try:
37-
player.process_stars_purchase_payment(
37+
player.start_stars_purchase_payment(
3838
purchase_id, payment_id, current_datetime, tracking,
3939
)
4040
except PaymentIsAlreadyBeingMadeError:

src/ttt/entities/core/player/player.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
from ttt.entities.core.player.stars_purchase import StarsPurchase
1010
from ttt.entities.core.player.win import Win
1111
from ttt.entities.core.stars import Stars
12-
from ttt.entities.finance.payment.payment import cancel_payment, complete_payment
12+
from ttt.entities.finance.payment.payment import (
13+
cancel_payment,
14+
complete_payment,
15+
)
1316
from ttt.entities.finance.payment.success import PaymentSuccess
14-
from ttt.entities.finance.rubles import Rubles
1517
from ttt.entities.math.random import Random, deviated_int
1618
from ttt.entities.text.emoji import Emoji
1719
from ttt.entities.tools.assertion import assert_
@@ -205,7 +207,7 @@ def start_stars_purchase(
205207
)
206208
self.stars_purchases.append(stars_purchase)
207209

208-
def process_stars_purchase_payment(
210+
def start_stars_purchase_payment(
209211
self,
210212
purchase_id: UUID,
211213
payment_id: UUID,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""
2+
add `state` to `payments` table.
3+
4+
Revision ID: 1dd1ac771b91
5+
Revises: ce3ee64d0d3b
6+
Create Date: 2025-07-02 05:32:15.368212
7+
8+
"""
9+
10+
from collections.abc import Sequence
11+
12+
import sqlalchemy as sa
13+
from alembic import op
14+
from sqlalchemy.dialects import postgresql
15+
16+
17+
revision: str = "1dd1ac771b91"
18+
down_revision: str | None = "ce3ee64d0d3b"
19+
branch_labels: str | Sequence[str] | None = None
20+
depends_on: str | Sequence[str] | None = None
21+
22+
23+
def upgrade() -> None:
24+
sa.Enum(
25+
"in_process",
26+
"cancelled",
27+
"completed",
28+
name="payment_state",
29+
).create(op.get_bind())
30+
31+
op.add_column(
32+
"payments",
33+
sa.Column(
34+
"state",
35+
postgresql.ENUM(
36+
"in_process",
37+
"cancelled",
38+
"completed",
39+
name="payment_state",
40+
create_type=False,
41+
),
42+
nullable=True,
43+
),
44+
)
45+
46+
op.execute("""
47+
UPDATE payments SET state = 'cancelled' WHERE is_cancelled;
48+
""")
49+
op.execute("""
50+
UPDATE payments SET state = 'completed' WHERE success_id IS NOT NULL;
51+
""")
52+
op.execute("""
53+
UPDATE payments SET state = 'in_process'
54+
WHERE NOT is_cancelled AND success_id IS NULL;
55+
""")
56+
57+
op.drop_column("payments", "is_cancelled")
58+
op.alter_column("payments", "state", nullable=False)
59+
60+
61+
def downgrade() -> None:
62+
op.add_column(
63+
"payments",
64+
sa.Column(
65+
"is_cancelled", sa.BOOLEAN(), autoincrement=False, nullable=True,
66+
),
67+
)
68+
op.execute("""
69+
UPDATE payments SET is_cancelled = true WHERE state = 'cancelled';
70+
""")
71+
op.alter_column("payments", "is_cancelled", nullable=False)
72+
73+
op.drop_column("payments", "state")
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
rename `stars_purchases.new_stars` to `stars_purchases.stars`.
3+
4+
Revision ID: 46880a6a8cff
5+
Revises: b19b4f555d6d
6+
Create Date: 2025-07-02 05:11:35.208443
7+
8+
"""
9+
from collections.abc import Sequence
10+
11+
from alembic import op
12+
13+
14+
revision: str = "46880a6a8cff"
15+
down_revision: str | None = "b19b4f555d6d"
16+
branch_labels: str | Sequence[str] | None = None
17+
depends_on: str | Sequence[str] | None = None
18+
19+
20+
def upgrade() -> None:
21+
op.alter_column(
22+
"stars_purchases",
23+
"new_stars",
24+
new_column_name="stars",
25+
)
26+
27+
28+
def downgrade() -> None:
29+
op.alter_column(
30+
"stars_purchases",
31+
"stars",
32+
new_column_name="new_stars",
33+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
make `stars_purchases.payment_id` nullable.
3+
4+
Revision ID: 86d75da6c607
5+
Revises: 46880a6a8cff
6+
Create Date: 2025-07-02 05:15:03.842458
7+
8+
"""
9+
from collections.abc import Sequence
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
14+
15+
revision: str = "86d75da6c607"
16+
down_revision: str | None = "46880a6a8cff"
17+
branch_labels: str | Sequence[str] | None = None
18+
depends_on: str | Sequence[str] | None = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.alter_column("stars_purchases", "payment_id",
24+
existing_type=sa.UUID(),
25+
nullable=True)
26+
# ### end Alembic commands ###
27+
28+
29+
def downgrade() -> None:
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.alter_column("stars_purchases", "payment_id",
32+
existing_type=sa.UUID(),
33+
nullable=False)
34+
# ### end Alembic commands ###
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
exclude nulls from `ix_stars_purchases_payment_id`.
3+
4+
Revision ID: ce3ee64d0d3b
5+
Revises: 86d75da6c607
6+
Create Date: 2025-07-02 05:19:22.411169
7+
8+
"""
9+
10+
from collections.abc import Sequence
11+
12+
import sqlalchemy as sa
13+
from alembic import op
14+
15+
16+
revision: str = "ce3ee64d0d3b"
17+
down_revision: str | None = "86d75da6c607"
18+
branch_labels: str | Sequence[str] | None = None
19+
depends_on: str | Sequence[str] | None = None
20+
21+
22+
def upgrade() -> None:
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.drop_index(
25+
op.f("ix_stars_purchases_payment_id"), table_name="stars_purchases",
26+
)
27+
op.create_index(
28+
op.f("ix_stars_purchases_payment_id"),
29+
"stars_purchases",
30+
["payment_id"],
31+
unique=False,
32+
postgresql_where=sa.text("payment_id IS NOT NULL"),
33+
)
34+
# ### end Alembic commands ###
35+
36+
37+
def downgrade() -> None:
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.drop_index(
40+
op.f("ix_stars_purchases_payment_id"), table_name="stars_purchases",
41+
)
42+
op.create_index(
43+
op.f("ix_stars_purchases_payment_id"),
44+
"stars_purchases",
45+
["payment_id"],
46+
unique=False,
47+
)
48+
# ### end Alembic commands ###

src/ttt/infrastructure/sqlalchemy/tables.py

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from enum import StrEnum
55
from uuid import UUID
66

7-
from sqlalchemy import CHAR, BigInteger, ForeignKey
7+
from sqlalchemy import CHAR, BigInteger, ForeignKey, Index
88
from sqlalchemy.dialects import postgresql
99
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
1010

@@ -25,7 +25,7 @@
2525
from ttt.entities.core.player.player import Player
2626
from ttt.entities.core.player.stars_purchase import StarsPurchase
2727
from ttt.entities.core.player.win import Win
28-
from ttt.entities.finance.payment.payment import Payment
28+
from ttt.entities.finance.payment.payment import Payment, PaymentState
2929
from ttt.entities.finance.payment.success import PaymentSuccess
3030
from ttt.entities.finance.rubles import Rubles
3131
from ttt.entities.math.matrix import Matrix
@@ -36,6 +36,34 @@
3636
class Base(DeclarativeBase): ...
3737

3838

39+
class TablePaymentState(StrEnum):
40+
in_process = "in_process"
41+
cancelled = "cancelled"
42+
completed = "completed"
43+
44+
def entity(self) -> PaymentState:
45+
match self:
46+
case TablePaymentState.in_process:
47+
return PaymentState.in_process
48+
case TablePaymentState.cancelled:
49+
return PaymentState.cancelled
50+
case TablePaymentState.completed:
51+
return PaymentState.completed
52+
53+
@classmethod
54+
def of(cls, it: PaymentState) -> "TablePaymentState":
55+
match it:
56+
case PaymentState.in_process:
57+
return TablePaymentState.in_process
58+
case PaymentState.cancelled:
59+
return TablePaymentState.cancelled
60+
case PaymentState.completed:
61+
return TablePaymentState.completed
62+
63+
64+
payment_state = postgresql.ENUM(TablePaymentState, name="payment_state")
65+
66+
3967
class TablePayment(Base):
4068
__tablename__ = "payments"
4169

@@ -45,21 +73,25 @@ class TablePayment(Base):
4573
completion_datetime: Mapped[datetime | None]
4674
success_id: Mapped[str | None]
4775
success_gateway_id: Mapped[str | None]
48-
is_cancelled: Mapped[bool]
76+
state: Mapped[TablePaymentState] = mapped_column(payment_state)
4977

5078
def entity(self) -> Payment:
5179
if self.success_id is None or self.success_gateway_id is None:
5280
success = None
5381
else:
5482
success = PaymentSuccess(self.success_id, self.success_gateway_id)
5583

84+
paid_rubles = Rubles.with_total_kopecks(
85+
self.paid_rubles_total_kopecks,
86+
)
87+
5688
return Payment(
57-
self.id,
58-
Rubles.with_total_kopecks(self.paid_rubles_total_kopecks),
59-
self.start_datetime,
60-
self.completion_datetime,
61-
success,
62-
self.is_cancelled,
89+
id_=self.id,
90+
paid_rubles=paid_rubles,
91+
start_datetime=self.start_datetime,
92+
completion_datetime=self.completion_datetime,
93+
success=success,
94+
state=self.state.entity(),
6395
)
6496

6597
@classmethod
@@ -78,7 +110,7 @@ def of(cls, it: Payment) -> "TablePayment":
78110
completion_datetime=it.completion_datetime,
79111
success_id=success_id,
80112
success_gateway_id=success_gateway_id,
81-
is_cancelled=it.is_cancelled,
113+
state=TablePaymentState.of(it.state),
82114
)
83115

84116

@@ -120,13 +152,23 @@ class TableStarsPurchase(Base):
120152
index=True,
121153
)
122154
location_chat_id: Mapped[int] = mapped_column(BigInteger())
123-
new_stars: Mapped[int]
124-
payment_id: Mapped[UUID] = mapped_column(
155+
stars: Mapped[int]
156+
payment_id: Mapped[UUID | None] = mapped_column(
125157
ForeignKey("payments.id", deferrable=True, initially="DEFERRED"),
126-
index=True,
127158
)
128159

129-
payment = relationship(TablePayment, lazy="joined")
160+
payment: Mapped[TablePayment | None] = relationship(
161+
TablePayment,
162+
lazy="joined",
163+
)
164+
165+
__table_args__ = (
166+
Index(
167+
"ix_stars_purchases_payment_id",
168+
payment_id,
169+
postgresql_where=(payment_id.is_not(None)),
170+
),
171+
)
130172

131173
def entity(self) -> StarsPurchase:
132174
return StarsPurchase(
@@ -135,8 +177,8 @@ def entity(self) -> StarsPurchase:
135177
self.location_player_id,
136178
self.location_chat_id,
137179
),
138-
new_stars=self.new_stars,
139-
payment=self.payment.entity(),
180+
stars=self.stars,
181+
payment=None if self.payment is None else self.payment.entity(),
140182
)
141183

142184
@classmethod
@@ -145,8 +187,8 @@ def of(cls, it: StarsPurchase) -> "TableStarsPurchase":
145187
id=it.id_,
146188
location_player_id=it.location.player_id,
147189
location_chat_id=it.location.chat_id,
148-
new_stars=it.new_stars,
149-
payment_id=it.payment.id_,
190+
stars=it.stars,
191+
payment_id=None if it.payment is None else it.payment.id_,
150192
)
151193

152194

0 commit comments

Comments
 (0)