# workflows.py from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status from django.db import transaction from django.db.models import F from decimal import Decimal from app.models import ( Transaction,User, Transaction_Detail, Product, Product_Status, Payment_Schedule, Transaction_Phase, Transaction_Status, Payment_Status, Entry_Category, Payment_Plan,Product_Booked ) from app.payment import account_entry_api from datetime import datetime, timedelta # Corrected: Import timedelta @api_view(['POST']) @transaction.atomic def create_reservation(request): """ Tạo một giao dịch giữ chỗ (24h hoặc giữ chỗ thường). """ data = request.data try: phase_code = data.get('phase_code') deposit_received = data.get('deposit_received') customer_id = data.get('customer_id') product_id = data.get('product_id') policy_id = data.get('policy_id') user_id = data.get('user_id') origin_price = data.get('origin_price') discount_amount = data.get('discount_amount', 0) sale_price = data.get('sale_price') deposit_amount = data.get('deposit_amount') installments = data.get('installments', []) payment_plan = data.get('payment_plan', None) reservation_phase = Transaction_Phase.objects.get(code=phase_code) initial_detail_status = Transaction_Status.objects.get(code='new') # Correction: Use 'unpaid' for unconfirmed payment status unconfirmed_payment_status = Payment_Status.objects.get(code='unpaid') if phase_code == 'reserved24H': product_status_code = 'resvered24H' elif phase_code == 'deposit': product_status_code = 'deposit' elif phase_code == 'fulfillwish': product_status_code = 'deposit' else: product_status_code = 'resvered' product_status_reserved = Product_Status.objects.get(code=product_status_code) transaction_obj = Transaction.objects.create( customer_id=customer_id, product_id=product_id, policy_id=policy_id, phase=reservation_phase, date=datetime.now().strftime('%Y-%m-%d'), origin_price=origin_price, discount_amount=discount_amount, sale_price=sale_price, deposit_amount=deposit_amount, deposit_received=0, deposit_remaining=deposit_amount, amount_received=0, payment_plan=payment_plan ) amount_recived = installments[0].get('amount') if installments else 0 transaction_detail_obj = Transaction_Detail.objects.create( transaction=transaction_obj, phase=reservation_phase, status=initial_detail_status, date=datetime.now().strftime('%Y-%m-%d'),amount=deposit_amount,amount_recived=amount_recived, due_date=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=2), creator_id=user_id ) product_obj = Product.objects.get(id=product_id) product_obj.status = product_status_reserved product_obj.save() product_booked_obj = Product_Booked.objects.create( transaction=transaction_obj, product=product_obj ) schedules = [] for i, installment in enumerate(installments): schedule = Payment_Schedule.objects.create( txn_detail=transaction_detail_obj, from_date=datetime.now().strftime('%Y-%m-%d'), to_date=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=installment.get('due_days', 0)), amount=installment.get('amount'), cycle=i + 1, cycle_days=installment.get('due_days', 0), type_id=1, status=unconfirmed_payment_status, updater_id=user_id, detail={'note': f"Thanh toán cọc đợt {i+1}"} ) schedules.append(schedule.id) return Response({ 'message': f'Giao dịch "{reservation_phase.name}" đã được tạo thành công.', 'transaction_id': transaction_obj.id, 'transaction_code': transaction_obj.code, 'transaction_detail_id': transaction_detail_obj.id, 'product_status': product_obj.status.name, 'payment_schedule_ids': schedules }, status=status.HTTP_201_CREATED) except Exception as e: return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) @api_view(['POST']) @transaction.atomic def confirm_payment_schedule(request): """ Kế toán xác nhận một công nợ (Payment_Schedule). Job chạy ngầm sẽ lo việc chuyển trạng thái detail. """ data = request.data try: payment_schedule_id = data.get('payment_schedule_id') accountant_id = data.get('user_id') internal_account_code = data.get('internal_account_code', 'HOAC02VND') # Correction: Use 'paid' for confirmed payment status confirmed_payment_status = Payment_Status.objects.get(code='paid') schedule_obj = Payment_Schedule.objects.select_related('txn_detail', 'txn_detail__transaction__customer', 'status').get(id=payment_schedule_id) if schedule_obj.status.code == 'paid': return Response({'message': 'Công nợ này đã được xác nhận trước đó.'}, status=status.HTTP_200_OK) schedule_obj.status = confirmed_payment_status schedule_obj.updater_id = accountant_id entry_content = f"Xác nhận thanh toán đợt {schedule_obj.cycle} cho GD {schedule_obj.txn_detail.code} của KH {schedule_obj.txn_detail.transaction.customer.code}" deposit_category = Entry_Category.objects.get(code='THU_COC') entry_data = account_entry_api( code=internal_account_code, amount=schedule_obj.amount, content=entry_content, type='CR', category=deposit_category.id, userid=accountant_id, ref=schedule_obj.txn_detail.code ) if 'error' in entry_data: raise Exception(entry_data['error']) schedule_obj.entry_id = entry_data['id'] schedule_obj.save() transaction_obj = schedule_obj.txn_detail.transaction schedule_amount = Decimal(str(schedule_obj.amount)) transaction_obj.amount_received = (transaction_obj.amount_received or Decimal('0')) + schedule_amount transaction_obj.deposit_received = (transaction_obj.deposit_received or Decimal('0')) + schedule_amount transaction_obj.deposit_remaining = (transaction_obj.deposit_remaining or Decimal('0')) - schedule_amount transaction_obj.save() return Response({ 'message': 'Công nợ đã được xác nhận thành công.' }, status=status.HTTP_200_OK) except Exception as e: return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) @api_view(['POST']) @transaction.atomic def approve_transaction_detail(request): """ Duyệt một Transaction_Detail. Nếu phase là 'reserved24H', sẽ tự động chuyển Transaction sang phase 'reserved'. """ data = request.data try: transaction_detail_id = data.get('transaction_detail_id') approver_id = data.get('user_id') detail_obj = Transaction_Detail.objects.select_related('status', 'phase', 'transaction').get(id=transaction_detail_id) user_obj = User.objects.get(id=approver_id) if detail_obj.status.code != 'pending': return Response( {'error': f'Giao dịch không ở trạng thái "Chờ duyệt". Trạng thái hiện tại: {detail_obj.status.name}'}, status=status.HTTP_400_BAD_REQUEST ) # Correction: Use 'approved' for approved status approved_status = Transaction_Status.objects.get(code='approved') detail_obj.status = approved_status detail_obj.approver = user_obj detail_obj.approve_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') detail_obj.save() if detail_obj.phase.code == 'reserved24H': transaction_obj = detail_obj.transaction next_phase_obj = Transaction_Phase.objects.get(code='reserved') transaction_obj.phase = next_phase_obj transaction_obj.save() productId = transaction_obj.product_id product_obj = Product.objects.get(id=productId) product_obj.status = Product_Status.objects.get(code='resvered') product_obj.save() new_detail_obj = Transaction_Detail.objects.create( transaction=transaction_obj, phase=next_phase_obj, status=Transaction_Status.objects.get(code='new'), date=datetime.now().strftime('%Y-%m-%d'), amount=0, creator_id=approver_id ) return Response({ 'message': 'Giao dịch đã được duyệt và tự động chuyển sang giai đoạn "Giữ chỗ".', 'transaction_id': transaction_obj.id, 'new_transaction_phase': next_phase_obj.name, 'new_transaction_detail_id': new_detail_obj.id }, status=status.HTTP_200_OK) else: return Response({ 'message': 'Giao dịch đã được duyệt thành công.', 'transaction_detail_id': detail_obj.id, 'new_status': approved_status.name }, status=status.HTTP_200_OK) except Exception as e: return Response({'error ': str(e)}, status=status.HTTP_400_BAD_REQUEST) @api_view(['POST']) @transaction.atomic def advance_transaction_phase(request): """ Chuyển một Transaction sang một giai đoạn mới và tự động tạo công nợ nếu cần. """ data = request.data try: transaction_id = data.get('transaction_id') next_phase_code = data.get('next_phase_code') user_id = data.get('user_id') product_status = data.get('product_status') if not all([transaction_id, next_phase_code, user_id]): return Response({'error': 'transaction_id, next_phase_code, và user_id là bắt buộc.'}, status=status.HTTP_400_BAD_REQUEST) transaction_obj = Transaction.objects.select_related('phase', 'product', 'policy').get(id=transaction_id) next_phase_obj = Transaction_Phase.objects.get(code=next_phase_code) initial_detail_status = Transaction_Status.objects.get(code='new') next_product_status = Product_Status.objects.get(code=product_status) transaction_obj.phase = next_phase_obj transaction_obj.save() productId = transaction_obj.product_id product_obj = Product.objects.get(id=productId) product_obj.status = next_product_status product_obj.save() new_detail_obj = Transaction_Detail.objects.create( transaction=transaction_obj, phase=next_phase_obj, status=initial_detail_status, date=datetime.now().strftime('%Y-%m-%d'), amount=0, creator_id=user_id ) if next_phase_code == 'pertrade': unconfirmed_payment_status = Payment_Status.objects.get(code='unpaid') total_plan_amount = 0 # Ưu tiên kế hoạch thanh toán riêng trong giao dịch if transaction_obj.payment_plan and isinstance(transaction_obj.payment_plan, list): for i, plan_item in enumerate(transaction_obj.payment_plan): plan_amount = Decimal(str(plan_item.get('amount', 0))) due_days = plan_item.get('due_days', 0) total_plan_amount += plan_amount Payment_Schedule.objects.create( txn_detail=new_detail_obj, from_date=datetime.now().strftime('%Y-%m-%d'), to_date=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=due_days), amount=plan_amount, cycle=i + 1, cycle_days=due_days, type_id=2, # Giả sử type_id=2 cho thanh toán mua bán status=unconfirmed_payment_status, updater_id=user_id, detail={'note': f"Thanh toán đợt {i + 1} theo kế hoạch riêng"} ) new_detail_obj.amount = total_plan_amount new_detail_obj.save() # Nếu không có kế hoạch riêng, dùng chính sách bán hàng elif transaction_obj.policy: payment_plans_from_policy = Payment_Plan.objects.filter(policy=transaction_obj.policy).select_related('type').order_by('cycle') if payment_plans_from_policy.exists(): for plan in payment_plans_from_policy: plan_amount = 0 if plan.type.code == 'percentage': plan_amount = (transaction_obj.sale_price * plan.value) / 100 elif plan.type.code == 'money': plan_amount = plan.value total_plan_amount += Decimal(str(plan_amount)) Payment_Schedule.objects.create( txn_detail=new_detail_obj, from_date=datetime.now().strftime('%Y-%m-%d'), to_date=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=plan.days), amount=plan_amount, cycle=plan.cycle, cycle_days=plan.days, type_id=2, status=unconfirmed_payment_status, updater_id=user_id, detail={'note': f"Thanh toán đợt {plan.cycle} theo chính sách {transaction_obj.policy.name}"} ) new_detail_obj.amount = total_plan_amount new_detail_obj.save() else: raise Exception("Giao dịch không có chính sách bán hàng hoặc kế hoạch thanh toán riêng để tạo công nợ.") return Response({ 'message': f'Giao dịch đã được chuyển thành công sang giai đoạn "{next_phase_obj.name}".', 'transaction_id': transaction_obj.id, 'new_transaction_detail_id': new_detail_obj.id, 'new_transaction_detail_code': new_detail_obj.code, 'product_id': productId, 'customer':transaction_obj.customer.id, 'new_product_status': next_product_status.name }, status=status.HTTP_200_OK) except Transaction.DoesNotExist: return Response({'error': 'Giao dịch không tồn tại.'}, status=status.HTTP_404_NOT_FOUND) except Transaction_Phase.DoesNotExist: return Response({'error': f'Giai đoạn mới "{next_phase_code}" không hợp lệ.'}, status=status.HTTP_400_BAD_REQUEST) except Exception as e: return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)