2026-06-02 13:14:10 +08:00
const {
requireAuth ,
requireEnterpriseAdmin ,
distributeCredits ,
2026-06-08 15:46:28 +08:00
creditsToCreditUnits ,
2026-06-02 13:14:10 +08:00
getEnterpriseFinancials ,
getEnterpriseName ,
pool ,
getPeriodStart ,
buildDailyTrend ,
generateOrderNo ,
clampPositiveInteger ,
clampNonNegativeInteger ,
} = require ( "./context" ) ;
function registerEnterpriseRoutes ( router ) {
// ── Enterprise: Dashboard & Financials ────────────────────────────────
router . get ( "/enterprise/dashboard" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const financials = await getEnterpriseFinancials ( req . user . enterpriseId ) ;
const {
rows : [ countRow ] ,
} = await pool . query (
"SELECT COUNT(*) AS count FROM users WHERE enterprise_id = $1 AND enabled = 1 AND is_enterprise_admin = 0" ,
[ req . user . enterpriseId ] ,
) ;
res . json ( {
enterpriseName : req . user . enterpriseName ,
enterpriseCode : req . user . enterpriseCode ,
balanceCents : financials . balanceCents ,
activePackages : financials . activePackages ,
subAccountCount : Number ( countRow ? . count || 0 ) ,
} ) ;
} ) ;
router . get ( "/enterprise/financials" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const financials = await getEnterpriseFinancials ( req . user . enterpriseId ) ;
res . json ( {
balanceCents : financials . balanceCents ,
activePackages : financials . activePackages . map ( ( p ) => ( {
id : p . id ,
packageName : p . package _name ,
remainingImage : p . remaining _image ,
remainingVideo : p . remaining _video ,
remainingText : p . remaining _text ,
expiresAt : p . expires _at ,
activatedAt : p . activated _at ,
} ) ) ,
recentTransactions : financials . recentTransactions . map ( ( t ) => ( {
id : t . id ,
type : t . type ,
amountCents : t . amount _cents ,
balanceAfterCents : t . balance _after _cents ,
description : t . description ,
createdAt : t . created _at ,
} ) ) ,
} ) ;
} ) ;
router . get ( "/enterprise/usage/summary" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const { period = "30d" } = req . query ;
const periodStart = getPeriodStart ( period ) ;
const ledgerDateJoin = periodStart ? ` AND cl.created_at >= ${ periodStart } ` : "" ;
const ledgerDateWhere = periodStart ? ` AND created_at >= ${ periodStart } ` : "" ;
const recordsLimit = clampPositiveInteger ( req . query . limit , 50 , 200 ) ;
try {
const {
rows : [ enterprise ] ,
} = await pool . query (
"SELECT id, name, balance_cents FROM enterprises WHERE id = $1 AND enabled = 1 LIMIT 1" ,
[ req . user . enterpriseId ] ,
) ;
if ( ! enterprise ) return res . status ( 404 ) . json ( { error : "企业不存在或已禁用" } ) ;
const { rows : members } = await pool . query (
`
SELECT
u.id AS user_id,
u.username,
COALESCE(em.role, CASE WHEN u.is_enterprise_admin = 1 THEN 'admin' ELSE 'employee' END) AS member_role,
COALESCE(SUM(CASE WHEN cl.status IN ('reserved', 'charged') THEN cl.amount_cents ELSE 0 END), 0) AS used_cents,
COUNT(CASE WHEN cl.status IN ('reserved', 'charged') THEN 1 END) AS task_count,
MAX(cl.created_at) AS last_used_at
FROM users u
LEFT JOIN enterprise_members em ON em.enterprise_id = u.enterprise_id AND em.user_id = u.id
LEFT JOIN credit_ledger cl ON cl.enterprise_id = u.enterprise_id AND cl.user_id = u.id ${ ledgerDateJoin }
WHERE u.enterprise_id = $ 1 AND u.enabled = 1
GROUP BY u.id, u.username, em.role, u.is_enterprise_admin
ORDER BY used_cents DESC, u.id ASC
` ,
[ req . user . enterpriseId ] ,
) ;
const { rows : modelBreakdown } = await pool . query (
`
SELECT
COALESCE(model, 'unknown') AS model,
COALESCE(SUM(amount_cents), 0) AS used_cents,
COUNT(*) AS task_count
FROM credit_ledger
WHERE enterprise_id = $ 1
AND status IN ('reserved', 'charged')
${ ledgerDateWhere }
GROUP BY COALESCE(model, 'unknown')
ORDER BY used_cents DESC
LIMIT 50
` ,
[ req . user . enterpriseId ] ,
) ;
const {
rows : [ totalRow ] ,
} = await pool . query (
`
SELECT COALESCE(SUM(amount_cents), 0) AS total_used_cents
FROM credit_ledger
WHERE enterprise_id = $ 1
AND status IN ('reserved', 'charged')
${ ledgerDateWhere }
` ,
[ req . user . enterpriseId ] ,
) ;
const { rows : records } = await pool . query (
`
SELECT cl.*, u.username, gt.params_json AS task_params_json
FROM credit_ledger cl
LEFT JOIN users u ON u.id = cl.user_id
LEFT JOIN generation_tasks gt ON gt.id = cl.task_id
WHERE cl.enterprise_id = $ 1
AND cl.status IN ('reserved', 'charged')
${ periodStart ? ` AND cl.created_at >= ${ periodStart } ` : "" }
ORDER BY cl.created_at DESC
LIMIT $ 2
` ,
[ req . user . enterpriseId , recordsLimit ] ,
) ;
const { rows : trendRows } = await pool . query (
`
SELECT
to_char(date_trunc('day', created_at), 'YYYY-MM-DD') AS day,
COUNT(*) AS task_count,
COALESCE(SUM(amount_cents), 0) AS used_cents
FROM credit_ledger
WHERE enterprise_id = $ 1
AND status IN ('reserved', 'charged')
AND created_at >= NOW() - INTERVAL '6 days'
GROUP BY day
ORDER BY day ASC
` ,
[ req . user . enterpriseId ] ,
) ;
const dailyTrend = buildDailyTrend ( trendRows ) ;
res . json ( {
enterpriseId : String ( enterprise . id ) ,
enterpriseName : enterprise . name ,
balanceCents : Number ( enterprise . balance _cents || 0 ) ,
totalUsedCents : Number ( totalRow ? . total _used _cents || 0 ) ,
members : members . map ( ( row ) => ( {
userId : Number ( row . user _id ) ,
username : row . username ,
role : row . member _role || "employee" ,
usedCents : Number ( row . used _cents || 0 ) ,
taskCount : Number ( row . task _count || 0 ) ,
lastUsedAt : row . last _used _at || null ,
} ) ) ,
modelBreakdown : modelBreakdown . map ( ( row ) => ( {
model : row . model ,
usedCents : Number ( row . used _cents || 0 ) ,
taskCount : Number ( row . task _count || 0 ) ,
} ) ) ,
records : records . map ( ( row ) => {
let prompt = "" ;
try {
const params = row . task _params _json ? JSON . parse ( row . task _params _json ) : { } ;
prompt = params . prompt || "" ;
} catch { }
return {
id : String ( row . id ) ,
userId : row . user _id == null ? "" : Number ( row . user _id ) ,
username : row . username || "" ,
model : row . model || "" ,
taskType : row . task _type ,
resolution : row . resolution || null ,
durationSeconds : row . duration _seconds == null ? null : Number ( row . duration _seconds ) ,
amountCents : Number ( row . amount _cents || 0 ) ,
prompt ,
status : row . status ,
createdAt : row . created _at ,
} ;
} ) ,
} ) ;
} catch ( error ) {
console . error ( "[enterprise/usage/summary] failed" , error ) ;
res . status ( 500 ) . json ( { error : "企业用量汇总加载失败" } ) ;
}
} ) ;
router . get ( "/enterprise/usage/records" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const limit = clampPositiveInteger ( req . query . limit , 50 , 200 ) ;
const offset = clampNonNegativeInteger ( req . query . offset , 0 , 100000 ) ;
const userId = req . query . userId || req . query . user _id ;
const model = String ( req . query . model || "" ) . trim ( ) ;
const dateFrom = req . query . from || req . query . date _from ;
const dateTo = req . query . to || req . query . date _to ;
const where = [ "cl.enterprise_id = $1" ] ;
const params = [ req . user . enterpriseId ] ;
if ( userId ) {
params . push ( userId ) ;
where . push ( ` cl.user_id = $ ${ params . length } ` ) ;
}
if ( model ) {
params . push ( model ) ;
where . push ( ` cl.model = $ ${ params . length } ` ) ;
}
if ( dateFrom ) {
params . push ( ` ${ dateFrom } T00:00:00.000Z ` ) ;
where . push ( ` cl.created_at >= $ ${ params . length } ` ) ;
}
if ( dateTo ) {
params . push ( ` ${ dateTo } T23:59:59.999Z ` ) ;
where . push ( ` cl.created_at <= $ ${ params . length } ` ) ;
}
const whereSql = ` WHERE ${ where . join ( " AND " ) } ` ;
try {
const {
rows : [ countRow ] ,
} = await pool . query ( ` SELECT COUNT(*) AS total FROM credit_ledger cl ${ whereSql } ` , params ) ;
const { rows } = await pool . query (
`
SELECT cl.*, u.username, gt.params_json AS task_params_json
FROM credit_ledger cl
LEFT JOIN users u ON u.id = cl.user_id
LEFT JOIN generation_tasks gt ON gt.id = cl.task_id
${ whereSql }
ORDER BY cl.created_at DESC
LIMIT $ ${ params . length + 1 }
OFFSET $ ${ params . length + 2 }
` ,
[ ... params , limit , offset ] ,
) ;
res . json ( {
items : rows . map ( ( row ) => {
let prompt = "" ;
try {
const params = row . task _params _json ? JSON . parse ( row . task _params _json ) : { } ;
prompt = params . prompt || "" ;
} catch { }
return {
id : String ( row . id ) ,
userId : row . user _id == null ? "" : Number ( row . user _id ) ,
username : row . username || "" ,
model : row . model || "" ,
taskType : row . task _type ,
resolution : row . resolution || null ,
durationSeconds : row . duration _seconds == null ? null : Number ( row . duration _seconds ) ,
amountCents : Number ( row . amount _cents || 0 ) ,
prompt ,
status : row . status ,
createdAt : row . created _at ,
} ;
} ) ,
total : Number ( countRow ? . total || 0 ) ,
limit ,
offset ,
} ) ;
} catch ( error ) {
console . error ( "[enterprise/usage/records] failed" , error ) ;
res . status ( 500 ) . json ( { error : "企业用量记录加载失败" } ) ;
}
} ) ;
router . post ( "/enterprise/recharge" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const { amountCents , paymentMethod = "wechat" } = req . body ;
if ( ! amountCents || amountCents <= 0 )
return res . status ( 400 ) . json ( { error : "充值金额必须大于0" } ) ;
const orderNo = generateOrderNo ( ) ;
const enterpriseName = await getEnterpriseName ( req . user . enterpriseId ) ;
try {
await pool . query (
`
INSERT INTO payment_orders (order_no, enterprise_id, enterprise_name, type, amount_cents, payment_method, status)
VALUES ( $ 1, $ 2, $ 3, 'recharge', $ 4, $ 5, 'pending')
` ,
[ orderNo , req . user . enterpriseId , enterpriseName , Number ( amountCents ) , paymentMethod ] ,
) ;
res . json ( { orderNo , amountCents , paymentMethod } ) ;
} catch ( error ) {
console . error ( "[enterprise/recharge] failed" , error ) ;
res . status ( 500 ) . json ( { error : "创建充值订单失败" } ) ;
}
} ) ;
router . post ( "/enterprise/distribute" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
2026-06-08 15:46:28 +08:00
const { userId , amountCredits , amountCents , distributions } = req . body ;
2026-06-02 13:14:10 +08:00
try {
if ( distributions && Array . isArray ( distributions ) ) {
for ( const d of distributions ) {
2026-06-08 15:46:28 +08:00
const creditUnits =
d . amountCredits !== undefined && d . amountCredits !== null && d . amountCredits !== ""
? creditsToCreditUnits ( d . amountCredits )
: Number ( d . amountCents ) ;
if ( ! d . userId || ! creditUnits || creditUnits <= 0 ) {
2026-06-02 13:14:10 +08:00
return res
. status ( 400 )
2026-06-08 15:46:28 +08:00
. json ( { error : "每条分发记录必须包含有效的 userId 和 amountCredits" } ) ;
2026-06-02 13:14:10 +08:00
}
2026-06-08 15:46:28 +08:00
await distributeCredits ( req . user . enterpriseId , d . userId , creditUnits , req . user . id ) ;
2026-06-02 13:14:10 +08:00
}
res . json ( { success : true , count : distributions . length } ) ;
2026-06-08 15:46:28 +08:00
} else if ( userId && ( amountCredits || amountCents ) ) {
const creditUnits =
amountCredits !== undefined && amountCredits !== null && amountCredits !== ""
? creditsToCreditUnits ( amountCredits )
: Number ( amountCents ) ;
if ( ! creditUnits || creditUnits <= 0 ) return res . status ( 400 ) . json ( { error : "分发积分必须大于0" } ) ;
2026-06-02 13:14:10 +08:00
const result = await distributeCredits (
req . user . enterpriseId ,
userId ,
2026-06-08 15:46:28 +08:00
creditUnits ,
2026-06-02 13:14:10 +08:00
req . user . id ,
) ;
res . json ( { success : true , ... result } ) ;
} else {
return res . status ( 400 ) . json ( { error : "缺少分发参数" } ) ;
}
} catch ( error ) {
console . error ( "[enterprise/distribute] failed" , error ) ;
res . status ( 400 ) . json ( { error : "分发参数处理失败" } ) ;
}
} ) ;
router . get (
"/enterprise/employee-consumption" ,
requireAuth ,
requireEnterpriseAdmin ,
async ( req , res ) => {
const { period = "30d" } = req . query ;
const periodStart = getPeriodStart ( period ) ;
const whereClause = periodStart ? ` AND acl.created_at >= ${ periodStart } ` : "" ;
const { rows } = await pool . query (
`
SELECT
u.id AS user_id,
u.username,
u.balance_cents AS current_balance_cents,
COUNT(acl.id) AS total_calls,
2026-06-08 15:46:28 +08:00
COALESCE(SUM(CASE WHEN acl.cost_estimate IS NOT NULL THEN CAST(ROUND((acl.cost_estimate * 10000)::numeric) AS INTEGER) ELSE 0 END), 0) AS total_cost_cents,
2026-06-02 13:14:10 +08:00
MAX(acl.created_at) AS last_active
FROM users u
LEFT JOIN api_call_logs acl ON acl.user_id = u.id AND acl.status = 'success'
WHERE u.enterprise_id = $ 1 AND u.enabled = 1 AND u.is_enterprise_admin = 0 ${ whereClause }
GROUP BY u.id, u.username, u.balance_cents
ORDER BY total_cost_cents DESC
` ,
[ req . user . enterpriseId ] ,
) ;
res . json (
rows . map ( ( r ) => ( {
userId : Number ( r . user _id ) ,
username : r . username ,
currentBalanceCents : Number ( r . current _balance _cents ) ,
totalCalls : Number ( r . total _calls ) ,
totalCostCents : Number ( r . total _cost _cents ) ,
lastActive : r . last _active ,
} ) ) ,
) ;
} ,
) ;
router . post (
"/enterprise/purchase-package" ,
requireAuth ,
requireEnterpriseAdmin ,
async ( req , res ) => {
const { packageId , paymentMethod = "wechat" } = req . body ;
if ( ! packageId ) return res . status ( 400 ) . json ( { error : "缺少套餐ID" } ) ;
const {
rows : [ pkg ] ,
} = await pool . query ( "SELECT * FROM packages WHERE id = $1 AND enabled = 1" , [ packageId ] ) ;
if ( ! pkg ) return res . status ( 404 ) . json ( { error : "套餐不存在或已下架" } ) ;
const orderNo = generateOrderNo ( ) ;
const enterpriseName = await getEnterpriseName ( req . user . enterpriseId ) ;
try {
await pool . query (
`
INSERT INTO payment_orders (order_no, enterprise_id, enterprise_name, type, amount_cents, package_id, payment_method, status)
VALUES ( $ 1, $ 2, $ 3, 'package', $ 4, $ 5, $ 6, 'pending')
` ,
[
orderNo ,
req . user . enterpriseId ,
enterpriseName ,
pkg . price _cents ,
packageId ,
paymentMethod ,
] ,
) ;
res . json ( { orderNo , amountCents : pkg . price _cents , packageId , paymentMethod } ) ;
} catch ( error ) {
console . error ( "[enterprise/purchase-package] failed" , error ) ;
res . status ( 500 ) . json ( { error : "创建套餐订单失败" } ) ;
}
} ,
) ;
// ── Enterprise: Invoices ──────────────────────────────────────────────
router . post (
"/enterprise/invoice-apply" ,
requireAuth ,
requireEnterpriseAdmin ,
async ( req , res ) => {
const { paymentOrderId , type = "general" , title , taxNo } = req . body ;
if ( ! title ) return res . status ( 400 ) . json ( { error : "缺少发票抬头" } ) ;
let amountCents = 0 ;
if ( paymentOrderId ) {
const {
rows : [ order ] ,
} = await pool . query (
"SELECT * FROM payment_orders WHERE id = $1 AND enterprise_id = $2 AND status = $3" ,
[ paymentOrderId , req . user . enterpriseId , "paid" ] ,
) ;
if ( ! order ) return res . status ( 404 ) . json ( { error : "支付订单不存在或未支付" } ) ;
amountCents = order . amount _cents ;
}
const enterpriseName = await getEnterpriseName ( req . user . enterpriseId ) ;
try {
const {
rows : [ row ] ,
} = await pool . query (
`
INSERT INTO invoices (enterprise_id, enterprise_name, payment_order_id, type, title, tax_no, amount_cents, status)
VALUES ( $ 1, $ 2, $ 3, $ 4, $ 5, $ 6, $ 7, 'pending')
RETURNING id
` ,
[
req . user . enterpriseId ,
enterpriseName ,
paymentOrderId || null ,
type ,
title ,
taxNo || null ,
amountCents ,
] ,
) ;
res . json ( { id : row . id , success : true } ) ;
} catch ( error ) {
console . error ( "[enterprise/invoice-apply] failed" , error ) ;
res . status ( 500 ) . json ( { error : "申请发票失败" } ) ;
}
} ,
) ;
router . get ( "/enterprise/invoices" , requireAuth , requireEnterpriseAdmin , async ( req , res ) => {
const { rows } = await pool . query (
"SELECT * FROM invoices WHERE enterprise_id = $1 ORDER BY id DESC" ,
[ req . user . enterpriseId ] ,
) ;
res . json (
rows . map ( ( row ) => ( {
id : Number ( row . id ) ,
type : row . type ,
title : row . title ,
taxNo : row . tax _no ,
amountCents : row . amount _cents ,
status : row . status ,
invoiceNo : row . invoice _no ,
invoiceUrl : row . invoice _url ,
issuedAt : row . issued _at ,
createdAt : row . created _at ,
} ) ) ,
) ;
} ) ;
}
module . exports = {
registerEnterpriseRoutes ,
} ;