@ -16,6 +16,8 @@ export interface Client {
/ * * D 1 8 W S - F s u p p o r t - a c c e s s d a t a ( A P E X p a r i t y ) . T h e D B p a s s w o r d i s N O T h e r e —
* it is encrypted at rest and readable only via revealClientDbPassword ( audited ) . * /
anydesk? : string ; os? : string ; district? : string ; sector? : string
/** D35: 'PACS' (co-op society) or 'Store' (retail). */
clientType : string
/** True when an encrypted DB password is stored (the UI shows •••• + Reveal). */
hasDbPassword : boolean
}
@ -24,6 +26,7 @@ interface ClientRow {
id : string ; code : string ; name : string ; gstin : string | null ; state_code : string
address : string ; contacts : string ; status : string ; owner_id : string | null ; notes : string
anydesk : string | null ; os : string | null ; district : string | null ; sector : string | null
client_type : string | null
db_password_enc : string | null
source : string ; created_at : string
}
@ -40,6 +43,7 @@ function toClient(r: ClientRow): Client {
. . . ( r . os !== null ? { os : r.os } : { } ) ,
. . . ( r . district !== null ? { district : r.district } : { } ) ,
. . . ( r . sector !== null ? { sector : r.sector } : { } ) ,
clientType : r.client_type ? ? 'PACS' ,
hasDbPassword : r.db_password_enc !== null , // the ciphertext itself never leaves the repo
}
}
@ -50,7 +54,7 @@ function assertGstin(gstin: string): void {
}
export async function listClients (
db : DB , q? : string , filters : { district? : string ; sector? : string } = { } ,
db : DB , q? : string , filters : { district? : string ; sector? : string ; clientType? : string } = { } ,
) : Promise < Client [ ] > {
let where = ' WHERE 1=1'
const args : unknown [ ] = [ ]
@ -70,6 +74,9 @@ export async function listClients(
if ( filters . sector !== undefined && filters . sector !== '' ) {
where += ` AND LOWER(sector) LIKE LOWER(?) ` ; args . push ( ` % ${ filters . sector } % ` )
}
if ( filters . clientType !== undefined && filters . clientType !== '' ) {
where += ` AND client_type=? ` ; args . push ( filters . clientType )
}
const rows = await db . all < ClientRow > ( ` SELECT * FROM client ${ where } ORDER BY name ` , . . . args )
return rows . map ( toClient )
}
@ -82,7 +89,12 @@ export async function getClient(db: DB, id: string): Promise<Client | null> {
export interface ClientInput {
code? : string ; name : string ; gstin? : string ; stateCode : string ; address? : string
contacts? : ClientContact [ ] ; status? : Client [ 'status' ] ; notes? : string
anydesk? : string ; os? : string ; district? : string ; sector? : string
anydesk? : string ; os? : string ; district? : string ; sector? : string ; clientType? : string
}
/** New clients default their type from the name (…STORE → Store), like the backfill. */
function typeFromName ( name : string ) : string {
return /store[s]?$/i . test ( name . trim ( ) ) ? 'Store' : 'PACS'
}
export async function createClient ( db : DB , userId : string , input : ClientInput ) : Promise < Client > {
@ -92,12 +104,13 @@ export async function createClient(db: DB, userId: string, input: ClientInput):
const code = input . code ? ? ` CL ${ String ( n + 1 ) . padStart ( 4 , '0' ) } `
await db . run (
` INSERT INTO client (id, code, name, gstin, state_code, address, contacts, status,
anydesk , os , district , sector , notes, created_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )` ,
anydesk , os , district , sector , client_type, notes, created_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )` ,
id , code , input . name , input . gstin ? ? null , input . stateCode ,
input . address ? ? '' , JSON . stringify ( input . contacts ? ? [ ] ) ,
input . status ? ? 'active' ,
input . anydesk ? ? null , input . os ? ? null , input . district ? ? null , input . sector ? ? null ,
input . clientType ? ? typeFromName ( input . name ) ,
input . notes ? ? '' , new Date ( ) . toISOString ( ) ,
)
const client = ( await getClient ( db , id ) ) !
@ -110,6 +123,7 @@ export interface ClientPatch {
contacts? : ClientContact [ ] ; status? : Client [ 'status' ] ; notes? : string
/** '' clears the field (stored NULL). */
anydesk? : string ; os? : string ; district? : string ; sector? : string
clientType? : string
}
export async function updateClient ( db : DB , userId : string , id : string , patch : ClientPatch ) : Promise < Client > {
@ -125,6 +139,7 @@ export async function updateClient(db: DB, userId: string, id: string, patch: Cl
if ( patch . contacts !== undefined ) { sets . push ( 'contacts=?' ) ; args . push ( JSON . stringify ( patch . contacts ) ) }
if ( patch . status !== undefined ) { sets . push ( 'status=?' ) ; args . push ( patch . status ) }
if ( patch . notes !== undefined ) { sets . push ( 'notes=?' ) ; args . push ( patch . notes ) }
if ( patch . clientType !== undefined ) { sets . push ( 'client_type=?' ) ; args . push ( patch . clientType ) }
const optText = ( col : string , v : string | undefined ) = > {
if ( v !== undefined ) { sets . push ( ` ${ col } =? ` ) ; args . push ( v . trim ( ) === '' ? null : v . trim ( ) ) }
}