Files
Card-data-sorting/expenses/views.py

180 lines
7.4 KiB
Python

import io
import json
from datetime import date, datetime
from urllib.parse import urlencode
import chardet
from django.http import JsonResponse
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 build_monthly_report, import_csv_lines
from .models import Expense, ExpenseCategory, Store
def csv_upload(request):
context = {}
if request.method == 'POST' and request.FILES.get('csv_file'):
upload = request.FILES['csv_file']
raw = upload.read()
detected = chardet.detect(raw)
encoding = detected.get('encoding') or 'utf-8'
text = raw.decode(encoding, errors='ignore')
lines = io.StringIO(text)
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}
)
return redirect(f"/expenses/?{query}")
return render(request, 'expenses/csv_upload.html', context)
def expense_list(request):
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(
request,
'expenses/expense_list.html',
{
'expenses': expenses,
'stores': stores,
'categories': categories,
'imported': request.GET.get('imported'),
'duplicated': request.GET.get('duplicated'),
'encoding': request.GET.get('encoding'),
'include_canceled': include_canceled,
},
)
@require_POST
def expense_update(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)
expense = get_object_or_404(Expense, pk=expense_id)
fields = {}
for key in ('store_id', 'expense_category_id', 'owner_type', 'note'):
if key in payload:
value = payload[key]
if value in ('', 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:
if not isinstance(fields['store_id'], int):
return JsonResponse({'status': 'error', 'message': 'store_idの型が不正です。'}, status=400)
if not Store.objects.filter(id=fields['store_id'], is_active=True).exists():
return JsonResponse({'status': 'error', 'message': 'store_idが不正です。'}, status=400)
if 'expense_category_id' in fields and fields['expense_category_id'] is not None:
if not isinstance(fields['expense_category_id'], int):
return JsonResponse({'status': 'error', 'message': 'expense_category_idの型が不正です。'}, status=400)
if not ExpenseCategory.objects.filter(
id=fields['expense_category_id'], is_active=True
).exists():
return JsonResponse(
{'status': 'error', 'message': 'expense_category_idが不正です。'}, 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)
if len(fields['note']) > 2000:
return JsonResponse({'status': 'error', 'message': 'noteが長すぎます。'}, status=400)
if fields:
for key, value in fields.items():
setattr(expense, key, value)
expense.human_confirmed = True
expense.save()
return JsonResponse({'status': 'ok'})
def monthly_report(request):
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):
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'})