~homeworkprod/byceps

ref: 4237b3ec9496efe95dcce82bea3207ab9de4d520 byceps/byceps/services/shop/order/models/order.py -rw-r--r-- 5.8 KiB
4237b3ec — Jochen Kupperschmidt Move ticketing blueprint into `site` subpackage 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
"""
byceps.services.shop.order.models.order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:Copyright: 2006-2020 Jochen Kupperschmidt
:License: Modified BSD, see LICENSE for details.
"""

from datetime import datetime
from typing import Optional, Set

from sqlalchemy.ext.hybrid import hybrid_property

from .....database import BaseQuery, db, generate_uuid
from .....typing import UserID
from .....util.instances import ReprBuilder

from ....user.models.user import User

from ...shop.transfer.models import ShopID

from ..transfer.models import (
    Address,
    Order as OrderTransferObject,
    OrderNumber,
    PaymentMethod,
    PaymentState,
)


class OrderQuery(BaseQuery):

    def for_shop(self, shop_id: ShopID) -> BaseQuery:
        return self.filter_by(shop_id=shop_id)

    def placed_by(self, user_id: UserID) -> BaseQuery:
        return self.filter_by(placed_by_id=user_id)


class Order(db.Model):
    """An order for articles, placed by a user."""

    __tablename__ = 'shop_orders'
    query_class = OrderQuery

    id = db.Column(db.Uuid, default=generate_uuid, primary_key=True)
    created_at = db.Column(db.DateTime, nullable=False)
    shop_id = db.Column(db.UnicodeText, db.ForeignKey('shops.id'), index=True, nullable=False)
    order_number = db.Column(db.UnicodeText, unique=True, nullable=False)
    placed_by_id = db.Column(db.Uuid, db.ForeignKey('users.id'), index=True, nullable=False)
    placed_by = db.relationship(User, foreign_keys=[placed_by_id])
    first_names = db.Column(db.UnicodeText, nullable=False)
    last_name = db.Column(db.UnicodeText, nullable=False)
    country = db.Column(db.UnicodeText, nullable=False)
    zip_code = db.Column(db.UnicodeText, nullable=False)
    city = db.Column(db.UnicodeText, nullable=False)
    street = db.Column(db.UnicodeText, nullable=False)
    total_amount = db.Column(db.Numeric(7, 2), nullable=False)
    invoice_created_at = db.Column(db.DateTime, nullable=True)
    _payment_method = db.Column('payment_method', db.UnicodeText, nullable=True)
    _payment_state = db.Column('payment_state', db.UnicodeText, index=True, nullable=False)
    payment_state_updated_at = db.Column(db.DateTime, nullable=True)
    payment_state_updated_by_id = db.Column(db.Uuid, db.ForeignKey('users.id'), nullable=True)
    payment_state_updated_by = db.relationship(User, foreign_keys=[payment_state_updated_by_id])
    cancelation_reason = db.Column(db.UnicodeText, nullable=True)
    shipping_required = db.Column(db.Boolean, nullable=False)
    shipped_at = db.Column(db.DateTime, nullable=True)

    def __init__(
        self,
        shop_id: ShopID,
        order_number: OrderNumber,
        placed_by_id: UserID,
        first_names: str,
        last_name: str,
        country: str,
        zip_code: str,
        city: str,
        street,
        *,
        created_at: Optional[datetime] = None,
    ) -> None:
        if created_at is None:
            created_at = datetime.utcnow()
        self.created_at = created_at
        self.shop_id = shop_id
        self.order_number = order_number
        self.placed_by_id = placed_by_id
        self.first_names = first_names
        self.last_name = last_name
        self.country = country
        self.zip_code = zip_code
        self.city = city
        self.street = street
        self.payment_state = PaymentState.open

    @hybrid_property
    def payment_method(self) -> Optional[PaymentMethod]:
        if self._payment_method is None:
            return None

        return PaymentMethod[self._payment_method]

    @payment_method.setter
    def payment_method(self, method: PaymentMethod) -> None:
        assert method is not None
        self._payment_method = method.name

    @hybrid_property
    def payment_state(self) -> PaymentState:
        return PaymentState[self._payment_state]

    @payment_state.setter
    def payment_state(self, state: PaymentState) -> None:
        assert state is not None
        self._payment_state = state.name

    @property
    def is_open(self) -> bool:
        return self.payment_state == PaymentState.open

    @property
    def is_canceled(self) -> bool:
        return self.payment_state in {
            PaymentState.canceled_before_paid,
            PaymentState.canceled_after_paid,
        }

    @property
    def is_paid(self) -> bool:
        return self.payment_state == PaymentState.paid

    @property
    def is_invoiced(self) -> bool:
        return self.invoice_created_at is not None

    @property
    def is_shipping_required(self) -> bool:
        return self.shipping_required

    @property
    def is_shipped(self) -> bool:
        return self.shipped_at is not None

    def to_transfer_object(self) -> OrderTransferObject:
        address = Address(
            self.country,
            self.zip_code,
            self.city,
            self.street,
        )

        items = [item.to_transfer_object() for item in self.items]

        return OrderTransferObject(
            self.id,
            self.shop_id,
            self.order_number,
            self.created_at,
            self.placed_by_id,
            self.first_names,
            self.last_name,
            address,
            self.total_amount,
            items,
            self.payment_method,
            self.payment_state,
            self.is_open,
            self.is_canceled,
            self.is_paid,
            self.is_invoiced,
            self.is_shipping_required,
            self.is_shipped,
            self.cancelation_reason,
        )

    def __repr__(self) -> str:
        item_count = len(self.items)

        return ReprBuilder(self) \
            .add_with_lookup('id') \
            .add('shop', self.shop_id) \
            .add_with_lookup('order_number') \
            .add('placed_by', self.placed_by.screen_name) \
            .add_custom(f'{item_count:d} items') \
            .add_custom(self.payment_state.name) \
            .build()