@ -29,14 +29,25 @@ function endOfMonth(today: string): string {
return new Date ( Date . UTC ( y ! , m ! , 0 ) ) . toISOString ( ) . slice ( 0 , 10 ) // day 0 of next month = last day of this
}
export function dashboardView ( db : DB , today : string , viewer ? : { id : string ; role : string } ) : DashboardView {
export function dashboardView (
db : DB , today : string , viewer ? : { id : string ; role : string } , opts : { mine? : boolean } = { } ,
) : DashboardView {
// My Day (D18 WS-E): staff are ALWAYS scoped to their own book (ownerScope forces
// it); owner/manager opt in via mine=true. "Mine" = clients I own, documents and
// payments I created, my follow-ups.
const meId = viewer !== undefined && ( opts . mine === true || ownerScope ( viewer ) !== undefined )
? viewer . id
: undefined
const mineDoc = meId !== undefined ? ` AND (d.created_by=? OR c.owner_id=?) ` : ''
const mineClient = meId !== undefined ? ` AND c.owner_id=? ` : ''
const overdueRows = db . prepare (
` SELECT d.id, d.doc_no, d.client_id, d.doc_date, c.name AS client_name
FROM document d JOIN client c ON c . id = d . client_id
WHERE d . doc_type = 'INVOICE' AND d . doc_no IS NOT NULL
AND d . status NOT IN ( 'paid' , 'cancelled' , 'lost' ) AND d . doc_date <= ?
AND d . status NOT IN ( 'paid' , 'cancelled' , 'lost' ) AND d . doc_date <= ? $ { mineDoc }
ORDER BY d . doc_date ` ,
) . all ( today ) as { id : string ; doc_no : string | null ; client_id : string ; doc_date : string ; client_name : string } [ ]
) . all ( . . . ( meId !== undefined ? [ today , meId , meId ] : [ today ] ) ) as { id : string ; doc_no : string | null ; client_id : string ; doc_date : string ; client_name : string } [ ]
const overdue : DashboardView [ 'overdue' ] = [ ]
let overduePaise = 0
for ( const r of overdueRows ) {
@ -53,8 +64,8 @@ export function dashboardView(db: DB, today: string, viewer?: { id: string; role
const dueThisWeek = db . prepare (
` SELECT rp.id AS plan_id, rp.client_id, rp.next_run, rp.amount_paise, c.name AS client_name
FROM recurring_plan rp JOIN client c ON c . id = rp . client_id
WHERE rp . active = 1 AND rp . next_run >= ? AND rp . next_run <= ? ORDER BY rp . next_run ` ,
) . all ( today , weekEnd ) . map ( ( r ) = > {
WHERE rp . active = 1 AND rp . next_run >= ? AND rp . next_run <= ? $ { mineClient } ORDER BY rp . next_run ` ,
) . all ( . . . ( meId !== undefined ? [ today , weekEnd , meId ] : [ today , weekEnd ] ) ) . map ( ( r ) = > {
const row = r as { plan_id : string ; client_id : string ; next_run : string ; amount_paise : number | null ; client_name : string }
return { planId : row.plan_id , clientId : row.client_id , clientName : row.client_name , nextRun : row.next_run , amountPaise : row.amount_paise }
} )
@ -62,30 +73,35 @@ export function dashboardView(db: DB, today: string, viewer?: { id: string; role
const renewalsThisMonth = db . prepare (
` SELECT cm.id AS cm_id, cm.client_id, cm.next_renewal, c.name AS client_name
FROM client_module cm JOIN client c ON c . id = cm . client_id
WHERE cm . active = 1 AND cm . next_renewal >= ? AND cm . next_renewal <= ? ORDER BY cm . next_renewal ` ,
) . all ( today , endOfMonth ( today ) ) . map ( ( r ) = > {
WHERE cm . active = 1 AND cm . next_renewal >= ? AND cm . next_renewal <= ? $ { mineClient } ORDER BY cm . next_renewal ` ,
) . all ( . . . ( meId !== undefined ? [ today , endOfMonth ( today ) , meId ] : [ today , endOfMonth ( today ) ] ) ) . map ( ( r ) = > {
const row = r as { cm_id : string ; client_id : string ; next_renewal : string ; client_name : string }
return { clientModuleId : row.cm_id , clientId : row.client_id , clientName : row.client_name , nextRenewal : row.next_renewal }
} )
const followUpsToday = listOpenFollowUps ( db , today ) . map ( ( f ) = > ( {
id : f.id , clientId : f.clientId , clientName : f.clientName , onDate : f.onDate ,
followUpOn : f.followUpOn ! , notes : f.notes ,
} ) )
const followUpsToday = listOpenFollowUps ( db , today )
. filter ( ( f ) = > meId === undefined || f . staffId === meId ) // my follow-ups only under My Day
. map ( ( f ) = > ( {
id : f.id , clientId : f.clientId , clientName : f.clientName , onDate : f.onDate ,
followUpOn : f.followUpOn ! , notes : f.notes ,
} ) )
const recentPayments = db . prepare (
` SELECT p.id, p.client_id, p.received_on, p.amount_paise, p.mode, c.name AS client_name
FROM payment p JOIN client c ON c . id = p . client_id ORDER BY p . id DESC LIMIT 10 ` ,
) . all ( ) . map ( ( r ) = > {
FROM payment p JOIN client c ON c . id = p . client_id
WHERE 1 = 1 $ { meId !== undefined ? ' AND (p.created_by=? OR c.owner_id=?)' : '' }
ORDER BY p . id DESC LIMIT 10 ` ,
) . all ( . . . ( meId !== undefined ? [ meId , meId ] : [ ] ) ) . map ( ( r ) = > {
const row = r as { id : string ; client_id : string ; received_on : string ; amount_paise : number ; mode : string ; client_name : string }
return { id : row.id , clientId : row.client_id , clientName : row.client_name , receivedOn : row.received_on , amountPaise : row.amount_paise , mode : row.mode }
} )
// Same owner scope as GET /reminders: staff see only their own queue (spec §6e).
// Same owner scope as GET /reminders: staff see only their own queue (spec §6e);
// a managerial My Day narrows the queue to their own rows via ownerId.
const queuePage = viewer !== undefined
? listQueue ( db , { viewerId : viewer.id , viewerRole : viewer.role } )
? listQueue ( db , { viewerId : viewer.id , viewerRole : viewer.role , . . . ( meId !== undefined ? { ownerId : meId } : { } ) } )
: listQueue ( db )
const counts = queueCounts ( db , viewer !== undefined ? ownerScope ( viewer ) : undefined )
const counts = queueCounts ( db , viewer !== undefined ? ( meId ? ? ownerScope ( viewer ) ) : undefined )
return {
today , overdue , dueThisWeek , renewalsThisMonth , followUpsToday , recentPayments ,