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) connection = self._get_connection(resolved_id)
try: try:
with connection.cursor() as cursor: with connection.cursor() as cursor:
# Comprehensive query to get all necessary data in one go # Get current month and year for invoice lookup
sql = """ 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 SELECT
c.*, c.*,
pi.name as package_name, pi.name as package_name,
pi.price as package_price, 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 FROM customer c
LEFT JOIN services s ON c.no_services = s.no_services 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 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) cursor.execute(sql)
customers = cursor.fetchall() customers = cursor.fetchall()
@@ -244,11 +256,15 @@ class BillingDatabase:
else: else:
# Handle generic text search # Handle generic text search
token_lower = token.lower() 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 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('no_wa', '')).lower() or
token_lower in str(cust.get('address', '')).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 match_all = False
break break

View File

@@ -95,7 +95,10 @@ async def list_tools() -> List[Tool]:
"type": "object", "type": "object",
"properties": { "properties": {
"query": { "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)." "description": "Search query (User ID, Name, or IP address). Leave empty to list all (use with limit)."
}, },
"limit": { "limit": {
@@ -158,6 +161,33 @@ async def list_tools() -> List[Tool]:
"type": "object", "type": "object",
"properties": {}, "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() db = get_billing_db()
if name == "search_customers": if name == "search_customers":
query = arguments.get("query", "") query = str(arguments.get("query", "")) # Convert to string in case it's a number
limit = arguments.get("limit", 5) limit = arguments.get("limit", 20) # Increased default limit for Telegram
isp_name = arguments.get("isp_name", "1") # Default logic in simple mode 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 # 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')}")] return [TextContent(type="text", text=f"Error searching ({isp_name}): {result.get('error')}")]
customers = result.get('customers', []) customers = result.get('customers', [])
total_found = result.get('total', len(customers))
if not customers: if not customers:
return [TextContent(type="text", text=f"No customers found matching '{query}'.")] return [TextContent(type="text", text=f"No customers found matching '{query}'.")]
# Format output # Format output with total count
output_lines = [f"Found {len(customers)} customers (ISP: {result.get('server_id')}):"] 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: 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 = [ details = [
f"User: {c.get('user_mikrotik', 'N/A')}", f"User: {c.get('user_mikrotik', 'N/A')}",
f"Name: {c.get('name', 'N/A')}", f"Name: {c.get('name', 'N/A')}",
f"Status: {c.get('c_status', '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)) 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))] 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}") raise ValueError(f"Unknown tool: {name}")
async def main(): async def main():