${esc(p.description||'No description has been recorded.')}
Milestones
Planned and completed delivery points
${(p.milestones||[]).length?p.milestones.map(x=>`
${esc(x.name)}${esc(x.reference_no||'')} · Due ${fmtDate(x.due_date)}
${statusPill(x.status)}
`).join(''):emptyState('No milestones recorded','Add milestones to make progress measurable.','','calendar')}`;m.querySelector('[data-modal-finance]').onclick=()=>{closeModal();navigate(`/finance?project_id=${id}`)};m.querySelector('[data-modal-document]').onclick=()=>{closeModal();navigate(`/documents?project_id=${id}`)}}catch(ex){showError(ex,m.querySelector('.modal-body'))}}
public/assets/bcos-s25-3.js-347-
public/assets/bcos-s25-3.js-348-async function viewFinance(page){const projectsData=await api('/api/projects?status=ACTIVE');const projects=projectsData.projects||[];const params=new URLSearchParams(location.search);let projectId=params.get('project_id')||projects[0]?.id||'';page.innerHTML=`${pageHead('Finance & Project Funds','Track approved budgets, client funds, expenditure, invoices, balances and potential refunds.',projectId&&has('finance.manage_invoices')?button('Create invoice','primary','id="new-invoice"','plus'):'','Commercial Stewardship')}${projectId?loading(5):emptyState('Select a project','Choose a project to view its approved financial position.','','money')}`;$('#finance-project').onchange=e=>{projectId=e.target.value;history.replaceState({},'',`/finance${projectId?`?project_id=${encodeURIComponent(projectId)}`:''}`);loadFinance(projectId)};$('#new-invoice')?.addEventListener('click',()=>newInvoiceModal(projectId));$('#record-funding').onclick=()=>recordFundingModal(projectId);if(projectId)await loadFinance(projectId)}
public/assets/bcos-s25-3.js-349-async function loadFinance(projectId){const el=$('#finance-view');if(!projectId){el.innerHTML=emptyState('Select a project','Choose a project to view its finances.','','money');return}el.innerHTML=loading(5);try{const d=await api(`/api/finance/project?project_id=${encodeURIComponent(projectId)}`);const s=d.summary||{},c=s.base_currency||'USD',budget=num(s.approved_budget),spent=num(s.approved_expenditure),funds=num(s.client_funds_received),committed=Math.max(0,num(s.approved_commitments||s.committed_expenditure||0)),available=num(s.available_balance);const spentPct=budget?Math.min(100,spent/budget*100):0,committedPct=budget?Math.min(100,committed/budget*100):0,availablePct=budget?Math.max(0,Math.min(100,available/budget*100)):0;el.innerHTML=`${metricCard('Approved budget',fmtMoney(budget,c),'Approved financial envelope','money')}${metricCard('Funds received',fmtMoney(funds,c),'Cash received from client','download')}${metricCard('Approved expenditure',fmtMoney(spent,c),'Paid or recognised','chart')}${metricCard('Available balance',fmtMoney(available,c),'Uncommitted balance','money')}${metricCard('Potential refund',fmtMoney(s.potential_refund,c),'Subject to reconciliation','refresh')}${metricCard('Outstanding funding',fmtMoney(s.outstanding_funding,c),'Still required','clock')}
Project fund utilisation
Spent, committed and available amounts must remain distinct
`).join(''):emptyState(empty,'Records will appear after an authorised transaction is created.','','money')}
public/assets/bcos-s25-3.js-351-async function newInvoiceModal(projectId){if(!projectId){toast('Select a project','Choose a project before creating an invoice.','error');return}const p=await api(`/api/projects/show?id=${encodeURIComponent(projectId)}`);const m=openModal('Create draft invoice','The invoice remains a draft until authorised and issued.',``);m.querySelector('[data-close]').onclick=closeModal;m.querySelector('#invoice-form').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,fd=Object.fromEntries(new FormData(f).entries()),btn=f.querySelector('[type="submit"]');btn.disabled=true;const body={project_id:projectId,client_organization_id:p.client_organization_id,currency_code:fd.currency_code,issue_date:fd.issue_date,due_date:fd.due_date,status:'DRAFT',purchase_order_reference:fd.purchase_order_reference||null,notes:fd.notes||null,lines:[{description:fd.description,quantity:num(fd.quantity),unit_price:num(fd.unit_price),tax_rate:num(fd.tax_rate)}]};try{const r=await api('/api/finance/invoices',{method:'POST',body:JSON.stringify(body)});closeModal();toast('Draft invoice created',`${r.reference_no} · ${fmtMoney(r.total_amount,fd.currency_code)}`);await loadFinance(projectId)}catch(ex){toast('Invoice creation failed',ex.message,'error')}finally{btn.disabled=false}}}
public/assets/bcos-s25-3.js-352-async function recordFundingModal(projectId){if(!projectId){toast('Select a project','Choose a project before recording funding.','error');return}const p=await api(`/api/projects/show?id=${encodeURIComponent(projectId)}`);const m=openModal('Record client funding','Record only funds confirmed by reliable evidence.',``);m.querySelector('[data-close]').onclick=closeModal;m.querySelector('#funding-form').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,body=Object.fromEntries(new FormData(f).entries()),btn=f.querySelector('[type="submit"]');body.project_id=projectId;body.client_organization_id=p.client_organization_id;body.status='PENDING';btn.disabled=true;try{const r=await api('/api/finance/funding',{method:'POST',body:JSON.stringify(body)});closeModal();toast('Funding recorded',r.reference_no);await loadFinance(projectId)}catch(ex){toast('Funding record failed',ex.message,'error')}finally{btn.disabled=false}}}
public/assets/bcos-s25-3.js-353-
public/assets/bcos-s25-3.js-354-async function viewDocuments(page){const params=new URLSearchParams(location.search);const selectedProject=params.get('project_id')||'';const [profiles,clients,projectsData]=await Promise.all([api('/api/administration/legal-profiles').catch(()=>[]),api('/api/clients?status=ACTIVE&limit=100').catch(()=>({clients:[]})),api('/api/projects?status=ACTIVE').catch(()=>({projects:[]}))]);const types=[['PROPOSAL','Proposal'],['QUOTATION','Quotation'],['PRO_FORMA_INVOICE','Pro Forma Invoice'],['INVOICE','Invoice'],['ENGAGEMENT_LETTER','Engagement Letter'],['SERVICE_AGREEMENT','Service Agreement'],['CONTRACT','Contract'],['NDA','NDA'],['NCNDA','NCNDA'],['PURCHASE_ORDER','Purchase Order'],['REPORT','Report'],['DOCUMENT','Other Document']];page.innerHTML=`${pageHead('Commercial Documents & Records Centre','Generate controlled, versioned documents with unique references and issuing-entity snapshots.','','Controlled Documents')}
${emptyState('Document preview','Complete the form and create a controlled draft. The unique reference and preview will appear here.','','file')}
`;
public/assets/bcos-s25-3.js-355- $$('[data-document-type]').forEach(b=>b.onclick=()=>{$$('[data-document-type]').forEach(x=>x.classList.remove('active'));b.classList.add('active');const f=$('#document-form');f.document_type.value=b.dataset.documentType;const label=types.find(x=>x[0]===b.dataset.documentType)?.[1]||'Controlled Document';f.title.value=defaultDocumentTitle(b.dataset.documentType,label)});$('#document-form').onsubmit=createDocument;
public/assets/bcos-s25-3.js-356-}
public/assets/bcos-s25-3.js-357-function defaultDocumentTitle(type,label){const map={NDA:'Mutual Non-Disclosure Agreement',NCNDA:'Non-Circumvention and Non-Disclosure Agreement',PRO_FORMA_INVOICE:'Pro Forma Invoice',INVOICE:'Commercial Invoice',ENGAGEMENT_LETTER:'Professional Engagement Letter',SERVICE_AGREEMENT:'Professional Services Agreement',CONTRACT:'Commercial Contract',PROPOSAL:'Professional Services Proposal'};return map[type]||label}
public/assets/bcos-s25-3.js-358-async function createDocument(e){e.preventDefault();const f=e.currentTarget,btn=f.querySelector('[type="submit"]'),fd=Object.fromEntries(new FormData(f).entries());btn.disabled=true;btn.textContent='Creating controlled draft…';const content={purpose:fd.purpose||'To be completed during controlled review.',scope_and_deliverables:fd.scope||'To be completed during controlled review.',commercial_terms:fd.commercial_terms||'To be confirmed and approved.',confidentiality_and_non_circumvention:fd.confidentiality||'Use approved clauses from the controlled clause library.',governing_law_and_dispute_resolution:fd.governing_law||'Transaction-specific legal review required.',additional_terms:fd.additional_terms||'None recorded.',signatures:'Authorised signatories, names, titles and dates.'};const body={document_type:fd.document_type,title:fd.title,issuing_legal_entity_id:fd.issuing_legal_entity_id,jurisdiction_code:fd.jurisdiction_code,client_organization_id:fd.client_organization_id||null,project_id:fd.project_id||null,language_mode:fd.language_mode,classification:fd.classification,watermark_text:'DRAFT',content};try{const r=await api('/api/documents',{method:'POST',body:JSON.stringify(body)});const preview=$('#document-preview .paper');preview.innerHTML=`
${esc(r.reference)} · Version ${esc(r.version)} Integrity hash: ${esc(r.hash)}
`;toast('Controlled draft created',r.reference)}catch(ex){toast('Document generation failed',ex.message,'error')}finally{btn.disabled=false;btn.innerHTML=`${icon('file')}Create controlled draft`}}
public/assets/bcos-s25-3.js-359-
public/assets/bcos-s25-3.js-360-async function viewReports(page){const [templates,runs]=await Promise.all([api('/api/reports/templates').catch(()=>[]),api('/api/reports/runs').catch(()=>[])]);page.innerHTML=`${pageHead('Reports & Executive Outputs','Generate governed PDF, editable DOCX and Excel outputs from verified BCOS records.',has('reports.generate')?button('Generate report','primary','id="generate-report"','plus'):'','Report Factory')}${metricCard('Templates',templates.length,'Available report structures','file')}${metricCard('Recent runs',runs.length,'Generated outputs','chart')}${metricCard('PDF','Supported','Controlled output','download')}${metricCard('DOCX & XLSX','Supported','Editable and analytical','download')}
Recent reports
Controlled outputs, verification hashes and approval status
${runs.length?`
Reference
Title
Type
Status
Generated
Formats
${runs.map(r=>`
${esc(r.reference_no)}
${esc(r.title)}
${esc(r.report_type)}
${statusPill(r.status)}
${fmtDateTime(r.generated_at)}
${esc(r.output_formats||'')}
`).join('')}
`:emptyState('No reports generated','Generate a report from verified project, financial, risk or assessment records.','','chart')}`;$('#generate-report')?.addEventListener('click',()=>generateReportModal(templates))}
public/assets/bcos-s25-3.js-361-function generateReportModal(templates){const m=openModal('Generate controlled report','Select the report context and approved outputs.',``);m.querySelector('[data-close]').onclick=closeModal;m.querySelector('#report-form').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,fd=new FormData(f),body=Object.fromEntries(fd.entries());body.formats=fd.getAll('format');body.context_type=body.report_type;body.include_charts=true;body.include_appendices=true;body.watermark_text=body.classification;const btn=f.querySelector('[type="submit"]');btn.disabled=true;try{const r=await api('/api/reports/generate',{method:'POST',body:JSON.stringify(body)});closeModal();toast('Report generated',r.reference_no||'Controlled output created');await loadRoute()}catch(ex){toast('Report generation failed',ex.message,'error')}finally{btn.disabled=false}}}
public/assets/bcos-s25-3.js-362-
public/assets/bcos-s25-3.js-363-async function viewGovernance(page){const d=await api('/api/governance/dashboard');const e=d.executive||{};page.innerHTML=`${pageHead('Governance, Legal, Risk & Quality','One view of obligations, enterprise risks, quality findings, incidents and resilience.','','Enterprise Assurance')}${metricCard('Legal matters',e.open_legal_matters||0,'Open register items','file')}${metricCard('Compliance due',e.compliance_due_30_days||0,'Next 30 days','calendar')}${metricCard('Overdue compliance',e.overdue_compliance||0,'Immediate attention','risk',num(e.overdue_compliance)?'bad':'good')}${metricCard('High risks',e.high_risks||0,'Enterprise register','shield')}${metricCard('Serious findings',e.serious_audit_findings||0,'Audit and quality','check')}${metricCard('Open complaints',e.open_complaints||0,'Service accountability','message')}
Compliance calendar
Upcoming and overdue obligations
${(d.calendar||[]).length?d.calendar.map(x=>`
${esc(x.title)}${esc(x.record_type)} · ${esc(x.reference_no)} · Due ${fmtDate(x.due_date)}
${statusPill(x.priority)}
`).join(''):emptyState('No compliance dates recorded','Add verified statutory and policy obligations.','','calendar')}
`}
public/assets/bcos-s25-3.js-364-async function viewAssessments(page){const d=await api('/api/assessments');const items=Array.isArray(d)?d:(d.assessments||[]);page.innerHTML=`${pageHead('Technical Item Assessments','Evidence-led decisions for vehicles, machinery, equipment, software and other assets.',has('assessments.manage')?button('New assessment','primary','','plus'):'','Assessment Centre')}${items.length?items.map(x=>`
`).join(''):emptyState('No notifications','Your notification inbox is clear.','','bell')}`}
public/assets/bcos-s25-3.js-368-async function viewCollaboration(page){const items=await api('/api/collaboration/conversations');page.innerHTML=`${pageHead('Enterprise Conversations','Secure project, client, approval and direct discussions with governed participants.',has('collaboration.message')?button('New conversation','primary','','plus'):'','Collaboration')}${items.length?items.map(x=>`
`).join(''):emptyState('No conversations','Create a governed conversation when project collaboration begins.','','message')}`}
public/assets/bcos-s25-3.js-369-async function viewSearch(page){const q=new URLSearchParams(location.search).get('q')||'';page.innerHTML=`${pageHead('Enterprise Search','Find governed clients, projects, documents, reports, assessments, finance and knowledge within your permissions.','','Search')}${q?loading(4):emptyState('Search BCOS','Enter at least two characters to search governed information.','','search')}`;$('#search-form').onsubmit=e=>{e.preventDefault();const term=$('#search-q').value.trim();history.replaceState({},'',`/search?q=${encodeURIComponent(term)}`);runSearch(term)};if(q)await runSearch(q)}
public/assets/bcos-s25-3.js-370-async function runSearch(q){const el=$('#search-results');if(q.length<2){el.innerHTML=emptyState('Enter more detail','Search requires at least two characters.','','search');return}el.innerHTML=loading(4);try{const rows=await api(`/api/search?q=${encodeURIComponent(q)}`);el.innerHTML=rows.length?`
`;$$('[data-settings]').forEach(b=>b.onclick=()=>activateSettingsTab(b.dataset.settings));await Promise.all([loadProfileSettings(),loadSecuritySettings(),loadNotificationSettings(),has('administration.view')?loadLegalSettings():Promise.resolve()]);const appearanceButton=$('#save-appearance');if(appearanceButton)appearanceButton.onclick=()=>{const density=$('#density').value;localStorage.setItem('bcos_density',density);document.documentElement.dataset.density=density;toast('Appearance saved',density==='compact'?'Compact information density enabled.':'Comfortable information density enabled.')}}
public/assets/bcos-s25-3.js-375-function activateSettingsTab(tab){$$('[data-settings]').forEach(b=>b.classList.toggle('active',b.dataset.settings===tab));$$('[data-section]').forEach(s=>s.classList.toggle('active',s.dataset.section===tab))}
public/assets/bcos-s25-3.js-376-async function loadProfileSettings(){const el=$('[data-section="profile"]');el.innerHTML=`
Profile details
Keep your name, locale and timezone accurate for project records and notifications.
Protect your BCOS account and sign out devices that you do not recognise.
${mfa.enabled?'Multi-factor authentication is enabled':'Multi-factor authentication is not enabled'}${mfa.enabled?`${fmtNum(mfa.recovery_codes_remaining)} unused recovery code(s) remain.`:'Enable an authenticator app before granting external client production access.'}
${s.is_current?'Current session':'Authorised session'}${esc(s.user_agent||'Unknown device')} ${esc(s.ip_address||'Unknown address')} · Last active ${fmtDateTime(s.last_seen_at)}
`).join(''):emptyState('No active sessions','Active devices will appear here.','','device')}`;$('#password-form').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,body=Object.fromEntries(new FormData(f).entries()),btn=f.querySelector('[type="submit"]');if(body.new_password!==body.confirm_password){toast('Passwords do not match','','error');return}btn.disabled=true;try{await api('/api/account/password',{method:'POST',body:JSON.stringify(body)});f.reset();toast('Password changed','Other active devices were signed out.');await loadSecuritySettings()}catch(ex){toast('Password not changed',ex.message,'error')}finally{btn.disabled=false}};$('#revoke-other').onclick=async()=>{try{const r=await api('/api/account/sessions/revoke-other',{method:'POST'});toast('Other devices signed out',`${fmtNum(r.revoked)} session(s) revoked.`);await loadSecuritySettings()}catch(ex){toast('Sessions not revoked',ex.message,'error')}};$('#enable-mfa')?.addEventListener('click',beginMfaModal);$('#disable-mfa')?.addEventListener('click',disableMfaModal)}catch(ex){showError(ex,el)}}
public/assets/bcos-s25-3.js:378:async function beginMfaModal(){try{const d=await api('/api/account/mfa/begin',{method:'POST'});const m=openModal('Enable multi-factor authentication','Add BCOS to an authenticator app, then confirm the current six-digit code.',`
Manual setup key${esc(d.secret)}
In Microsoft Authenticator, Google Authenticator, 1Password or another TOTP app, choose to enter a setup key manually. Account: ${esc(state.user.email)}. Type: time based.
Advanced authenticator URI`);m.querySelector('[data-close]').onclick=closeModal;m.querySelector('#copy-mfa-secret').onclick=async()=>{try{await navigator.clipboard.writeText(d.secret);toast('Setup key copied')}catch{m.querySelector('#mfa-secret').focus?.();toast('Select and copy the setup key manually')}};m.querySelector('#verify-mfa-form').onsubmit=async e=>{e.preventDefault();const btn=e.currentTarget.querySelector('[type="submit"]');btn.disabled=true;try{const result=await api('/api/account/mfa/verify',{method:'POST',body:JSON.stringify({code:e.currentTarget.code.value.trim()})});state.user.mfa_required=1;const codes=(result.recovery_codes||[]).join('\n');m.innerHTML=`
${icon('shield')}
MFA enabled
Store these one-time recovery codes securely. Each code works once. BCOS will not show this list again.
`;m.querySelector('#print-recovery').onclick=()=>window.print();m.querySelector('#finish-mfa').onclick=async()=>{closeModal();toast('MFA enabled','Your account now requires a code at sign-in.');await loadSecuritySettings()}}catch(ex){toast('MFA not enabled',ex.message,'error');btn.disabled=false}}}catch(ex){toast('MFA enrolment could not start',ex.message,'error')}}
public/assets/bcos-s25-3.js:379:function disableMfaModal(){const m=openModal('Disable multi-factor authentication','This reduces account security. Confirm with your password and current authenticator code.',``);m.querySelector('[data-close]').onclick=closeModal;m.querySelector('#disable-mfa-form').onsubmit=async e=>{e.preventDefault();const f=e.currentTarget,btn=f.querySelector('[type="submit"]');btn.disabled=true;try{await api('/api/account/mfa/disable',{method:'POST',body:JSON.stringify(Object.fromEntries(new FormData(f).entries()))});state.user.mfa_required=0;closeModal();toast('MFA disabled','','error');await loadSecuritySettings()}catch(ex){toast('MFA not disabled',ex.message,'error');btn.disabled=false}}}
public/assets/bcos-s25-3.js-380-async function loadNotificationSettings(){const el=$('[data-section="notifications"]');try{const p=await api('/api/notifications/preferences');el.innerHTML=`
Notification preferences
Choose how BCOS should surface important work without overwhelming you.