Fix: Update invoice status logic to use current month invoice table, fixing incorrect payment status display

This commit is contained in:
2026-02-03 22:40:57 +08:00
parent a7dcba09fb
commit 4717906d14
3 changed files with 155 additions and 13 deletions

View File

@@ -148,16 +148,28 @@ class BillingDatabase:
connection = self._get_connection(resolved_id)
try:
with connection.cursor() as cursor:
# Comprehensive query to get all necessary data in one go
sql = """
# Get current month and year for invoice lookup
from datetime import datetime as dt
current_month = dt.now().strftime('%m')
current_year = dt.now().year
# Comprehensive query to get all necessary data including current invoice status
sql = f"""
SELECT
c.*,
pi.name as package_name,
pi.price as package_price,
pi.description as package_description
pi.description as package_description,
inv.status as invoice_status,
inv.month as invoice_month,
inv.year as invoice_year,
inv.inv_due_date as invoice_due_date
FROM customer c
LEFT JOIN services s ON c.no_services = s.no_services
LEFT JOIN package_item pi ON s.item_id = pi.p_item_id
LEFT JOIN invoice inv ON c.no_services = inv.no_services
AND inv.month = '{current_month}'
AND inv.year = {current_year}
"""
cursor.execute(sql)
customers = cursor.fetchall()
@@ -244,11 +256,15 @@ class BillingDatabase:
else:
# Handle generic text search
token_lower = token.lower()
# Check multiple fields
# Check multiple fields including email, customer ID, and KTP
if not (token_lower in str(cust.get('name', '')).lower() or
token_lower in str(cust.get('no_wa', '')).lower() or
token_lower in str(cust.get('address', '')).lower() or
token_lower in str(cust.get('user_profile', '')).lower()):
token_lower in str(cust.get('user_profile', '')).lower() or
token_lower in str(cust.get('email', '')).lower() or
token_lower in str(cust.get('no_services', '')).lower() or
token_lower in str(cust.get('user_mikrotik', '')).lower() or
token_lower in str(cust.get('no_ktp', '')).lower()):
match_all = False
break

View File

@@ -95,7 +95,10 @@ async def list_tools() -> List[Tool]:
"type": "object",
"properties": {
"query": {
"type": "string",
"oneOf": [
{"type": "string"},
{"type": "integer"}
],
"description": "Search query (User ID, Name, or IP address). Leave empty to list all (use with limit)."
},
"limit": {
@@ -158,6 +161,33 @@ async def list_tools() -> List[Tool]:
"type": "object",
"properties": {},
}
),
Tool(
name="get_customer_details",
description="Get detailed information about a specific customer by their User ID (no_services) or phone number.",
inputSchema={
"type": "object",
"properties": {
"customer_id": {
"oneOf": [
{"type": "string"},
{"type": "integer"}
],
"description": "The customer's User ID (no_services field, e.g. '221001182866')"
},
"phone_number": {
"oneOf": [
{"type": "string"},
{"type": "integer"}
],
"description": "The customer's phone number (alternative to customer_id)"
},
"isp_name": {
"type": "string",
"description": "Optional: Specific ISP/Provider name. If omitted, defaults to first available."
}
}
}
)
]
@@ -166,8 +196,8 @@ async def call_tool(name: str, arguments: Any) -> List[TextContent | ImageConten
db = get_billing_db()
if name == "search_customers":
query = arguments.get("query", "")
limit = arguments.get("limit", 5)
query = str(arguments.get("query", "")) # Convert to string in case it's a number
limit = arguments.get("limit", 20) # Increased default limit for Telegram
isp_name = arguments.get("isp_name", "1") # Default logic in simple mode
# If isp_name is not provided, BillingDatabase likely handles "1" as default fallback
@@ -180,19 +210,47 @@ async def call_tool(name: str, arguments: Any) -> List[TextContent | ImageConten
return [TextContent(type="text", text=f"Error searching ({isp_name}): {result.get('error')}")]
customers = result.get('customers', [])
total_found = result.get('total', len(customers))
if not customers:
return [TextContent(type="text", text=f"No customers found matching '{query}'.")]
# Format output
output_lines = [f"Found {len(customers)} customers (ISP: {result.get('server_id')}):"]
# Format output with total count
if total_found > len(customers):
output_lines = [f"📊 Ditemukan {total_found} pelanggan, menampilkan {len(customers)} hasil (ISP: {result.get('server_id')}):"]
else:
output_lines = [f"📊 Ditemukan {total_found} pelanggan (ISP: {result.get('server_id')}):"]
for c in customers:
# Format key details
# Determine payment status from invoice table (current month)
invoice_status = c.get('invoice_status', '')
if invoice_status:
if invoice_status.upper() == 'SUDAH BAYAR':
payment_status = "✅ Lunas"
elif invoice_status.upper() == 'BELUM BAYAR':
payment_status = "⚠️ Belum Bayar"
else:
payment_status = f"📋 {invoice_status}"
else:
# Fallback to action field if no invoice exists
action = c.get('action', 0)
payment_status = "✅ Lunas" if action == 1 else "⏳ Belum Ada Invoice"
# Format amount
amount = c.get('cust_amount', 0)
amount_str = f"Rp{amount:,}".replace(",", ".") if amount else "N/A"
# Use invoice due date if available
due_date = c.get('invoice_due_date') or f"Tgl {c.get('due_date', 'N/A')}"
# Format key details with billing info
details = [
f"User: {c.get('user_mikrotik', 'N/A')}",
f"Name: {c.get('name', 'N/A')}",
f"Status: {c.get('c_status', 'N/A')}",
f"Address: {c.get('address', 'N/A')}",
f"Packet: {c.get('user_profile', 'N/A')}"
f"Packet: {c.get('user_profile', 'N/A')}",
f"Tagihan: {amount_str}",
f"Jatuh Tempo: {due_date}",
payment_status
]
output_lines.append(" | ".join(details))
@@ -301,6 +359,74 @@ async def call_tool(name: str, arguments: Any) -> List[TextContent | ImageConten
return [TextContent(type="text", text="\n".join(output))]
elif name == "get_customer_details":
customer_id = arguments.get("customer_id")
phone_number = arguments.get("phone_number")
isp_name = arguments.get("isp_name", "1")
# Convert to string if provided (handles integer inputs)
if customer_id is not None:
customer_id = str(customer_id)
if phone_number is not None:
phone_number = str(phone_number)
if not customer_id and not phone_number:
return [TextContent(type="text", text="Error: Either customer_id or phone_number must be provided.")]
result = db.get_customer_details(
customer_id=customer_id,
phone_number=phone_number,
server_id=isp_name
)
if not result['success']:
return [TextContent(type="text", text=f"Error: {result.get('error')}")]
c = result.get('customer', {})
# Determine payment status form invoice table (current month)
invoice_status = c.get('invoice_status', '')
if invoice_status:
if invoice_status.upper() == 'SUDAH BAYAR':
payment_status = "✅ Lunas"
elif invoice_status.upper() == 'BELUM BAYAR':
payment_status = "⚠️ Belum Bayar"
else:
payment_status = f"📋 {invoice_status}"
else:
# Fallback
action = c.get('action', 0)
payment_status = "✅ Lunas" if action == 1 else "⏳ Belum Ada Invoice"
# Use invoice due date if available
due_date = c.get('invoice_due_date') or f"Tanggal {c.get('due_date', 'N/A')}"
# Format amount
amount = c.get('cust_amount', 0)
amount_str = f"Rp{amount:,}".replace(",", ".") if amount else "N/A"
# Format detailed output with billing info
output_lines = [
"📋 Customer Details:",
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
f"👤 Name: {c.get('name', 'N/A')}",
f"🆔 User ID: {c.get('no_services', 'N/A')}",
f"🌐 Mikrotik User: {c.get('user_mikrotik', 'N/A')}",
f"📊 Status: {c.get('c_status', 'N/A')}",
f"📦 Package: {c.get('user_profile', 'N/A')} ({c.get('package_name', 'N/A')})",
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
f"💰 Tagihan: {amount_str}",
f"📅 Jatuh Tempo: {due_date}",
f"💳 Status Bayar: {payment_status}",
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
f"📍 Address: {c.get('address', 'N/A')}",
f"📱 Phone: {c.get('no_wa', 'N/A')}",
f"📧 Email: {c.get('email', 'N/A')}",
f"🔗 Router: {c.get('router', 'N/A')}",
]
return [TextContent(type="text", text="\n".join(output_lines))]
raise ValueError(f"Unknown tool: {name}")
async def main():