1import{c as ce,a as g,j as e,v as me,k as b,I as R,R as M,N as I,b as Q,f as ue,m as xe,T as Y,B as F,g as Z,o as oe,y as he,x as ge,F as le,z as fe,n as pe,D as je}from"./index-DykHQTdy.js";import{a as o,L as ee}from"./react-ai5N7DV-.js";import{u as K}from"./params-Ch2Bbdnc.js";import{u as be,a as _e}from"./resource-DV2F2rUy.js";import{S as se}from"./Segmented-ymgBL82v.js";import{S as q}from"./Stat-0GldZ1Vq.js";import{T as de,a as N}from"./Table-C6VCsVMr.js";import"./motion-BDgCEaEF.js";import"./use-transform-DbSEnY4C.js";const Ne=je,u=s=>`$${Number(s??0).toLocaleString("en-AU",{minimumFractionDigits:2,maximumFractionDigits:2})}`,X=s=>s?new Date(s.slice(0,10)+"T00:00:00").toLocaleDateString("en-AU",{day:"numeric",month:"short",year:"numeric"}):"â",te=s=>s.toISOString().slice(0,10),ae=s=>s==="paid"?"success":s==="authorised"?"brand":s==="draft"||s==="submitted"?"warning":s==="voided"||s==="deleted"?"danger":"neutral",re=[{value:"open",label:"Open"},{value:"draft",label:"Drafts"},{value:"authorised",label:"Awaiting payment"},{value:"paid",label:"Paid"},{value:"all",label:"All"}],ve=s=>`${s.parent_first_name} ${s.parent_last_name}`.trim();function Le({me:s}){var P,W,B,U;const[_,d]=K("view"),[i,k]=K("id"),[l,n]=K("new"),p=re.some(a=>a.value===_)?_:"open",[T,y]=o.useState(!0),[v,j]=o.useState(null),[L,$]=o.useState(l==="1"),E=ce(s==null?void 0:s.permissions,"billing.manage"),{data:f,error:G,reload:z}=be(JSON.stringify([p,T,s==null?void 0:s.user_id]),()=>Promise.all([g.billing.status(),g.billing.list({status:p==="all"?void 0:p,include_test:T,limit:500})])),x=(f==null?void 0:f[0])??null,h=(f==null?void 0:f[1])??null,{run:H,err:r,ok:A,busy:c,setOk:V}=_e(z),J=r??G;o.useEffect(()=>{h&&j(a=>a&&(h.find(t=>t.id===a.id)??null))},[h]),o.useEffect(()=>{if(i&&h){const a=h.find(t=>t.id===i||t.invoice_code===i);a?j(a):g.billing.get(i).then(j).catch(()=>{})}},[i,h]);const O=(a,t)=>H(t,void 0,a),S=a=>{j(a),k(a?a.invoice_code:null,{replace:!0})},w=x?x.active_lessons-x.lessons_with_fee:0,C=(x==null?void 0:x.by_status)??{};return e.jsxs("div",{className:"space-y-6",children:[e.jsx(me,{eyebrow:"Billing",title:"Invoices",lede:"Fees live on lessons. An invoice is drafted here from a family's sessions, created in Xero, and comes back with its number, status and payments.",actions:E&&e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsxs(b,{variant:"secondary",disabled:!!c,onClick:()=>O("sync",async()=>{const a=await g.billing.syncAll();return`${a.checked} open invoice${a.checked===1?"":"s"} checked with Xero, ${a.changed} changed.`}),children:[e.jsx(R,{name:"pulse",size:16})," ",c==="sync"?"Syncingâ¦":"Sync with Xero"]}),e.jsxs(b,{"data-guide":"billing-draft",onClick:()=>$(!0),disabled:L,children:[e.jsx(R,{name:"briefcase",size:16})," Invoice a period"]})]})}),e.jsx(M,{children:e.jsxs("div",{className:"grid grid-cols-2 gap-3 sm:grid-cols-4",children:[e.jsx(q,{label:"Drafts",value:((P=C.draft)==null?void 0:P.count)??0,hint:u((W=C.draft)==null?void 0:W.total)}),e.jsx(q,{label:"Awaiting payment",value:((B=C.authorised)==null?void 0:B.count)??0,hint:`${u((U=C.authorised)==null?void 0:U.due)} due`,accent:!0}),e.jsx(q,{label:"Overdue",value:(x==null?void 0:x.overdue.count)??0,hint:u(x==null?void 0:x.overdue.due)}),e.jsx(q,{label:"Lessons with a fee",value:x?`${x.lessons_with_fee}/${x.active_lessons}`:"â",hint:w>0?`${w} still need one`:"every active lesson priced"})]})}),w>0&&e.jsxs(I,{tone:"warning",children:[w," active lesson",w===1?" has":"s have"," no fee yet, so their sessions cannot be invoiced. Set one on each lesson (",e.jsx(ee,{to:`${Q}/lessons`,className:"underline",children:"Lessons"}),") or with ",e.jsx("code",{className:"font-mono",children:"artin lessons set LN0001 --set fee_per_lesson=60"}),"."]}),e.jsx(I,{tone:"danger",children:J}),e.jsx(I,{tone:"success",children:A}),e.jsx(ue,{children:L&&E&&e.jsx(xe.div,{initial:{opacity:0,y:-6},animate:{opacity:1,y:0},exit:{opacity:0,y:-6},children:e.jsx(we,{onClose:()=>{$(!1),n(null,{replace:!0})},onDone:a=>{V(a),$(!1),z()}})},"draft")}),e.jsx(M,{delay:.05,children:e.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[e.jsx(se,{"aria-label":"Which invoices",value:p,options:re,onChange:a=>d(a,{replace:!0})}),e.jsx("div",{className:"ml-auto",children:e.jsx(Y,{label:"show test invoices",checked:T,onChange:a=>y(a.target.checked)})})]})}),e.jsxs("div",{className:Z("grid gap-6",v&&"lg:grid-cols-[1fr_minmax(20rem,26rem)]"),children:[e.jsx(M,{delay:.08,className:"min-w-0",children:e.jsx(de,{head:["Invoice","Family","Period","Status","Total","Due"],empty:h&&h.length===0?p==="open"?"No open invoices. Draft some from a period's sessions.":"Nothing here.":h?void 0:"Loadingâ¦",children:h==null?void 0:h.map(a=>e.jsxs("tr",{className:Z("cursor-pointer",(v==null?void 0:v.id)===a.id&&"bg-page-3/80"),onClick:()=>S(a),children:[e.jsxs(N,{mono:!0,children:[e.jsx("span",{className:"text-fg",children:a.invoice_code}),a.xero_number&&e.jsx("span",{className:"block text-fg-muted",children:a.xero_number}),a.is_test&&e.jsx(F,{tone:"neutral",className:"ml-1",children:"test"})]}),e.jsxs(N,{children:[e.jsx("span",{className:"text-fg",children:ve(a)}),e.jsx("span",{className:"block text-xs text-fg-muted",children:a.parent_email})]}),e.jsxs(N,{className:"whitespace-nowrap text-fg-muted",children:[X(a.period_start)," â ",X(a.period_end)]}),e.jsxs(N,{children:[e.jsx(F,{tone:ae(a.status),children:a.status}),a.status==="authorised"&&a.due_date&&a.due_date<te(new Date)&&e.jsx(F,{tone:"danger",className:"ml-1",children:"overdue"})]}),e.jsx(N,{className:"whitespace-nowrap text-fg",children:u(a.total)}),e.jsx(N,{className:"whitespace-nowrap",children:a.status==="paid"?e.jsx("span",{className:"text-fg-muted",children:"paid"}):a.xero_invoice_id?u(a.amount_due):e.jsx("span",{className:"text-fg-muted",children:"not in Xero"})})]},a.id))})}),v&&e.jsx(M,{className:"min-w-0",children:e.jsx(ye,{inv:v,manage:E,busy:c,onAct:O,onClose:()=>S(null)})})]}),e.jsx(ke,{}),e.jsxs("p",{className:"text-xs text-fg-muted",children:["Same data: ",e.jsx("code",{className:"font-mono",children:"GET /api/invoices"}),", ",e.jsx("code",{className:"font-mono",children:"artin billing run --period last_month --issue"}),", ",e.jsx("code",{className:"font-mono",children:"artin billing issue INV0001"}),". Xero connection and invoice defaults: ",e.jsx(ee,{to:`${Q}/settings#xero`,className:"underline",children:"Settings"}),"."]})]})}function ye({inv:s,manage:_,busy:d,onAct:i,onClose:k}){const l=!!s.xero_invoice_id;return e.jsxs("div",{className:"surface-raised space-y-4 p-5 border-accent-glow",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsx(oe,{eyebrow:`${s.invoice_code}${s.xero_number?` · Xero ${s.xero_number}`:""}`,children:`${s.parent_first_name} ${s.parent_last_name}`.trim()}),e.jsx("button",{type:"button","aria-label":"Close",onClick:k,className:"rounded-md p-1 text-fg-muted hover:bg-page-3",children:e.jsx(R,{name:"close",size:16})})]}),e.jsxs("dl",{className:"grid grid-cols-2 gap-x-4 gap-y-1.5 text-sm",children:[e.jsx("dt",{className:"text-fg-muted",children:"Status"}),e.jsxs("dd",{children:[e.jsx(F,{tone:ae(s.status),children:s.status}),s.xero_status&&s.xero_status.toLowerCase()!==s.status&&e.jsxs("span",{className:"ml-2 text-xs text-fg-muted",children:["Xero: ",s.xero_status]})]}),e.jsx("dt",{className:"text-fg-muted",children:"Period"}),e.jsxs("dd",{className:"text-fg",children:[X(s.period_start)," â ",X(s.period_end)]}),e.jsx("dt",{className:"text-fg-muted",children:"Total"}),e.jsxs("dd",{className:"text-fg",children:[u(s.total),Number(s.total_tax)>0&&e.jsxs("span",{className:"text-xs text-fg-muted",children:[" incl. ",u(s.total_tax)," tax"]})]}),l&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-fg-muted",children:"Paid / due"}),e.jsxs("dd",{className:"text-fg",children:[u(s.amount_paid)," / ",u(s.amount_due)]})]}),s.issue_date&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-fg-muted",children:"Issued"}),e.jsx("dd",{className:"text-fg",children:X(s.issue_date)})]}),s.due_date&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-fg-muted",children:"Due"}),e.jsx("dd",{className:"text-fg",children:X(s.due_date)})]}),e.jsx("dt",{className:"text-fg-muted",children:"Synced"}),e.jsx("dd",{className:"text-fg",children:s.synced_at?new Date(s.synced_at).toLocaleStr
1ing("en-AU"):"never"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"mb-1.5 text-xs font-medium uppercase tracking-wider text-fg-muted",children:"Lines"}),e.jsx("ul",{className:"divide-y divide-[var(--border)] text-sm",children:s.lines.map((n,p)=>e.jsxs("li",{className:"flex justify-between gap-3 py-2",children:[e.jsxs("span",{className:"min-w-0",children:[e.jsx("span",{className:"block text-fg",children:n.description}),e.jsxs("span",{className:"text-xs text-fg-muted",children:[n.quantity," à ",u(n.unit_amount),n.billing_mode?` · ${n.billing_mode}`:"",n.kind==="credit"?` · credit (${n.reason})`:"",n.absent?` · ${n.absent} absent`:""]})]}),e.jsx("span",{className:"shrink-0 text-fg",children:u(n.amount)})]},p))})]}),s.online_url&&e.jsxs(pe,{href:s.online_url,target:"_blank",rel:"noreferrer",variant:"secondary",size:"sm",children:[e.jsx(R,{name:"arrow",size:14})," View in Xero (online invoice)"]}),_&&e.jsxs("div",{className:"flex flex-wrap gap-2 border-t border-line pt-3",children:[(s.status==="draft"||s.status==="submitted")&&e.jsx(b,{size:"sm",disabled:!!d,onClick:()=>i("issue",async()=>{const n=await g.billing.issue(s.id);return`${n.invoice_code} issued in Xero as ${n.xero_number??n.status}.`}),children:d==="issue"?"Issuingâ¦":"Issue in Xero"}),(s.status==="draft"||s.status==="submitted")&&e.jsx(b,{size:"sm",variant:"secondary",disabled:!!d,onClick:()=>i("issue-mail",async()=>{const n=await g.billing.issue(s.id,!0);return`${n.invoice_code} issued as ${n.xero_number??n.status}; Xero is emailing it.`}),children:d==="issue-mail"?"Issuingâ¦":"Issue and email"}),!l&&s.status==="draft"&&e.jsx(b,{size:"sm",variant:"ghost",disabled:!!d,onClick:()=>i("push",async()=>{const n=await g.billing.push(s.id);return`${n.invoice_code} created in Xero as ${n.xero_number??n.xero_status}.`}),children:d==="push"?"Pushingâ¦":"Push as Xero draft"}),l&&s.status==="authorised"&&e.jsx(b,{size:"sm",variant:"secondary",disabled:!!d,onClick:()=>{confirm("Ask Xero to email this invoice to the parent?")&&i("mail",async()=>(await g.billing.authorise(s.id,!0),"Xero is emailing the invoice."))},children:d==="mail"?"Sendingâ¦":"Email via Xero"}),l&&e.jsx(b,{size:"sm",variant:"ghost",disabled:!!d,onClick:()=>i("sync1",async()=>{const n=await g.billing.sync(s.id);return`${n.invoice_code}: ${n.status}, ${u(n.amount_paid)} paid.`}),children:"Re-read from Xero"}),s.status!=="paid"&&s.status!=="voided"&&s.status!=="deleted"&&e.jsx(b,{size:"sm",variant:"danger",className:"ml-auto",disabled:!!d,onClick:()=>{confirm(l?`Void ${s.invoice_code} in Xero?`:`Delete draft ${s.invoice_code}? Its sessions become billable again.`)&&i("void",async()=>(await g.billing.void(s.id),l?`${s.invoice_code} voided.`:`${s.invoice_code} deleted.`))},children:l?"Void":"Delete draft"})]})]})}function we({onClose:s,onDone:_}){var a;const d=new Date,[i,k]=o.useState("all"),[l,n]=o.useState([]),[p,T]=o.useState(""),[y,v]=o.useState(te(new Date(d.getFullYear(),d.getMonth()-1,1))),[j,L]=o.useState(te(new Date(d.getFullYear(),d.getMonth(),0))),[$,E]=o.useState(!1),[f,G]=o.useState(!0),[z,x]=o.useState(!1),[h,H]=o.useState([]),[r,A]=o.useState(null),[c,V]=o.useState(null),[J,O]=o.useState(null),[S,w]=o.useState(null);o.useEffect(()=>{g.parents.list({limit:2e3}).then(t=>{n(t.filter(m=>m.is_active))}).catch(()=>{})},[]),o.useEffect(()=>{g.billing.period("last_month").then(t=>H(t.options)).catch(()=>{})},[]);const C="font-mono [color-scheme:light_dark]",P=()=>{A(null),V(null)},W=((a=h.find(t=>t.start===y&&t.end===j))==null?void 0:a.key)??"custom";async function B(t,m){O(null),w(t);try{await m()}catch(D){O(Ne(D))}finally{w(null)}}const U=i==="one"?!!r&&r.lines.length>0&&r.missing_fee.length===0:!!c&&c.drafted.length>0;return e.jsxs("div",{className:"surface-raised space-y-4 p-5 border-accent-glow",children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3",children:[e.jsx(oe,{eyebrow:"Invoice a period",children:"From sessions in a period"}),e.jsx(se,{"aria-label":"Who to bill",value:i,options:[{value:"all",label:"Every family"},{value:"one",label:"One family"}],onChange:t=>{k(t),P()}})]}),h.length>0&&e.jsx(se,{"aria-label":"Period",value:W,options:[...h.map(t=>({value:t.key,label:t.label})),{value:"custom",label:"Custom"}],onChange:t=>{const m=h.find(D=>D.key===t);m&&(v(m.start),L(m.end),P())}}),e.jsxs(he,{cols:"sm:grid-cols-4",children:[i==="one"&&e.jsxs(ge,{label:"Family",value:p,onChange:t=>{T(t.target.value),A(null)},children:[e.jsx("option",{value:"",children:"Choose a parentâ¦"}),l.map(t=>e.jsxs("option",{value:t.id,children:[t.first_name," ",t.last_name,t.email?` · ${t.email}`:"",t.is_test?" (test)":""]},t.id))]}),e.jsx(le,{label:"From",type:"date",value:y,onChange:t=>{v(t.target.value),P()},className:C}),e.jsx(le,{label:"To",type:"date",value:j,onChange:t=>{L(t.target.value),P()},className:C}),e.jsxs(fe,{className:"gap-3",children:[i==="all"&&e.jsx(Y,{label:"include test families",checked:$,onChange:t=>E(t.target.checked)}),e.jsx(b,{variant:"secondary",disabled:S!==null||i==="one"&&!p,onClick:()=>B("preview",async()=>{i==="one"?A(await g.billing.preview({parent_id:p,start:y,end:j})):V(await g.billing.run({start:y,end:j,dry_run:!0,include_test:$}))}),children:S==="preview"?"Lookingâ¦":"Preview"})]})]}),i==="all"&&e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(Y,{label:"issue in Xero straight away",checked:f,onChange:t=>G(t.target.checked)}),f&&e.jsx(Y,{label:"and email each family",checked:z,onChange:t=>x(t.target.checked)}),e.jsx("span",{className:"text-xs text-fg-muted",children:f?"Each draft is pushed and approved; a Xero failure leaves that one as a draft here.":"Drafts wait here until you issue them."})]}),e.jsx(I,{tone:"danger",children:J}),r&&e.jsxs("div",{className:"space-y-2 text-sm",children:[r.missing_fee.length>0&&e.jsxs(I,{tone:"warning",children:["No fee on ",r.missing_fee.map(t=>`${t.lesson_code} (${t.name})`).join(", "),": set one before drafting."]}),r.lines.length===0&&r.missing_fee.length===0&&e.jsxs("p",{className:"text-fg-muted",children:["Nothing to bill: ",r.skipped.already_billed," session",r.skipped.already_billed===1?"":"s"," already invoiced, ",r.sessions-r.skipped.already_billed," otherwise."]}),r.lines.map((t,m)=>e.jsxs("div",{className:"flex justify-between gap-3 border-b border-line py-1.5 last:border-0",children:[e.jsx("span",{className:t.kind==="credit"?"text-fg-muted":"text-fg",children:t.description}),e.jsx("span",{className:"shrink-0 text-fg",children:u(t.amount)})]},m)),r.lines.length>0&&e.jsxs("div",{className:"flex justify-between font-medium text-fg",children:[e.jsxs("span",{children:["Total for ",r.parent]}
1),e.jsx("span",{children:u(r.total)})]}),(r.skipped.already_billed>0||r.skipped.absent>0||r.skipped.not_yet>0)&&e.jsxs("p",{className:"text-xs text-fg-muted",children:["Skipped: ",r.skipped.already_billed," already invoiced, ",r.skipped.absent," absent (not charged), ",r.skipped.not_yet," ahead (postpaid: charged once run)."]})]}),c&&e.jsxs("div",{className:"space-y-2 text-sm",children:[c.drafted.length===0&&e.jsxs("p",{className:"text-fg-muted",children:["Nothing to draft in this period",c.nothing_to_bill.length?` (${c.nothing_to_bill.length} famil${c.nothing_to_bill.length===1?"y":"ies"} already fully invoiced)`:"","."]}),c.drafted.map(t=>e.jsxs("div",{className:"flex justify-between gap-3 border-b border-line py-1.5 last:border-0",children:[e.jsxs("span",{className:"text-fg",children:[t.parent," ",e.jsxs("span",{className:"text-xs text-fg-muted",children:["· ",t.lines," line",t.lines===1?"":"s",t.invoice_code?` · ${t.invoice_code}`:""]}),t.status&&e.jsx(F,{tone:ae(t.status),className:"ml-2",children:t.status}),t.error&&e.jsx("span",{className:"ml-2 text-xs text-danger",children:t.error})]}),e.jsx("span",{className:"shrink-0 text-fg",children:u(t.total)})]},t.parent_id)),c.drafted.length>0&&e.jsxs("div",{className:"flex justify-between font-medium text-fg",children:[e.jsxs("span",{children:[c.drafted.length," invoice",c.drafted.length===1?"":"s"]}),e.jsx("span",{children:u(c.drafted.reduce((t,m)=>t+m.total,0))})]}),c.missing_fee.length>0&&e.jsxs(I,{tone:"warning",children:["Not drafted (a lesson without a fee): ",c.missing_fee.map(t=>`${t.parent} â ${t.lessons.map(m=>m.lesson_code).join(", ")}`).join("; "),"."]})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(b,{disabled:S!==null||!U,onClick:()=>B("draft",async()=>{var ne;if(i==="one"){const ie=await g.billing.draft({parent_id:p,start:y,end:j});_(`${ie.invoice_code} drafted for ${u(ie.total)}.`);return}const t=await g.billing.run({start:y,end:j,include_test:$,issue:f,email:z}),m=t.drafted.length,D=((ne=t.failed)==null?void 0:ne.length)??0;_(f?`${m-D} of ${m} invoice${m===1?"":"s"} issued in Xero${D?`; ${D} left as drafts (see the run)`:""}.`:`${m} invoice${m===1?"":"s"} drafted.`)}),children:S==="draft"?f&&i==="all"?"Issuingâ¦":"Draftingâ¦":i==="one"?"Draft invoice":f?`Draft and issue${c?` ${c.drafted.length}`:""}`:"Draft all"}),e.jsx(b,{variant:"ghost",onClick:s,children:"Cancel"}),e.jsx("span",{className:"text-xs text-fg-muted",children:"A session on a live invoice is never charged twice, so running twice is safe. Prepaid students are charged for the period ahead, postpaid ones for what has run; a charged session that is cancelled or missed after leaving comes back as a credit."})]})]})}function ke(){const[s,_]=o.useState(null),[d,i]=o.useState(!1);o.useEffect(()=>{d&&!s&&g.billing.fees().then(_).catch(()=>_([]))},[d,s]);const k=o.useMemo(()=>(s??[]).filter(l=>l.fee_per_lesson==null).length,[s]);return e.jsx(M,{delay:.1,children:e.jsxs("div",{className:"surface p-4",children:[e.jsxs("button",{type:"button",className:"flex w-full items-center justify-between text-left",onClick:()=>i(l=>!l),"aria-expanded":d,children:[e.jsx("span",{className:"text-sm font-medium text-fg",children:"Fees per lesson"}),e.jsxs("span",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s?`${s.length} active · ${k} without a fee`:"show",e.jsx(R,{name:"chevron",size:14,className:Z("transition-transform",d&&"rotate-180")})]})]}),d&&e.jsx(de,{className:"mt-3",head:["Lesson","Type","On roster","Fee per lesson"],minWidth:"min-w-[28rem]",empty:s&&s.length===0?"No active lessons.":s?void 0:"Loadingâ¦",children:s==null?void 0:s.map(l=>e.jsxs("tr",{children:[e.jsxs(N,{children:[e.jsx(ee,{to:`${Q}/lessons?q=${l.lesson_code}`,className:"text-fg hover:underline",children:l.name})," ",e.jsx("span",{className:"font-mono text-xs text-fg-muted",children:l.lesson_code}),l.is_test&&e.jsx(F,{tone:"neutral",className:"ml-1",children:"test"})]}),e.jsx(N,{className:"text-fg-muted",children:l.lesson_type}),e.jsx(N,{className:"text-fg",children:l.roster_count}),e.jsx(N,{children:l.fee_per_lesson==null?e.jsx(F,{tone:"warning",children:"not set"}):e.jsx("span",{className:"text-fg",children:u(l.fee_per_lesson)})})]},l.lesson_code))})]})})}export{Le as StaffBilling};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.