This commit is contained in:
anhduy-tech
2026-01-04 22:49:23 +07:00
parent 476ecf5f9f
commit 15bb6720b6
31 changed files with 1 additions and 327 deletions

View File

@@ -1,325 +0,0 @@
# 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)