This commit is contained in:
anhduy-tech
2026-02-24 11:09:09 +07:00
parent f94611f973
commit 39aea8784e
11 changed files with 93 additions and 9 deletions

View File

@@ -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}