1414from django .db import models , transaction
1515from django .http .response import StreamingHttpResponse
1616from drf_spectacular .utils import OpenApiParameter , OpenApiTypes , extend_schema , extend_schema_view
17- from rest_framework import exceptions , parsers , request , response , status , viewsets
17+ from rest_framework import exceptions , mixins , parsers , request , response , status , viewsets
1818from rest_framework .decorators import action
1919from shop .order import exports , imports
2020from shop .order .models import Order , OrderProductOptionRelation , OrderProductRelation
2121from shop .payment_history .models import PURCHASED_STATUSES , REFUNDABLE_STATUSES , PaymentHistory
2222from shop .product .models import Product
23- from shop .serializers .refund import OrderTotalRefundSerializer
23+ from shop .serializers .refund import OrderProductRefundSerializer , OrderTotalRefundSerializer
2424
2525logger = getLogger (__name__ )
2626
27- ADMIN_METHODS = ["list" , "retrieve" ]
27+ ADMIN_METHODS = ["list" , "retrieve" , "partial_update" ]
2828
2929
3030@extend_schema_view (** {m : extend_schema (tags = [OpenAPITag .ADMIN_SHOP_ORDER ]) for m in ADMIN_METHODS })
31- class OrderAdminViewSet (JsonSchemaViewSet , viewsets .ReadOnlyModelViewSet ):
32- http_method_names = ["get" , "post" ]
31+ class OrderAdminViewSet (
32+ JsonSchemaViewSet ,
33+ mixins .ListModelMixin ,
34+ mixins .RetrieveModelMixin ,
35+ mixins .UpdateModelMixin ,
36+ viewsets .GenericViewSet ,
37+ ):
38+ http_method_names = ["get" , "post" , "patch" ]
3339 serializer_class = OrderAdminSerializer
3440 filterset_class = OrderAdminFilterSet
3541 permission_classes = [IsSuperUser ]
3642 queryset = (
37- Order .objects .filter_active ()
43+ Order .objects .filter_has_payment_histories ()
44+ .filter (models .Exists (OrderProductRelation .objects .filter (order = models .OuterRef ("pk" ))))
3845 .select_related_with_user ("user" , "customer_info" )
3946 .prefetch_related (
4047 models .Prefetch (
@@ -54,7 +61,6 @@ class OrderAdminViewSet(JsonSchemaViewSet, viewsets.ReadOnlyModelViewSet):
5461 models .Prefetch (
5562 "payment_histories" ,
5663 queryset = PaymentHistory .objects .filter_active ().order_by ("-created_at" ),
57- to_attr = "_payment_histories_by_latest" ,
5864 ),
5965 )
6066 .annotate (
@@ -83,6 +89,33 @@ def refund(self, request: request.Request, pk: typing.Any = None) -> response.Re
8389 serializer .refund ()
8490 return response .Response (status = status .HTTP_204_NO_CONTENT )
8591
92+ @extend_schema (
93+ summary = "주문 부분 환불 (환불 승인자 TOTP 필수)" ,
94+ tags = [OpenAPITag .ADMIN_SHOP_ORDER_REFUND ],
95+ parameters = [OpenApiParameter (name = "totp" , location = OpenApiParameter .QUERY , required = True )],
96+ responses = {status .HTTP_204_NO_CONTENT : None },
97+ )
98+ @action (detail = True , methods = ["post" ], url_path = r"products/(?P<rel_id>[^/.]+)/refund" )
99+ @transaction .atomic
100+ def refund_product (
101+ self ,
102+ request : request .Request ,
103+ pk : typing .Any = None ,
104+ rel_id : typing .Any = None ,
105+ ) -> response .Response :
106+ order_product_rel = OrderProductRelation .objects .filter (order_id = pk , id = rel_id ).first ()
107+ if not order_product_rel :
108+ raise exceptions .NotFound ("OrderProductRelation not found." )
109+
110+ serializer = OrderProductRefundSerializer (
111+ instance = order_product_rel ,
112+ data = {"totp" : request .query_params .get ("totp" )},
113+ context = {"check_refundable_date" : False },
114+ )
115+ serializer .is_valid (raise_exception = True )
116+ serializer .refund ()
117+ return response .Response (status = status .HTTP_204_NO_CONTENT )
118+
86119 @extend_schema (
87120 summary = "주문 CSV 가져오기 템플릿 다운로드" ,
88121 tags = [OpenAPITag .ADMIN_SHOP_ORDER ],
0 commit comments