Fix: Update invoice status logic to use current month invoice table, fixing incorrect payment status display
This commit is contained in:
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
142
src/server.py
142
src/server.py
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user