fix(docs): summary line for legacy imports + tidy Record-payment panel

- Tax invoice line table: APEX-imported documents carry only totals (0 lines), so the
  table printed blank with just the grand total. Add a display-only fallback summary
  line derived from the doc's own totals — description from the doc-number's module
  prefix, qty 1, rate = taxable, GST% back-computed. Issued docs unchanged (immutability).
  New HQ documents already carry full line detail; unaffected.
- DocumentView: the Record-payment form dropped in bare between the share row and the
  PDF. Wrap it in a titled card with a Close button so it reads as a clean section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 2 days ago
parent 11d45e805c
commit d68fc257a2

@ -219,10 +219,16 @@ export function DocumentView() {
<ShareActions doc={doc} client={client.data} shares={full.data.shares} onChange={full.reload} /> <ShareActions doc={doc} client={client.data} shares={full.data.shares} onChange={full.reload} />
{paying && ( {paying && (
<PaymentForm <div className="wf-card" style={{ padding: 14, marginTop: 12 }}>
clientId={doc.clientId} <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
onDone={() => { setPaying(false); full.reload() }} <h3 style={{ margin: 0 }}>Record payment {doc.docNo !== null ? `${doc.docNo}` : ''}</h3>
/> <Button onClick={() => setPaying(false)}>Close</Button>
</div>
<PaymentForm
clientId={doc.clientId}
onDone={() => { setPaying(false); full.reload() }}
/>
</div>
)} )}
{pdfErr !== undefined && <Notice tone="warn">PDF preview unavailable: {pdfErr}</Notice>} {pdfErr !== undefined && <Notice tone="warn">PDF preview unavailable: {pdfErr}</Notice>}

@ -74,6 +74,29 @@ function lineRow(l: BillLine, i: number, contents: string[]): string {
</tr>` </tr>`
} }
/**
* Legacy APEX-imported documents carry only totals (no per-line detail). Rather than print
* a blank line table, show ONE summary line derived from the document's own stored totals:
* description from the doc-number's module prefix (e.g. "SMS/2024/10057" "SMS"), qty 1,
* rate = taxable, and the GST% back-computed from the tax on the taxable value. Display-only
* issued documents are never modified (immutability rule).
*/
function fallbackLineRow(doc: Doc): string {
const tax = doc.cgstPaise + doc.sgstPaise + doc.igstPaise
const rateBp = doc.taxablePaise > 0 ? Math.round((tax / doc.taxablePaise) * 10000) : 0
const label = doc.docNo !== null && doc.docNo.includes('/') ? doc.docNo.split('/')[0]! : 'Software services'
return `<tr>
<td class="c">1</td>
<td>${esc(label)}</td>
<td class="c"></td>
<td class="r">1</td>
<td class="r">${formatINR(doc.taxablePaise)}</td>
<td class="r">${formatINR(doc.taxablePaise)}</td>
<td class="r">${rateBp / 100}%</td>
<td class="r">${formatINR(doc.payablePaise)}</td>
</tr>`
}
function totalsRows(doc: Doc): string { function totalsRows(doc: Doc): string {
const rows: [string, Paise][] = [['Taxable Value', doc.taxablePaise]] const rows: [string, Paise][] = [['Taxable Value', doc.taxablePaise]]
if (doc.igstPaise > 0) { if (doc.igstPaise > 0) {
@ -254,7 +277,9 @@ export function documentHtml(doc: Doc, client: Client, company: Record<string, s
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${doc.payload.lines.map((l, i) => lineRow(l, i, doc.payload.lineContents?.[i] ?? [])).join('\n')} ${doc.payload.lines.length > 0
? doc.payload.lines.map((l, i) => lineRow(l, i, doc.payload.lineContents?.[i] ?? [])).join('\n')
: fallbackLineRow(doc)}
</tbody> </tbody>
</table>`} </table>`}

Loading…
Cancel
Save