Merge branch 'feature/dds' into merge/dds-into-dev-latest

Brings the DDS (Daily Deposit Scheme) UI from feature/dds onto the
current dev-latest:
- Agent settlement (Agent Tran), Close Party account, DDS reports,
  Party-Savings link field
- Agent collection entry screen, per-agent GL account, maturity auto
  close
- Approvals tab, vouchers, payout & close, Sundry GL search, closure
  tab and fixes
- Menu: DDS Party Master, DDS Accounts, DDS Collection, DDS Reports and
  Locker links placed in the Deposits mega panel groups; Agent Master,
  Commission Slab and DDS Batch Processing under Organization; DDS
  Approvals tab in the checker inbox

No content conflicts. Nothing from dev-latest is removed.

Merge adjustments:
- package-lock.json: kept dev-latest's lock file. feature/dds does not
  add or change any dependency in package.json; its lock file was only
  regenerated (about 18k lines removed and 12k added), which would have
  changed resolved versions for packages already on dev-latest.
- Dropped src/app/deposits/party-master/CBS-Backend.code-workspace, a
  VS Code workspace file holding local folder paths, not app code.
- angular.json: bundle budget raised to 18mb warning / 20mb error, as on
  feature/dds.

Lint fixes required by the pre-commit hook (no behaviour change):
- Prettier formatting on 12 DDS files.
- create-party.component.scss: the dense data-entry section deliberately
  re-targets the base selectors to override them, so
  no-duplicate-selectors is disabled for that section instead of
  merging rules and changing the cascade.
- notice-dialog / create-dds-account-type scss: stylelint-disable-line
  for the "ch" width unit.
- commission-slab-master.component.scss: stylelint auto-fix.

