This commit is contained in:
anhduy-tech
2026-01-30 16:37:18 +07:00
parent 69937da0b6
commit f297a5e1b4
29 changed files with 374 additions and 142 deletions

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.1.7 on 2026-01-30 08:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0372_payment_schedule_amount_remain_and_more'),
]
operations = [
migrations.RemoveField(
model_name='transaction',
name='ovd_days',
),
migrations.AddField(
model_name='internal_entry',
name='allocation_remain',
field=models.DecimalField(decimal_places=2, max_digits=35, null=True),
),
]

View File

@@ -1408,7 +1408,6 @@ class Transaction(AutoCodeModel):
deposit_remaining = models.DecimalField(max_digits=35, decimal_places=2, null=True) deposit_remaining = models.DecimalField(max_digits=35, decimal_places=2, null=True)
amount_received = models.DecimalField(max_digits=35, decimal_places=2, null=True) amount_received = models.DecimalField(max_digits=35, decimal_places=2, null=True)
amount_remain = models.DecimalField(max_digits=35, decimal_places=2, null=True) amount_remain = models.DecimalField(max_digits=35, decimal_places=2, null=True)
ovd_days = models.IntegerField(null=True)
penalty_amount = models.DecimalField(null=True, max_digits=35, decimal_places=2) penalty_amount = models.DecimalField(null=True, max_digits=35, decimal_places=2)
early_discount_amount = models.DecimalField(max_digits=35, decimal_places=2, null=True) early_discount_amount = models.DecimalField(max_digits=35, decimal_places=2, null=True)
payment_plan = models.JSONField(null=True) payment_plan = models.JSONField(null=True)
@@ -1672,6 +1671,7 @@ class Internal_Entry(AutoCodeModel):
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)
allocation_remain = models.DecimalField(null=True, max_digits=35, decimal_places=2)
allocation_detail = models.JSONField(null=True) allocation_detail = models.JSONField(null=True)
create_time = models.DateTimeField(null=True, auto_now_add=True) create_time = models.DateTimeField(null=True, auto_now_add=True)
update_time = models.DateTimeField(null=True, auto_now=True) update_time = models.DateTimeField(null=True, auto_now=True)

View File

