This commit is contained in:
anhduy-tech
2026-02-09 16:56:55 +07:00
parent d6eec950e9
commit c2efa46260
37 changed files with 199 additions and 121 deletions

View File

@@ -246,9 +246,11 @@ def allocate_payment_to_schedules(product_id):
# --- BƯỚC 4: LÃI DỰ PHÒNG ĐẾN NAY ---
days_from_entry_to_today = max(0, (today - entry_date).days)
print(f" - Lai du phong: {days_from_entry_to_today} , ngay nhap: {entry_date}, ngay hien tai: {today}")
additional_penalty_to_today = Decimal('0')
if amount_remain_after > 0:
additional_penalty_to_today = amount_remain_after * Decimal(days_from_entry_to_today) * DAILY_PENALTY_RATE
print(f"lai du phong la : {additional_penalty_to_today}")
# --- CẬP NHẬT DỮ LIỆU ---
sch.paid_amount = Decimal(str(sch.paid_amount or 0)) + to_principal
@@ -257,8 +259,11 @@ def allocate_payment_to_schedules(product_id):
sch.penalty_amount = penalty_to_this_entry + additional_penalty_to_today
sch.penalty_remain = max(Decimal('0'), sch.penalty_amount - sch.penalty_paid)
sch.remain_amount = sch.amount_remain + sch.penalty_remain
sch.ovd_days = days_for_trace + days_from_entry_to_today
if amount_remain_after > 0:
sch.ovd_days = max(0, (today - sch.to_date).days)
else :
sch.ovd_days = days_for_trace + days_from_entry_to_today
print(f"Lai la : {penalty_to_this_entry + additional_penalty_to_today} = {sch.penalty_amount}")
# Ghi Trace
sch_entry_list = sch.entry or []
sch_entry_list.append({
@@ -586,8 +591,11 @@ def account_multi_entry(request):
@api_view(['POST'])
def delete_entry(request):
"""View function để xóa bút toán (tương thích với urls.py)"""
"""Xóa bút toán - reset sạch entry = [], lưu hết trước, xóa entry sau, đặt lại txn/txndetail, rồi phân bổ lại"""
entry_id = request.data.get('id')
if not entry_id:
return Response({'error': 'Thiếu id bút toán'}, status=400)
try:
with transaction.atomic():
@@ -597,7 +605,7 @@ def delete_entry(request):
return Response({
'error': f'Bút toán với ID {entry_id} không tồn tại'
}, status=404)
entry_info = {
'id': entry.id,
'code': entry.code,
@@ -609,59 +617,70 @@ def delete_entry(request):
'allocation_amount': float(entry.allocation_amount or 0),
'allocation_remain': float(entry.allocation_remain or 0)
}
if entry.type.code != 'CR':
return Response({'error': 'Hiện chỉ hỗ trợ xóa bút toán thu tiền (CR)'}, status=400)
product_id = entry.product_id
if not product_id:
return Response({'error': 'Bút toán không gắn với product nào'}, status=400)
allocation_detail = entry.allocation_detail or []
schedules_reversed = []
total_principal_reversed = Decimal('0')
total_penalty_reversed = Decimal('0')
total_reduction_reversed = Decimal('0')
# =================================================================
# Bước 1: Reset các lịch bị ảnh hưởng theo công thức & lưu
# =================================================================
for allocation in allocation_detail:
schedule_id = allocation.get('schedule_id')
allocated_amount = Decimal(str(allocation.get('amount', 0)))
principal = Decimal(str(allocation.get('principal', 0)))
penalty = Decimal(str(allocation.get('penalty', 0)))
allocation_type = allocation.get('type', 'PAYMENT')
if not schedule_id or allocated_amount <= 0:
continue
try:
schedule = Payment_Schedule.objects.select_for_update().get(id=schedule_id)
current_amount_remain = Decimal(str(schedule.amount_remain or 0))
# Tính tổng principal đã phân bổ vào đúng lịch này từ tất cả entry CR
principal_allocated_to_schedule = Decimal('0')
for e in Internal_Entry.objects.filter(product_id=product_id, type__code='CR'):
for alloc in (e.allocation_detail or []):
if alloc.get('schedule_id') == schedule.id:
principal_allocated_to_schedule += Decimal(str(alloc.get('principal', 0)))
if allocation_type == 'REDUCTION':
schedule.penalty_reduce = (schedule.penalty_reduce or Decimal('0')) - allocated_amount
schedule.penalty_remain = (schedule.penalty_remain or Decimal('0')) + allocated_amount
schedule.remain_amount = (schedule.remain_amount or Decimal('0')) + allocated_amount
total_reduction_reversed += allocated_amount
schedule.save(update_fields=['penalty_reduce', 'penalty_remain', 'remain_amount'])
else:
schedule.paid_amount = (schedule.paid_amount or Decimal('0')) - principal
schedule.penalty_paid = (schedule.penalty_paid or Decimal('0')) - penalty
schedule.amount_remain = (schedule.amount_remain or Decimal('0')) + principal
schedule.penalty_remain = (schedule.penalty_remain or Decimal('0')) + penalty
schedule.remain_amount = (schedule.remain_amount or Decimal('0')) + allocated_amount
total_principal_reversed += principal
total_penalty_reversed += penalty
if schedule.amount_remain > 0 or schedule.penalty_remain > 0:
try:
unpaid_status = Payment_Status.objects.get(id=1)
schedule.status = unpaid_status
except Payment_Status.DoesNotExist:
pass
schedule.save(update_fields=[
'paid_amount', 'penalty_paid', 'amount_remain',
'penalty_remain', 'remain_amount', 'status'
])
# Xóa entry trace của bút toán này
schedule_entries = schedule.entry or []
schedule_entries = [e for e in schedule_entries if e.get('code') != entry.code]
schedule.entry = safe_json_serialize(schedule_entries)
schedule.save(update_fields=['entry'])
# Reset theo công thức
schedule.entry = []
schedule.amount_remain = current_amount_remain + principal_allocated_to_schedule
schedule.remain_amount = schedule.amount_remain
schedule.paid_amount = schedule.amount - schedule.amount_remain
schedule.penalty_paid = Decimal('0')
schedule.penalty_reduce = Decimal('0')
schedule.penalty_amount = Decimal('0')
schedule.penalty_remain = Decimal('0')
schedule.ovd_days = 0
schedule.status = Payment_Status.objects.get(id=1)
schedule.save(update_fields=[
'entry', 'paid_amount', 'amount_remain', 'remain_amount',
'penalty_paid', 'penalty_reduce', 'penalty_remain', 'ovd_days', 'status','penalty_amount'
])
schedules_reversed.append({
'schedule_id': schedule.id,
'schedule_code': schedule.code,
@@ -670,101 +689,146 @@ def delete_entry(request):
'penalty_reversed': float(penalty),
'type': allocation_type
})
except Payment_Schedule.DoesNotExist:
continue
# =================================================================
# Bước 2: Reset allocation của tất cả entry CR của sản phẩm & lưu
# =================================================================
all_cr_entries = Internal_Entry.objects.filter(
product_id=product_id,
type__code='CR'
)
for e in all_cr_entries:
e.allocation_detail = []
e.allocation_amount = Decimal('0')
e.allocation_remain = Decimal(str(e.amount))
e.save(update_fields=[
'allocation_detail', 'allocation_amount', 'allocation_remain'
])
# =================================================================
# Bước 3: Hoàn tác số dư tài khoản (lưu trước khi xóa entry)
# =================================================================
account = Internal_Account.objects.select_for_update().get(id=entry.account_id)
entry_amount = float(entry.amount)
if entry.type.code == 'CR':
account.balance = (account.balance or 0) - entry_amount
else:
account.balance = (account.balance or 0) + entry_amount
account.save(update_fields=['balance'])
# =================================================================
# Bước 4: XÓA ENTRY (sau khi reset và lưu hết)
# =================================================================
entry.delete()
# =================================================================
# Bước 5: ĐẶT LẠI Transaction & Transaction_Detail TRƯỚC KHI PHÂN BỔ LẠI
# =================================================================
txn_detail_updated = False
txn_updated = False
if entry.product:
try:
booked = Product_Booked.objects.filter(product=entry.product).first()
if booked and booked.transaction:
txn = booked.transaction
# Lấy tất cả lịch liên quan
all_schedules = Payment_Schedule.objects.filter(
txn_detail__transaction=txn
)
# Tính tổng paid_amount từ các lịch (trước khi phân bổ lại)
total_paid_all = Decimal('0')
total_remain_all = Decimal('0')
total_deposit_paid = Decimal('0')
for sch in all_schedules:
paid = Decimal(str(sch.paid_amount or 0))
remain = Decimal(str(sch.amount_remain or 0))
total_paid_all += paid
total_remain_all += remain
if sch.type_id == 1: # type=1 là lịch đặt cọc
total_deposit_paid += paid
# Tính lại Transaction_Detail
try:
current = Transaction_Current.objects.get(transaction=txn)
txn_detail = current.detail
except Transaction_Current.DoesNotExist:
txn_detail = Transaction_Detail.objects.filter(transaction=txn).order_by('-create_time').first()
if txn_detail:
if entry.account_id != 5:
fields_to_update = []
if total_principal_reversed > 0:
txn_detail.amount_received = F('amount_received') - total_principal_reversed
txn_detail.amount_remaining = F('amount_remaining') + total_principal_reversed
fields_to_update.extend(['amount_received', 'amount_remaining'])
if total_penalty_reversed > 0 and hasattr(txn_detail, 'penalty_amount'):
txn_detail.penalty_amount = F('penalty_amount') - total_penalty_reversed
fields_to_update.append('penalty_amount')
if fields_to_update:
txn_detail.save(update_fields=fields_to_update)
txn_detail.refresh_from_db()
txn_detail_updated = True
if txn_detail.amount_remaining > 0:
try:
unpaid_status = Transaction_Status.objects.get(id=1)
txn_detail.status = unpaid_status
txn_detail.save(update_fields=['status'])
except:
pass
if entry.account_id != 5:
fields_to_update = []
if total_principal_reversed > 0:
txn.amount_received = F('amount_received') - total_principal_reversed
txn.amount_remain = F('amount_remain') + total_principal_reversed
fields_to_update.extend(['amount_received', 'amount_remain'])
if total_penalty_reversed > 0 and hasattr(txn, 'penalty_amount'):
txn.penalty_amount = F('penalty_amount') - total_penalty_reversed
fields_to_update.append('penalty_amount')
if fields_to_update:
txn.save(update_fields=fields_to_update)
txn.refresh_from_db()
txn_updated = True
if txn.amount_remain > 0:
if txn_detail:
# amount_received = amount_remaining hiện tại + tổng paid_amount từ lịch
txn_detail.amount_received = Decimal(str(txn_detail.amount_remaining or 0)) + total_paid_all
# amount_remaining = amount - amount_received vừa tính
txn_detail.amount_remaining = Decimal(str(txn_detail.amount or 0)) - txn_detail.amount_received
txn_detail.save(update_fields=['amount_received', 'amount_remaining'])
txn_detail.refresh_from_db()
txn_detail_updated = True
if txn_detail.amount_remaining > 0:
try:
unpaid_status = Transaction_Status.objects.get(id=1)
txn.status = unpaid_status
txn.save(update_fields=['status'])
txn_detail.status = unpaid_status
txn_detail.save(update_fields=['status'])
except:
pass
# Tính lại Transaction - đặt lại các trường về trạng thái ban đầu
txn.amount_received = Decimal('0')
txn.amount_remain = Decimal(str(txn.amount or 0)) # amount gốc hợp đồng
if hasattr(txn, 'deposit_received'):
txn.deposit_received = Decimal('0')
txn.deposit_remaining = txn.deposit_amount if hasattr(txn, 'deposit_amount') else Decimal('0')
txn.save(update_fields=['amount_received', 'amount_remain', 'deposit_received', 'deposit_remaining'])
txn.refresh_from_db()
txn_updated = True
if txn.amount_remain > 0:
try:
unpaid_status = Transaction_Status.objects.get(id=1)
txn.status = unpaid_status
txn.save(update_fields=['status'])
except:
pass
except Exception as e:
print(f"Lỗi khi hoàn tác Transaction: {str(e)}")
account = Internal_Account.objects.select_for_update().get(id=entry.account_id)
entry_amount = float(entry.amount)
if entry.type.code == 'CR':
account.balance = (account.balance or 0) - entry_amount
else:
account.balance = (account.balance or 0) + entry_amount
account.save(update_fields=['balance'])
entry.delete()
return Response({
'success': True,
'message': 'Đã xóa bút toán và hoàn tác tất cả phân bổ thành công',
'entry': entry_info,
'reversed': {
'schedules_count': len(schedules_reversed),
'schedules': schedules_reversed,
'total_principal_reversed': float(total_principal_reversed),
'total_penalty_reversed': float(total_penalty_reversed),
'total_reduction_reversed': float(total_reduction_reversed),
'transaction_detail_updated': txn_detail_updated,
'transaction_updated': txn_updated
},
'account_balance_restored': True
})
print(f"Lỗi khi đặt lại Transaction trước phân bổ: {str(e)}")
# =================================================================
# Bước 6: Phân bổ lại toàn bộ sản phẩm (sẽ tự tính lại txn/txndetail đúng)
# =================================================================
def trigger_reallocate():
if product_id:
try:
allocate_payment_to_schedules(product_id)
allocate_penalty_reduction(product_id)
except Exception as exc:
print(f"Lỗi khi re-allocate sau xóa: {exc}")
traceback.print_exc()
transaction.on_commit(trigger_reallocate)
return Response({
'success': True,
'message': 'Đã xóa bút toán, reset sạch entry = [], lưu hết trước, xóa entry sau, đặt lại txn/txndetail trước phân bổ, đang phân bổ lại toàn bộ...',
'entry': entry_info,
'reversed': {
'schedules_count': len(schedules_reversed),
'schedules': schedules_reversed,
'transaction_detail_updated': txn_detail_updated,
'transaction_updated': txn_updated
},
'account_balance_restored': True
})
except Exception as e:
import traceback
print(traceback.format_exc())