Cover business interests via a proper continuity instrument
Same gap as land parcels: business assets were routed into the generic trust fields (trustee/successor/beneficiaries), which don't fit — a business needs a continuity instrument, not a trustee holding title. New "business-continuity" channel in Nomination Registry, modelled on the digital-waqif docs repo's own business-succession framework (Clause 9): - Structure selector (sole proprietorship / partnership / company). Sole proprietorship surfaces an explicit warning: it legally dies with the owner, so the only fast-path options are converting to a company or dedicating as a waqf-owned enterprise. - Instrument selector: partnership continuation clause, shareholder buy-sell agreement (the standard fast-path for company shares, often Takaful-funded), or a direct redirect to Family Waqf Designator for a waqf-owned enterprise (nothing duplicated here — reuses that module). - Dedicated business-continuity-draft.txt export, same treatment as the trust deed and waqfiyya exports. - Death Trigger packet text updated with the business-specific execution action (execute buy-sell / continuation clause, file Takaful claim if funded — a private agreement between owners, not a probate filing). e2e-business.cjs: new suite covering exposed->warning->instrument selection->export->100% coverage->execution packet, plus the waqf-enterprise redirect disabling the Add button. 10/10 passing. Re-verified e2e-uat.cjs (32/32), e2e-fastpath.cjs (16/16), and e2e-trust.cjs (12/12) — 70/70 total, no regressions.
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
const { chromium } = require('playwright');
|
||||||
|
const BASE = 'https://moslem04.falahos.my/';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
|
||||||
|
await page.goto(BASE, { waitUntil: 'networkidle' });
|
||||||
|
const clickTab = async label => { await page.locator('nav button.tab', { hasText: label }).click(); await page.waitForTimeout(200); };
|
||||||
|
|
||||||
|
await clickTab('Assets');
|
||||||
|
await page.locator('.field:has-text("Description") input').fill('Bakery Sdn Bhd shares');
|
||||||
|
await page.locator('.field:has-text("Estimated value") input').fill('300000');
|
||||||
|
await page.locator('.form-card select').first().selectOption('Business interest');
|
||||||
|
await page.locator('button.btn-primary', { hasText: 'Add asset' }).click();
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
|
||||||
|
await clickTab('Coverage');
|
||||||
|
const rowText = await page.locator('.asset-row', { hasText: 'Bakery' }).textContent();
|
||||||
|
console.log('Coverage row for business asset:', rowText.trim());
|
||||||
|
|
||||||
|
await clickTab('Nominate');
|
||||||
|
const assetSelect = page.locator('select').first();
|
||||||
|
const val = await assetSelect.evaluate(el => Array.from(el.options).find(o => o.textContent.includes('Bakery'))?.value);
|
||||||
|
await assetSelect.selectOption(val);
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
const suggestionText = await page.locator('.suggestion').textContent().catch(() => 'NO SUGGESTION');
|
||||||
|
console.log('Suggestion for business asset:', suggestionText.trim());
|
||||||
|
|
||||||
|
const channelOptions = await page.locator('.form-card select').nth(1).locator('option').allTextContents();
|
||||||
|
console.log('Available channel types:', channelOptions);
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
main();
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// Verifies business interests get covered via a proper continuity instrument,
|
||||||
|
// including the sole-proprietorship warning and the waqf-enterprise redirect.
|
||||||
|
const { chromium } = require('playwright');
|
||||||
|
const BASE = 'https://moslem04.falahos.my/';
|
||||||
|
const results = [];
|
||||||
|
const consoleErrors = [];
|
||||||
|
function record(name, pass, detail = '') { results.push({ name, pass, detail }); console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ' — ' + detail : ''}`); }
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const browser = await chromium.launch();
|
||||||
|
const page = await browser.newPage({ viewport: { width: 390, height: 844 } });
|
||||||
|
page.on('console', m => { if (m.type() === 'error') consoleErrors.push(m.text()); });
|
||||||
|
page.on('pageerror', e => consoleErrors.push(e.message));
|
||||||
|
|
||||||
|
await page.goto(BASE, { waitUntil: 'networkidle' });
|
||||||
|
const clickTab = async label => { await page.locator('nav button.tab', { hasText: label }).click(); await page.waitForTimeout(200); };
|
||||||
|
const selectByText = async (locator, text) => {
|
||||||
|
const val = await locator.evaluate((el, t) => Array.from(el.options).find(o => o.textContent.includes(t))?.value, text);
|
||||||
|
await locator.selectOption(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a company-shares business asset
|
||||||
|
await clickTab('Assets');
|
||||||
|
await page.locator('.field:has-text("Description") input').fill('Bakery Sdn Bhd shares');
|
||||||
|
await page.locator('.field:has-text("Estimated value") input').fill('300000');
|
||||||
|
await page.locator('.form-card select').first().selectOption('Business interest');
|
||||||
|
await page.locator('button.btn-primary', { hasText: 'Add asset' }).click();
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
|
||||||
|
await clickTab('Coverage');
|
||||||
|
const rowBefore = await page.locator('.asset-row', { hasText: 'Bakery' }).textContent();
|
||||||
|
record('Coverage: business interest initially exposed with continuity suggestion', rowBefore.includes('Not covered') && /business continuity/i.test(rowBefore), rowBefore.trim().slice(0, 200));
|
||||||
|
|
||||||
|
// Go to Nominate, select business-continuity channel
|
||||||
|
await clickTab('Nominate');
|
||||||
|
await selectByText(page.locator('select').first(), 'Bakery');
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
await page.locator('.form-card select').nth(1).selectOption('business-continuity');
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
|
||||||
|
const businessFieldsVisible = await page.locator('.trust-fields').isVisible();
|
||||||
|
record('Nomination: business continuity fields appear', businessFieldsVisible);
|
||||||
|
|
||||||
|
// Default structure = company, default instrument = shareholder-buy-sell — no warning expected
|
||||||
|
const warningVisibleForCompany = await page.locator('.business-warning').isVisible().catch(() => false);
|
||||||
|
record('Nomination: no sole-prop warning shown for company structure', !warningVisibleForCompany);
|
||||||
|
|
||||||
|
// Switch to sole proprietorship -> warning should appear
|
||||||
|
await page.locator('.trust-fields select').first().selectOption('sole-prop');
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
const soleWarningVisible = await page.locator('.business-warning').first().isVisible();
|
||||||
|
const soleWarningText = await page.locator('.business-warning').first().textContent();
|
||||||
|
record('Nomination: sole-proprietorship shows "dies with you" warning', soleWarningVisible && soleWarningText.includes('dies with you'), soleWarningText.trim().slice(0, 150));
|
||||||
|
|
||||||
|
// Switch back to company, pick shareholder buy-sell, fill and save
|
||||||
|
await page.locator('.trust-fields select').first().selectOption('company');
|
||||||
|
await page.waitForTimeout(150);
|
||||||
|
const instrumentSelect = page.locator('.trust-fields select').nth(1);
|
||||||
|
await instrumentSelect.selectOption('shareholder-buy-sell');
|
||||||
|
await page.waitForTimeout(150);
|
||||||
|
|
||||||
|
const inputs = page.locator('.trust-fields .field input');
|
||||||
|
await inputs.nth(0).fill('Co-founder Rahman');
|
||||||
|
await inputs.nth(1).fill('Fair market valuation, Takaful-funded');
|
||||||
|
await page.locator('button.btn-primary', { hasText: 'Add business continuity instrument' }).click();
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
|
||||||
|
const rowCreated = await page.locator('.nomination-row', { hasText: 'Bakery' }).isVisible();
|
||||||
|
record('Nomination: business continuity row created', rowCreated);
|
||||||
|
|
||||||
|
const [download] = await Promise.all([
|
||||||
|
page.waitForEvent('download'),
|
||||||
|
page.locator('.nomination-row', { hasText: 'Bakery' }).locator('.export-btn').click()
|
||||||
|
]);
|
||||||
|
record('Nomination: business continuity export downloads', download.suggestedFilename() === 'business-continuity-draft.txt', download.suggestedFilename());
|
||||||
|
|
||||||
|
// Coverage should now show 100%
|
||||||
|
await clickTab('Coverage');
|
||||||
|
const pct = await page.locator('.big-percent').textContent();
|
||||||
|
record('Coverage: business interest now covered (100%)', pct.trim() === '100%', pct);
|
||||||
|
|
||||||
|
// Waqf-enterprise redirect path: try selecting it and confirm Add button disables
|
||||||
|
await clickTab('Nominate');
|
||||||
|
await page.locator('.form-card select').nth(1).selectOption('business-continuity');
|
||||||
|
await page.waitForTimeout(150);
|
||||||
|
await page.locator('.trust-fields select').nth(1).selectOption('waqf-enterprise');
|
||||||
|
await page.waitForTimeout(150);
|
||||||
|
const redirectNoteVisible = await page.locator('.business-warning', { hasText: 'Family Waqf Designator' }).isVisible();
|
||||||
|
const addDisabled = await page.locator('button.btn-primary', { hasText: 'business continuity' }).isDisabled();
|
||||||
|
record('Nomination: waqf-enterprise option redirects to Family Waqf, disables Add', redirectNoteVisible && addDisabled);
|
||||||
|
|
||||||
|
// Death Trigger packet for business asset
|
||||||
|
await clickTab('Trigger');
|
||||||
|
const attestorInputs = page.locator('.attestor-row input');
|
||||||
|
await attestorInputs.nth(0).fill('Executor A');
|
||||||
|
await page.locator('.attestor-row .confirm-btn').nth(0).click();
|
||||||
|
await attestorInputs.nth(1).fill('Witness B');
|
||||||
|
await page.locator('.attestor-row .confirm-btn').nth(1).click();
|
||||||
|
await page.locator('.field:has-text("Date of death") input').fill('2026-08-13');
|
||||||
|
await page.locator('.field:has-text("Death certificate reference") input').fill('DC-BIZ-001');
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
await page.locator('button.btn-danger-solid').click();
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
|
||||||
|
const packetForBiz = await page.locator('.packet-row', { hasText: 'Bakery' }).isVisible();
|
||||||
|
record('Death Trigger: execution packet generated for business via continuity instrument', packetForBiz);
|
||||||
|
|
||||||
|
record('No uncaught JS console errors', consoleErrors.length === 0, consoleErrors.join(' || '));
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
const passCount = results.filter(r => r.pass).length;
|
||||||
|
const failCount = results.length - passCount;
|
||||||
|
console.log(`\n${passCount} passed, ${failCount} failed, ${results.length} total`);
|
||||||
|
if (failCount > 0) results.filter(r => !r.pass).forEach(r => console.log(` - ${r.name}: ${r.detail}`));
|
||||||
|
process.exit(failCount > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
main().catch(e => { console.error('SCRIPT ERROR:', e); process.exit(2); });
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
takaful: 'Takaful / insurance nomination',
|
takaful: 'Takaful / insurance nomination',
|
||||||
'bank-mandate': 'Bank death mandate',
|
'bank-mandate': 'Bank death mandate',
|
||||||
trust: 'Trust / nominee holding',
|
trust: 'Trust / nominee holding',
|
||||||
|
'business-continuity': 'Business continuity instrument',
|
||||||
'hibah-flagged-capped': 'Hibah flagged (marad al-mawt) — capped, partially exposed',
|
'hibah-flagged-capped': 'Hibah flagged (marad al-mawt) — capped, partially exposed',
|
||||||
none: 'Not covered — exposed to Faraid/probate'
|
none: 'Not covered — exposed to Faraid/probate'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,6 +77,8 @@
|
|||||||
? 'ACTION: File a death claim with the Takaful/insurance provider. As a nominated\ntrust under Insurance Act 1996 s.166 (or Takaful equivalent), payout goes directly\nto the named beneficiary — no probate required.'
|
? 'ACTION: File a death claim with the Takaful/insurance provider. As a nominated\ntrust under Insurance Act 1996 s.166 (or Takaful equivalent), payout goes directly\nto the named beneficiary — no probate required.'
|
||||||
: row.channel === 'bank-mandate'
|
: row.channel === 'bank-mandate'
|
||||||
? 'ACTION: Notify the bank with the death certificate and the death-mandate /\njoint-account documentation on file. Funds release per the mandate terms — this\nis a bank process, not a court process.'
|
? 'ACTION: Notify the bank with the death certificate and the death-mandate /\njoint-account documentation on file. Funds release per the mandate terms — this\nis a bank process, not a court process.'
|
||||||
|
: row.channel === 'business-continuity'
|
||||||
|
? 'ACTION: Execute the pre-agreed buy-sell provision or partnership continuation\nclause on file. If Takaful-funded, file the Takaful death claim to release the\nbuyout funds. This is a private agreement between co-owners/shareholders, not a\nprobate filing.'
|
||||||
: 'ACTION: Contact the trustee/nominee holder to execute the pre-arranged transfer\nunder the trust deed. This is an internal trustee record update, not a probate\nfiling.',
|
: 'ACTION: Contact the trustee/nominee holder to execute the pre-arranged transfer\nunder the trust deed. This is an internal trustee record update, not a probate\nfiling.',
|
||||||
'',
|
'',
|
||||||
'This packet was generated by Nur Falah on a local device. It is a drafting aid;',
|
'This packet was generated by Nur Falah on a local device. It is a drafting aid;',
|
||||||
|
|||||||
@@ -19,10 +19,22 @@
|
|||||||
{ value: 'epf', label: 'EPF nomination', note: 'Pays the nominee directly under EPF Act 1991 s.51 — bypasses probate by law.' },
|
{ value: 'epf', label: 'EPF nomination', note: 'Pays the nominee directly under EPF Act 1991 s.51 — bypasses probate by law.' },
|
||||||
{ value: 'takaful', label: 'Takaful / insurance nomination', note: 'Nomination as trust under Insurance Act 1996 s.166 (or Takaful equivalent) — pays the named beneficiary directly.' },
|
{ value: 'takaful', label: 'Takaful / insurance nomination', note: 'Nomination as trust under Insurance Act 1996 s.166 (or Takaful equivalent) — pays the named beneficiary directly.' },
|
||||||
{ value: 'bank-mandate', label: 'Bank death mandate / joint account', note: 'Ask the bank for a death-benefit nomination, or hold as joint account with survivorship.' },
|
{ value: 'bank-mandate', label: 'Bank death mandate / joint account', note: 'Ask the bank for a death-benefit nomination, or hold as joint account with survivorship.' },
|
||||||
{ value: 'trust', label: 'Pre-funded trust / nominee holding', note: 'For land or illiquid assets with no nomination bypass — requires setting up a real trust or nominee entity now, while alive.' }
|
{ value: 'trust', label: 'Pre-funded trust / nominee holding', note: 'For land or illiquid assets with no nomination bypass — requires setting up a real trust or nominee entity now, while alive.' },
|
||||||
|
{ value: 'business-continuity', label: 'Business continuity instrument', note: 'A business dies with its structure by default: a sole proprietorship dies with the owner, a partnership dissolves on any partner\'s death unless the agreement says otherwise, and company shares fall into probate like any other asset unless a buy-sell or shareholder agreement already routes them.' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const emptyForm = () => ({ linkedAssetId: '', type: 'epf', institution: '', nomineeeName: '', referenceNumber: '', trusteeName: '', successorTrustee: '', trustBeneficiaries: '' });
|
const BUSINESS_STRUCTURES = [
|
||||||
|
{ value: 'sole-prop', label: 'Sole proprietorship', warning: 'A sole proprietorship legally dies with you — there is no entity left to transfer. The only fast-path options are converting it to a company with a shareholder agreement, or dedicating it as a waqf-owned enterprise now, while alive.' },
|
||||||
|
{ value: 'partnership', label: 'Partnership', warning: null },
|
||||||
|
{ value: 'company', label: 'Company (shares)', warning: null }
|
||||||
|
];
|
||||||
|
const BUSINESS_INSTRUMENTS = [
|
||||||
|
{ value: 'partnership-continuation', label: 'Partnership continuation clause', note: 'Prevents automatic dissolution on a partner\'s death — but does not by itself transfer the deceased partner\'s share; pair it with a buy-sell agreement for that.' },
|
||||||
|
{ value: 'shareholder-buy-sell', label: 'Shareholder agreement with buy-sell provision', note: 'Pre-agreed, often Takaful-funded, automatic buyout of the deceased\'s shares by co-shareholders at a pre-set valuation — the standard fast-path for company shares.' },
|
||||||
|
{ value: 'waqf-enterprise', label: 'Dedicate as waqf-owned enterprise', note: 'Use the Family Waqf Designator for this instead — a waqf-owned enterprise survives you and serves your purpose in perpetuity, same as any other waqf corpus.' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const emptyForm = () => ({ linkedAssetId: '', type: 'epf', institution: '', nomineeeName: '', referenceNumber: '', trusteeName: '', successorTrustee: '', trustBeneficiaries: '', businessStructure: 'company', businessInstrument: 'shareholder-buy-sell', successorOwner: '', buySellTerms: '' });
|
||||||
|
|
||||||
let nominations = $state(load('nominations', []));
|
let nominations = $state(load('nominations', []));
|
||||||
let form = $state(emptyForm());
|
let form = $state(emptyForm());
|
||||||
@@ -31,6 +43,9 @@
|
|||||||
if (!form.linkedAssetId) return;
|
if (!form.linkedAssetId) return;
|
||||||
if (form.type === 'trust') {
|
if (form.type === 'trust') {
|
||||||
if (!form.trusteeName) return;
|
if (!form.trusteeName) return;
|
||||||
|
} else if (form.type === 'business-continuity') {
|
||||||
|
if (form.businessInstrument === 'waqf-enterprise') return; // redirect only, nothing to save here
|
||||||
|
if (!form.successorOwner) return;
|
||||||
} else if (!form.nomineeeName) {
|
} else if (!form.nomineeeName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -39,6 +54,37 @@
|
|||||||
form = emptyForm();
|
form = emptyForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportBusinessContinuity(n) {
|
||||||
|
const asset = assets.find(a => a.id === n.linkedAssetId);
|
||||||
|
const structureLabel = BUSINESS_STRUCTURES.find(s => s.value === n.businessStructure)?.label;
|
||||||
|
const instrumentLabel = BUSINESS_INSTRUMENTS.find(i => i.value === n.businessInstrument)?.label;
|
||||||
|
const lines = [
|
||||||
|
'BUSINESS CONTINUITY INSTRUMENT — DRAFTING AID',
|
||||||
|
`Generated: ${new Date().toISOString().slice(0, 10)}`,
|
||||||
|
`Business: ${asset ? asset.description : '(asset removed)'}`,
|
||||||
|
`Structure: ${structureLabel}`,
|
||||||
|
`Instrument: ${instrumentLabel}`,
|
||||||
|
`Successor / buyer: ${n.successorOwner}`,
|
||||||
|
`Terms: ${n.buySellTerms || '________________________'}`,
|
||||||
|
'',
|
||||||
|
n.businessInstrument === 'shareholder-buy-sell'
|
||||||
|
? 'ACTION: this shares are transferred per a pre-agreed buy-sell provision in the\nshareholder agreement, often funded by a Takaful/insurance payout timed to the\nbuyout value. Once the agreement is executed and (ideally) funded, the transfer\nat death is a private buyout between shareholders — not a probate filing.'
|
||||||
|
: 'ACTION: a partnership continuation clause prevents automatic dissolution on\ndeath, but the deceased partner\'s share itself still needs a buy-sell provision\nor a will-directed transfer to actually move — pair this with a buy-sell\nagreement for full coverage.',
|
||||||
|
'',
|
||||||
|
'ACTION REQUIRED: this document is a drafting aid only. A lawyer must draft the',
|
||||||
|
'actual shareholder/partnership agreement, and it must be signed and (for a',
|
||||||
|
'buy-sell) ideally funded by a Takaful/insurance policy while all parties are',
|
||||||
|
'alive for this to have legal effect and actually bypass probate.',
|
||||||
|
'',
|
||||||
|
'DISCLAIMER: Not a fatwa. Not legal advice. Drafting aid only.'
|
||||||
|
];
|
||||||
|
const blob = new Blob([lines.join('\n')], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url; a.download = 'business-continuity-draft.txt'; a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
function exportTrustDeed(n) {
|
function exportTrustDeed(n) {
|
||||||
const asset = assets.find(a => a.id === n.linkedAssetId);
|
const asset = assets.find(a => a.id === n.linkedAssetId);
|
||||||
const lines = [
|
const lines = [
|
||||||
@@ -107,12 +153,37 @@
|
|||||||
<label class="field"><span>Successor trustee</span><input type="text" bind:value={form.successorTrustee} /></label>
|
<label class="field"><span>Successor trustee</span><input type="text" bind:value={form.successorTrustee} /></label>
|
||||||
<label class="field"><span>Beneficiaries</span><input type="text" bind:value={form.trustBeneficiaries} placeholder="e.g. equal split among children" /></label>
|
<label class="field"><span>Beneficiaries</span><input type="text" bind:value={form.trustBeneficiaries} placeholder="e.g. equal split among children" /></label>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if form.type === 'business-continuity'}
|
||||||
|
<div class="trust-fields">
|
||||||
|
<label class="field"><span>Business structure</span>
|
||||||
|
<select bind:value={form.businessStructure}>
|
||||||
|
{#each BUSINESS_STRUCTURES as s}<option value={s.value}>{s.label}</option>{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{#if BUSINESS_STRUCTURES.find(s => s.value === form.businessStructure)?.warning}
|
||||||
|
<p class="business-warning">{BUSINESS_STRUCTURES.find(s => s.value === form.businessStructure).warning}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<label class="field"><span>Continuity instrument</span>
|
||||||
|
<select bind:value={form.businessInstrument}>
|
||||||
|
{#each BUSINESS_INSTRUMENTS as i}<option value={i.value}>{i.label}</option>{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<p class="trust-note">{BUSINESS_INSTRUMENTS.find(i => i.value === form.businessInstrument)?.note}</p>
|
||||||
|
|
||||||
|
{#if form.businessInstrument === 'waqf-enterprise'}
|
||||||
|
<p class="business-warning">Go to the Family Waqf Designator tab and select this business as the corpus asset — nothing to save here.</p>
|
||||||
|
{:else}
|
||||||
|
<label class="field"><span>{form.businessInstrument === 'shareholder-buy-sell' ? 'Buying co-shareholder(s)' : 'Successor partner'}</span><input type="text" bind:value={form.successorOwner} /></label>
|
||||||
|
<label class="field"><span>Terms</span><input type="text" bind:value={form.buySellTerms} placeholder="e.g. valuation method, Takaful-funded buyout" /></label>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<label class="field"><span>Institution</span><input type="text" bind:value={form.institution} placeholder="e.g. KWSP, Bank Islam, Takaful Ikhlas" /></label>
|
<label class="field"><span>Institution</span><input type="text" bind:value={form.institution} placeholder="e.g. KWSP, Bank Islam, Takaful Ikhlas" /></label>
|
||||||
<label class="field"><span>Nominee name</span><input type="text" bind:value={form.nomineeeName} /></label>
|
<label class="field"><span>Nominee name</span><input type="text" bind:value={form.nomineeeName} /></label>
|
||||||
<label class="field"><span>Reference / policy / account number</span><input type="text" bind:value={form.referenceNumber} /></label>
|
<label class="field"><span>Reference / policy / account number</span><input type="text" bind:value={form.referenceNumber} /></label>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="btn-primary" onclick={addNomination}>Add {form.type === 'trust' ? 'trust setup' : 'nomination'}</button>
|
<button class="btn-primary" disabled={form.type === 'business-continuity' && form.businessInstrument === 'waqf-enterprise'} onclick={addNomination}>Add {form.type === 'trust' ? 'trust setup' : form.type === 'business-continuity' ? 'business continuity instrument' : 'nomination'}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each nominations as n (n.id)}
|
{#each nominations as n (n.id)}
|
||||||
@@ -121,16 +192,20 @@
|
|||||||
<div class="nomination-row">
|
<div class="nomination-row">
|
||||||
<div class="nomination-info">
|
<div class="nomination-info">
|
||||||
<strong>{asset ? asset.description : '(asset removed)'}</strong>
|
<strong>{asset ? asset.description : '(asset removed)'}</strong>
|
||||||
<span class="muted">{channelInfo?.label}{n.type !== 'trust' ? ` · ${n.institution || '—'}` : ''}</span>
|
<span class="muted">{channelInfo?.label}{n.type !== 'trust' && n.type !== 'business-continuity' ? ` · ${n.institution || '—'}` : ''}</span>
|
||||||
{#if n.type === 'trust'}
|
{#if n.type === 'trust'}
|
||||||
<span class="muted">Trustee: {n.trusteeName} · Successor: {n.successorTrustee || '—'}</span>
|
<span class="muted">Trustee: {n.trusteeName} · Successor: {n.successorTrustee || '—'}</span>
|
||||||
<span class="muted">Beneficiaries: {n.trustBeneficiaries || '—'}</span>
|
<span class="muted">Beneficiaries: {n.trustBeneficiaries || '—'}</span>
|
||||||
|
{:else if n.type === 'business-continuity'}
|
||||||
|
<span class="muted">{BUSINESS_INSTRUMENTS.find(i => i.value === n.businessInstrument)?.label}</span>
|
||||||
|
<span class="muted">Successor: {n.successorOwner} · Terms: {n.buySellTerms || '—'}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="muted">Nominee: {n.nomineeeName} · Ref: {n.referenceNumber || '—'}</span>
|
<span class="muted">Nominee: {n.nomineeeName} · Ref: {n.referenceNumber || '—'}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="row-actions">
|
<div class="row-actions">
|
||||||
{#if n.type === 'trust'}<button class="export-btn" onclick={() => exportTrustDeed(n)}>Export</button>{/if}
|
{#if n.type === 'trust'}<button class="export-btn" onclick={() => exportTrustDeed(n)}>Export</button>{/if}
|
||||||
|
{#if n.type === 'business-continuity'}<button class="export-btn" onclick={() => exportBusinessContinuity(n)}>Export</button>{/if}
|
||||||
<button onclick={() => remove(n.id)}>✕</button>
|
<button onclick={() => remove(n.id)}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,4 +236,5 @@
|
|||||||
.trust-note { font-size: 11.5px; color: #C9A84C; line-height: 1.5; margin-bottom: 10px; }
|
.trust-note { font-size: 11.5px; color: #C9A84C; line-height: 1.5; margin-bottom: 10px; }
|
||||||
.row-actions { display: flex; gap: 8px; align-items: center; }
|
.row-actions { display: flex; gap: 8px; align-items: center; }
|
||||||
.export-btn { background: rgba(46,204,113,0.15); color: #2ECC71; border: none; border-radius: 8px; padding: 6px 10px; font-size: 11px; cursor: pointer; font-weight: 600; }
|
.export-btn { background: rgba(46,204,113,0.15); color: #2ECC71; border: none; border-radius: 8px; padding: 6px 10px; font-size: 11px; cursor: pointer; font-weight: 600; }
|
||||||
|
.business-warning { font-size: 11.5px; color: #EF4444; background: rgba(239,68,68,0.08); border-radius: 8px; padding: 8px 10px; margin-bottom: 12px; line-height: 1.5; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function computeCoverage() {
|
|||||||
export function suggestedChannel(assetType) {
|
export function suggestedChannel(assetType) {
|
||||||
const t = (assetType || '').toLowerCase();
|
const t = (assetType || '').toLowerCase();
|
||||||
if (t.includes('cash') || t.includes('bank')) return { channel: 'bank-mandate', label: 'Bank death mandate / joint account', note: 'Ask your bank about a death-benefit nomination or convert to a joint account with survivorship — pays out directly, no probate.' };
|
if (t.includes('cash') || t.includes('bank')) return { channel: 'bank-mandate', label: 'Bank death mandate / joint account', note: 'Ask your bank about a death-benefit nomination or convert to a joint account with survivorship — pays out directly, no probate.' };
|
||||||
if (t.includes('business')) return { channel: 'trust', label: 'Pre-funded trust / nominee holding', note: 'Business interests generally need a shareholder agreement or trust structure set up now — a legal step beyond this app.' };
|
if (t.includes('business')) return { channel: 'business-continuity', label: 'Business continuity instrument', note: 'A sole proprietorship dies with you; a partnership dissolves on death unless it has a continuation clause; company shares fall into probate unless a shareholder buy-sell agreement already routes them. Set up in the Nomination Registry, or dedicate as a waqf-owned enterprise via Family Waqf Designator.' };
|
||||||
if (t.includes('digital')) return { channel: 'bank-mandate', label: 'Multi-sig / pre-authorized release', note: 'Crypto and digital accounts: use a multi-sig wallet or exchange beneficiary feature where available.' };
|
if (t.includes('digital')) return { channel: 'bank-mandate', label: 'Multi-sig / pre-authorized release', note: 'Crypto and digital accounts: use a multi-sig wallet or exchange beneficiary feature where available.' };
|
||||||
if (t.includes('property') || t.includes('vehicle') || t.includes('jewelry') || t.includes('valuables')) return { channel: 'trust', label: 'Pre-funded trust / nominee holding, or Hibah/Waqf now', note: 'Land and physical assets have no nomination-based bypass in most jurisdictions. Either complete a Hibah/Waqf now (ownership transfers immediately), or set up a trust/nominee holding structure — both require real-world legal steps this app can prepare paperwork for but cannot execute alone.' };
|
if (t.includes('property') || t.includes('vehicle') || t.includes('jewelry') || t.includes('valuables')) return { channel: 'trust', label: 'Pre-funded trust / nominee holding, or Hibah/Waqf now', note: 'Land and physical assets have no nomination-based bypass in most jurisdictions. Either complete a Hibah/Waqf now (ownership transfers immediately), or set up a trust/nominee holding structure — both require real-world legal steps this app can prepare paperwork for but cannot execute alone.' };
|
||||||
return { channel: 'nomination', label: 'EPF / Takaful nomination', note: 'For retirement funds and insurance/takaful, use the scheme\'s own nomination form — these pay the nominee directly by law, bypassing probate.' };
|
return { channel: 'nomination', label: 'EPF / Takaful nomination', note: 'For retirement funds and insurance/takaful, use the scheme\'s own nomination form — these pay the nominee directly by law, bypassing probate.' };
|
||||||
|
|||||||
Reference in New Issue
Block a user