677 lines
28 KiB
Python
677 lines
28 KiB
Python
from app.models import *
|
|
from rest_framework.decorators import api_view
|
|
from rest_framework.response import Response
|
|
from django.db import transaction
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from django.db.models import F
|
|
import threading
|
|
|
|
# ==========================================================================================
|
|
def getcode(code, Model):
|
|
try:
|
|
obj = Model.objects.latest('id')
|
|
val = 1 if obj is None else obj.id + 1
|
|
except Exception:
|
|
val = 1
|
|
length = len(str(val))
|
|
text = '0' * (6 - length)
|
|
return f"{code}{text}{val}"
|
|
|
|
# ==========================================================================================
|
|
def account_entry_api(code, amount, content, type, category, userid, ref=None, product=None, customer=None, date=None):
|
|
try:
|
|
user = User.objects.get(id=userid)
|
|
entry_type = Entry_Type.objects.get(code=type)
|
|
entry_category = Entry_Category.objects.get(id=category)
|
|
system_date = date if date else datetime.now().strftime("%Y-%m-%d")
|
|
amount = float(amount)
|
|
|
|
with transaction.atomic():
|
|
account = Internal_Account.objects.select_for_update().get(code=code)
|
|
start_balance = account.balance or 0
|
|
|
|
if entry_type.code == 'DR' and start_balance < amount:
|
|
return {'error': 'Số dư không đủ để thực hiện giao dịch.'}, None
|
|
|
|
if entry_type.code == 'CR':
|
|
account.balance += amount
|
|
else:
|
|
account.balance -= amount
|
|
|
|
account.save()
|
|
account.refresh_from_db()
|
|
new_balance = account.balance
|
|
|
|
# Tất cả entry CR đều có allocation_remain ban đầu = amount
|
|
entry = Internal_Entry.objects.create(
|
|
category=entry_category,
|
|
content=content,
|
|
amount=amount,
|
|
inputer=user,
|
|
approver=user,
|
|
type=entry_type,
|
|
balance_before=start_balance,
|
|
balance_after=new_balance,
|
|
account=account,
|
|
date=system_date,
|
|
ref=ref,
|
|
product=None if product is None else Product.objects.get(id=product),
|
|
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,allocation_amount,allocation_remain'
|
|
fields = text.split(',')
|
|
response_data = Internal_Entry.objects.filter(id=entry.id).values(*fields).first()
|
|
return response_data, entry
|
|
|
|
except User.DoesNotExist:
|
|
return {'error': f"Người dùng với ID {userid} không tồn tại."}, None
|
|
except Internal_Account.DoesNotExist:
|
|
return {'error': f"Tài khoản nội bộ với mã '{code}' không tồn tại."}, None
|
|
except Entry_Type.DoesNotExist:
|
|
return {'error': f"Loại bút toán với mã '{type}' không tồn tại."}, None
|
|
except Entry_Category.DoesNotExist:
|
|
return {'error': f"Danh mục bút toán với ID '{category}' không tồn tại."}, None
|
|
except Exception as e:
|
|
return {'error': f"Đã xảy ra lỗi không mong muốn: {str(e)}"}, None
|
|
|
|
# ==========================================================================================
|
|
# HÀM LẤY RULE TỪ BIZ_SETTING (detail là string)
|
|
# ==========================================================================================
|
|
def get_allocation_rule():
|
|
try:
|
|
rule_setting = Biz_Setting.objects.get(code='rule')
|
|
rule_value = (rule_setting.detail or 'principal-fee').strip()
|
|
|
|
if rule_value.lower() in ['fee-principal', 'phạt trước', 'phat truoc']:
|
|
return 'fee-principal'
|
|
else:
|
|
return 'principal-fee'
|
|
|
|
except Biz_Setting.DoesNotExist:
|
|
return 'principal-fee'
|
|
|
|
# ==========================================================================================
|
|
# 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(product_id):
|
|
"""
|
|
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()
|
|
updated_schedules = []
|
|
updated_entries = []
|
|
errors = []
|
|
|
|
# Lấy status "đã thanh toán" một lần
|
|
paid_payment_status = None
|
|
paid_txn_status = None
|
|
try:
|
|
paid_payment_status = Payment_Status.objects.get(id=2)
|
|
except Payment_Status.DoesNotExist:
|
|
errors.append("Không tìm thấy Payment_Status id=2 (đã thanh toán)")
|
|
|
|
try:
|
|
paid_txn_status = Transaction_Status.objects.get(id=2)
|
|
except Transaction_Status.DoesNotExist:
|
|
errors.append("Không tìm thấy Transaction_Status id=2 (đã thanh toán)")
|
|
|
|
with transaction.atomic():
|
|
try:
|
|
# 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:
|
|
errors.append(f"Product {product_id}: Không tìm thấy Transaction")
|
|
return {
|
|
"status": "error",
|
|
"errors": errors
|
|
}
|
|
|
|
txn = booked.transaction
|
|
|
|
# Lấy transaction detail
|
|
txn_detail = None
|
|
try:
|
|
current = Transaction_Current.objects.get(transaction=txn)
|
|
txn_detail = current.detail
|
|
except (Transaction_Current.DoesNotExist, AttributeError):
|
|
txn_detail = Transaction_Detail.objects.filter(
|
|
transaction=txn
|
|
).order_by('-create_time').first()
|
|
|
|
if not txn_detail:
|
|
errors.append(f"Product {product_id}: Không tìm thấy Transaction_Detail")
|
|
return {
|
|
"status": "error",
|
|
"errors": errors
|
|
}
|
|
|
|
# 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,
|
|
status__id=1
|
|
).order_by('cycle', 'from_date')
|
|
|
|
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
|
|
|
|
entry_allocation_detail = entry.allocation_detail or []
|
|
entry_principal_allocated = Decimal('0')
|
|
entry_penalty_allocated = Decimal('0')
|
|
|
|
# Phân bổ vào các lịch
|
|
for sch in schedules:
|
|
if remaining <= 0:
|
|
break
|
|
|
|
penalty_remain = Decimal(str(sch.penalty_remain or 0))
|
|
amount_remain = Decimal(str(sch.amount_remain or 0))
|
|
paid_amount = Decimal(str(sch.paid_amount or 0))
|
|
penalty_paid = Decimal(str(sch.penalty_paid or 0))
|
|
remain_amount = Decimal(str(sch.remain_amount or 0))
|
|
|
|
to_penalty = Decimal('0')
|
|
to_principal = Decimal('0')
|
|
|
|
# Áp dụng quy tắc phân bổ
|
|
if allocation_rule == 'fee-principal':
|
|
# Phạt trước
|
|
to_penalty = min(remaining, penalty_remain)
|
|
remaining -= to_penalty
|
|
penalty_paid += to_penalty
|
|
penalty_remain -= to_penalty
|
|
|
|
to_principal = min(remaining, amount_remain)
|
|
remaining -= to_principal
|
|
paid_amount += to_principal
|
|
amount_remain -= to_principal
|
|
else:
|
|
# Gốc trước
|
|
to_principal = min(remaining, amount_remain)
|
|
remaining -= to_principal
|
|
paid_amount += to_principal
|
|
amount_remain -= to_principal
|
|
|
|
to_penalty = min(remaining, penalty_remain)
|
|
remaining -= to_penalty
|
|
penalty_paid += to_penalty
|
|
penalty_remain -= to_penalty
|
|
|
|
allocated_here = to_penalty + to_principal
|
|
|
|
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.penalty_paid = penalty_paid
|
|
sch.amount_remain = amount_remain
|
|
sch.penalty_remain = penalty_remain
|
|
sch.remain_amount = max(Decimal('0'), remain_amount - allocated_here)
|
|
|
|
# Lưu trace vào schedule
|
|
schedule_entry_list = sch.entry or []
|
|
|
|
date_value = entry.date
|
|
if hasattr(date_value, 'isoformat'):
|
|
date_value = date_value.isoformat()
|
|
else:
|
|
date_value = str(date_value)
|
|
|
|
schedule_entry_list.append({
|
|
"code": entry.code,
|
|
"amount": float(allocated_here),
|
|
"date": date_value,
|
|
"type": "CR",
|
|
"principal": float(to_principal),
|
|
"penalty": float(to_penalty),
|
|
"rule": allocation_rule
|
|
})
|
|
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:
|
|
sch.status = paid_payment_status
|
|
|
|
sch.save(update_fields=[
|
|
'paid_amount', 'penalty_paid', 'amount_remain',
|
|
'penalty_remain', 'remain_amount', 'entry', 'status'
|
|
])
|
|
|
|
if sch.id not in updated_schedules:
|
|
updated_schedules.append(sch.id)
|
|
|
|
# Lưu chi tiết phân bổ vào entry
|
|
entry_allocation_detail.append({
|
|
"schedule_id": sch.id,
|
|
"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()
|
|
|
|
# Kiểm tra và cập nhật status nếu đã thanh toán đủ
|
|
if txn_detail.amount_remaining <= 0 and paid_txn_status:
|
|
txn_detail.status = paid_txn_status
|
|
txn_detail.save(update_fields=['status'])
|
|
|
|
# Cập nhật Transaction - 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.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()
|
|
|
|
# Kiểm tra và cập nhật status nếu đã thanh toán đủ
|
|
if txn.amount_remain <= 0 and paid_txn_status:
|
|
txn.status = paid_txn_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:
|
|
errors.append(f"Product {product_id}: Lỗi phân bổ - {str(exc)}")
|
|
import traceback
|
|
print(traceback.format_exc())
|
|
|
|
return {
|
|
"status": "success" if not errors else "partial_failure",
|
|
"updated_schedules": updated_schedules,
|
|
"updated_entries": updated_entries,
|
|
"errors": errors,
|
|
"rule_used": allocation_rule,
|
|
"total_principal_allocated": float(total_principal_allocated),
|
|
"total_penalty_allocated": float(total_penalty_allocated)
|
|
}
|
|
|
|
# ==========================================================================================
|
|
# HÀM MIỄN LÃI - XỬ LÝ ENTRY TỪ TÀI KHOẢN MIỄN LÃI (ID=5)
|
|
# ==========================================================================================
|
|
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.
|
|
"""
|
|
if not product_id:
|
|
return {"status": "no_product", "message": "Không có product_id"}
|
|
|
|
updated_schedules = []
|
|
updated_entries = []
|
|
errors = []
|
|
|
|
with transaction.atomic():
|
|
try:
|
|
# 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:
|
|
errors.append(f"Product {product_id}: Không tìm thấy Transaction")
|
|
return {
|
|
"status": "error",
|
|
"errors": errors
|
|
}
|
|
|
|
txn = booked.transaction
|
|
|
|
txn_detail = None
|
|
try:
|
|
current = Transaction_Current.objects.get(transaction=txn)
|
|
txn_detail = current.detail
|
|
except (Transaction_Current.DoesNotExist, AttributeError):
|
|
txn_detail = Transaction_Detail.objects.filter(
|
|
transaction=txn
|
|
).order_by('-create_time').first()
|
|
|
|
if not txn_detail:
|
|
errors.append(f"Product {product_id}: Không tìm thấy Transaction_Detail")
|
|
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)
|
|
schedules = Payment_Schedule.objects.select_for_update().filter(
|
|
txn_detail=txn_detail,
|
|
status=1
|
|
).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": []
|
|
}
|
|
|
|
# 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
|
|
|
|
entry_allocation_detail = entry.allocation_detail or []
|
|
entry_reduction_allocated = Decimal('0')
|
|
|
|
for schedule in schedules:
|
|
if remaining_reduce <= 0:
|
|
break
|
|
|
|
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))
|
|
|
|
# Chỉ miễn tối đa bằng số phạt còn lại
|
|
to_reduce = min(remaining_reduce, current_penalty_remain)
|
|
|
|
if to_reduce <= 0:
|
|
continue
|
|
|
|
remaining_reduce -= to_reduce
|
|
entry_reduction_allocated += to_reduce
|
|
|
|
# Cập nhật các trường
|
|
schedule.penalty_reduce = current_penalty_reduce + to_reduce
|
|
schedule.penalty_remain = current_penalty_remain - to_reduce
|
|
|
|
# GIẢM TỔNG CÒN LẠI (remain_amount)
|
|
schedule.remain_amount = max(Decimal('0'), current_remain_amount - to_reduce)
|
|
|
|
# KHÔNG ĐỘNG ĐẾN amount_remain (nợ gốc còn lại)
|
|
|
|
# Ghi trace bút toán miễn lãi vào schedule
|
|
schedule_entry_list = schedule.entry or []
|
|
|
|
date_value = entry.date
|
|
if hasattr(date_value, 'isoformat'):
|
|
date_value = date_value.isoformat()
|
|
else:
|
|
date_value = str(date_value)
|
|
|
|
schedule_entry_list.append({
|
|
"code": entry.code,
|
|
"amount": float(to_reduce),
|
|
"date": date_value,
|
|
"type": "REDUCTION",
|
|
"note": "Miễn lãi phạt quá hạn"
|
|
})
|
|
schedule.entry = schedule_entry_list
|
|
|
|
# Lưu lại schedule
|
|
schedule.save(update_fields=[
|
|
'penalty_reduce', 'penalty_remain', 'remain_amount', 'entry'
|
|
])
|
|
|
|
if schedule.id not in updated_schedules:
|
|
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:
|
|
errors.append(f"Product {product_id}: Lỗi miễn lãi - {str(exc)}")
|
|
import traceback
|
|
print(traceback.format_exc())
|
|
|
|
return {
|
|
"status": "success" if not errors else "partial_failure",
|
|
"updated_schedules": updated_schedules,
|
|
"updated_entries": updated_entries,
|
|
"errors": errors,
|
|
"message": f"Đã miễn lãi cho {len(updated_schedules)} lịch thanh toán"
|
|
}
|
|
|
|
# ==========================================================================================
|
|
# BACKGROUND FUNCTION - NHẬN PRODUCT_ID
|
|
# ==========================================================================================
|
|
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:
|
|
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Background allocation started for product_id={product_id}")
|
|
|
|
# Phân bổ thanh toán thông thường
|
|
normal_result = allocate_payment_to_schedules(product_id)
|
|
|
|
# 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("Reduction:", 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)}")
|
|
import traceback
|
|
print(traceback.format_exc())
|
|
|
|
# ==========================================================================================
|
|
# API TẠO MỘT BÚT TOÁN
|
|
# ==========================================================================================
|
|
@api_view(['POST'])
|
|
def account_entry(request):
|
|
print(request.data.get('date'))
|
|
ref = request.data.get('ref')
|
|
|
|
response_data, created_entry = account_entry_api(
|
|
code=request.data['code'],
|
|
amount=request.data['amount'],
|
|
content=request.data['content'],
|
|
type=request.data['type'],
|
|
category=request.data['category'],
|
|
userid=request.data['user'],
|
|
ref=ref,
|
|
product=request.data.get('product'),
|
|
customer=request.data.get('customer'),
|
|
date=request.data.get('date')
|
|
)
|
|
|
|
if 'error' in response_data:
|
|
return Response(response_data, status=400)
|
|
|
|
# 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(
|
|
target=background_allocate,
|
|
args=(product_id_to_allocate,),
|
|
daemon=True
|
|
)
|
|
thread.start()
|
|
|
|
transaction.on_commit(run_allocation)
|
|
|
|
return response
|
|
|
|
# ==========================================================================================
|
|
# API TẠO NHIỀU BÚT TOÁN
|
|
# ==========================================================================================
|
|
@api_view(['POST'])
|
|
def account_multi_entry(request):
|
|
try:
|
|
result = []
|
|
product_ids = set() # Thu thập các product_id cần phân bổ
|
|
data_list = request.data.get('data', [])
|
|
|
|
with transaction.atomic():
|
|
for obj in data_list:
|
|
response_data, created_entry = account_entry_api(
|
|
code=obj['Tài khoản'],
|
|
amount=obj['amount'],
|
|
content=obj['content'],
|
|
type='CR',
|
|
category=obj['category'],
|
|
userid=request.data.get('user'),
|
|
ref=obj.get('ref'),
|
|
product=obj.get('product'),
|
|
customer=obj.get('customer'),
|
|
date=obj.get('date')
|
|
)
|
|
|
|
result.append(response_data)
|
|
|
|
if created_entry and created_entry.product_id:
|
|
product_ids.add(created_entry.product_id)
|
|
|
|
# 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(
|
|
target=background_allocate,
|
|
args=(product_id,),
|
|
daemon=True
|
|
)
|
|
thread.start()
|
|
|
|
transaction.on_commit(run_allocations)
|
|
|
|
return response
|
|
|
|
except Exception as e:
|
|
print({'error': f"Đã xảy ra lỗi không mong muốn: {str(e)}"})
|
|
return Response({'error': str(e)}, status=400) |