This commit is contained in:
anhduy-tech
2026-02-10 10:14:23 +07:00
parent 41a51c65f1
commit 63e5d71ca2
8 changed files with 326 additions and 75 deletions

View File

@@ -21,7 +21,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-_u202k$8qq2p*cr_eo(7k!0ngr5^n)27@85+5oy8&41(u6&j54' SECRET_KEY = 'django-insecure-_u202k$8qq2p*cr_eo(7k!0ngr5^n)27@85+5oy8&41(u6&j54'
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = False
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
@@ -81,8 +81,8 @@ ASGI_APPLICATION = 'api.asgi.application'
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases # https://docs.djangoproject.com/en/4.1/ref/settings/#databases
#prod:5.223.52.193 dev:5.223.42.146 #prod:5.223.52.193 dev:5.223.42.146
MODE = 'dev' MODE = 'prod'
DBHOST = '172.17.0.1' if MODE == 'prod' else '5.223.42.146' DBHOST = '172.17.0.1' if MODE == 'prod' else '5.223.52.193'
DATABASES = { DATABASES = {
'default': { 'default': {

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2026-02-09 10:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0374_payment_schedule_link_payment_schedule_ref_code'),
]
operations = [
migrations.AlterField(
model_name='internal_entry',
name='ref',
field=models.CharField(max_length=30, null=True, unique=True),
),
]

View File

@@ -0,0 +1,36 @@
# Generated by Django 5.1.7 on 2026-02-10 03:07
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0375_alter_internal_entry_ref'),
]
operations = [
migrations.RemoveField(
model_name='payment_schedule',
name='link',
),
migrations.RemoveField(
model_name='payment_schedule',
name='ref_code',
),
migrations.CreateModel(
name='Invoice',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('link', models.CharField(max_length=100, null=True)),
('ref_code', models.CharField(max_length=30, null=True)),
('amount', models.DecimalField(decimal_places=2, max_digits=35)),
('note', models.CharField(max_length=100, null=True)),
('payment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='invoice', to='app.payment_schedule')),
],
options={
'db_table': 'invoice',
},
),
]

View File

@@ -1667,7 +1667,7 @@ class Internal_Entry(AutoCodeModel):
inputer = models.ForeignKey(User, null=False, related_name='+', on_delete=models.PROTECT) inputer = models.ForeignKey(User, null=False, related_name='+', on_delete=models.PROTECT)
account = models.ForeignKey(Internal_Account, null=False, related_name='+', on_delete=models.PROTECT) account = models.ForeignKey(Internal_Account, null=False, related_name='+', on_delete=models.PROTECT)
date = models.DateField(null=False) date = models.DateField(null=False)
ref = models.CharField(max_length=30, null=True) ref = models.CharField(max_length=30, null=True, unique=True)
customer = models.ForeignKey(Customer, null=True, related_name='entrycus', on_delete=models.PROTECT) customer = models.ForeignKey(Customer, null=True, related_name='entrycus', on_delete=models.PROTECT)
product = models.ForeignKey(Product, null=True, related_name='+', on_delete=models.PROTECT) product = models.ForeignKey(Product, null=True, related_name='+', on_delete=models.PROTECT)
allocation_amount = models.DecimalField(null=True, max_digits=35, decimal_places=2) allocation_amount = models.DecimalField(null=True, max_digits=35, decimal_places=2)
@@ -1694,8 +1694,6 @@ class Payment_Schedule(AutoCodeModel):
code_prefix = "SH" code_prefix = "SH"
code_padding = 5 code_padding = 5
code = models.CharField(max_length=30, null=True, unique=True) code = models.CharField(max_length=30, null=True, unique=True)
link = models.CharField(max_length=100, null=True)
ref_code = models.CharField(max_length=30, null=True)
from_date = models.DateField(null=False) from_date = models.DateField(null=False)
to_date = models.DateField(null=False) to_date = models.DateField(null=False)
amount = models.DecimalField(max_digits=35, decimal_places=2) amount = models.DecimalField(max_digits=35, decimal_places=2)
@@ -1722,6 +1720,19 @@ class Payment_Schedule(AutoCodeModel):
class Meta: class Meta:
db_table = 'payment_schedule' db_table = 'payment_schedule'
class Invoice(AutoCodeModel):
code_prefix = "HD"
code_padding = 5
link = models.CharField(max_length=100, null=True)
ref_code = models.CharField(max_length=30, null=True)
amount = models.DecimalField(max_digits=35, decimal_places=2)
payment = models.ForeignKey(Payment_Schedule, null=False, related_name='invoice', on_delete=models.PROTECT)
note = models.CharField(max_length=100, null=True)
class Meta:
db_table = 'invoice'
class Phone_Otp(models.Model): class Phone_Otp(models.Model):
code = models.CharField(max_length=30, null=False, unique=True) code = models.CharField(max_length=30, null=False, unique=True)

View File

@@ -262,7 +262,7 @@ def allocate_payment_to_schedules(product_id):
if amount_remain_after > 0: if amount_remain_after > 0:
sch.ovd_days = max(0, (today - sch.to_date).days) sch.ovd_days = max(0, (today - sch.to_date).days)
else : else :
sch.ovd_days = days_for_trace + days_from_entry_to_today sch.ovd_days = days_for_trace
print(f"Lai la : {penalty_to_this_entry + additional_penalty_to_today} = {sch.penalty_amount}") print(f"Lai la : {penalty_to_this_entry + additional_penalty_to_today} = {sch.penalty_amount}")
# Ghi Trace # Ghi Trace
sch_entry_list = sch.entry or [] sch_entry_list = sch.entry or []
@@ -336,6 +336,7 @@ def allocate_penalty_reduction(product_id):
""" """
Xử lý miễn giảm tiền phạt cho một sản phẩm cụ thể. Xử lý miễn giảm tiền phạt cho một sản phẩm cụ thể.
Quét các entry CR từ tài khoản miễn lãi (id=5) có allocation_remain > 0. Quét các entry CR từ tài khoản miễn lãi (id=5) có allocation_remain > 0.
Sau khi miễn giảm, kiểm tra và đóng lịch/giao dịch nếu đủ điều kiện.
""" """
if not product_id: if not product_id:
return {"status": "no_product", "message": "Không có product_id"} return {"status": "no_product", "message": "Không có product_id"}
@@ -351,10 +352,7 @@ def allocate_penalty_reduction(product_id):
booked = Product_Booked.objects.filter(product=product).first() booked = Product_Booked.objects.filter(product=product).first()
if not booked or not booked.transaction: if not booked or not booked.transaction:
errors.append(f"Product {product_id}: Không tìm thấy Transaction") errors.append(f"Product {product_id}: Không tìm thấy Transaction")
return { return {"status": "error", "errors": errors}
"status": "error",
"errors": errors
}
txn = booked.transaction txn = booked.transaction
@@ -369,10 +367,7 @@ def allocate_penalty_reduction(product_id):
if not txn_detail: if not txn_detail:
errors.append(f"Product {product_id}: Không tìm thấy Transaction_Detail") errors.append(f"Product {product_id}: Không tìm thấy Transaction_Detail")
return { return {"status": "error", "errors": errors}
"status": "error",
"errors": errors
}
reduction_entries = Internal_Entry.objects.select_for_update().filter( reduction_entries = Internal_Entry.objects.select_for_update().filter(
product=product, product=product,
@@ -385,53 +380,44 @@ def allocate_penalty_reduction(product_id):
return { return {
"status": "success", "status": "success",
"message": "Không có entry miễn lãi cần xử lý", "message": "Không có entry miễn lãi cần xử lý",
"updated_schedules": [], "updated_schedules": [], "updated_entries": [], "errors": []
"updated_entries": [],
"errors": []
} }
schedules = Payment_Schedule.objects.select_for_update().filter( schedules = Payment_Schedule.objects.select_for_update().filter(
txn_detail=txn_detail, txn_detail=txn_detail,
status=1 status__id=1 # Chỉ xử lý các lịch chưa thanh toán
).order_by('cycle', 'from_date') ).order_by('cycle', 'from_date')
if not schedules.exists(): if not schedules.exists():
return { return {
"status": "success", "status": "success",
"message": "Không có lịch thanh toán cần miễn lãi", "message": "Không có lịch thanh toán cần miễn lãi",
"updated_schedules": [], "updated_schedules": [], "updated_entries": [], "errors": []
"updated_entries": [],
"errors": []
} }
for entry in reduction_entries: for entry in reduction_entries:
remaining_reduce = Decimal(str(entry.allocation_remain)) remaining_reduce = Decimal(str(entry.allocation_remain))
if remaining_reduce <= 0: continue
if remaining_reduce <= 0:
continue
entry_allocation_detail = entry.allocation_detail or [] entry_allocation_detail = entry.allocation_detail or []
entry_reduction_allocated = Decimal('0') entry_reduction_allocated = Decimal('0')
for schedule in schedules: for schedule in schedules:
if remaining_reduce <= 0: if remaining_reduce <= 0: break
break
# Chỉ miễn giảm cho các lịch còn nợ phạt
current_penalty_remain = Decimal(str(schedule.penalty_remain or 0)) current_penalty_remain = Decimal(str(schedule.penalty_remain or 0))
current_penalty_reduce = Decimal(str(schedule.penalty_reduce or 0)) if current_penalty_remain <= 0: continue
current_remain_amount = Decimal(str(schedule.remain_amount or 0))
to_reduce = min(remaining_reduce, current_penalty_remain) to_reduce = min(remaining_reduce, current_penalty_remain)
if to_reduce <= 0: continue
if to_reduce <= 0:
continue
remaining_reduce -= to_reduce remaining_reduce -= to_reduce
entry_reduction_allocated += to_reduce entry_reduction_allocated += to_reduce
schedule.penalty_reduce = current_penalty_reduce + to_reduce schedule.penalty_reduce = (schedule.penalty_reduce or Decimal('0')) + to_reduce
schedule.penalty_remain = current_penalty_remain - to_reduce schedule.penalty_remain -= to_reduce
schedule.remain_amount = current_remain_amount - to_reduce schedule.remain_amount -= to_reduce
sch_entry_list = schedule.entry or [] sch_entry_list = schedule.entry or []
sch_entry_list.append({ sch_entry_list.append({
@@ -442,18 +428,23 @@ def allocate_penalty_reduction(product_id):
}) })
schedule.entry = safe_json_serialize(sch_entry_list) schedule.entry = safe_json_serialize(sch_entry_list)
schedule.save(update_fields=[ schedule.save(update_fields=['penalty_reduce', 'penalty_remain', 'remain_amount', 'entry'])
'penalty_reduce', 'penalty_remain', 'remain_amount', 'entry'
]) # KIỂM TRA ĐỂ ĐÓNG LỊCH
if schedule.remain_amount <= 0 and schedule.amount_remain <= 0:
try:
paid_status = Payment_Status.objects.get(id=2)
schedule.status = paid_status
schedule.save(update_fields=['status'])
except Payment_Status.DoesNotExist:
errors.append("Không tìm thấy Payment_Status id=2")
if schedule.id not in updated_schedules: if schedule.id not in updated_schedules:
updated_schedules.append(schedule.id) updated_schedules.append(schedule.id)
entry_allocation_detail.append({ entry_allocation_detail.append({
"schedule_id": schedule.id, "schedule_id": schedule.id, "schedule_code": schedule.code,
"schedule_code": schedule.code, "amount": float(to_reduce), "type": "REDUCTION",
"amount": float(to_reduce),
"type": "REDUCTION",
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S") "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}) })
@@ -465,6 +456,23 @@ def allocate_penalty_reduction(product_id):
if entry.id not in updated_entries: if entry.id not in updated_entries:
updated_entries.append(entry.id) updated_entries.append(entry.id)
# KIỂM TRA ĐỂ ĐÓNG TOÀN BỘ GIAO DỊCH
try:
txn_detail.refresh_from_db()
has_pending_sch = Payment_Schedule.objects.filter(txn_detail=txn_detail, status__id=1).exists()
if txn_detail.amount_remaining <= 0 and not has_pending_sch:
paid_txn_status = Transaction_Status.objects.get(id=2)
txn_detail.status = paid_txn_status
txn.status = paid_txn_status
txn_detail.save(update_fields=['status'])
txn.save(update_fields=['status'])
print(f"Transaction for product {product_id} closed after penalty reduction.")
except Transaction_Status.DoesNotExist:
errors.append("Không tìm thấy Transaction_Status id=2")
except Exception as e:
errors.append(f"Lỗi khi đóng transaction sau miễn giảm: {str(e)}")
except Exception as exc: except Exception as exc:
errors.append(str(exc)) errors.append(str(exc))
import traceback import traceback
@@ -479,17 +487,180 @@ def allocate_penalty_reduction(product_id):
# ========================================================================================== # ==========================================================================================
def reset_product_state_before_allocation(product_id):
"""
Reset toàn bộ trạng thái công nợ của sản phẩm về ban đầu TRƯỚC KHI chạy phân bổ.
Sử dụng logic tính toán giống delete_entry nhưng không xóa entry và không hoàn tác tài khoản.
"""
with transaction.atomic():
try:
product = Product.objects.get(id=product_id)
booked = Product_Booked.objects.filter(product=product).first()
if not (booked and booked.transaction):
print(f"Reset: Không tìm thấy transaction cho product {product_id}")
return
txn = booked.transaction
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 not txn_detail:
print(f"Reset: Không tìm thấy txn_detail cho product {product_id}")
return
# =================================================================
# Bước 1: Reset các lịch thanh toán theo công thức (GIỐNG delete_entry)
# =================================================================
all_schedules = Payment_Schedule.objects.select_for_update().filter(txn_detail=txn_detail)
unpaid_status = Payment_Status.objects.get(id=1)
for schedule in all_schedules:
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)))
# Reset theo công thức - GIỐNG delete_entry
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 = unpaid_status
schedule.save(update_fields=[
'entry', 'paid_amount', 'amount_remain', 'remain_amount',
'penalty_paid', 'penalty_reduce', 'penalty_remain', 'ovd_days', 'status', 'penalty_amount'
])
# =================================================================
# Bước 2: Reset allocation của tất cả entry CR của sản phẩm (GIỐNG delete_entry)
# =================================================================
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: Đặt lại Transaction_Detail từ các lịch và LƯU TRƯỚC (GIỐNG delete_entry)
# =================================================================
if txn_detail:
# Lấy tổng paid của các lịch THUỘC detail này
schedules_of_this_detail = Payment_Schedule.objects.filter(txn_detail=txn_detail)
detail_total_paid = Decimal('0')
for sch in schedules_of_this_detail:
paid = Decimal(str(sch.paid_amount or 0))
detail_total_paid += paid
# Cập nhật Transaction_Detail
txn_detail.amount_received = detail_total_paid
txn_detail.amount_remaining = Decimal(str(txn_detail.amount or 0)) - detail_total_paid
# ===== LƯU NGAY TRANSACTION_DETAIL =====
txn_detail.save(update_fields=['amount_received', 'amount_remaining'])
txn_detail.refresh_from_db()
# Đặt lại status về unpaid nếu còn nợ
if txn_detail.amount_remaining > 0:
try:
unpaid_txn_status = Transaction_Status.objects.get(id=1)
txn_detail.status = unpaid_txn_status
txn_detail.save(update_fields=['status'])
except:
pass
# =================================================================
# Bước 4: SAU KHI LƯU TXN_DETAIL, Query lại TẤT CẢ DETAIL và tính Transaction (GIỐNG delete_entry)
# =================================================================
# Query lại TẤT CẢ detail sau khi đã lưu
all_details = Transaction_Detail.objects.filter(transaction=txn)
txn_total_received = Decimal('0')
txn_total_deposit_received = Decimal('0')
for detail in all_details:
# Refresh để lấy giá trị mới nhất từ DB
detail.refresh_from_db()
txn_total_received += Decimal(str(detail.amount_received or 0))
# Tính deposit từ các lịch deposit thuộc detail này
deposit_schedules = Payment_Schedule.objects.filter(
txn_detail=detail,
type_id=1
)
for dep_sch in deposit_schedules:
txn_total_deposit_received += Decimal(str(dep_sch.paid_amount or 0))
# Cập nhật Transaction - SỬA: amount -> sale_price
txn.amount_received = txn_total_received
txn.amount_remain = Decimal(str(txn.sale_price or 0)) - txn_total_received # ← SỬA ĐÂY
if hasattr(txn, 'deposit_received'):
txn.deposit_received = txn_total_deposit_received
if hasattr(txn, 'deposit_remaining') and hasattr(txn, 'deposit_amount'):
txn.deposit_remaining = Decimal(str(txn.deposit_amount or 0)) - txn_total_deposit_received
# ===== LƯU TRANSACTION =====
txn.save(update_fields=['amount_received', 'amount_remain', 'deposit_received', 'deposit_remaining'])
txn.refresh_from_db()
# Đặt lại status về unpaid nếu còn nợ
if txn.amount_remain > 0:
try:
unpaid_txn_status = Transaction_Status.objects.get(id=1)
txn.status = unpaid_txn_status
txn.save(update_fields=['status'])
except:
pass
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Đã reset thành công trạng thái cho product_id={product_id}")
except Exception as e:
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Lỗi khi reset product state for product_id={product_id}: {str(e)}")
import traceback
print(traceback.format_exc())
def background_allocate(product_id): def background_allocate(product_id):
"""Background task để chạy allocation sau khi tạo entry""" """
Background task để chạy allocation.
Luồng xử lý: Reset toàn bộ -> Phân bổ tiền trả nợ -> Phân bổ miễn giảm.
"""
try: try:
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation started for product_id={product_id}") print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation started for product_id={product_id}")
# 1. Reset toàn bộ trạng thái công nợ của sản phẩm về gốc
reset_product_state_before_allocation(product_id)
# 2. Chạy phân bổ thanh toán (tiền khách trả)
normal_result = allocate_payment_to_schedules(product_id) normal_result = allocate_payment_to_schedules(product_id)
# 3. Chạy phân bổ miễn giảm (tiền công ty giảm cho khách)
reduction_result = allocate_penalty_reduction(product_id) reduction_result = allocate_penalty_reduction(product_id)
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation completed for product_id={product_id}:") print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation completed for product_id={product_id}:")
print("Normal:", normal_result) print("Normal allocation result:", normal_result)
print("Reduction:", reduction_result) print("Penalty reduction result:", reduction_result)
except Exception as e: except Exception as e:
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation error for product_id={product_id}: {str(e)}") print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation error for product_id={product_id}: {str(e)}")
@@ -736,27 +907,7 @@ def delete_entry(request):
if booked and booked.transaction: if booked and booked.transaction:
txn = booked.transaction txn = booked.transaction
# Lấy tất cả lịch liên quan # Tính lại Transaction_Detail hiện tại TRƯỚC
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: try:
current = Transaction_Current.objects.get(transaction=txn) current = Transaction_Current.objects.get(transaction=txn)
txn_detail = current.detail txn_detail = current.detail
@@ -764,10 +915,23 @@ def delete_entry(request):
txn_detail = Transaction_Detail.objects.filter(transaction=txn).order_by('-create_time').first() txn_detail = Transaction_Detail.objects.filter(transaction=txn).order_by('-create_time').first()
if txn_detail: if txn_detail:
# amount_received = amount_remaining hiện tại + tổng paid_amount từ lịch # Lấy tổng paid của các lịch THUỘC detail này
txn_detail.amount_received = Decimal(str(txn_detail.amount_remaining or 0)) + total_paid_all schedules_of_this_detail = Payment_Schedule.objects.filter(txn_detail=txn_detail)
# amount_remaining = amount - amount_received vừa tính detail_total_paid = Decimal('0')
txn_detail.amount_remaining = Decimal(str(txn_detail.amount or 0)) - txn_detail.amount_received detail_deposit_paid = Decimal('0')
for sch in schedules_of_this_detail:
paid = Decimal(str(sch.paid_amount or 0))
detail_total_paid += paid
if sch.type_id == 1: # deposit
detail_deposit_paid += paid
# Cập nhật Transaction_Detail
txn_detail.amount_received = detail_total_paid
txn_detail.amount_remaining = Decimal(str(txn_detail.amount or 0)) - detail_total_paid
# ===== LƯU NGAY =====
txn_detail.save(update_fields=['amount_received', 'amount_remaining']) txn_detail.save(update_fields=['amount_received', 'amount_remaining'])
txn_detail.refresh_from_db() txn_detail.refresh_from_db()
txn_detail_updated = True txn_detail_updated = True
@@ -780,12 +944,34 @@ def delete_entry(request):
except: except:
pass pass
# Tính lại Transaction - đặt lại các trường về trạng thái ban đầu # ====== SAU KHI LƯU HẾT TXN_DETAIL, MỚI TÍNH TRANSACTION ======
txn.amount_received = Decimal('0') # Query lại TẤT CẢ detail sau khi đã lưu
txn.amount_remain = Decimal(str(txn.amount or 0)) # amount gốc hợp đồng all_details = Transaction_Detail.objects.filter(transaction=txn)
txn_total_received = Decimal('0')
txn_total_deposit_received = Decimal('0')
for detail in all_details:
# Lấy giá trị mới nhất từ DB (đã refresh)
detail.refresh_from_db()
txn_total_received += Decimal(str(detail.amount_received or 0))
# Tính deposit từ các lịch deposit thuộc detail này
deposit_schedules = Payment_Schedule.objects.filter(
txn_detail=detail,
type_id=1
)
for dep_sch in deposit_schedules:
txn_total_deposit_received += Decimal(str(dep_sch.paid_amount or 0))
# Cập nhật Transaction
txn.amount_received = txn_total_received
txn.amount_remain = Decimal(str(txn.sale_price or 0)) - txn_total_received
if hasattr(txn, 'deposit_received'): if hasattr(txn, 'deposit_received'):
txn.deposit_received = Decimal('0') txn.deposit_received = txn_total_deposit_received
txn.deposit_remaining = txn.deposit_amount if hasattr(txn, 'deposit_amount') else Decimal('0') if hasattr(txn, 'deposit_remaining') and hasattr(txn, 'deposit_amount'):
txn.deposit_remaining = Decimal(str(txn.deposit_amount or 0)) - txn_total_deposit_received
txn.save(update_fields=['amount_received', 'amount_remain', 'deposit_received', 'deposit_remaining']) txn.save(update_fields=['amount_received', 'amount_remain', 'deposit_received', 'deposit_remaining'])
txn.refresh_from_db() txn.refresh_from_db()