1"use strict";(self.webpackChunkphpvms_docs=self.webpackChunkphpvms_docs||[]).push([[5495],{8896(e,n,s){s.r(n),s.d(n,{assets:()=>o,contentTitle:()=>l,default:()=>c,frontMatter:()=>a,metadata:()=>r,toc:()=>h});const r=JSON.parse('{"id":"guides/basics","title":"Key Concepts","description":"phpvms is built around a set of entities that work together to model how a real","source":"@site/docs/guides/basics.md","sourceDirName":"guides","slug":"/guides/basics","permalink":"/8.x/guides/basics","draft":false,"unlisted":false,"editUrl":"https://github.com/phpvms/docs/tree/master/docs/guides/basics.md","tags":[],"version":"current","frontMatter":{"id":"basics","title":"Key Concepts","sidebar_label":"Key Concepts"},"sidebar":"docs","previous":{"title":"Cron and Scheduled Tasks","permalink":"/8.x/installation/cron"},"next":{"title":"Deeper Dive","permalink":"/8.x/guides/deeper-dive"}}');var t=s(74848),i=s(28453);const a={id:"basics",title:"Key Concepts",sidebar_label:"Key Concepts"},l="How phpvms Works",o={},h=[{value:"Airlines",id:"airlines",level:2},{value:"Subfleets",id:"subfleets",level:2},{value:"The Pilot's Loop",id:"the-pilots-loop",level:2},{value:"Configuration Order",id:"configuration-order",level:2},{value:"Journals",id:"journals",level:2},{value:"Fares and Fare classes",id:"fares-and-fare-classes",level:2},{value:"Subfleet fares",id:"subfleet-fares",level:3},{value:"Flight fares",id:"flight-fares",level:3},{value:"Expenses",id:"expenses",level:2},{value:"Custom expenses",id:"custom-expenses",level:3},{value:"Further reading",id:"further-reading",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mermaid:"mermaid",ol:"ol",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"how-phpvms-works",children:"How phpvms Works"})}),"\n",(0,t.jsx)(n.p,{children:"phpvms is built around a set of entities that work together to model how a real\nairline operates. This page explains how those pieces fit together \u2014 and the\norder to set them up \u2014 before you dive into the admin panel."}),"\n",(0,t.jsxs)(n.p,{children:["For per-entity field reference, see the ",(0,t.jsx)(n.a,{href:"/8.x/guides/deeper-dive",children:"Deeper Dive"}),". For\ntricky terminology, see the ",(0,t.jsx)(n.a,{href:"/8.x/guides/glossary",children:"Glossary"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"airlines",children:"Airlines"}),"\n",(0,t.jsx)(n.p,{children:"An airline owns multiple subfleets; each subfleet contains aircraft. Aircraft\nnever live outside a subfleet."}),"\n",(0,t.jsx)(n.mermaid,{value:"flowchart TD\n A[Airline] --\x3e S1[Subfleet]\n A --\x3e S2[Subfleet]\n A --\x3e S3[Subfleet]\n S1 --\x3e AC1[Aircraft]\n S1 --\x3e AC2[Aircraft]\n S2 --\x3e AC3[Aircraft]\n S3 --\x3e AC4[Aircraft]\n S3 --\x3e AC5[Aircraft]"}),"\n",(0,t.jsx)(n.h2,{id:"subfleets",children:"Subfleets"}),"\n",(0,t.jsxs)(n.p,{children:["A subfleet is a key unit: it bundles ",(0,t.jsx)(n.strong,{children:"fares"})," (with overridable\nprice/cost/capacity), gates access by ",(0,t.jsx)(n.strong,{children:"rank"}),", optionally requires ",(0,t.jsx)(n.strong,{children:"type\nratings"}),", and is what ",(0,t.jsx)(n.strong,{children:"flights"})," reference when scheduling."]}),"\n",(0,t.jsx)(n.mermaid,{value:"flowchart LR\n F[Fares<br/>price/cost/capacity] -.attached to.-> S\n R[Ranks<br/>with pay rates] -.allowed on.-> S\n T[Type Ratings] -.required by.-> S\n S((Subfleet)) --\x3e AC[Aircraft]\n S -.assigned to.-> FL[Flights]"}),"\n",(0,t.jsx)(n.h2,{id:"the-pilots-loop",children:"The Pilot's Loop"}),"\n",(0,t.jsxs)(n.p,{children:["Two scopes: what an ",(0,t.jsx)(n.strong,{children:"admin"})," configures, and what a ",(0,t.jsx)(n.strong,{children:"pilot"})," drives. Dashed\narrows cross between them \u2014 that's where pilot activity references\nadmin-configured entities."]}),"\n",(0,t.jsx)(n.mermaid,{value:'flowchart TB\n subgraph AIRLINE_SCOPE["Airline & Operations (Admin manages)"]\n direction TB\n AL[Airline]\n AP[Airports<br/>some flagged as hubs]\n SF[Subfleets]\n AC[Aircraft]\n FA[Fares]\n RK[Ranks]\n TR[Type Ratings]\n FL[Flights]\n\n AL --\x3e SF\n SF --\x3e AC\n SF -.->|allows| FA\n SF -.->|gated by| RK\n SF -.->|requires| TR\n FL -.->|uses| SF\n FL -.->|departs / arrives| AP\n SF -.->|optional base| AP\n end\n\n subgraph PILOT_SCOPE["Pilot Scope (User-driven)"]\n direction TB\n US[User]\n BD[Bid]\n PR[PIREP]\n AW[Awards]\n\n US --\x3e|reserves| BD\n US --\x3e|files| PR\n US -.->|earns| AW\n US -.->|holds| TR2[Type Ratings]\n end\n\n %% Cross-scope: how the pilot scope connects to operations\n BD -.->|on a| FL\n BD -.->|with an| AC\n PR -.->|against a| FL\n PR -.->|in an| AC\n US -.->|belongs to| AL\n US -.->|home hub| AP\n US -.->|has a| RK\n TR2 === TR'}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"==="})," link shows pilot type-ratings and subfleet-required type-ratings are\nthe same entity, just rendered in both scopes for clarity."]}),"\n",(0,t.jsx)(n.p,{children:"The whole airline economy hangs off accepted PIREPs: a pilot bids on a flight +\naircraft, files a PIREP, an admin accepts it, and the system posts journal\nentries (revenue, fuel, pay), credits hours, re-checks awards, and may\nauto-promote rank. Until a PIREP is accepted, no money moves and no hours are\ncredited."}),"\n",(0,t.jsx)(n.h2,{id:"configuration-order",children:"Configuration Order"}),"\n",(0,t.jsx)(n.p,{children:"After installation, configure your airline in this order. Each step depends on\nthe previous one."}),"\n",(0,t.jsx)(n.mermaid,{value:"flowchart TD\n A[1. Settings &<br/>Airline] --\x3e B[2. Airports<br/>+ Hubs]\n B --\x3e C[3. Aircraft Types<br/>& SimBrief Airframes]\n C --\x3e D[4. Subfleets]\n D --\x3e E[5. Aircraft<br/>assigned to Subfleets]\n D --\x3e F[6. Fares<br/>attached to Subfleets]\n D --\x3e G[7. Ranks<br/>allowed Subfleets]\n E --\x3e H[8. Flights]\n F --\x3e H\n G --\x3e H\n H --\x3e I[9. Awards<br/>optional]\n H --\x3e J[10. Open registration]"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Why this order:"})," Subfleets are a branching unit \u2014 Aircraft live inside them,\nFares attach to them, Ranks gate them, and Flights reference them. Get airlines,\nairports, and the aircraft types you need first, then build subfleets, then\neverything else slots in."]}),"\n",(0,t.jsx)(n.h1,{id:"finances",children:"Finances"}),"\n",(0,t.jsxs)(n.p,{children:["Money in phpvms moves through journals. Pilots earn pay, airlines collect\nrevenue, and expenses post against either. None of this happens until a PIREP is\n",(0,t.jsx)(n.strong,{children:"accepted"})," \u2014 that's the trigger."]}),"\n",(0,t.jsx)(n.h2,{id:"journals",children:"Journals"}),"\n",(0,t.jsx)(n.p,{children:"Journals hold transactions. One journal per:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"Every airline"}),"\n",(0,t.jsx)(n.li,{children:"Every user"}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["The balance of a journal = sum of credits \u2212 sum of debits. Each transaction also\nhas a ",(0,t.jsx)(n.strong,{children:"group"}),", which financial reports use to roll up totals."]}),"\n",(0,t.jsx)(n.p,{children:"The journaling system means airlines and users can be paid or charged with\nhistorical records preserved, and reports can run across any time window."}),"\n",(0,t.jsx)(n.h2,{id:"fares-and-fare-classes",children:"Fares and Fare classes"}),"\n",(0,t.jsx)(n.p,{children:"Fares are the prices passengers pay for seats (or that cargo classes pay for\nweight). When a PIREP is filed, phpvms looks up the fare price in this order:"}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Per-flight fare"})," \u2014 ",(0,t.jsx)(n.code,{children:"flight_fare"})," pivot, set when the fare is attached to a\nspecific flight"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Per-subfleet fare"})," \u2014 ",(0,t.jsx)(n.code,{children:"subfleet_fare"})," pivot, defaults that apply to any\nflight using that subfleet"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"The base fare"})," \u2014 global fare record (the fallback)"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["When a fare is attached to a subfleet or flight, you can set the value as either\na fixed amount or a ",(0,t.jsx)(n.strong,{children:"percentage"}),". The ",(0,t.jsx)(n.code,{children:"%"})," sign is required to trigger\npercentage mode. ",(0,t.jsx)(n.code,{children:"100%"}),' means "use the base value". ',(0,t.jsx)(n.code,{children:"200%"}),' means "double it".']}),"\n",(0,t.jsxs)(n.p,{children:['The percentage mode is powerful: define one global "Economy" fare at $100,\nattach it to a holiday flight at ',(0,t.jsx)(n.code,{children:"200%"}),", and that flight's economy seat\nautomatically prices at $200 \u2014 without creating a duplicate fare record."]}),"\n",(0,t.jsx)(n.h3,{id:"subfleet-fares",children:"Subfleet fares"}),"\n",(0,t.jsxs)(n.p,{children:["Subfleets need fares attached to them. These fares are shared across every\naircraft in the subfleet. You can override ",(0,t.jsx)(n.strong,{children:"cost, price, and capacity"}),' per\nsubfleet \u2014 one subfleet might have more economy seats than another, but they\nboth reference the same global "Economy" fare.']}),"\n",(0,t.jsx)(n.h3,{id:"flight-fares",children:"Flight fares"}),"\n",(0,t.jsx)(n.p,{children:"Adding a fare to a flight overrides the subfleet's value for that flight only."}),"\n",(0,t.jsxs)(n.p,{children:["The override can be a fixed amount or a multiplier. Default (no value set)
1=\n",(0,t.jsx)(n.code,{children:"100%"})," of whatever the subfleet would charge."]}),"\n",(0,t.jsxs)(n.p,{children:["This is useful for ",(0,t.jsx)(n.strong,{children:"aircraft substitutions"}),". A normal route might only have\nEconomy and First. But if a substitute aircraft offers Premium Economy too,\nattach Premium Economy to the flight at ",(0,t.jsx)(n.code,{children:"120%"})," \u2014 and any PIREP filed on that\nflight will use that price."]}),"\n",(0,t.jsx)(n.h2,{id:"expenses",children:"Expenses"}),"\n",(0,t.jsx)(n.p,{children:"Expenses are arbitrary debits against an airline or user. Three types:"}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Type"}),(0,t.jsx)(n.th,{children:"When it fires"}),(0,t.jsx)(n.th,{children:"Notes"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Flight"})}),(0,t.jsx)(n.td,{children:"Each flight flown"}),(0,t.jsx)(n.td,{children:"Can charge the airline or the user/pilot"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Daily"})}),(0,t.jsx)(n.td,{children:"Once per day"}),(0,t.jsx)(n.td,{children:"Requires cron"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Monthly"})}),(0,t.jsx)(n.td,{children:"Once per month"}),(0,t.jsx)(n.td,{children:"Requires cron"})]})]})]}),"\n",(0,t.jsx)(n.admonition,{type:"note",children:(0,t.jsxs)(n.p,{children:["Daily and monthly expenses depend on the ",(0,t.jsx)(n.a,{href:"/8.x/installation/cron",children:"cron job"}),"\nbeing set up correctly."]})}),"\n",(0,t.jsx)(n.p,{children:'Beyond the global "Expenses" admin section, expenses can also attach to specific\nobjects so you can model fees as granularly as you want:'}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Object"}),(0,t.jsx)(n.th,{children:"Flight expense"}),(0,t.jsx)(n.th,{children:"Daily expense"}),(0,t.jsx)(n.th,{children:"Monthly expense"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Aircraft"})}),(0,t.jsx)(n.td,{children:"Rental, catering \u2014 chargeable to pilot"}),(0,t.jsx)(n.td,{children:"Cleaning"}),(0,t.jsx)(n.td,{children:"Lease, MRO costs"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Airport"})}),(0,t.jsx)(n.td,{children:"Landing fees (arrival only) \u2014 pilot-chargeable"}),(0,t.jsx)(n.td,{children:"Daily airport fees"}),(0,t.jsx)(n.td,{children:"Gate charges"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.strong,{children:"Subfleet"})}),(0,t.jsx)(n.td,{children:"Per-flight fee, pilot-chargeable"}),(0,t.jsx)(n.td,{children:"Daily subfleet charges"}),(0,t.jsx)(n.td,{children:"Monthly fees"})]})]})]}),"\n",(0,t.jsx)(n.h3,{id:"custom-expenses",children:"Custom expenses"}),"\n",(0,t.jsxs)(n.p,{children:["For dynamic logic \u2014 charging a pilot for a hard landing, or a flight that\nexceeded its planned time \u2014 write a ",(0,t.jsx)(n.code,{children:"Listener"})," for the ",(0,t.jsx)(n.code,{children:"Expenses"})," event. See\n",(0,t.jsx)(n.code,{children:"app/Listeners/ExpenseListener"})," for the reference implementation. Custom\nexpenses can also fire on daily and monthly schedules."]}),"\n",(0,t.jsx)(n.h2,{id:"further-reading",children:"Further reading"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.expertflyer.com/sessionlessClassList.do",children:"ExpertFlyer's real-world fare class list"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://forum.phpvms.net/topic/24329-connecting-flights/",children:"Forum: Connecting flights"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://www.quora.com/What-is-the-difference-between-Multi-leg-and-Multi-segment-flights",children:"Quora: Multi-leg vs multi-segment"})}),"\n"]})]})}function c(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>a,x:()=>l});var r=s(96540);const t={},i=r.createContext(t);function a(e){const n=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),r.createElement(i.Provider,{value:n},e.children)}}}]);
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.