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)
|
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
|
||||||
|
|
||||||
|
|||||||
142
src/server.py
142
src/server.py
@@ -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():
|
||||||
|
|||||||
Reference in New Issue
Block a user