changes
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -14,10 +14,9 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: re_path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.urls import re_path
|
||||
from app import views, cob, payment, contract, cleardata, email, backup, server, workflows,api_workflow, importdata
|
||||
from app import views, cob, payment, contract, cleardata, email, backup, server,api_workflow, importdata
|
||||
|
||||
urlpatterns = [
|
||||
# New Workflow Endpoints
|
||||
re_path("workflow/execute/$", api_workflow.execute_workflow),
|
||||
|
||||
# Existing Endpoints
|
||||
|
||||
325
app/workflows.py
325
app/workflows.py
@@ -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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user