Coverage for website/sales/admin/shift_admin.py: 80.00%
117 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from django.contrib import admin, messages
2from django.contrib.admin import register
3from django.utils.translation import gettext_lazy as _
5from payments.models import Payment
6from sales.models.order import Order
7from sales.models.shift import SelfOrderPeriod, Shift
8from sales.services import is_manager
11class SelfOrderPeriodInline(admin.TabularInline):
12 model = SelfOrderPeriod
13 ordering = ("start",)
14 extra = 0
15 fields = (
16 "start",
17 "end",
18 )
20 def has_change_permission(self, request, obj=None):
21 if obj and obj.locked: 21 ↛ 22line 21 didn't jump to line 22 because the condition on line 21 was never true
22 return False
23 if obj and not is_manager(request.member, obj): 23 ↛ 24line 23 didn't jump to line 24 because the condition on line 23 was never true
24 return False
25 return super().has_change_permission(request, obj)
28class OrderInline(admin.TabularInline):
29 model = Order
30 ordering = ("created_at",)
31 extra = 0
32 show_change_link = True
33 can_delete = False
35 fields = (
36 "created_at",
37 "id",
38 "order_description",
39 "discount",
40 "total_amount",
41 "paid",
42 "payer",
43 )
45 readonly_fields = (
46 "created_at",
47 "id",
48 "order_description",
49 "discount",
50 "total_amount",
51 "paid",
52 "payer",
53 )
55 def has_add_permission(self, request, obj):
56 return False
58 def has_change_permission(self, request, obj=None):
59 if obj and obj.locked: 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true
60 return False
62 if obj and not is_manager(request.member, obj): 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true
63 return False
65 return super().has_change_permission(request, obj)
67 def get_queryset(self, request):
68 queryset = super().get_queryset(request)
69 queryset = queryset.select_properties(
70 "total_amount", "subtotal", "num_items", "age_restricted"
71 )
72 queryset = queryset.prefetch_related("payment")
73 queryset = queryset.prefetch_related("order_items__product__product")
74 queryset = queryset.prefetch_related("payer")
75 return queryset
77 def total_amount(self, obj):
78 if obj.total_amount:
79 return f"€{obj.total_amount:.2f}"
80 return "-"
82 def discount(self, obj):
83 if obj.discount:
84 return f"€{obj.discount:.2f}"
85 return "-"
87 def paid(self, obj):
88 if obj.total_amount is None or obj.total_amount == 0:
89 return None
90 return obj.payment is not None
92 paid.boolean = True
95@register(Shift)
96class ShiftAdmin(admin.ModelAdmin):
97 inlines = [
98 SelfOrderPeriodInline,
99 OrderInline,
100 ]
101 search_fields = (
102 "id",
103 "title",
104 "start",
105 "end",
106 )
107 filter_horizontal = ("managers",)
108 date_hierarchy = "start"
109 list_display_links = (
110 "id",
111 "title",
112 )
113 list_display = (
114 "id",
115 "title",
116 "start",
117 "end",
118 "active",
119 "locked",
120 "product_list",
121 "num_orders",
122 "total_revenue",
123 )
124 fields = (
125 "title",
126 "start",
127 "end",
128 "active",
129 "product_list",
130 "managers",
131 "product_sales",
132 "payment_method_sales",
133 "num_orders",
134 "total_revenue",
135 "locked",
136 )
138 readonly_fields = (
139 "active",
140 "total_revenue",
141 "num_orders",
142 "product_sales",
143 "payment_method_sales",
144 )
146 def get_readonly_fields(self, request, obj=None):
147 fields = super().get_readonly_fields(request, obj)
148 if not obj or obj.locked: 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true
149 fields += ("locked",)
150 return fields
152 def get_queryset(self, request):
153 queryset = super().get_queryset(request)
155 if not request.member: 155 ↛ 156line 155 didn't jump to line 156 because the condition on line 155 was never true
156 queryset = queryset.none()
157 elif not request.member.has_perm("sales.override_manager"): 157 ↛ 158line 157 didn't jump to line 158 because the condition on line 157 was never true
158 queryset = queryset.filter(
159 managers__in=request.member.get_member_groups()
160 ).distinct()
162 queryset = queryset.select_properties(
163 "active",
164 "total_revenue",
165 "total_revenue_paid",
166 "num_orders",
167 "num_orders_paid",
168 )
169 queryset = queryset.prefetch_related("event")
170 return queryset
172 def has_view_permission(self, request, obj=None):
173 if obj and not is_manager(request.member, obj):
174 return False
175 return super().has_view_permission(request, obj)
177 def has_change_permission(self, request, obj=None):
178 if obj and obj.locked:
179 return False
180 if obj and not is_manager(request.member, obj): 180 ↛ 181line 180 didn't jump to line 181 because the condition on line 180 was never true
181 return False
182 return super().has_change_permission(request, obj)
184 def has_delete_permission(self, request, obj=None):
185 if obj and obj.locked: 185 ↛ 186line 185 didn't jump to line 186 because the condition on line 185 was never true
186 return False
187 if obj and not is_manager(request.member, obj):
188 return False
189 return super().has_delete_permission(request, obj)
191 def changelist_view(self, request, extra_context=None):
192 if not (request.member and request.member.has_perm("sales.override_manager")): 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true
193 self.message_user(
194 request,
195 _("You are only seeing shifts that you are managing."),
196 messages.WARNING,
197 )
198 return super().changelist_view(request, extra_context)
200 def active(self, obj):
201 return obj.active
203 active.boolean = True
205 def num_orders(self, obj):
206 if obj.num_orders and obj.num_orders != obj.num_orders_paid: 206 ↛ 208line 206 didn't jump to line 208 because the condition on line 206 was always true
207 return f"{obj.num_orders} ({obj.num_orders - obj.num_orders_paid} {_('unpaid')})"
208 return obj.num_orders
210 def total_revenue(self, obj):
211 if obj.total_revenue and obj.total_revenue != obj.total_revenue_paid: 211 ↛ 213line 211 didn't jump to line 213 because the condition on line 211 was always true
212 return f"€{obj.total_revenue:.2f} (€{obj.total_revenue-obj.total_revenue_paid:.2f} {_('unpaid')})"
213 return f"€{obj.total_revenue or 0:.2f}"
215 def product_sales(self, obj):
216 output = "\n".join(f"- {k}: {v}x" for k, v in obj.product_sales.items())
217 if obj.num_orders != obj.num_orders_paid: 217 ↛ 219line 217 didn't jump to line 219 because the condition on line 217 was always true
218 return f"{output}\n{_('This includes some orders that are unpaid.')}"
219 return output
221 def payment_method_sales(self, obj):
222 output = "\n".join(
223 f"- {dict(Payment.PAYMENT_TYPE)[k] if k else _('Unpaid')}: €{v or '':.2f}"
224 for k, v in obj.payment_method_sales.items()
225 )
226 return output