changes
This commit is contained in:
@@ -122,8 +122,6 @@ def get_allocation_rule():
|
||||
|
||||
# ==========================================================================================
|
||||
|
||||
DAILY_PENALTY_RATE = Decimal('0.0005') # 0.05% mỗi ngày
|
||||
|
||||
def safe_json_serialize(obj):
|
||||
"""Serialize an toàn cho JSONField"""
|
||||
if isinstance(obj, (datetime, date)):
|
||||
@@ -220,6 +218,47 @@ def reset_cr_entries_allocation(product_id, exclude_entry_id=None):
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
def close_paid_schedules(txn):
|
||||
"""
|
||||
Quét toàn bộ Payment_Schedule thuộc txn_detail và đóng (status=2) những lịch
|
||||
đã được thanh toán đầy đủ.
|
||||
|
||||
Điều kiện đóng một lịch:
|
||||
- status_id == 1 (đang mở/chưa đóng)
|
||||
- amount_remain <= 0 (đã trả hết gốc)
|
||||
- penalty_remain <= 0 (đã trả hết phạt)
|
||||
|
||||
Hàm không raise exception — lỗi từng lịch được bỏ qua và in ra log,
|
||||
các lịch còn lại vẫn được xử lý bình thường.
|
||||
|
||||
Returns:
|
||||
list[int]: danh sách id các lịch vừa được đóng.
|
||||
"""
|
||||
all_txn_details = Transaction_Detail.objects.filter(transaction=txn)
|
||||
closed_ids = []
|
||||
try:
|
||||
paid_status = Payment_Status.objects.get(id=2)
|
||||
except Payment_Status.DoesNotExist:
|
||||
print("[close_paid_schedules] Không tìm thấy Payment_Status id=2, bỏ qua.")
|
||||
return closed_ids
|
||||
|
||||
for txn_detail in all_txn_details:
|
||||
schedules = Payment_Schedule.objects.filter(
|
||||
txn_detail=txn_detail,
|
||||
)
|
||||
|
||||
for sch in schedules:
|
||||
try:
|
||||
|
||||
if sch.remain_amount <= 0:
|
||||
sch.status = paid_status
|
||||
sch.save(update_fields=['status'])
|
||||
closed_ids.append(sch.id)
|
||||
except Exception as e:
|
||||
print(f"[close_paid_schedules] Lỗi khi đóng schedule {sch.id}: {e}")
|
||||
print(f"[close_paid_schedules] Đã đóng {len(closed_ids)} lịch")
|
||||
return closed_ids
|
||||
|
||||
def recalc_txn_from_schedules(txn, all_txn_details, paid_txn_status):
|
||||
"""
|
||||
Tính lại Transaction và tất cả Transaction_Detail từ trạng thái hiện tại của các lịch.
|
||||
@@ -280,6 +319,31 @@ def recalc_txn_from_schedules(txn, all_txn_details, paid_txn_status):
|
||||
txn.save(update_fields=['amount_received', 'amount_remain'])
|
||||
txn.refresh_from_db()
|
||||
|
||||
# ==========================================================================================
|
||||
|
||||
# Mapping phase_id → DAILY_PENALTY_RATE
|
||||
# Phase 1, 2, 4: 0.03%/ngày | Các phase khác: 0.05%/ngày
|
||||
PENALTY_RATE_LOW_PHASES = [1, 2, 4]
|
||||
PENALTY_RATE_LOW = Decimal('0.0003') # 0.03%
|
||||
PENALTY_RATE_HIGH = Decimal('0.0005') # 0.05%
|
||||
|
||||
def get_penalty_rate_for_schedule(schedule):
|
||||
"""
|
||||
Trả về DAILY_PENALTY_RATE tương ứng với phase của Transaction_Detail gắn với lịch.
|
||||
- Phase 1, 2, 4 → 0.03%/ngày (0.0003)
|
||||
- Các phase khác → 0.05%/ngày (0.0005)
|
||||
"""
|
||||
try:
|
||||
phase_id = schedule.txn_detail.phase_id
|
||||
print(f"phase_id: {phase_id}")
|
||||
if phase_id in PENALTY_RATE_LOW_PHASES:
|
||||
return PENALTY_RATE_LOW
|
||||
return PENALTY_RATE_HIGH
|
||||
except Exception:
|
||||
# Nếu không lấy được phase thì dùng mức cao (an toàn hơn)
|
||||
return PENALTY_RATE_HIGH
|
||||
|
||||
|
||||
# ==========================================================================================
|
||||
def allocate_payment_to_schedules(product_id):
|
||||
if not product_id:
|
||||
@@ -289,7 +353,6 @@ def allocate_payment_to_schedules(product_id):
|
||||
paid_payment_status = Payment_Status.objects.filter(id=2).first()
|
||||
paid_txn_status = Transaction_Status.objects.filter(id=2).first()
|
||||
today = datetime.now().date()
|
||||
DAILY_PENALTY_RATE = Decimal('0.0005')
|
||||
|
||||
with transaction.atomic():
|
||||
try:
|
||||
@@ -338,6 +401,10 @@ def allocate_payment_to_schedules(product_id):
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
# --- XÁC ĐỊNH LÃI PHẠT THEO PHASE ---
|
||||
DAILY_PENALTY_RATE = get_penalty_rate_for_schedule(sch)
|
||||
print(f"DAILY_PENALTY_RATE: {DAILY_PENALTY_RATE}")
|
||||
|
||||
current_amount_remain = Decimal(str(sch.amount_remain or 0))
|
||||
|
||||
# --- BƯỚC 1: LẤY LÃI TÍCH LŨY TỪ TRACE ---
|
||||
@@ -426,13 +493,10 @@ def allocate_payment_to_schedules(product_id):
|
||||
"penalty_to_this_entry": float(penalty_to_this_entry),
|
||||
"amount_remain_before": float(current_amount_remain),
|
||||
"amount_remain_after_allocation": float(amount_remain_after),
|
||||
"DAILY_PENALTY_RATE": float(DAILY_PENALTY_RATE)
|
||||
})
|
||||
sch.entry = sch_entry_list
|
||||
|
||||
# Đóng lịch: chỉ khi status hiện tại là 1
|
||||
if sch.status_id == 1 and sch.amount_remain <= 0 and sch.penalty_remain <= 0:
|
||||
sch.status = paid_payment_status
|
||||
|
||||
sch.save()
|
||||
if sch.id not in updated_schedules:
|
||||
updated_schedules.append(sch.id)
|
||||
@@ -458,6 +522,7 @@ def allocate_payment_to_schedules(product_id):
|
||||
|
||||
# Cập nhật Transaction và Transaction_Detail
|
||||
if total_principal_allocated > 0 or total_penalty_allocated > 0:
|
||||
close_paid_schedules(txn)
|
||||
recalc_txn_from_schedules(txn, all_txn_details, paid_txn_status)
|
||||
|
||||
return {"status": "success", "updated_schedules": updated_schedules}
|
||||
|
||||
Reference in New Issue
Block a user