from appPublic.log import debug, exception from appPublic.uniqueID import getID from datetime import datetime async def get_credit_stats(sor, orgid): """ Get credit summary statistics for an organization. Returns total_credit, total_used, total_available, usage_pct, customer_count. """ sql = """ SELECT COALESCE(SUM(credit_limit), 0) as total_credit, COALESCE(SUM(used_credit), 0) as total_used, COALESCE(SUM(available_credit), 0) as total_available, COUNT(*) as customer_count, COUNT(CASE WHEN status = 'active' THEN 1 END) as active_count, COUNT(CASE WHEN status = 'expired' THEN 1 END) as expired_count FROM credit_limit WHERE orgid = ${orgid}$ """ recs = await sor.sqlExe(sql, {'orgid': orgid}) if recs and len(recs) > 0: r = recs[0] total_credit = float(r.total_credit or 0) total_used = float(r.total_used or 0) total_available = float(r.total_available or 0) usage_pct = round((total_used / total_credit * 100), 1) if total_credit > 0 else 0 return { 'total_credit': total_credit, 'total_used': total_used, 'total_available': total_available, 'usage_pct': usage_pct, 'customer_count': int(r.customer_count or 0), 'active_count': int(r.active_count or 0), 'expired_count': int(r.expired_count or 0) } return { 'total_credit': 0, 'total_used': 0, 'total_available': 0, 'usage_pct': 0, 'customer_count': 0, 'active_count': 0, 'expired_count': 0 } async def get_my_credit_list(sor, orgid): """ Get all credit limit records for the current user's organization, with organization name and account info for display. """ sql = """ SELECT cl.*, org.orgname as orgname_text, sub.name as subject_name, CASE WHEN cl.credit_limit > 0 THEN ROUND(cl.used_credit / cl.credit_limit * 100, 1) ELSE 0 END as usage_pct FROM credit_limit cl LEFT JOIN organization org ON cl.orgid = org.id COLLATE utf8mb4_unicode_ci LEFT JOIN account acc ON cl.accountid = acc.id COLLATE utf8mb4_unicode_ci LEFT JOIN subject sub ON acc.subjectid = sub.id COLLATE utf8mb4_unicode_ci WHERE cl.orgid = ${orgid}$ ORDER BY cl.created_at DESC """ recs = await sor.sqlExe(sql, {'orgid': orgid}) return recs async def get_all_customer_credits(sor, orgid, status_filter=None): """ Get all customer credit limits for management view. For distributor sales to see all their customers' credit status. """ where_clause = "WHERE cl.orgid = ${orgid}$" params = {'orgid': orgid} if status_filter and status_filter != 'all': where_clause += " AND cl.status = ${status}$" params['status'] = status_filter sql = f""" SELECT cl.*, org.orgname as orgname_text, sub.name as subject_name, acc.balance as account_balance, CASE WHEN cl.credit_limit > 0 THEN ROUND(cl.used_credit / cl.credit_limit * 100, 1) ELSE 0 END as usage_pct FROM credit_limit cl LEFT JOIN organization org ON cl.orgid = org.id COLLATE utf8mb4_unicode_ci LEFT JOIN account acc ON cl.accountid = acc.id COLLATE utf8mb4_unicode_ci LEFT JOIN subject sub ON acc.subjectid = sub.id COLLATE utf8mb4_unicode_ci {where_clause} ORDER BY cl.updated_at DESC """ recs = await sor.sqlExe(sql, params) return recs async def get_credit_limit_for_account(sor, accid): """ Get active credit limit for an account. Returns credit_limit record if active and valid, None otherwise. """ sql = """ SELECT * FROM credit_limit WHERE accountid = ${accid}$ AND status = 'active' AND (valid_from IS NULL OR valid_from <= CURRENT_DATE) AND (valid_to IS NULL OR valid_to >= CURRENT_DATE) ORDER BY created_at DESC LIMIT 1 """ recs = await sor.sqlExe(sql, {'accid': accid}) if len(recs) == 0: return None return recs[0] async def update_used_credit(sor, accid, new_used_amount): """ Update used_credit and available_credit for an account. new_used_amount is the absolute value of negative balance. """ credit = await get_credit_limit_for_account(sor, accid) if credit is None: return new_used = new_used_amount new_available = credit['credit_limit'] - new_used sql = """ UPDATE credit_limit SET used_credit = ${used}$, available_credit = ${available}$, updated_at = CURRENT_TIMESTAMP WHERE id = ${id}$ """ await sor.sqlExe(sql, { 'used': new_used, 'available': new_available, 'id': credit['id'] }) debug(f'Updated credit for {accid}: used={new_used}, available={new_available}') async def set_credit_limit(sor, accountid, orgid, credit_limit_amount, valid_from=None, valid_to=None, created_by=None, remark=None): """ Set or update credit limit for an account. If a credit limit already exists, update it; otherwise create new. """ # Check if credit limit exists existing = await get_credit_limit_for_account(sor, accountid) if existing: # Update existing sql = """ UPDATE credit_limit SET credit_limit = ${credit_limit}$, available_credit = ${credit_limit}$ - used_credit, valid_from = ${valid_from}$, valid_to = ${valid_to}$, remark = ${remark}$, updated_at = CURRENT_TIMESTAMP WHERE id = ${id}$ """ await sor.sqlExe(sql, { 'credit_limit': credit_limit_amount, 'valid_from': valid_from, 'valid_to': valid_to, 'remark': remark, 'id': existing['id'] }) debug(f'Updated credit limit for {accountid}: {credit_limit_amount}') return existing['id'] else: # Create new new_id = getID() ns = { 'id': new_id, 'accountid': accountid, 'orgid': orgid, 'credit_limit': credit_limit_amount, 'used_credit': 0, 'available_credit': credit_limit_amount, 'valid_from': valid_from, 'valid_to': valid_to, 'status': 'active', 'created_at': datetime.now(), 'updated_at': datetime.now(), 'created_by': created_by, 'remark': remark } await sor.C('credit_limit', ns) debug(f'Created credit limit for {accountid}: {credit_limit_amount}') return new_id