From b4267a0c5788c0056f9234d782989d4d472266a8 Mon Sep 17 00:00:00 2001 From: fuyuUSG <234328264+fuyuUSG@users.noreply.github.com> Date: Sun, 7 Dec 2025 00:21:42 +0900 Subject: [PATCH 1/7] =?UTF-8?q?both:=E3=83=87=E3=83=BC=E3=82=BF=E3=83=99?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=81=AE=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=A8=E5=90=84=E6=83=85=E5=A0=B1=E3=81=AE=E7=B4=90=E3=81=A5?= =?UTF-8?q?=E3=81=91=E4=BF=AE=E6=AD=A3=E3=83=BB=E8=A8=88=E7=AE=97=E5=AE=9F?= =?UTF-8?q?=E8=A1=8C=E5=BE=8C=E6=88=BB=E3=82=8B=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=A7=E6=88=BB=E3=81=A3=E3=81=A6=E3=83=A1=E3=83=B3=E3=83=90?= =?UTF-8?q?=E3=83=BC=E5=89=8A=E9=99=A4=E3=81=97=E3=81=A6=E3=81=8B=E3=82=89?= =?UTF-8?q?=E8=AB=8B=E6=B1=82=E7=94=BB=E9=9D=A2=E3=81=AB=E9=80=B2=E3=82=80?= =?UTF-8?q?=E3=81=A8=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC0=E3=81=95?= =?UTF-8?q?=E3=82=93=E3=81=8C=E5=87=BA=E7=8F=BE=E3=81=99=E3=82=8B=E4=B8=8D?= =?UTF-8?q?=E5=85=B7=E5=90=88=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/migrations/0001_initial.py | 16 +-- ...embers_event_id_alter_payments_event_id.py | 24 ---- .../migrations/0003_alter_results_event_id.py | 19 --- backend/app/models.py | 18 ++- backend/app/views.py | 108 ++++++++++++++---- frontend/src/lib/fetchResult.ts | 4 +- frontend/src/pages/AlgorithmAndResultPage.tsx | 3 +- 7 files changed, 110 insertions(+), 82 deletions(-) delete mode 100644 backend/app/migrations/0002_alter_members_event_id_alter_payments_event_id.py delete mode 100644 backend/app/migrations/0003_alter_results_event_id.py diff --git a/backend/app/migrations/0001_initial.py b/backend/app/migrations/0001_initial.py index d9b0941..8e7e3be 100644 --- a/backend/app/migrations/0001_initial.py +++ b/backend/app/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.8 on 2025-11-20 12:04 +# Generated by Django 5.2.9 on 2025-12-05 10:33 import django.db.models.deletion from django.db import migrations, models @@ -26,7 +26,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('member_id', models.IntegerField()), ('name', models.CharField(max_length=50)), - ('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members', to='app.events')), + ('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members', to='app.events', to_field='url_end_code')), ], ), migrations.CreateModel( @@ -34,11 +34,11 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('payment_id', models.IntegerField()), - ('paid_by', models.IntegerField()), - ('paid_for', models.IntegerField()), ('amount', models.IntegerField()), ('note', models.CharField(blank=True, max_length=200, null=True)), - ('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='app.events')), + ('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='app.events', to_field='url_end_code')), + ('paid_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments_paid', to='app.members')), + ('paid_for', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments_for', to='app.members')), ], ), migrations.CreateModel( @@ -46,10 +46,10 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('result_id', models.IntegerField()), - ('paid_by', models.IntegerField()), - ('paid_for', models.IntegerField()), ('amount', models.IntegerField()), - ('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='app.events')), + ('event_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='app.events', to_field='url_end_code')), + ('paid_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results_paid', to='app.members')), + ('paid_for', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results_for', to='app.members')), ], ), ] diff --git a/backend/app/migrations/0002_alter_members_event_id_alter_payments_event_id.py b/backend/app/migrations/0002_alter_members_event_id_alter_payments_event_id.py deleted file mode 100644 index f7b9da2..0000000 --- a/backend/app/migrations/0002_alter_members_event_id_alter_payments_event_id.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.2.8 on 2025-11-29 11:06 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='members', - name='event_id', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members', to='app.events', to_field='url_end_code'), - ), - migrations.AlterField( - model_name='payments', - name='event_id', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='app.events', to_field='url_end_code'), - ), - ] diff --git a/backend/app/migrations/0003_alter_results_event_id.py b/backend/app/migrations/0003_alter_results_event_id.py deleted file mode 100644 index 7b7ccec..0000000 --- a/backend/app/migrations/0003_alter_results_event_id.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.2.8 on 2025-11-29 11:10 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0002_alter_members_event_id_alter_payments_event_id'), - ] - - operations = [ - migrations.AlterField( - model_name='results', - name='event_id', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='app.events', to_field='url_end_code'), - ), - ] diff --git a/backend/app/models.py b/backend/app/models.py index b4d2b40..2c084c5 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -41,13 +41,17 @@ class Payments(models.Model): #支払いテーブル related_name='payments', on_delete=models.CASCADE) payment_id = models.IntegerField() - paid_by = models.IntegerField() - paid_for = models.IntegerField() + paid_by = models.ForeignKey(Members, + on_delete=models.CASCADE, + related_name='payments_paid') + paid_for = models.ForeignKey(Members, + on_delete=models.CASCADE, + related_name='payments_for') amount = models.IntegerField() note = models.CharField(max_length=200, blank=True, null=True) #メモするところ def __str__(self): - return f"Payments {self.payment_id} in {self.event_id.url_end_code}" + return f"Payments {self.payment_id}: {self.paid_by.name} -> {self.paid_for.name}" class Results(models.Model): #結果テーブル @@ -56,8 +60,12 @@ class Results(models.Model): #結果テーブル related_name='results', on_delete=models.CASCADE) result_id = models.IntegerField() - paid_by = models.IntegerField() - paid_for = models.IntegerField() + paid_by = models.ForeignKey(Members, + on_delete=models.CASCADE, + related_name='results_paid') + paid_for = models.ForeignKey(Members, + on_delete=models.CASCADE, + related_name='results_for') amount = models.IntegerField() def __str__(self): diff --git a/backend/app/views.py b/backend/app/views.py index d8d8f34..3c455f4 100644 --- a/backend/app/views.py +++ b/backend/app/views.py @@ -2,7 +2,9 @@ from django.http import JsonResponse from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt +from django.shortcuts import get_object_or_404 import json +import traceback from channels.layers import get_channel_layer from asgiref.sync import async_to_sync @@ -29,35 +31,91 @@ def post(self, request, *args, **kwargs): input_data = json.loads(request.body) algo_id = input_data.get("algorithm_id") - group_id = input_data.get("group_id") + group_id = input_data.get("group_id") or input_data.get("event_id") + + # イベント特定 + event = None + if group_id: + event = models.Events.objects.filter(url_end_code=group_id).first() + if not event: + return JsonResponse({"error": "Event not found"}, status=404) + + # 支払情報保存 + if event: + payments_list = input_data.get("payments", []) + models.Payments.objects.filter(event_id=event).delete() + + new_payments = [] + for p in payments_list: + try: + payer = models.Members.objects.get(event_id=event, member_id=int(p.get("paid_by"))) + receiver = models.Members.objects.get(event_id=event, member_id=int(p.get("paid_for"))) + + new_payments.append(models.Payments( + event_id=event, + payment_id=int(p.get("id", 0)), + paid_by=payer, + paid_for=receiver, + amount=int(p.get("amount", 0)), + note=p.get("note", "") + )) + except (models.Members.DoesNotExist, ValueError): + continue + + models.Payments.objects.bulk_create(new_payments) + # アルゴリズム実行部分 output_data = None process_function = ALGORITHM_MAP.get(algo_id) if process_function: output_data = process_function(input_data) + else: + return JsonResponse( + {"error": f"Invalid algorithm_id: {algo_id}"}, status=400 + ) + + # 結果保存と通知部分 + if event and output_data: + models.Results.objects.filter(event_id=event).delete() + + new_results = [] + for r in output_data: + try: + payer = models.Members.objects.get(event_id=event, member_id=int(r.get("paid_by"))) + receiver = models.Members.objects.get(event_id=event, member_id=int(r.get("paid_for"))) + + new_results.append(models.Results( + event_id=event, + result_id=int(r.get("id", 0)), + paid_by=payer, + paid_for=receiver, + amount=int(r.get("amount", 0)) + )) + except (models.Members.DoesNotExist, ValueError): + continue + + models.Results.objects.bulk_create(new_results) # WebSocketでグループ全員に通知を送る処理 if group_id: - channel_layer = get_channel_layer() + try: + channel_layer = get_channel_layer() - group_name = f'warikan_{group_id}' - - async_to_sync(channel_layer.group_send)( - group_name, - { - 'type': 'chat_message', - 'message': { - 'results': output_data + group_name = f'warikan_{group_id}' + + async_to_sync(channel_layer.group_send)( + group_name, + { + 'type': 'chat_message', + 'message': { + 'results': output_data + } } - } - ) - - else: - return JsonResponse( - {"error": f"Invalid algorithm_id: {algo_id}"}, status=400 - ) + ) + except Exception as ws_error: + print(f"Warning: WebSocket通知に失敗: {ws_error}") return JsonResponse(output_data, safe=False) @@ -93,7 +151,7 @@ def get_queryset(self): qs = super().get_queryset() event_code = self.request.query_params.get("event_id") if event_code: - qs = qs.filter(event_id=event_code) + qs = qs.filter(event_id__url_end_code=event_code) return qs @action(detail=False, methods=["delete"]) @@ -102,7 +160,7 @@ def delete_by_key(self, request): member_id = request.query_params.get("member_id") deleted, _ = models.Members.objects.filter( - event_id=event_id, + event_id__url_end_code=event_id, member_id=member_id ).delete() @@ -117,7 +175,7 @@ def get_queryset(self): qs = super().get_queryset() event_code = self.request.query_params.get("event_id") if event_code: - qs = qs.filter(event_id=event_code) + qs = qs.filter(event_id__url_end_code=event_code) return qs @action(detail=False, methods=["delete"]) @@ -126,7 +184,7 @@ def delete_by_key(self, request): payment_id = request.query_params.get("payment_id") deleted, _ = models.Payments.objects.filter( - event_id=event_id, + event_id__url_end_code=event_id, payment_id=payment_id ).delete() @@ -138,7 +196,9 @@ def patch_by_key(self, request): payment_id = request.data.get("payment_id") try: - obj = models.Payments.objects.get(event_id=event_id, payment_id=payment_id) + obj = models.Payments.objects.get( + event_id__url_end_code=event_id, + payment_id=payment_id) except models.Payments.DoesNotExist: return Response({"error": "payment not found"}, status=404) @@ -157,13 +217,13 @@ def get_queryset(self): qs = super().get_queryset() event_code = self.request.query_params.get("event_id") if event_code: - qs = qs.filter(event_id=event_code) + qs = qs.filter(event_id__url_end_code=event_code) return qs @action(detail=False, methods=["delete"]) def delete_by_event(self, request): event_id = request.query_params.get("event_id") - deleted, _ = models.Results.objects.filter(event_id=event_id).delete() + deleted, _ = models.Results.objects.filter(event_id__url_end_code=event_id).delete() return Response({"status": "deleted", "count": deleted}, status=200) \ No newline at end of file diff --git a/frontend/src/lib/fetchResult.ts b/frontend/src/lib/fetchResult.ts index 7b30ae5..ec18b7e 100644 --- a/frontend/src/lib/fetchResult.ts +++ b/frontend/src/lib/fetchResult.ts @@ -5,6 +5,7 @@ import { toCamelCaseObject, toSnakeCaseObject } from "./caseConvert"; interface FetchResultParams { algorithmId: number; payments: Payment[]; + eventId: string; } /** @@ -25,10 +26,11 @@ async function fetchResult(params: FetchResultParams): Promise { const url = `${apiEndpoint}/api/v1/calculate/`; // リクエストボディをsnake_caseに変換 - // API仕様に従って、algorithm_idとpaymentsを送信 + // API仕様に従って、algorithm_idとpaymentsとevent_idを送信 const requestBody = toSnakeCaseObject({ algorithmId: params.algorithmId, payments: params.payments, + eventId: params.eventId, }); try { diff --git a/frontend/src/pages/AlgorithmAndResultPage.tsx b/frontend/src/pages/AlgorithmAndResultPage.tsx index afd0cc4..14229ae 100644 --- a/frontend/src/pages/AlgorithmAndResultPage.tsx +++ b/frontend/src/pages/AlgorithmAndResultPage.tsx @@ -31,6 +31,7 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { const handleSubmit = async () => { if (!algorithmId) return setError("アルゴリズムを選択してください"); if (payments.length === 0) return setError("請求を追加してください"); + if (!eventId) return setError("イベントIDが見つかりません"); setIsLoading(true); setError(null); @@ -39,7 +40,7 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { if (eventId) { await deleteResult(eventId); } - const fetchedResults = await fetchResult({ algorithmId, payments }); + const fetchedResults = await fetchResult({ algorithmId, payments, eventId }); setResults(fetchedResults); if (eventId) { From c8816d548787ed37e377d8d41b624ca1856e848b Mon Sep 17 00:00:00 2001 From: fuyuUSG <234328264+fuyuUSG@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:21:30 +0900 Subject: [PATCH 2/7] =?UTF-8?q?both:=E3=83=87=E3=83=BC=E3=82=BF=E3=83=99?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=81=AE=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=A8=E5=90=84=E6=83=85=E5=A0=B1=E3=81=AE=E7=B4=90=E3=81=A5?= =?UTF-8?q?=E3=81=91=E4=BF=AE=E6=AD=A3=E3=83=BB=E8=A8=88=E7=AE=97=E5=AE=9F?= =?UTF-8?q?=E8=A1=8C=E5=BE=8C=E6=88=BB=E3=82=8B=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=A7=E6=88=BB=E3=81=A3=E3=81=A6=E3=83=A1=E3=83=B3=E3=83=90?= =?UTF-8?q?=E3=83=BC=E5=89=8A=E9=99=A4=E3=81=97=E3=81=A6=E3=81=8B=E3=82=89?= =?UTF-8?q?=E8=AB=8B=E6=B1=82=E7=94=BB=E9=9D=A2=E3=81=AB=E9=80=B2=E3=82=80?= =?UTF-8?q?=E3=81=A8=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC0=E3=81=95?= =?UTF-8?q?=E3=82=93=E3=81=8C=E5=87=BA=E7=8F=BE=E3=81=99=E3=82=8B=E4=B8=8D?= =?UTF-8?q?=E5=85=B7=E5=90=88=E4=BF=AE=E6=AD=A3=E3=83=BBci=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AlgorithmAndResultPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/AlgorithmAndResultPage.tsx b/frontend/src/pages/AlgorithmAndResultPage.tsx index 14229ae..41400c5 100644 --- a/frontend/src/pages/AlgorithmAndResultPage.tsx +++ b/frontend/src/pages/AlgorithmAndResultPage.tsx @@ -40,7 +40,11 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { if (eventId) { await deleteResult(eventId); } - const fetchedResults = await fetchResult({ algorithmId, payments, eventId }); + const fetchedResults = await fetchResult({ + algorithmId, + payments, + eventId + }); setResults(fetchedResults); if (eventId) { From cfa9345fc0f1cc68231a419262f3f6ead8b08fd7 Mon Sep 17 00:00:00 2001 From: fuyuUSG <234328264+fuyuUSG@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:25:30 +0900 Subject: [PATCH 3/7] =?UTF-8?q?both:=E3=83=87=E3=83=BC=E3=82=BF=E3=83=99?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E3=81=AE=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=A8=E5=90=84=E6=83=85=E5=A0=B1=E3=81=AE=E7=B4=90=E3=81=A5?= =?UTF-8?q?=E3=81=91=E4=BF=AE=E6=AD=A3=E3=83=BB=E8=A8=88=E7=AE=97=E5=AE=9F?= =?UTF-8?q?=E8=A1=8C=E5=BE=8C=E6=88=BB=E3=82=8B=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=A7=E6=88=BB=E3=81=A3=E3=81=A6=E3=83=A1=E3=83=B3=E3=83=90?= =?UTF-8?q?=E3=83=BC=E5=89=8A=E9=99=A4=E3=81=97=E3=81=A6=E3=81=8B=E3=82=89?= =?UTF-8?q?=E8=AB=8B=E6=B1=82=E7=94=BB=E9=9D=A2=E3=81=AB=E9=80=B2=E3=82=80?= =?UTF-8?q?=E3=81=A8=E3=83=A1=E3=83=B3=E3=83=90=E3=83=BC0=E3=81=95?= =?UTF-8?q?=E3=82=93=E3=81=8C=E5=87=BA=E7=8F=BE=E3=81=99=E3=82=8B=E4=B8=8D?= =?UTF-8?q?=E5=85=B7=E5=90=88=E4=BF=AE=E6=AD=A3=E3=83=BBci=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AlgorithmAndResultPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/AlgorithmAndResultPage.tsx b/frontend/src/pages/AlgorithmAndResultPage.tsx index 41400c5..548317d 100644 --- a/frontend/src/pages/AlgorithmAndResultPage.tsx +++ b/frontend/src/pages/AlgorithmAndResultPage.tsx @@ -43,7 +43,7 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { const fetchedResults = await fetchResult({ algorithmId, payments, - eventId + eventId, }); setResults(fetchedResults); From 76e54667e3c8a8d2063d290974046e281e4fb219 Mon Sep 17 00:00:00 2001 From: fuyuUSG <234328264+fuyuUSG@users.noreply.github.com> Date: Wed, 10 Dec 2025 21:41:56 +0900 Subject: [PATCH 4/7] =?UTF-8?q?frontend:=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=83=99=E3=83=BC=E3=82=B9=E4=B8=8A=E3=81=A7=E3=81=AF=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=83=87?= =?UTF-8?q?=E3=83=BC=E3=82=BF=E3=81=8C=E3=83=95=E3=83=AD=E3=83=B3=E3=83=88?= =?UTF-8?q?=E5=81=B4=E3=81=A7=E5=8F=8D=E6=98=A0=E3=81=95=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E4=B8=8D=E5=85=B7=E5=90=88=E5=8F=8A=E3=81=B3=E6=88=BB?= =?UTF-8?q?=E3=82=8B=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=A7=E9=81=B7=E7=A7=BB?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=81=A8=E8=AB=8B=E6=B1=82=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E3=81=8C=E8=A6=8B=E3=81=9F=E7=9B=AE=E4=B8=8A=E6=B6=88=E5=A4=B1?= =?UTF-8?q?=E3=81=99=E3=82=8B=E4=B8=8D=E5=85=B7=E5=90=88=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AlgorithmAndResultPage.tsx | 98 +++++++++++--- frontend/src/pages/BillingPage.tsx | 125 ++++++++++++++---- 2 files changed, 176 insertions(+), 47 deletions(-) diff --git a/frontend/src/pages/AlgorithmAndResultPage.tsx b/frontend/src/pages/AlgorithmAndResultPage.tsx index 548317d..b35ecb9 100644 --- a/frontend/src/pages/AlgorithmAndResultPage.tsx +++ b/frontend/src/pages/AlgorithmAndResultPage.tsx @@ -2,12 +2,9 @@ import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import NetworkGraph from "@/components/NetworkGraph"; import SelectAlgorithm from "@/components/SelectAlgorithm"; -import { deleteResult } from "@/lib/deleteResult"; import { downloadCsv } from "@/lib/downloadCsv"; import { fetchResult } from "@/lib/fetchResult"; import { generateCsv } from "@/lib/generateCsv"; -import { getResult } from "@/lib/getResult"; -import { saveResult } from "@/lib/saveResult"; import type { Payment } from "@/types/payment"; import CardWrapper from "../components/CardWrapper"; import ResultTab from "../components/ResultTab"; @@ -20,6 +17,20 @@ type Props = { members: Member[]; }; +interface MemberResponse { + member_id: number; + name: string; + id: number; +} + +interface PaymentResponse { + payment_id: number; + paid_by: number; + paid_for: number; + amount: number; + note: string; +} + export default function AlgorithmAndResultPage({ payments, members }: Props) { const navigate = useNavigate(); const [algorithmId, setAlgorithmId] = useState(undefined); @@ -28,28 +39,25 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { const [error, setError] = useState(null); const { eventId } = useParams(); + const [currentMembers, setCurrentMembers] = useState(members); + const [currentPayments, setCurrentPayments] = useState(payments); + const handleSubmit = async () => { if (!algorithmId) return setError("アルゴリズムを選択してください"); - if (payments.length === 0) return setError("請求を追加してください"); + if (currentPayments.length === 0) return setError("請求を追加してください"); if (!eventId) return setError("イベントIDが見つかりません"); setIsLoading(true); setError(null); try { - if (eventId) { - await deleteResult(eventId); - } const fetchedResults = await fetchResult({ algorithmId, - payments, + payments: currentPayments, eventId, }); setResults(fetchedResults); - if (eventId) { - await saveResult(eventId, fetchedResults); - } } catch (err) { setError( err instanceof Error ? err.message : "計算中にエラーが発生しました", @@ -60,19 +68,69 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { }; const handleCsvExport = () => { - const csv = generateCsv(members, results); + const csv = generateCsv(currentMembers, results); downloadCsv(csv); }; useEffect(() => { - const loadSavedResults = async () => { + const loadData = async () => { if (!eventId) return; - const saved = await getResult(eventId); - if (saved.length > 0) { - setResults(saved); + + const pkToMemberId: Record = {}; + + // 最新のメンバー情報を取得 + try { + const memRes = await fetch(`http://127.0.0.1:8000/api/v1/members/?event_id=${eventId}`); + if (memRes.ok) { + const memData = await memRes.json(); + + if (Array.isArray(memData)) { + (memData as MemberResponse[]).forEach((p) => { + pkToMemberId[p.id] = Number(p.member_id); + if (pkToMemberId[p.id] === undefined) pkToMemberId[p.id] = Number(p.member_id); + }); + + const fetchedMembers = (memData as MemberResponse[]).map((p) => ({ + id: Number(p.member_id), + name: p.name, + })); + setCurrentMembers(fetchedMembers); // 画面のメンバーリストを最新にする + } + } + } catch (e) { + console.error("メンバー情報の更新に失敗", e); + } + + // 最新の支払い情報を取得 + try { + const payRes = await fetch(`http://127.0.0.1:8000/api/v1/payments/?event_id=${eventId}`); + if (payRes.ok) { + const payData = await payRes.json(); + if (Array.isArray(payData)) { + const fetchedPayments = (payData as PaymentResponse[]).map((p) => { + const paidByMapped = pkToMemberId[Number(p.paid_by)]; + const paidForMapped = pkToMemberId[Number(p.paid_for)]; + + const finalPaidBy = paidByMapped !== undefined ? paidByMapped : Number(p.paid_by); + const finalPaidFor = paidForMapped !== undefined ? paidForMapped : Number(p.paid_for); + + return { + id: Number(p.payment_id), + paidBy: finalPaidBy, + paidFor: finalPaidFor, + amount: p.amount, + memo: p.note, + }; + }); + setCurrentPayments(fetchedPayments); // 画面の支払いリストを最新にする + } + } + } catch (e) { + console.error("支払い情報の更新に失敗", e); } }; - loadSavedResults(); + + loadData(); }, [eventId]); return ( @@ -102,7 +160,7 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { From 23ad28079d3a4e62d3d5c3bfc5d1f41c2f3995b6 Mon Sep 17 00:00:00 2001 From: fuyuUSG <234328264+fuyuUSG@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:25:38 +0900 Subject: [PATCH 5/7] =?UTF-8?q?frontend:ci=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AlgorithmAndResultPage.tsx | 20 +++++++++++----- frontend/src/pages/BillingPage.tsx | 24 ++++++++++++------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/frontend/src/pages/AlgorithmAndResultPage.tsx b/frontend/src/pages/AlgorithmAndResultPage.tsx index b35ecb9..d4c962f 100644 --- a/frontend/src/pages/AlgorithmAndResultPage.tsx +++ b/frontend/src/pages/AlgorithmAndResultPage.tsx @@ -57,7 +57,6 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { eventId, }); setResults(fetchedResults); - } catch (err) { setError( err instanceof Error ? err.message : "計算中にエラーが発生しました", @@ -80,14 +79,17 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { // 最新のメンバー情報を取得 try { - const memRes = await fetch(`http://127.0.0.1:8000/api/v1/members/?event_id=${eventId}`); + const memRes = await fetch( + `http://127.0.0.1:8000/api/v1/members/?event_id=${eventId}`, + ); if (memRes.ok) { const memData = await memRes.json(); if (Array.isArray(memData)) { (memData as MemberResponse[]).forEach((p) => { pkToMemberId[p.id] = Number(p.member_id); - if (pkToMemberId[p.id] === undefined) pkToMemberId[p.id] = Number(p.member_id); + if (pkToMemberId[p.id] === undefined) + pkToMemberId[p.id] = Number(p.member_id); }); const fetchedMembers = (memData as MemberResponse[]).map((p) => ({ @@ -103,7 +105,9 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { // 最新の支払い情報を取得 try { - const payRes = await fetch(`http://127.0.0.1:8000/api/v1/payments/?event_id=${eventId}`); + const payRes = await fetch( + `http://127.0.0.1:8000/api/v1/payments/?event_id=${eventId}`, + ); if (payRes.ok) { const payData = await payRes.json(); if (Array.isArray(payData)) { @@ -111,8 +115,12 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { const paidByMapped = pkToMemberId[Number(p.paid_by)]; const paidForMapped = pkToMemberId[Number(p.paid_for)]; - const finalPaidBy = paidByMapped !== undefined ? paidByMapped : Number(p.paid_by); - const finalPaidFor = paidForMapped !== undefined ? paidForMapped : Number(p.paid_for); + const finalPaidBy = + paidByMapped !== undefined ? paidByMapped : Number(p.paid_by); + const finalPaidFor = + paidForMapped !== undefined + ? paidForMapped + : Number(p.paid_for); return { id: Number(p.payment_id), diff --git a/frontend/src/pages/BillingPage.tsx b/frontend/src/pages/BillingPage.tsx index 91f1288..c5169e1 100644 --- a/frontend/src/pages/BillingPage.tsx +++ b/frontend/src/pages/BillingPage.tsx @@ -68,7 +68,6 @@ export default function BillingPage({ // 保存が成功したら次の画面に進む navigate(`/${eventId}/algorithmAndresults`); - } catch (error) { console.error("保存エラー:", error); alert("データの保存に失敗しました。もう一度お試しください。"); @@ -83,25 +82,30 @@ export default function BillingPage({ const pkToMemberId: Record = {}; try { - const memRes = await fetch(`http://127.0.0.1:8000/api/v1/members/?event_id=${eventId}`); + const memRes = await fetch( + `http://127.0.0.1:8000/api/v1/members/?event_id=${eventId}`, + ); if (!memRes.ok) throw new Error("Members fetch failed"); const memData = await memRes.json(); if (Array.isArray(memData)) { (memData as MemberResponse[]).forEach((p) => { pkToMemberId[p.id] = Number(p.member_id); - if (pkToMemberId[p.id] === undefined) pkToMemberId[p.id] = Number(p.member_id); + if (pkToMemberId[p.id] === undefined) + pkToMemberId[p.id] = Number(p.member_id); }); setMembers( (memData as MemberResponse[]).map((p) => ({ id: Number(p.member_id), name: p.name, - })) + })), ); } - const payRes = await fetch(`http://127.0.0.1:8000/api/v1/payments/?event_id=${eventId}`); + const payRes = await fetch( + `http://127.0.0.1:8000/api/v1/payments/?event_id=${eventId}`, + ); if (!payRes.ok) throw new Error("Payments fetch failed"); const payData = await payRes.json(); @@ -111,8 +115,12 @@ export default function BillingPage({ const paidByMapped = pkToMemberId[Number(p.paid_by)]; const paidForMapped = pkToMemberId[Number(p.paid_for)]; - const finalPaidBy = paidByMapped !== undefined ? paidByMapped : Number(p.paid_by); - const finalPaidFor = paidForMapped !== undefined ? paidForMapped : Number(p.paid_for); + const finalPaidBy = + paidByMapped !== undefined ? paidByMapped : Number(p.paid_by); + const finalPaidFor = + paidForMapped !== undefined + ? paidForMapped + : Number(p.paid_for); return { id: p.payment_id, @@ -121,7 +129,7 @@ export default function BillingPage({ amount: p.amount, memo: p.note, }; - }) + }), ); } } catch (err) { From 03e3418756294b73d605100eae8601f01e3067c4 Mon Sep 17 00:00:00 2001 From: fuyuUSG <234328264+fuyuUSG@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:28:13 +0900 Subject: [PATCH 6/7] =?UTF-8?q?frontend:ci=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E5=86=8D=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AlgorithmAndResultPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/AlgorithmAndResultPage.tsx b/frontend/src/pages/AlgorithmAndResultPage.tsx index d4c962f..a7ee8c6 100644 --- a/frontend/src/pages/AlgorithmAndResultPage.tsx +++ b/frontend/src/pages/AlgorithmAndResultPage.tsx @@ -118,7 +118,7 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { const finalPaidBy = paidByMapped !== undefined ? paidByMapped : Number(p.paid_by); const finalPaidFor = - paidForMapped !== undefined + paidForMapped !== undefined ? paidForMapped : Number(p.paid_for); From 46f209ddffe3cc3552e0bb1878e0b0b856010048 Mon Sep 17 00:00:00 2001 From: fuyuUSG <234328264+fuyuUSG@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:33:43 +0900 Subject: [PATCH 7/7] =?UTF-8?q?frontend:ci=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/AlgorithmAndResultPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/AlgorithmAndResultPage.tsx b/frontend/src/pages/AlgorithmAndResultPage.tsx index a7ee8c6..f3bfdc5 100644 --- a/frontend/src/pages/AlgorithmAndResultPage.tsx +++ b/frontend/src/pages/AlgorithmAndResultPage.tsx @@ -76,7 +76,7 @@ export default function AlgorithmAndResultPage({ payments, members }: Props) { if (!eventId) return; const pkToMemberId: Record = {}; - + // 最新のメンバー情報を取得 try { const memRes = await fetch(