changes
This commit is contained in:
Binary file not shown.
@@ -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'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
@@ -81,8 +81,8 @@ ASGI_APPLICATION = 'api.asgi.application'
|
||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
|
||||
#prod:5.223.52.193 dev:5.223.42.146
|
||||
|
||||
MODE = 'dev'
|
||||
DBHOST = '172.17.0.1' if MODE == 'prod' else '5.223.42.146'
|
||||
MODE = 'prod'
|
||||
DBHOST = '172.17.0.1' if MODE == 'prod' else '5.223.52.193'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
18
app/migrations/0375_alter_internal_entry_ref.py
Normal file
18
app/migrations/0375_alter_internal_entry_ref.py
Normal 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),
|
||||
),
|
||||
]
|
||||
36
app/migrations/0376_remove_payment_schedule_link_and_more.py
Normal file
36
app/migrations/0376_remove_payment_schedule_link_and_more.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1667,7 +1667,7 @@ class Internal_Entry(AutoCodeModel):
|
||||
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)
|
||||
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)
|
||||
product = models.ForeignKey(Product, null=True, related_name='+', on_delete=models.PROTECT)
|
||||
allocation_amount = models.DecimalField(null=True, max_digits=35, decimal_places=2)
|
||||
@@ -1694,8 +1694,6 @@ class Payment_Schedule(AutoCodeModel):
|
||||
code_prefix = "SH"
|
||||
code_padding = 5
|
||||
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)
|
||||
to_date = models.DateField(null=False)
|
||||
amount = models.DecimalField(max_digits=35, decimal_places=2)
|
||||
@@ -1722,6 +1720,19 @@ class Payment_Schedule(AutoCodeModel):
|
||||
class Meta:
|
||||
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):
|
||||
code = models.CharField(max_length=30, null=False, unique=True)
|
||||
|
||||
324
app/payment.py
324
app/payment.py
@@ -262,7 +262,7 @@ def allocate_payment_to_schedules(product_id):
|
||||
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
|
||||
sch.ovd_days = days_for_trace
|
||||
print(f"Lai la : {penalty_to_this_entry + additional_penalty_to_today} = {sch.penalty_amount}")
|
||||
# Ghi Trace
|
||||
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ể.
|
||||
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:
|
||||
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()
|
||||
if not booked or not booked.transaction:
|
||||
errors.append(f"Product {product_id}: Không tìm thấy Transaction")
|
||||
return {
|
||||
"status": "error",
|
||||
"errors": errors
|
||||
}
|
||||
return {"status": "error", "errors": errors}
|
||||
|
||||
txn = booked.transaction
|
||||
|
||||
@@ -369,10 +367,7 @@ def allocate_penalty_reduction(product_id):
|
||||
|
||||
if not txn_detail:
|
||||
errors.append(f"Product {product_id}: Không tìm thấy Transaction_Detail")
|
||||
return {
|
||||
"status": "error",
|
||||
"errors": errors
|
||||
}
|
||||
return {"status": "error", "errors": errors}
|
||||
|
||||
reduction_entries = Internal_Entry.objects.select_for_update().filter(
|
||||
product=product,
|
||||
@@ -385,53 +380,44 @@ def allocate_penalty_reduction(product_id):
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Không có entry miễn lãi cần xử lý",
|
||||
"updated_schedules": [],
|
||||
"updated_entries": [],
|
||||
"errors": []
|
||||
"updated_schedules": [], "updated_entries": [], "errors": []
|
||||
}
|
||||
|
||||
schedules = Payment_Schedule.objects.select_for_update().filter(
|
||||
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')
|
||||
|
||||
if not schedules.exists():
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Không có lịch thanh toán cần miễn lãi",
|
||||
"updated_schedules": [],
|
||||
"updated_entries": [],
|
||||
"errors": []
|
||||
"updated_schedules": [], "updated_entries": [], "errors": []
|
||||
}
|
||||
|
||||
for entry in reduction_entries:
|
||||
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_reduction_allocated = Decimal('0')
|
||||
|
||||
for schedule in schedules:
|
||||
if remaining_reduce <= 0:
|
||||
break
|
||||
if remaining_reduce <= 0: 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_reduce = Decimal(str(schedule.penalty_reduce or 0))
|
||||
current_remain_amount = Decimal(str(schedule.remain_amount or 0))
|
||||
if current_penalty_remain <= 0: continue
|
||||
|
||||
to_reduce = min(remaining_reduce, current_penalty_remain)
|
||||
|
||||
if to_reduce <= 0:
|
||||
continue
|
||||
if to_reduce <= 0: continue
|
||||
|
||||
remaining_reduce -= to_reduce
|
||||
entry_reduction_allocated += to_reduce
|
||||
|
||||
schedule.penalty_reduce = current_penalty_reduce + to_reduce
|
||||
schedule.penalty_remain = current_penalty_remain - to_reduce
|
||||
schedule.remain_amount = current_remain_amount - to_reduce
|
||||
schedule.penalty_reduce = (schedule.penalty_reduce or Decimal('0')) + to_reduce
|
||||
schedule.penalty_remain -= to_reduce
|
||||
schedule.remain_amount -= to_reduce
|
||||
|
||||
sch_entry_list = schedule.entry or []
|
||||
sch_entry_list.append({
|
||||
@@ -442,18 +428,23 @@ def allocate_penalty_reduction(product_id):
|
||||
})
|
||||
schedule.entry = safe_json_serialize(sch_entry_list)
|
||||
|
||||
schedule.save(update_fields=[
|
||||
'penalty_reduce', 'penalty_remain', 'remain_amount', 'entry'
|
||||
])
|
||||
schedule.save(update_fields=['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:
|
||||
updated_schedules.append(schedule.id)
|
||||
|
||||
entry_allocation_detail.append({
|
||||
"schedule_id": schedule.id,
|
||||
"schedule_code": schedule.code,
|
||||
"amount": float(to_reduce),
|
||||
"type": "REDUCTION",
|
||||
"schedule_id": schedule.id, "schedule_code": schedule.code,
|
||||
"amount": float(to_reduce), "type": "REDUCTION",
|
||||
"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:
|
||||
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:
|
||||
errors.append(str(exc))
|
||||
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):
|
||||
"""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:
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation completed for product_id={product_id}:")
|
||||
print("Normal:", normal_result)
|
||||
print("Reduction:", reduction_result)
|
||||
print("Normal allocation result:", normal_result)
|
||||
print("Penalty reduction result:", reduction_result)
|
||||
|
||||
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)}")
|
||||
@@ -736,27 +907,7 @@ def delete_entry(request):
|
||||
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
|
||||
# Tính lại Transaction_Detail hiện tại TRƯỚC
|
||||
try:
|
||||
current = Transaction_Current.objects.get(transaction=txn)
|
||||
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()
|
||||
|
||||
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
|
||||
# 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')
|
||||
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.refresh_from_db()
|
||||
txn_detail_updated = True
|
||||
@@ -780,12 +944,34 @@ def delete_entry(request):
|
||||
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
|
||||
# ====== SAU KHI LƯU HẾT TXN_DETAIL, MỚI TÍNH TRANSACTION ======
|
||||
# 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:
|
||||
# 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'):
|
||||
txn.deposit_received = Decimal('0')
|
||||
txn.deposit_remaining = txn.deposit_amount if hasattr(txn, 'deposit_amount') else Decimal('0')
|
||||
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
|
||||
|
||||
txn.save(update_fields=['amount_received', 'amount_remain', 'deposit_received', 'deposit_remaining'])
|
||||
txn.refresh_from_db()
|
||||
|
||||
Reference in New Issue
Block a user