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 @@