From d301ddcbfbcbac879190204a185ea7e5eb8620ab Mon Sep 17 00:00:00 2001 From: president Date: Sun, 21 Dec 2025 11:56:38 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=87=E3=83=BC=E3=82=BF=E3=83=99=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=83=AB=E3=83=BC=E3=83=AB=E3=81=ABowner=5Ftype?= =?UTF-8?q?=E3=81=A8is=5Fcanceled=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?= =?UTF-8?q?=E3=80=81is=5Fbusiness=E3=82=92=E5=BB=83=E6=AD=A2=E3=80=82Expen?= =?UTF-8?q?se=E3=83=A2=E3=83=87=E3=83=AB=E3=81=A8=E9=96=A2=E9=80=A3?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=83=9E=E3=82=A4=E3=82=B0=E3=83=AC=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E5=AE=9F=E6=96=BD=E3=80=82?= =?UTF-8?q?=E6=98=8E=E7=B4=B0=E7=B7=A8=E9=9B=86UI=E3=82=92owner=5Ftype?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=E3=81=AB=E6=9B=B4=E6=96=B0=E3=81=97=E3=80=81?= =?UTF-8?q?=E5=8F=96=E3=82=8A=E6=B6=88=E3=81=97=E6=B8=88=E3=81=BF=E3=81=AE?= =?UTF-8?q?=E7=B5=8C=E8=B2=BB=E3=82=92=E4=B8=80=E8=A6=A7=E3=81=8B=E3=82=89?= =?UTF-8?q?=E9=99=A4=E5=A4=96=E3=80=82=E4=BD=9C=E6=A5=AD=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E3=82=92diary.md=E3=81=AB=E8=BF=BD=E8=A8=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Doc/diary.md | 5 ++ database.rules | 3 + expenses/migrations/0002_owner_type_cancel.py | 56 +++++++++++++++++++ expenses/models.py | 8 ++- expenses/views.py | 21 +++++-- templates/expenses/expense_list.html | 52 ++++------------- 仕様書.md | 3 +- 7 files changed, 100 insertions(+), 48 deletions(-) create mode 100644 expenses/migrations/0002_owner_type_cancel.py diff --git a/Doc/diary.md b/Doc/diary.md index 25eaac2..20bde23 100644 --- a/Doc/diary.md +++ b/Doc/diary.md @@ -17,3 +17,8 @@ - CSV取込後に明細編集画面へリダイレクトし、取込結果を一覧画面に表示 - 出光CSVパーサに備考/続行行の結合と外貨備考の取り込みを追加 - source_hashに備考を含め、noteをExpenseへ保存 +- database.rules に合わせて owner_type と is_canceled を導入し is_business を廃止 +- 明細編集UI/APIを owner_type 選択に更新、取り消し済みは一覧から除外 +- 0002_owner_type_cancel マイグレーションを追加し既存データを移行 +- 仕様書のDB項目を owner_type / is_canceled に更新 +- リモートDBに対してマイグレーションを実行 diff --git a/database.rules b/database.rules index 73514a7..9eda544 100644 --- a/database.rules +++ b/database.rules @@ -30,3 +30,6 @@ root@x85-131-243-202:~# sudo -u postgres psql -c "\du" postgres | Superuser, Create role, Create DB, Replication, Bypass RLS president | + +source .venv/bin/activate +DB_NAME=accounting DB_USER=account_user DB_PASSWORD=account_Hideyukey-1234 DB_HOST=labo.sunamura-llc.com DB_PORT=5432 .venv/bin/python manage.py runserver diff --git a/expenses/migrations/0002_owner_type_cancel.py b/expenses/migrations/0002_owner_type_cancel.py new file mode 100644 index 0000000..612a4e8 --- /dev/null +++ b/expenses/migrations/0002_owner_type_cancel.py @@ -0,0 +1,56 @@ +# Generated by Django 4.2.27 on 2025-12-20 00:00 + +from django.db import migrations, models + + +def map_is_business_to_owner_type(apps, schema_editor): + Expense = apps.get_model('expenses', 'Expense') + for expense in Expense.objects.all(): + if expense.is_business is True: + expense.owner_type = 'company' + elif expense.is_business is False: + expense.owner_type = 'personal' + else: + expense.owner_type = 'pending' + expense.save(update_fields=['owner_type']) + + +def reverse_owner_type_to_is_business(apps, schema_editor): + Expense = apps.get_model('expenses', 'Expense') + for expense in Expense.objects.all(): + if expense.owner_type == 'company': + expense.is_business = True + elif expense.owner_type == 'personal': + expense.is_business = False + else: + expense.is_business = None + expense.save(update_fields=['is_business']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('expenses', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='expense', + name='owner_type', + field=models.CharField( + choices=[('company', 'Company'), ('personal', 'Personal'), ('pending', 'Pending')], + default='pending', + max_length=20, + ), + ), + migrations.AddField( + model_name='expense', + name='is_canceled', + field=models.BooleanField(default=False), + ), + migrations.RunPython(map_is_business_to_owner_type, reverse_owner_type_to_is_business), + migrations.RemoveField( + model_name='expense', + name='is_business', + ), + ] diff --git a/expenses/models.py b/expenses/models.py index d5d4aee..bb7f34f 100644 --- a/expenses/models.py +++ b/expenses/models.py @@ -21,6 +21,11 @@ class Expense(models.Model): SOURCE_CHOICES = [ ('idemitsu', 'Idemitsu'), ] + OWNER_TYPE_CHOICES = [ + ('company', 'Company'), + ('personal', 'Personal'), + ('pending', 'Pending'), + ] use_date = models.DateField() description = models.CharField(max_length=255) @@ -29,12 +34,13 @@ class Expense(models.Model): expense_category = models.ForeignKey( ExpenseCategory, null=True, blank=True, on_delete=models.SET_NULL ) - is_business = models.BooleanField(null=True, blank=True) + owner_type = models.CharField(max_length=20, choices=OWNER_TYPE_CHOICES, default='pending') note = models.TextField(blank=True) source = models.CharField(max_length=50, choices=SOURCE_CHOICES) source_hash = models.CharField(max_length=64) ai_score = models.FloatField(null=True, blank=True) human_confirmed = models.BooleanField(default=False) + is_canceled = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/expenses/views.py b/expenses/views.py index 28cfa6f..26369ac 100644 --- a/expenses/views.py +++ b/expenses/views.py @@ -30,7 +30,11 @@ def csv_upload(request): def expense_list(request): - expenses = Expense.objects.select_related('store', 'expense_category').order_by('-use_date', '-id')[:200] + expenses = ( + Expense.objects.filter(is_canceled=False) + .select_related('store', 'expense_category') + .order_by('-use_date', '-id')[:200] + ) stores = Store.objects.filter(is_active=True).order_by('name') categories = ExpenseCategory.objects.filter(is_active=True).order_by('name') return render( @@ -57,11 +61,14 @@ def expense_update(request, expense_id: int): return JsonResponse({'status': 'error', 'message': 'JSONはオブジェクトで送信してください。'}, status=400) expense = get_object_or_404(Expense, pk=expense_id) fields = {} - for key in ('store_id', 'expense_category_id', 'is_business', 'note'): + for key in ('store_id', 'expense_category_id', 'owner_type', 'note'): if key in payload: value = payload[key] if value in ('', None): - fields[key] = None + if key == 'owner_type': + fields[key] = 'pending' + else: + fields[key] = None else: fields[key] = value if 'store_id' in fields and fields['store_id'] is not None: @@ -78,9 +85,11 @@ def expense_update(request, expense_id: int): return JsonResponse( {'status': 'error', 'message': 'expense_category_idが不正です。'}, status=400 ) - if 'is_business' in fields and fields['is_business'] is not None: - if not isinstance(fields['is_business'], bool): - return JsonResponse({'status': 'error', 'message': 'is_businessの型が不正です。'}, status=400) + if 'owner_type' in fields and fields['owner_type'] is not None: + if not isinstance(fields['owner_type'], str): + return JsonResponse({'status': 'error', 'message': 'owner_typeの型が不正です。'}, status=400) + if fields['owner_type'] not in {'company', 'personal', 'pending'}: + return JsonResponse({'status': 'error', 'message': 'owner_typeが不正です。'}, status=400) if 'note' in fields and fields['note'] is not None: if not isinstance(fields['note'], str): return JsonResponse({'status': 'error', 'message': 'noteの型が不正です。'}, status=400) diff --git a/templates/expenses/expense_list.html b/templates/expenses/expense_list.html index c03e188..bde39c1 100644 --- a/templates/expenses/expense_list.html +++ b/templates/expenses/expense_list.html @@ -19,7 +19,7 @@ 金額 店舗区分 経費区分 - 会社/家計 + 区分 備考 @@ -50,39 +50,17 @@ - - - +