Verified with tsc -p src/tsconfig.app.json --noEmit, stylelint, prettier
and eslint on all merged files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 15:10:37 +05:30
co-authored by Claude Opus 5.5
84 changed files with 17814 additions and 9 deletions
@@ -233,6 +233,343 @@ export class OrganizationService {
return this.http.get('/currencies');
}
/**
* @returns {Observable<any>} DDS Agents data
*/
getAgents(): Observable<any> {
return this.http.get('/dds/agents');
}
/**
* @param {string | number} agentId Agent ID.
* @returns {Observable<any>} DDS Agent.
*/
getAgent(agentId: string | number): Observable<any> {
return this.http.get(`/dds/agents/${agentId}`);
}
/**
* @param {any} agent Agent to be created.
* @returns {Observable<any>}
*/
createAgent(agent: any): Observable<any> {
return this.http.post('/dds/agents', agent);
}
/**
* @param {string | number} agentId Agent ID.
* @param {any} agent Agent changes to apply.
* @returns {Observable<any>}
*/
updateAgent(agentId: string | number, agent: any): Observable<any> {
return this.http.put(`/dds/agents/${agentId}`, agent);
}
/**
* @returns {Observable<any>} DDS Parties data
*/
getParties(): Observable<any> {
return this.http.get('/dds/parties');
}
/**
* The parties a given agent collects for — what the daily transaction screen works from, since
* it handles one agent's round at a time.
*
* @param {string | number} agentId Agent ID.
* @returns {Observable<any>} That agent's DDS Parties.
*/
getPartiesOfAgent(agentId: string | number): Observable<any> {
return this.http.get('/dds/parties', { params: new HttpParams().set('agentId', String(agentId)) });
}
/**
* @param {string | number} partyId Party ID.
* @returns {Observable<any>} DDS Party.
*/
getParty(partyId: string | number): Observable<any> {
return this.http.get(`/dds/parties/${partyId}`);
}
/**
* @param {any} party Party to be created.
* @returns {Observable<any>}
*/
createParty(party: any): Observable<any> {
return this.http.post('/dds/parties', party);
}
/**
* @param {string | number} partyId Party ID.
* @param {any} party Party changes to apply.
* @returns {Observable<any>}
*/
updateParty(partyId: string | number, party: any): Observable<any> {
return this.http.put(`/dds/parties/${partyId}`, party);
}
/**
* @param {string | number} partyId Party ID.
* @param {any} closeCommand { closeDate }
* @returns {Observable<any>}
*/
closeParty(partyId: string | number, closeCommand: any): Observable<any> {
return this.http.post(`/dds/parties/${partyId}?command=close`, closeCommand);
}
/**
* The DDS accounts whose term has run out and which are still open - the branch's list of closures
* waiting to be done. Nothing closes itself: a matured account is money the bank owes the customer,
* and where it goes has to be agreed with them.
*
* @returns {Observable<any>} Accounts due for closure, oldest maturity first.
*/
getPartiesDueForClosure(): Observable<any> {
return this.http.get('/dds/parties/dueforclosure');
}
/**
* The customer's own accounts a DDS maturity can be paid into - active savings accounts of theirs,
* with fixed and recurring deposits and their other DDS scheme accounts left out.
*
* The server applies the same conditions the payout itself does, so this list can never offer a
* choice that is then refused on save. Filtering here instead meant the two drifted apart: the
* accounts on offer once included a scheme account of a party that had already closed.
*
* @param {string | number} clientId Client ID.
* @returns {Observable<any>} id, accountNo, productName and balance for each.
*/
/** The liability GL accounts a DDS balance can be parked in, for the clerk to search and pick. */
getDdsSundryAccounts(): Observable<any> {
return this.http.get('/dds/parties/sundryaccounts');
}
/** DDS entries waiting on (or already past) a checker - PENDING, APPROVED or REJECTED. */
getDdsApprovals(status: string, fromDate?: string, toDate?: string): Observable<any> {
const params: any = { status };
if (fromDate) {
params.fromDate = fromDate;
}
if (toDate) {
params.toDate = toDate;
}
return this.http.get('/dds/approvals', { params });
}
getDdsApproval(commandId: string | number): Observable<any> {
return this.http.get(`/dds/approvals/${commandId}`);
}
/** Approves or rejects a DDS entry; an approval returns the voucher it moved under. */
actOnDdsApproval(commandId: string | number, command: 'approve' | 'reject'): Observable<any> {
return this.http.post(`/dds/approvals/${commandId}`, {}, { params: { command } });
}
/** How a closed DDS account was closed: by whom, what it came to and where the money went. */
getPartyClosure(partyId: string | number): Observable<any> {
return this.http.get(`/dds/parties/${partyId}/closure`);
}
getCreditableAccounts(clientId: string | number): Observable<any> {
return this.http.get('/dds/parties/creditaccounts', { params: { clientId: String(clientId) } });
}
/**
* Pays a matured DDS account out and closes it. What is paid is what the account holds, not the
* figure quoted at opening.
*
* @param {string | number} partyId Party ID.
* @param {any} payoutCommand { toSavingsAccountNo } - another savings account of the same customer
* to move the money into. Omit it when they have none here: the money is then parked in the
* matured-deposits payable account for the branch to send on to their account at another bank.
* @returns {Observable<any>}
*/
payOutParty(partyId: string | number, payoutCommand: any): Observable<any> {
return this.http.post(`/dds/parties/${partyId}?command=payout`, payoutCommand);
}
/**
* Closes a DDS account before its term is up, and pays out what is left - one act, unlike a
* maturity payout, which leaves the account open for the clerk to close separately.
*
* @param {string | number} partyId Party ID.
* @param {any} closeCommand { shortfallBorneBy, closeDate, dateFormat, locale, toSavingsAccountNo? }.
* closeDate, dateFormat and locale go up exactly as they do on an ordinary close.
* shortfallBorneBy is mandatory
* and is CUSTOMER (their interest is recalculated at the scheme's reduced rate and the
* difference taken back) or AGENT (the customer keeps a full month's interest and the agent
* funds the days left in it). The agent is charged a share of their commission either way.
* toSavingsAccountNo works as it does for a payout. The lock-in, the rate reduction and the
* agent's share come from the scheme and are applied by the server.
* @returns {Observable<any>}
*/
prematureCloseParty(partyId: string | number, closeCommand: any): Observable<any> {
return this.http.post(`/dds/parties/${partyId}?command=prematureclose`, closeCommand);
}
/**
* Closes a DDS account early inside its scheme's lock-in, for the reason given. Its own permission,
* so it goes to a manager for approval; the scheme's lock-in is not changed.
*/
prematureCloseOverrideParty(partyId: string | number, closeCommand: any): Observable<any> {
return this.http.post(`/dds/parties/${partyId}?command=prematurecloseoverride`, closeCommand);
}
/**
* @param {any} transaction Daily transaction batch (PARTY_TRAN collections or a WITHDRAW_DEPOSITE transfer) to post.
* @returns {Observable<any>}
*/
createPartyTransaction(transaction: any): Observable<any> {
return this.http.post('/dds/partytransactions', transaction);
}
/**
* What an agent wrote down on their round for a date, with what it adds up to.
*
* The counter reads it to count cash against; the agent's own screen shows it back as the record
* of their round. One call for both, so the two can never disagree about the same day.
*/
getAgentCollectionDay(agentId: any, entryDate: string): Observable<any> {
return this.http.get(`/dds/collectionentries?agentId=${agentId}&entryDate=${entryDate}`);
}
/**
* Turns an agent's outstanding entries for a date into real credits - all of them, together.
*
* No amount is sent. What settles is what the entries say, to the rupee: a figure typed at the
* counter would be a second opinion about a total the entries already give.
*/
settleAgentCollections(settlement: any): Observable<any> {
return this.http.post('/dds/collectionentries/settle', settlement);
}
/** Everything the agent still has outstanding, whatever day it was written - what a receipt is applied against. */
getAgentPendingCollections(agentId: any): Observable<any> {
return this.http.get(`/dds/collectionentries/pending?agentId=${agentId}`);
}
/**
* The cashier's receipts into the agent's GL account: the DDS receipts ready to apply, or - with a
* voucher number - that one voucher in whatever state, so the screen can say why it cannot be used.
*/
getAgentCashReceipts(agentId: any, voucherNo?: string, includeWaiting = false): Observable<any> {
const voucher = voucherNo ? `&voucherNo=${encodeURIComponent(voucherNo)}` : '';
const waiting = includeWaiting ? '&includeWaiting=true' : '';
return this.http.get(`/dds/collectionentries/cashreceipts?agentId=${agentId}${voucher}${waiting}`);
}
/** A cashier receipt's own Dr/Cr lines. */
getCashReceiptGlEntries(receiptId: any): Observable<any> {
return this.http.get(`/dds/collectionentries/cashreceipts/${receiptId}/glentries`);
}
/**
* An agent's DDS transactions. The daily entry screen uses this to show what has already been
* keyed for the agent today, so the same collection book is not entered twice.
*
* @param {string | number} agentId Agent ID.
* @returns {Observable<any>} That agent's transactions.
*/
getPartyTransactionsOfAgent(agentId: string | number): Observable<any> {
return this.http.get('/dds/partytransactions', {
params: new HttpParams().set('agentId', String(agentId))
});
}
/**
* @param {string | number} partyId DDS account (party) id.
* @returns {Observable<any>} Everything posted to that DDS account, most recent first.
*/
getPartyTransactionsOfParty(partyId: string | number): Observable<any> {
return this.http.get('/dds/partytransactions', {
params: new HttpParams().set('partyId', String(partyId))
});
}
/**
* @returns {Observable<any>} DDS Commission Slabs data
*/
getCommissionSlabs(): Observable<any> {
return this.http.get('/dds/commissionslabs');
}
/**
* @param {any[]} slabs New commission slab rows to add to the chart.
* @returns {Observable<any>}
*/
createCommissionSlabs(slabs: any[]): Observable<any> {
return this.http.post('/dds/commissionslabs', { slabs });
}
/**
* Corrects one saved commission slab. The backend re-checks the corrected range against the other
* slabs of its type and refuses an overlap.
*
* @param {string | number} slabId Commission slab ID.
* @param {any} slab The corrected slab.
* @returns {Observable<any>} Command result.
*/
updateCommissionSlab(slabId: string | number, slab: any): Observable<any> {
return this.http.put(`/dds/commissionslabs/${slabId}`, slab);
}
/**
* @param {any} batch Batch run filters (jobType, branchScope, jobDate, agentFrom/To, partyFrom/To) to generate a preview for.
* @returns {Observable<any>}
*/
generateDdsBatchRun(batch: any): Observable<any> {
return this.http.post('/dds/batchruns', batch);
}
/**
* @param {string | number} batchRunId Batch run ID.
* @returns {Observable<any>} Batch run, including its previewed/posted rows.
*/
getDdsBatchRun(batchRunId: string | number): Observable<any> {
return this.http.get(`/dds/batchruns/${batchRunId}`);
}
/**
* @returns {Observable<any>} Every batch run, most recent first — who ran what, and whether it was posted.
*/
getDdsBatchRuns(): Observable<any> {
return this.http.get('/dds/batchruns');
}
/**
* Ends the relationship with an agent. Refused while they still have accounts assigned, hold
* collected cash, or are owed commission — distinct from deactivating, which is never refused.
*
* @param {string | number} agentId Agent ID.
* @returns {Observable<any>}
*/
closeAgent(agentId: string | number): Observable<any> {
return this.http.post(`/dds/agents/${agentId}`, {});
}
/**
* @param {string | number} batchRunId Batch run ID to post - applies exactly the rows it previewed.
* @returns {Observable<any>}
*/
postDdsBatchRun(batchRunId: string | number): Observable<any> {
return this.http.post(`/dds/batchruns/${batchRunId}`, {});
}
/**
* @param {string} reportKey One of the backend DDS report keys.
* @param {any} filters fromDate, toDate (ISO strings), agentFrom/To, partyFrom/To.
* @returns {Observable<any>} { reportKey, available, message, columns, rows }
*/
runDdsReport(reportKey: string, filters: any): Observable<any> {
let httpParams = new HttpParams();
Object.keys(filters || {}).forEach((key) => {
if (filters[key] !== undefined && filters[key] !== null && filters[key] !== '') {
httpParams = httpParams.set(key, filters[key]);
}
});
return this.http.get(`/dds/reports/${reportKey}`, { params: httpParams });
}
/**
* @param {any[]} currencies
* @returns {Observable<any>} Currencies data