import io import json 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 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) result = import_csv_lines(lines) 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): expenses = Expense.objects.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( 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'), }, ) @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', 'is_business', 'note'): if key in payload: value = payload[key] if value in ('', None): 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 '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 '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): return render(request, 'expenses/monthly_report.html') def monthly_report_pdf(request): return render(request, 'expenses/monthly_report_pdf.html')