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 = False
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
@@ -81,7 +81,7 @@ 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 = 'prod'
|
||||
MODE = 'dev'
|
||||
DBHOST = '172.17.0.1' if MODE == 'prod' else '5.223.42.146'
|
||||
|
||||
DATABASES = {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
284
app/payment.py
284
app/payment.py
@@ -246,9 +246,11 @@ def allocate_payment_to_schedules(product_id):
|
||||
|
||||
# --- BƯỚC 4: LÃI DỰ PHÒNG ĐẾN NAY ---
|
||||
days_from_entry_to_today = max(0, (today - entry_date).days)
|
||||
print(f" - Lai du phong: {days_from_entry_to_today} , ngay nhap: {entry_date}, ngay hien tai: {today}")
|
||||
additional_penalty_to_today = Decimal('0')
|
||||
if amount_remain_after > 0:
|
||||
additional_penalty_to_today = amount_remain_after * Decimal(days_from_entry_to_today) * DAILY_PENALTY_RATE
|
||||
print(f"lai du phong la : {additional_penalty_to_today}")
|
||||
|
||||
# --- CẬP NHẬT DỮ LIỆU ---
|
||||
sch.paid_amount = Decimal(str(sch.paid_amount or 0)) + to_principal
|
||||
@@ -257,8 +259,11 @@ def allocate_payment_to_schedules(product_id):
|
||||
sch.penalty_amount = penalty_to_this_entry + additional_penalty_to_today
|
||||
sch.penalty_remain = max(Decimal('0'), sch.penalty_amount - sch.penalty_paid)
|
||||
sch.remain_amount = sch.amount_remain + sch.penalty_remain
|
||||
sch.ovd_days = days_for_trace + days_from_entry_to_today
|
||||
|
||||
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
|
||||
print(f"Lai la : {penalty_to_this_entry + additional_penalty_to_today} = {sch.penalty_amount}")
|
||||
# Ghi Trace
|
||||
sch_entry_list = sch.entry or []
|
||||
sch_entry_list.append({
|
||||
@@ -586,8 +591,11 @@ def account_multi_entry(request):
|
||||
|
||||
@api_view(['POST'])
|
||||
def delete_entry(request):
|
||||
"""View function để xóa bút toán (tương thích với urls.py)"""
|
||||
"""Xóa bút toán - reset sạch entry = [], lưu hết trước, xóa entry sau, đặt lại txn/txndetail, rồi phân bổ lại"""
|
||||
entry_id = request.data.get('id')
|
||||
|
||||
if not entry_id:
|
||||
return Response({'error': 'Thiếu id bút toán'}, status=400)
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
@@ -597,7 +605,7 @@ def delete_entry(request):
|
||||
return Response({
|
||||
'error': f'Bút toán với ID {entry_id} không tồn tại'
|
||||
}, status=404)
|
||||
|
||||
|
||||
entry_info = {
|
||||
'id': entry.id,
|
||||
'code': entry.code,
|
||||
@@ -609,59 +617,70 @@ def delete_entry(request):
|
||||
'allocation_amount': float(entry.allocation_amount or 0),
|
||||
'allocation_remain': float(entry.allocation_remain or 0)
|
||||
}
|
||||
|
||||
|
||||
if entry.type.code != 'CR':
|
||||
return Response({'error': 'Hiện chỉ hỗ trợ xóa bút toán thu tiền (CR)'}, status=400)
|
||||
|
||||
product_id = entry.product_id
|
||||
if not product_id:
|
||||
return Response({'error': 'Bút toán không gắn với product nào'}, status=400)
|
||||
|
||||
allocation_detail = entry.allocation_detail or []
|
||||
schedules_reversed = []
|
||||
total_principal_reversed = Decimal('0')
|
||||
total_penalty_reversed = Decimal('0')
|
||||
total_reduction_reversed = Decimal('0')
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Bước 1: Reset các lịch bị ảnh hưởng theo công thức & lưu
|
||||
# =================================================================
|
||||
for allocation in allocation_detail:
|
||||
schedule_id = allocation.get('schedule_id')
|
||||
allocated_amount = Decimal(str(allocation.get('amount', 0)))
|
||||
principal = Decimal(str(allocation.get('principal', 0)))
|
||||
penalty = Decimal(str(allocation.get('penalty', 0)))
|
||||
allocation_type = allocation.get('type', 'PAYMENT')
|
||||
|
||||
|
||||
if not schedule_id or allocated_amount <= 0:
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
schedule = Payment_Schedule.objects.select_for_update().get(id=schedule_id)
|
||||
|
||||
|
||||
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)))
|
||||
|
||||
if allocation_type == 'REDUCTION':
|
||||
schedule.penalty_reduce = (schedule.penalty_reduce or Decimal('0')) - allocated_amount
|
||||
schedule.penalty_remain = (schedule.penalty_remain or Decimal('0')) + allocated_amount
|
||||
schedule.remain_amount = (schedule.remain_amount or Decimal('0')) + allocated_amount
|
||||
total_reduction_reversed += allocated_amount
|
||||
schedule.save(update_fields=['penalty_reduce', 'penalty_remain', 'remain_amount'])
|
||||
else:
|
||||
schedule.paid_amount = (schedule.paid_amount or Decimal('0')) - principal
|
||||
schedule.penalty_paid = (schedule.penalty_paid or Decimal('0')) - penalty
|
||||
schedule.amount_remain = (schedule.amount_remain or Decimal('0')) + principal
|
||||
schedule.penalty_remain = (schedule.penalty_remain or Decimal('0')) + penalty
|
||||
schedule.remain_amount = (schedule.remain_amount or Decimal('0')) + allocated_amount
|
||||
total_principal_reversed += principal
|
||||
total_penalty_reversed += penalty
|
||||
|
||||
if schedule.amount_remain > 0 or schedule.penalty_remain > 0:
|
||||
try:
|
||||
unpaid_status = Payment_Status.objects.get(id=1)
|
||||
schedule.status = unpaid_status
|
||||
except Payment_Status.DoesNotExist:
|
||||
pass
|
||||
|
||||
schedule.save(update_fields=[
|
||||
'paid_amount', 'penalty_paid', 'amount_remain',
|
||||
'penalty_remain', 'remain_amount', 'status'
|
||||
])
|
||||
|
||||
# Xóa entry trace của bút toán này
|
||||
schedule_entries = schedule.entry or []
|
||||
schedule_entries = [e for e in schedule_entries if e.get('code') != entry.code]
|
||||
schedule.entry = safe_json_serialize(schedule_entries)
|
||||
schedule.save(update_fields=['entry'])
|
||||
|
||||
|
||||
# Reset theo công thức
|
||||
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 = Payment_Status.objects.get(id=1)
|
||||
|
||||
schedule.save(update_fields=[
|
||||
'entry', 'paid_amount', 'amount_remain', 'remain_amount',
|
||||
'penalty_paid', 'penalty_reduce', 'penalty_remain', 'ovd_days', 'status','penalty_amount'
|
||||
])
|
||||
|
||||
schedules_reversed.append({
|
||||
'schedule_id': schedule.id,
|
||||
'schedule_code': schedule.code,
|
||||
@@ -670,101 +689,146 @@ def delete_entry(request):
|
||||
'penalty_reversed': float(penalty),
|
||||
'type': allocation_type
|
||||
})
|
||||
|
||||
|
||||
except Payment_Schedule.DoesNotExist:
|
||||
continue
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Bước 2: Reset allocation của tất cả entry CR của sản phẩm & lưu
|
||||
# =================================================================
|
||||
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: Hoàn tác số dư tài khoản (lưu trước khi xóa entry)
|
||||
# =================================================================
|
||||
account = Internal_Account.objects.select_for_update().get(id=entry.account_id)
|
||||
entry_amount = float(entry.amount)
|
||||
if entry.type.code == 'CR':
|
||||
account.balance = (account.balance or 0) - entry_amount
|
||||
else:
|
||||
account.balance = (account.balance or 0) + entry_amount
|
||||
account.save(update_fields=['balance'])
|
||||
|
||||
# =================================================================
|
||||
# Bước 4: XÓA ENTRY (sau khi reset và lưu hết)
|
||||
# =================================================================
|
||||
entry.delete()
|
||||
|
||||
# =================================================================
|
||||
# Bước 5: ĐẶT LẠI Transaction & Transaction_Detail TRƯỚC KHI PHÂN BỔ LẠI
|
||||
# =================================================================
|
||||
txn_detail_updated = False
|
||||
txn_updated = False
|
||||
|
||||
|
||||
if entry.product:
|
||||
try:
|
||||
booked = Product_Booked.objects.filter(product=entry.product).first()
|
||||
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
|
||||
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 txn_detail:
|
||||
if entry.account_id != 5:
|
||||
fields_to_update = []
|
||||
if total_principal_reversed > 0:
|
||||
txn_detail.amount_received = F('amount_received') - total_principal_reversed
|
||||
txn_detail.amount_remaining = F('amount_remaining') + total_principal_reversed
|
||||
fields_to_update.extend(['amount_received', 'amount_remaining'])
|
||||
if total_penalty_reversed > 0 and hasattr(txn_detail, 'penalty_amount'):
|
||||
txn_detail.penalty_amount = F('penalty_amount') - total_penalty_reversed
|
||||
fields_to_update.append('penalty_amount')
|
||||
if fields_to_update:
|
||||
txn_detail.save(update_fields=fields_to_update)
|
||||
txn_detail.refresh_from_db()
|
||||
txn_detail_updated = True
|
||||
|
||||
if txn_detail.amount_remaining > 0:
|
||||
try:
|
||||
unpaid_status = Transaction_Status.objects.get(id=1)
|
||||
txn_detail.status = unpaid_status
|
||||
txn_detail.save(update_fields=['status'])
|
||||
except:
|
||||
pass
|
||||
|
||||
if entry.account_id != 5:
|
||||
fields_to_update = []
|
||||
if total_principal_reversed > 0:
|
||||
txn.amount_received = F('amount_received') - total_principal_reversed
|
||||
txn.amount_remain = F('amount_remain') + total_principal_reversed
|
||||
fields_to_update.extend(['amount_received', 'amount_remain'])
|
||||
if total_penalty_reversed > 0 and hasattr(txn, 'penalty_amount'):
|
||||
txn.penalty_amount = F('penalty_amount') - total_penalty_reversed
|
||||
fields_to_update.append('penalty_amount')
|
||||
if fields_to_update:
|
||||
txn.save(update_fields=fields_to_update)
|
||||
txn.refresh_from_db()
|
||||
txn_updated = True
|
||||
|
||||
if txn.amount_remain > 0:
|
||||
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
|
||||
txn_detail.save(update_fields=['amount_received', 'amount_remaining'])
|
||||
txn_detail.refresh_from_db()
|
||||
txn_detail_updated = True
|
||||
|
||||
if txn_detail.amount_remaining > 0:
|
||||
try:
|
||||
unpaid_status = Transaction_Status.objects.get(id=1)
|
||||
txn.status = unpaid_status
|
||||
txn.save(update_fields=['status'])
|
||||
txn_detail.status = unpaid_status
|
||||
txn_detail.save(update_fields=['status'])
|
||||
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
|
||||
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.save(update_fields=['amount_received', 'amount_remain', 'deposit_received', 'deposit_remaining'])
|
||||
txn.refresh_from_db()
|
||||
txn_updated = True
|
||||
|
||||
if txn.amount_remain > 0:
|
||||
try:
|
||||
unpaid_status = Transaction_Status.objects.get(id=1)
|
||||
txn.status = unpaid_status
|
||||
txn.save(update_fields=['status'])
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"Lỗi khi hoàn tác Transaction: {str(e)}")
|
||||
|
||||
account = Internal_Account.objects.select_for_update().get(id=entry.account_id)
|
||||
entry_amount = float(entry.amount)
|
||||
|
||||
if entry.type.code == 'CR':
|
||||
account.balance = (account.balance or 0) - entry_amount
|
||||
else:
|
||||
account.balance = (account.balance or 0) + entry_amount
|
||||
|
||||
account.save(update_fields=['balance'])
|
||||
|
||||
entry.delete()
|
||||
|
||||
return Response({
|
||||
'success': True,
|
||||
'message': 'Đã xóa bút toán và hoàn tác tất cả phân bổ thành công',
|
||||
'entry': entry_info,
|
||||
'reversed': {
|
||||
'schedules_count': len(schedules_reversed),
|
||||
'schedules': schedules_reversed,
|
||||
'total_principal_reversed': float(total_principal_reversed),
|
||||
'total_penalty_reversed': float(total_penalty_reversed),
|
||||
'total_reduction_reversed': float(total_reduction_reversed),
|
||||
'transaction_detail_updated': txn_detail_updated,
|
||||
'transaction_updated': txn_updated
|
||||
},
|
||||
'account_balance_restored': True
|
||||
})
|
||||
|
||||
print(f"Lỗi khi đặt lại Transaction trước phân bổ: {str(e)}")
|
||||
|
||||
# =================================================================
|
||||
# Bước 6: Phân bổ lại toàn bộ sản phẩm (sẽ tự tính lại txn/txndetail đúng)
|
||||
# =================================================================
|
||||
def trigger_reallocate():
|
||||
if product_id:
|
||||
try:
|
||||
allocate_payment_to_schedules(product_id)
|
||||
allocate_penalty_reduction(product_id)
|
||||
except Exception as exc:
|
||||
print(f"Lỗi khi re-allocate sau xóa: {exc}")
|
||||
traceback.print_exc()
|
||||
|
||||
transaction.on_commit(trigger_reallocate)
|
||||
|
||||
return Response({
|
||||
'success': True,
|
||||
'message': 'Đã xóa bút toán, reset sạch entry = [], lưu hết trước, xóa entry sau, đặt lại txn/txndetail trước phân bổ, đang phân bổ lại toàn bộ...',
|
||||
'entry': entry_info,
|
||||
'reversed': {
|
||||
'schedules_count': len(schedules_reversed),
|
||||
'schedules': schedules_reversed,
|
||||
'transaction_detail_updated': txn_detail_updated,
|
||||
'transaction_updated': txn_updated
|
||||
},
|
||||
'account_balance_restored': True
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
|
||||
@@ -35,7 +35,6 @@ def scan_and_run_due_jobs():
|
||||
if uninitialized_jobs.exists():
|
||||
#logger.info(f"Found {uninitialized_jobs.count()} uninitialized jobs. Calculating next run time...")
|
||||
|
||||
# Lấy timezone của dự án
|
||||
tz = pytz.timezone(settings.TIME_ZONE)
|
||||
|
||||
for job in uninitialized_jobs:
|
||||
@@ -69,12 +68,19 @@ def scan_and_run_due_jobs():
|
||||
|
||||
# BƯỚC 2: Quét và chạy các job đến hạn như bình thường
|
||||
#logger.info("Scanning for due batch jobs...")
|
||||
|
||||
# Lấy các job cần chạy (có next_run_at không null và đã đến hạn)
|
||||
due_jobs = Batch_Job.objects.filter(is_active=True, next_run_at__lte=now)
|
||||
active_jobs = Batch_Job.objects.filter(is_active=True)
|
||||
|
||||
# Tách riêng job chạy mỗi phút (* * * * *)
|
||||
every_minute_jobs = active_jobs.filter(cron_schedule="* * * * *")
|
||||
|
||||
# Các job bình thường (không phải * * * * *), kiểm tra next_run_at
|
||||
normal_due_jobs = active_jobs.filter(next_run_at__lte=now).exclude(cron_schedule="* * * * *")
|
||||
|
||||
# Gộp hai QuerySet bằng union (hoặc |)
|
||||
due_jobs = every_minute_jobs | normal_due_jobs
|
||||
|
||||
if not due_jobs.exists():
|
||||
#logger.info("-> No due jobs found at this time.")
|
||||
logger.info("-> No due jobs found at this time.")
|
||||
return
|
||||
|
||||
#logger.info(f"-> Found {due_jobs.count()} due jobs to run.")
|
||||
@@ -159,4 +165,3 @@ def start():
|
||||
# Chạy tác vụ quét job mỗi 5 giây
|
||||
scheduler.add_job(scan_and_run_due_jobs, 'interval', seconds=5, id='scan_due_jobs_job', replace_existing=True)
|
||||
scheduler.start()
|
||||
#logger.info("APScheduler started... Jobs will be scanned every 5 seconds.")
|
||||
|
||||
@@ -58,9 +58,18 @@ def generic_post_save_handler(sender, instance, created, **kwargs):
|
||||
"""
|
||||
def send_update_after_commit():
|
||||
change_type = "created" if created else "updated"
|
||||
# Re-fetch the instance to ensure we have the committed data
|
||||
refreshed_instance = sender.objects.get(pk=instance.pk)
|
||||
send_model_update(refreshed_instance, change_type)
|
||||
try:
|
||||
# Re-fetch the instance to ensure we have the committed data
|
||||
refreshed_instance = sender.objects.get(pk=instance.pk)
|
||||
send_model_update(refreshed_instance, change_type)
|
||||
except sender.DoesNotExist:
|
||||
# Object đã bị xóa (ví dụ: delete_entry vừa xóa Internal_Entry)
|
||||
# Bỏ qua việc gửi update, hoặc gửi thông báo "deleted" nếu cần
|
||||
print(f"Object {sender.__name__} {instance.pk} đã bị xóa, bỏ qua gửi update.")
|
||||
# Optional: vẫn gửi "deleted" để frontend biết object không còn
|
||||
send_model_update(instance, "deleted")
|
||||
except Exception as exc:
|
||||
print(f"Lỗi trong send_update_after_commit: {exc}")
|
||||
|
||||
transaction.on_commit(send_update_after_commit)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
static/files/20260209023953-entry.xlsx
Normal file
BIN
static/files/20260209023953-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209024800-entry.xlsx
Normal file
BIN
static/files/20260209024800-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209030231-entry.xlsx
Normal file
BIN
static/files/20260209030231-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209031004-entry.xlsx
Normal file
BIN
static/files/20260209031004-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209032303-entry.xlsx
Normal file
BIN
static/files/20260209032303-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209033719-entry.xlsx
Normal file
BIN
static/files/20260209033719-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209033909-entry.xlsx
Normal file
BIN
static/files/20260209033909-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209034316-entry.xlsx
Normal file
BIN
static/files/20260209034316-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209034507-entry.xlsx
Normal file
BIN
static/files/20260209034507-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209034655-entry.xlsx
Normal file
BIN
static/files/20260209034655-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209034929-entry.xlsx
Normal file
BIN
static/files/20260209034929-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209035605-entry.xlsx
Normal file
BIN
static/files/20260209035605-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209043501-entry.xlsx
Normal file
BIN
static/files/20260209043501-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209043712-entry.xlsx
Normal file
BIN
static/files/20260209043712-entry.xlsx
Normal file
Binary file not shown.
BIN
static/files/20260209044229-entry.xlsx
Normal file
BIN
static/files/20260209044229-entry.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user