月次レポート機能を実装し、経費の取り消し・復帰機能を追加。CSV取込画面にドラッグ&ドロップエリアを実装し、エラーメッセージ表示を追加。金額のカンマ区切り表示を全体に適用。faviconとapple-touch-iconを追加し、404エラーを回避。作業ログをdiary.mdに追記。
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import io
|
||||
import json
|
||||
from datetime import date, datetime
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import chardet
|
||||
@@ -8,7 +9,7 @@ from django.shortcuts import redirect
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from .services import import_csv_lines
|
||||
from .services import build_monthly_report, import_csv_lines
|
||||
from .models import Expense, ExpenseCategory, Store
|
||||
|
||||
|
||||
@@ -21,7 +22,11 @@ def csv_upload(request):
|
||||
encoding = detected.get('encoding') or 'utf-8'
|
||||
text = raw.decode(encoding, errors='ignore')
|
||||
lines = io.StringIO(text)
|
||||
result = import_csv_lines(lines)
|
||||
try:
|
||||
result = import_csv_lines(lines)
|
||||
except ValueError as exc:
|
||||
context['error_message'] = str(exc)
|
||||
return render(request, 'expenses/csv_upload.html', context)
|
||||
query = urlencode(
|
||||
{'imported': result.imported, 'duplicated': result.duplicated, 'encoding': encoding}
|
||||
)
|
||||
@@ -30,11 +35,11 @@ def csv_upload(request):
|
||||
|
||||
|
||||
def expense_list(request):
|
||||
expenses = (
|
||||
Expense.objects.filter(is_canceled=False)
|
||||
.select_related('store', 'expense_category')
|
||||
.order_by('-use_date', '-id')[:200]
|
||||
)
|
||||
include_canceled = request.GET.get('include_canceled') == '1'
|
||||
queryset = Expense.objects.select_related('store', 'expense_category')
|
||||
if not include_canceled:
|
||||
queryset = queryset.filter(is_canceled=False)
|
||||
expenses = queryset.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(
|
||||
@@ -47,6 +52,7 @@ def expense_list(request):
|
||||
'imported': request.GET.get('imported'),
|
||||
'duplicated': request.GET.get('duplicated'),
|
||||
'encoding': request.GET.get('encoding'),
|
||||
'include_canceled': include_canceled,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -104,8 +110,70 @@ def expense_update(request, expense_id: int):
|
||||
|
||||
|
||||
def monthly_report(request):
|
||||
return render(request, 'expenses/monthly_report.html')
|
||||
error_message = None
|
||||
target_month = request.GET.get('target_month')
|
||||
if target_month:
|
||||
try:
|
||||
start_date = datetime.strptime(target_month, '%Y-%m').date().replace(day=1)
|
||||
except ValueError:
|
||||
error_message = '年月の形式が不正です。'
|
||||
start_date = date.today().replace(day=1)
|
||||
else:
|
||||
start_date = date.today().replace(day=1)
|
||||
if start_date.month == 12:
|
||||
end_date = start_date.replace(year=start_date.year + 1, month=1)
|
||||
else:
|
||||
end_date = start_date.replace(month=start_date.month + 1)
|
||||
report = build_monthly_report(start_date, end_date)
|
||||
return render(
|
||||
request,
|
||||
'expenses/monthly_report.html',
|
||||
{
|
||||
'report': report,
|
||||
'target_month': start_date.strftime('%Y-%m'),
|
||||
'error_message': error_message,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def monthly_report_pdf(request):
|
||||
return render(request, 'expenses/monthly_report_pdf.html')
|
||||
target_month = request.GET.get('target_month')
|
||||
if target_month:
|
||||
try:
|
||||
start_date = datetime.strptime(target_month, '%Y-%m').date().replace(day=1)
|
||||
except ValueError:
|
||||
start_date = date.today().replace(day=1)
|
||||
else:
|
||||
start_date = date.today().replace(day=1)
|
||||
if start_date.month == 12:
|
||||
end_date = start_date.replace(year=start_date.year + 1, month=1)
|
||||
else:
|
||||
end_date = start_date.replace(month=start_date.month + 1)
|
||||
report = build_monthly_report(start_date, end_date)
|
||||
return render(
|
||||
request,
|
||||
'expenses/monthly_report_pdf.html',
|
||||
{
|
||||
'report': report,
|
||||
'target_month': start_date.strftime('%Y-%m'),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@require_POST
|
||||
def expense_cancel(request, expense_id: int):
|
||||
try:
|
||||
payload = json.loads(request.body.decode('utf-8'))
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'status': 'error', 'message': 'JSON形式が不正です。'}, status=400)
|
||||
if not isinstance(payload, dict):
|
||||
return JsonResponse({'status': 'error', 'message': 'JSONはオブジェクトで送信してください。'}, status=400)
|
||||
if 'is_canceled' not in payload:
|
||||
return JsonResponse({'status': 'error', 'message': 'is_canceledが必要です。'}, status=400)
|
||||
if not isinstance(payload['is_canceled'], bool):
|
||||
return JsonResponse({'status': 'error', 'message': 'is_canceledの型が不正です。'}, status=400)
|
||||
expense = get_object_or_404(Expense, pk=expense_id)
|
||||
expense.is_canceled = payload['is_canceled']
|
||||
expense.human_confirmed = True
|
||||
expense.save(update_fields=['is_canceled', 'human_confirmed', 'updated_at'])
|
||||
return JsonResponse({'status': 'ok'})
|
||||
|
||||
Reference in New Issue
Block a user