@@ -43,6 +43,7 @@ def account_entry_api(code, amount, content, type, category, userid, ref=None, p
account.refresh_from_db() account.refresh_from_db()
new_balance = account.balance new_balance = account.balance
# Tất cả entry CR đều có allocation_remain ban đầu = amount
entry = Internal_Entry.objects.create( entry = Internal_Entry.objects.create(
category=entry_category, category=entry_category,
content=content, content=content,
@@ -56,10 +57,13 @@ def account_entry_api(code, amount, content, type, category, userid, ref=None, p
date=system_date, date=system_date,
ref=ref, ref=ref,
product=None if product is None else Product.objects.get(id=product), product=None if product is None else Product.objects.get(id=product),
customer=None if customer is None else Customer.objects.get(id=customer) customer=None if customer is None else Customer.objects.get(id=customer),
allocation_amount=Decimal('0'),
allocation_remain=Decimal(str(amount)) if entry_type.code == 'CR' else Decimal('0'),
allocation_detail=[]
) )
text = 'id,account__currency__code,ref,balance_before,balance_after,code,account,account__code,account__branch__name,account__type__name,date,amount,content,inputer,inputer__fullname,approver,approver__fullname,create_time,update_time,type,type__code,type__name' text = 'id,account__currency__code,ref,balance_before,balance_after,code,account,account__code,account__branch__name,account__type__name,date,amount,content,inputer,inputer__fullname,approver,approver__fullname,create_time,update_time,type,type__code,type__name,allocation_amount,allocation_remain'
fields = text.split(',') fields = text.split(',')
response_data = Internal_Entry.objects.filter(id=entry.id).values(*fields).first() response_data = Internal_Entry.objects.filter(id=entry.id).values(*fields).first()
return response_data, entry return response_data, entry
@@ -92,14 +96,20 @@ def get_allocation_rule():
return 'principal-fee' return 'principal-fee'
# ========================================================================================== # ==========================================================================================
# HÀM PHÂN BỔ THÔNG THƯỜNG # HÀM PHÂN BỔ THEO PRODUCT_ID - QUÉT LẠI TOÀN BỘ ENTRY CŨ CÓ TIỀN THỪA
# ========================================================================================== # ==========================================================================================
def allocate_payment_to_schedules(entries): def allocate_payment_to_schedules(product_id):
if not entries: """
return {"status": "no_entries", "message": "Không có bút toán"} Phân bổ thanh toán cho một sản phẩm cụ thể.
Quét tất cả entry CR có allocation_remain > 0 của product này,
phân bổ tiếp vào các lịch chưa thanh toán (status=1).
"""
if not product_id:
return {"status": "no_product", "message": "Không có product_id"}
allocation_rule = get_allocation_rule() allocation_rule = get_allocation_rule()
updated_schedules = [] updated_schedules = []
updated_entries = []
errors = [] errors = []
# Lấy status "đã thanh toán" một lần # Lấy status "đã thanh toán" một lần
@@ -115,23 +125,23 @@ def allocate_payment_to_schedules(entries):
except Transaction_Status.DoesNotExist: except Transaction_Status.DoesNotExist:
errors.append("Không tìm thấy Transaction_Status id=2 (đã thanh toán)") errors.append("Không tìm thấy Transaction_Status id=2 (đã thanh toán)")
for entry in entries:
if entry.type.code != 'CR' or entry.account.id == 5:
continue
amount = Decimal(str(entry.amount))
if amount <= 0:
continue
with transaction.atomic(): with transaction.atomic():
try: try:
booked = Product_Booked.objects.filter(product=entry.product).first() # Lấy product
product = Product.objects.get(id=product_id)
# Lấy transaction của product
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"Entry {entry.code}: Không tìm thấy Transaction") errors.append(f"Product {product_id}: Không tìm thấy Transaction")
continue return {
"status": "error",
"errors": errors
}
txn = booked.transaction txn = booked.transaction
# Lấy transaction detail
txn_detail = None txn_detail = None
try: try:
current = Transaction_Current.objects.get(transaction=txn) current = Transaction_Current.objects.get(transaction=txn)
@@ -142,20 +152,61 @@ def allocate_payment_to_schedules(entries):
).order_by('-create_time').first() ).order_by('-create_time').first()
if not txn_detail: if not txn_detail:
errors.append(f"Entry {entry.code}: Không tìm thấy Transaction_Detail") errors.append(f"Product {product_id}: Không tìm thấy Transaction_Detail")
continue return {
"status": "error",
"errors": errors
}
schedules = Payment_Schedule.objects.filter( # QUÉT TẤT CẢ ENTRY CR CÓ TIỀN THỪA (allocation_remain > 0) - KHÔNG PHẢI TÀI KHOẢN MIỄN LÃI
entries_with_remain = Internal_Entry.objects.select_for_update().filter(
product=product,
type__code='CR',
allocation_remain__gt=0
).exclude(
account__id=5 # Loại trừ tài khoản miễn lãi
).order_by('date', 'create_time')
if not entries_with_remain.exists():
return {
"status": "success",
"message": "Không có entry nào cần phân bổ",
"updated_schedules": [],
"updated_entries": [],
"errors": []
}
# Lấy các lịch chưa thanh toán (status=1)
schedules = Payment_Schedule.objects.select_for_update().filter(
txn_detail=txn_detail, txn_detail=txn_detail,
status__id=1 # giả sử status=1 là chưa thanh toán status__id=1
).order_by('cycle', 'from_date') ).order_by('cycle', 'from_date')
if not schedules.exists(): if not schedules.exists():
return {
"status": "success",
"message": "Không có lịch thanh toán cần phân bổ",
"updated_schedules": [],
"updated_entries": [],
"errors": []
}
# TỔNG TIỀN PHÂN BỔ THÀNH CÔNG (PRINCIPAL + PENALTY)
total_principal_allocated = Decimal('0')
total_penalty_allocated = Decimal('0')
# PHÂN BỔ TỪNG ENTRY
for entry in entries_with_remain:
remaining = Decimal(str(entry.allocation_remain))
if remaining <= 0:
continue continue
remaining = amount entry_allocation_detail = entry.allocation_detail or []
total_allocated = Decimal('0') entry_principal_allocated = Decimal('0')
entry_penalty_allocated = Decimal('0')
# Phân bổ vào các lịch
for sch in schedules: for sch in schedules:
if remaining <= 0: if remaining <= 0:
break break
@@ -169,35 +220,48 @@ def allocate_payment_to_schedules(entries):
to_penalty = Decimal('0') to_penalty = Decimal('0')
to_principal = Decimal('0') to_principal = Decimal('0')
# Áp dụng quy tắc phân bổ
if allocation_rule == 'fee-principal': if allocation_rule == 'fee-principal':
# Phạt trước
to_penalty = min(remaining, penalty_remain) to_penalty = min(remaining, penalty_remain)
remaining -= to_penalty remaining -= to_penalty
penalty_paid += to_penalty penalty_paid += to_penalty
penalty_remain -= to_penalty penalty_remain -= to_penalty
to_principal = min(remaining, amount_remain) to_principal = min(remaining, amount_remain)
remaining -= to_principal remaining -= to_principal
paid_amount += to_principal paid_amount += to_principal
amount_remain -= to_principal amount_remain -= to_principal
else: else:
# Gốc trước
to_principal = min(remaining, amount_remain) to_principal = min(remaining, amount_remain)
remaining -= to_principal remaining -= to_principal
paid_amount += to_principal paid_amount += to_principal
amount_remain -= to_principal amount_remain -= to_principal
to_penalty = min(remaining, penalty_remain) to_penalty = min(remaining, penalty_remain)
remaining -= to_penalty remaining -= to_penalty
penalty_paid += to_penalty penalty_paid += to_penalty
penalty_remain -= to_penalty penalty_remain -= to_penalty
allocated_here = to_penalty + to_principal allocated_here = to_penalty + to_principal
total_allocated += allocated_here
if allocated_here <= 0:
continue
# Cập nhật entry tracking
entry_principal_allocated += to_principal
entry_penalty_allocated += to_penalty
# Cập nhật schedule
sch.paid_amount = paid_amount sch.paid_amount = paid_amount
sch.penalty_paid = penalty_paid sch.penalty_paid = penalty_paid
sch.amount_remain = amount_remain sch.amount_remain = amount_remain
sch.penalty_remain = penalty_remain sch.penalty_remain = penalty_remain
sch.remain_amount = max(Decimal('0'), remain_amount - allocated_here) sch.remain_amount = max(Decimal('0'), remain_amount - allocated_here)
entry_list = sch.entry or [] # Lưu trace vào schedule
schedule_entry_list = sch.entry or []
date_value = entry.date date_value = entry.date
if hasattr(date_value, 'isoformat'): if hasattr(date_value, 'isoformat'):
@@ -205,7 +269,7 @@ def allocate_payment_to_schedules(entries):
else: else:
date_value = str(date_value) date_value = str(date_value)
entry_list.append({ schedule_entry_list.append({
"code": entry.code, "code": entry.code,
"amount": float(allocated_here), "amount": float(allocated_here),
"date": date_value, "date": date_value,
@@ -214,8 +278,9 @@ def allocate_payment_to_schedules(entries):
"penalty": float(to_penalty), "penalty": float(to_penalty),
"rule": allocation_rule "rule": allocation_rule
}) })
sch.entry = entry_list sch.entry = schedule_entry_list
# Kiểm tra xem lịch đã thanh toán đủ chưa
if sch.amount_remain <= 0 and sch.penalty_remain <= 0 and paid_payment_status: if sch.amount_remain <= 0 and sch.penalty_remain <= 0 and paid_payment_status:
sch.status = paid_payment_status sch.status = paid_payment_status
@@ -224,62 +289,118 @@ def allocate_payment_to_schedules(entries):
'penalty_remain', 'remain_amount', 'entry', 'status' 'penalty_remain', 'remain_amount', 'entry', 'status'
]) ])
if sch.id not in updated_schedules:
updated_schedules.append(sch.id) updated_schedules.append(sch.id)
# Cập nhật Transaction_Detail # Lưu chi tiết phân bổ vào entry
txn_detail.amount_received = F('amount_received') + total_allocated entry_allocation_detail.append({
txn_detail.amount_remaining = F('amount_remaining') - total_allocated "schedule_id": sch.id,
txn_detail.save() "schedule_code": sch.code,
"amount": float(allocated_here),
"principal": float(to_principal),
"penalty": float(to_penalty),
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
# Cập nhật entry allocation info
total_allocated_for_entry = entry_principal_allocated + entry_penalty_allocated
entry.allocation_amount = (entry.allocation_amount or Decimal('0')) + total_allocated_for_entry
entry.allocation_remain = remaining # Số tiền còn thừa
entry.allocation_detail = entry_allocation_detail
entry.save(update_fields=['allocation_amount', 'allocation_remain', 'allocation_detail'])
if entry.id not in updated_entries:
updated_entries.append(entry.id)
# Cộng vào tổng
total_principal_allocated += entry_principal_allocated
total_penalty_allocated += entry_penalty_allocated
# Cập nhật Transaction_Detail - TÁCH RIÊNG PRINCIPAL VÀ PENALTY
if total_principal_allocated > 0 or total_penalty_allocated > 0:
# Cập nhật amount_received (chỉ cộng principal)
txn_detail.amount_received = F('amount_received') + total_principal_allocated
txn_detail.amount_remaining = F('amount_remaining') - total_principal_allocated
# Cập nhật penalty_amount
if hasattr(txn_detail, 'penalty_amount'):
txn_detail.penalty_amount = F('penalty_amount') + total_penalty_allocated
txn_detail.save(update_fields=['amount_received', 'amount_remaining', 'penalty_amount'])
else:
txn_detail.save(update_fields=['amount_received', 'amount_remaining'])
txn_detail.refresh_from_db() txn_detail.refresh_from_db()
# Kiểm tra và cập nhật status nếu đã thanh toán đủ
if txn_detail.amount_remaining <= 0 and paid_txn_status: if txn_detail.amount_remaining <= 0 and paid_txn_status:
txn_detail.status = paid_txn_status txn_detail.status = paid_txn_status
txn_detail.save(update_fields=['status']) txn_detail.save(update_fields=['status'])
# Cập nhật Transaction # Cập nhật Transaction - TÁCH RIÊNG PRINCIPAL VÀ PENALTY
txn.amount_received = F('amount_received') + total_allocated if total_principal_allocated > 0 or total_penalty_allocated > 0:
txn.amount_remain = F('amount_remain') - total_allocated # Cập nhật amount_received (chỉ cộng principal)
txn.save() txn.amount_received = F('amount_received') + total_principal_allocated
txn.amount_remain = F('amount_remain') - total_principal_allocated
# Cập nhật penalty_amount
if hasattr(txn, 'penalty_amount'):
txn.penalty_amount = F('penalty_amount') + total_penalty_allocated
txn.save(update_fields=['amount_received', 'amount_remain', 'penalty_amount'])
else:
txn.save(update_fields=['amount_received', 'amount_remain'])
txn.refresh_from_db() txn.refresh_from_db()
# Kiểm tra và cập nhật status nếu đã thanh toán đủ
if txn.amount_remain <= 0 and paid_txn_status: if txn.amount_remain <= 0 and paid_txn_status:
txn.status = paid_txn_status txn.status = paid_txn_status
txn.save(update_fields=['status']) txn.save(update_fields=['status'])
except Product.DoesNotExist:
errors.append(f"Product {product_id}: Không tồn tại")
except Exception as exc: except Exception as exc:
errors.append(f"Entry {entry.code}: Lỗi phân bổ - {str(exc)}") errors.append(f"Product {product_id}: Lỗi phân bổ - {str(exc)}")
import traceback
print(traceback.format_exc())
return { return {
"status": "success" if not errors else "partial_failure", "status": "success" if not errors else "partial_failure",
"updated_schedules": updated_schedules, "updated_schedules": updated_schedules,
"updated_entries": updated_entries,
"errors": errors, "errors": errors,
"rule_used": allocation_rule "rule_used": allocation_rule,
"total_principal_allocated": float(total_principal_allocated),
"total_penalty_allocated": float(total_penalty_allocated)
} }
# ========================================================================================== # ==========================================================================================
# HÀM MIỄN LÃI # HÀM MIỄN LÃI - XỬ LÝ ENTRY TỪ TÀI KHOẢN MIỄN LÃI (ID=5)
# ========================================================================================== # ==========================================================================================
def allocate_penalty_reduction(entries): def allocate_penalty_reduction(product_id):
if not entries: """
return {"status": "no_entries", "message": "Không có bút toán"} 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.
"""
if not product_id:
return {"status": "no_product", "message": "Không có product_id"}
updated_schedules = [] updated_schedules = []
updated_entries = []
errors = [] errors = []
for entry in entries:
if entry.type.code != 'CR' or entry.account.id != 5:
continue # Chỉ xử lý bút toán từ tài khoản miễn lãi
amount = Decimal(str(entry.amount))
if amount <= 0:
continue
with transaction.atomic(): with transaction.atomic():
try: try:
booked = Product_Booked.objects.filter(product=entry.product).first() # Lấy product
product = Product.objects.get(id=product_id)
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"Entry {entry.code}: Không tìm thấy Transaction") errors.append(f"Product {product_id}: Không tìm thấy Transaction")
continue return {
"status": "error",
"errors": errors
}
txn = booked.transaction txn = booked.transaction
@@ -293,19 +414,53 @@ def allocate_penalty_reduction(entries):
).order_by('-create_time').first() ).order_by('-create_time').first()
if not txn_detail: if not txn_detail:
errors.append(f"Entry {entry.code}: Không tìm thấy Transaction_Detail") errors.append(f"Product {product_id}: Không tìm thấy Transaction_Detail")
continue return {
"status": "error",
"errors": errors
}
# Lấy các entry CR từ tài khoản miễn lãi (id=5) có tiền thừa
reduction_entries = Internal_Entry.objects.select_for_update().filter(
product=product,
type__code='CR',
account__id=5,
allocation_remain__gt=0
).order_by('date', 'create_time')
if not reduction_entries.exists():
return {
"status": "success",
"message": "Không có entry miễn lãi cần xử lý",
"updated_schedules": [],
"updated_entries": [],
"errors": []
}
# Lấy các lịch chưa thanh toán (status=1) # Lấy các lịch chưa thanh toán (status=1)
schedules = Payment_Schedule.objects.filter( schedules = Payment_Schedule.objects.select_for_update().filter(
txn_detail=txn_detail, txn_detail=txn_detail,
status=1 status=1
).order_by('cycle', 'from_date') ).order_by('cycle', 'from_date')
if not schedules.exists(): 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": []
}
# Xử lý từng entry miễn lãi
for entry in reduction_entries:
remaining_reduce = Decimal(str(entry.allocation_remain))
if remaining_reduce <= 0:
continue continue
remaining_reduce = amount # số tiền miễn lãi còn lại để phân bổ entry_allocation_detail = entry.allocation_detail or []
entry_reduction_allocated = Decimal('0')
for schedule in schedules: for schedule in schedules:
if remaining_reduce <= 0: if remaining_reduce <= 0:
@@ -317,7 +472,12 @@ def allocate_penalty_reduction(entries):
# Chỉ miễn tối đa bằng số phạt còn lại # Chỉ miễn tối đa bằng số phạt còn lại
to_reduce = min(remaining_reduce, current_penalty_remain) to_reduce = min(remaining_reduce, current_penalty_remain)
if to_reduce <= 0:
continue
remaining_reduce -= to_reduce remaining_reduce -= to_reduce
entry_reduction_allocated += to_reduce
# Cập nhật các trường # Cập nhật các trường
schedule.penalty_reduce = current_penalty_reduce + to_reduce schedule.penalty_reduce = current_penalty_reduce + to_reduce
@@ -328,8 +488,8 @@ def allocate_penalty_reduction(entries):
# KHÔNG ĐỘNG ĐẾN amount_remain (nợ gốc còn lại) # KHÔNG ĐỘNG ĐẾN amount_remain (nợ gốc còn lại)
# Ghi trace bút toán miễn lãi # Ghi trace bút toán miễn lãi vào schedule
entry_list = schedule.entry or [] schedule_entry_list = schedule.entry or []
date_value = entry.date date_value = entry.date
if hasattr(date_value, 'isoformat'): if hasattr(date_value, 'isoformat'):
@@ -337,50 +497,82 @@ def allocate_penalty_reduction(entries):
else: else:
date_value = str(date_value) date_value = str(date_value)
entry_list.append({ schedule_entry_list.append({
"code": entry.code, "code": entry.code,
"amount": float(to_reduce), "amount": float(to_reduce),
"date": date_value, "date": date_value,
"type": "REDUCTION", "type": "REDUCTION",
"note": "Miễn lãi phạt quá hạn" "note": "Miễn lãi phạt quá hạn"
}) })
schedule.entry = entry_list schedule.entry = schedule_entry_list
# Lưu lại # Lưu lại schedule
schedule.save(update_fields=[ schedule.save(update_fields=[
'penalty_reduce', 'penalty_remain', 'remain_amount', 'entry' 'penalty_reduce', 'penalty_remain', 'remain_amount', 'entry'
]) ])
if schedule.id not in updated_schedules:
updated_schedules.append(schedule.id) updated_schedules.append(schedule.id)
# Lưu chi tiết vào entry
entry_allocation_detail.append({
"schedule_id": schedule.id,
"schedule_code": schedule.code,
"amount": float(to_reduce),
"type": "REDUCTION",
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
# Cập nhật entry allocation info
entry.allocation_amount = (entry.allocation_amount or Decimal('0')) + entry_reduction_allocated
entry.allocation_remain = remaining_reduce
entry.allocation_detail = entry_allocation_detail
entry.save(update_fields=['allocation_amount', 'allocation_remain', 'allocation_detail'])
if entry.id not in updated_entries:
updated_entries.append(entry.id)
except Product.DoesNotExist:
errors.append(f"Product {product_id}: Không tồn tại")
except Exception as exc: except Exception as exc:
errors.append(f"Entry {entry.code}: Lỗi miễn lãi - {str(exc)}") errors.append(f"Product {product_id}: Lỗi miễn lãi - {str(exc)}")
import traceback
print(traceback.format_exc())
return { return {
"status": "success" if not errors else "partial_failure", "status": "success" if not errors else "partial_failure",
"updated_schedules": updated_schedules, "updated_schedules": updated_schedules,
"updated_entries": updated_entries,
"errors": errors, "errors": errors,
"message": f"Đã miễn lãi cho {len(updated_schedules)} lịch thanh toán (chỉ giảm phạt + tổng còn lại)" "message": f"Đã miễn lãi cho {len(updated_schedules)} lịch thanh toán"
} }
# ========================================================================================== # ==========================================================================================
# BACKGROUND FUNCTION # BACKGROUND FUNCTION - NHẬN PRODUCT_ID
# ========================================================================================== # ==========================================================================================
def background_allocate(entries_created): def background_allocate(product_id):
"""
Chạy phân bổ ngầm cho một product_id cụ thể.
Quét tất cả entry cũ + mới có tiền thừa để phân bổ.
"""
try: try:
# Debug type date print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation started for product_id={product_id}")
for e in entries_created:
print(f"Debug entry {e.code}: date type = {type(e.date)}, value = {e.date}")
normal_result = allocate_payment_to_schedules(entries_created) # Phân bổ thanh toán thông thường
reduction_result = allocate_penalty_reduction(entries_created) normal_result = allocate_payment_to_schedules(product_id)
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation completed:") # Phân bổ miễn lãi
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("Normal:", normal_result)
print("Reduction:", reduction_result) print("Reduction:", reduction_result)
except Exception as e: except Exception as e:
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation error: {str(e)}") print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation error for product_id={product_id}: {str(e)}")
import traceback
print(traceback.format_exc())
# ========================================================================================== # ==========================================================================================
# API TẠO MỘT BÚT TOÁN # API TẠO MỘT BÚT TOÁN
@@ -398,26 +590,36 @@ def account_entry(request):
category=request.data['category'], category=request.data['category'],
userid=request.data['user'], userid=request.data['user'],
ref=ref, ref=ref,
product=request.data['product'], product=request.data.get('product'),
customer=request.data['customer'], customer=request.data.get('customer'),
date=request.data.get('date') date=request.data.get('date')
) )
if 'error' in response_data: if 'error' in response_data:
return Response(response_data, status=400) return Response(response_data, status=400)
if created_entry: # Lưu product_id để chạy sau khi response
product_id_to_allocate = created_entry.product_id if created_entry else None
# Tạo response trước
response = Response({
**response_data,
"message": "Bút toán đã tạo thành công. Phân bổ thanh toán đang chạy ngầm..."
})
# Chạy background allocation SAU KHI transaction đã commit
if product_id_to_allocate:
def run_allocation():
thread = threading.Thread( thread = threading.Thread(
target=background_allocate, target=background_allocate,
args=([created_entry],), args=(product_id_to_allocate,),
daemon=True daemon=True
) )
thread.start() thread.start()
return Response({ transaction.on_commit(run_allocation)
**response_data,
"message": "Bút toán đã tạo thành công. Phân bổ thanh toán đang chạy ngầm..." return response
})
# ========================================================================================== # ==========================================================================================
# API TẠO NHIỀU BÚT TOÁN # API TẠO NHIỀU BÚT TOÁN
@@ -426,7 +628,7 @@ def account_entry(request):
def account_multi_entry(request): def account_multi_entry(request):
try: try:
result = [] result = []
entries_created = [] product_ids = set() # Thu thập các product_id cần phân bổ
data_list = request.data.get('data', []) data_list = request.data.get('data', [])
with transaction.atomic(): with transaction.atomic():
@@ -438,29 +640,37 @@ def account_multi_entry(request):
type='CR', type='CR',
category=obj['category'], category=obj['category'],
userid=request.data.get('user'), userid=request.data.get('user'),
ref=obj['ref'], ref=obj.get('ref'),
product=obj['product'], product=obj.get('product'),
customer=obj['customer'], customer=obj.get('customer'),
date=obj['date'] date=obj.get('date')
) )
result.append(response_data) result.append(response_data)
if created_entry: if created_entry and created_entry.product_id:
entries_created.append(created_entry) product_ids.add(created_entry.product_id)
if entries_created: # Tạo response
response = Response({
"entries": result,
"message": f"Bút toán đã tạo thành công. Phân bổ thanh toán đang chạy ngầm cho {len(product_ids)} sản phẩm..."
})
# Chạy background allocation SAU KHI transaction đã commit
if product_ids:
def run_allocations():
for product_id in product_ids:
thread = threading.Thread( thread = threading.Thread(
target=background_allocate, target=background_allocate,
args=(entries_created,), args=(product_id,),
daemon=True daemon=True
) )
thread.start() thread.start()
return Response({ transaction.on_commit(run_allocations)
"entries": result,
"message": "Bút toán đã tạo thành công. Phân bổ thanh toán đang chạy ngầm..." return response
})
except Exception as e: except Exception as e:
print({'error': f"Đã xảy ra lỗi không mong muốn: {str(e)}"}) print({'error': f"Đã xảy ra lỗi không mong muốn: {str(e)}"})