/* =====================================================================
   NOWILLS SQC ERP — Restructured module catalogue (quarry ERP taxonomy),
   33 operational roles, and master/transactional records for new modules
   ===================================================================== */
(function(){
'use strict';
const D=DATA,D2=DATA2;

/* ---------------- 51-module navigation ---------------- */
DATA.MODULES_DEF=[
 ['exec','Executive Dashboard','dash','Executive'],
 ['qsite','Quarry Site Management','quarry','Quarry & Production'],
 ['geology','Geology & Reserves','layers','Quarry & Production'],
 ['drill','Drilling & Blasting','drill','Quarry & Production'],
 ['extract','Extraction & Loading','exc','Quarry & Production'],
 ['crush','Crushing & Screening','crush','Quarry & Production'],
 ['products','Aggregate Products','pile','Quarry & Production'],
 ['stockpile','Stockpile & Quarry Inventory','boxes','Quarry & Production'],
 ['weigh','Weighbridge','scale','Quarry & Production'],
 ['sales','Sales & Orders','tag','Commercial'],
 ['crm','Customers & CRM','users','Commercial'],
 ['pricing','Pricing & Discounts','pct','Commercial'],
 ['dispatch','Dispatch & Delivery','truck','Commercial'],
 ['comms','Communication Centre','mail','Commercial'],
 ['fleet','Fleet & Haulage','truck','Logistics & Assets'],
 ['equip','Equipment & Machinery','exc','Logistics & Assets'],
 ['maint','Maintenance','wrench','Logistics & Assets'],
 ['hire','Equipment Hire','key','Logistics & Assets'],
 ['fuel','Fuel Management','fuel','Logistics & Assets'],
 ['projects','Construction Projects','hardhat','Projects'],
 ['boq','Bill of Quantities','docs','Projects'],
 ['plan','Planning & Scheduling','cal','Projects'],
 ['site','Site Operations','site','Projects'],
 ['ipc','Interim Payment Certificates','cert','Projects'],
 ['costing','Project Costing & Profitability','budget','Projects'],
 ['tenders','Tenders & Bids','award','Projects'],
 ['contracts','Contract Management','doc','Projects'],
 ['mrp','Material Requirements (MRP)','cart','Projects'],
 ['proc','Procurement','cart','Supply Chain'],
 ['suppliers','Suppliers & Vendors','users','Supply Chain'],
 ['inventory','Warehouse & Stores','wh','Supply Chain'],
 ['hr','Human Resources','hr','People & Compliance'],
 ['payroll','Payroll','cash','People & Compliance'],
 ['hse','Health & Safety','shield','People & Compliance'],
 ['env','Environmental Management','leaf','People & Compliance'],
 ['rehab','Rehabilitation & Closure','leaf','People & Compliance'],
 ['qc','Quality Control & Laboratory','flask','People & Compliance'],
 ['gate','Security & Gate','shield','People & Compliance'],
 ['finance','Finance & Accounting','bank','Finance & Tax'],
 ['treasury','Cash, Bank & Treasury','cash','Finance & Tax'],
 ['budget','Budgeting & Cost Control','budget','Finance & Tax'],
 ['tax','Ghana Tax & Statutory','pct','Finance & Tax'],
 ['fixedassets','Fixed Assets','bank','Finance & Tax'],
 ['loans','Loans & Equipment Finance','doc','Finance & Tax'],
 ['reports','Reports & Analytics','report','Intelligence'],
 ['ai','AI & Business Intelligence','ai','Intelligence'],
 ['docs','Document Management','docs','Governance & System'],
 ['wflow','Approval Workflow Engine','flow','Governance & System'],
 ['setup','Setup & Configuration','sliders','Governance & System'],
 ['sync','Offline Mode & Synchronization','refresh','Governance & System'],
 ['admin','System Administration','gear','Governance & System']];

/* ---------------- 33 operational roles (spec §53) ---------------- */
const G={
 ops:['qsite','geology','drill','extract','crush','products','stockpile','weigh','fuel','equip','maint','qc','hse','rehab','gate','sync','reports'],
 proj:['projects','boq','plan','site','ipc','costing','contracts','mrp','tenders','qc','hse','docs','reports'],
 com:['sales','crm','pricing','dispatch','weigh','products','comms','reports'],
 fin:['finance','treasury','budget','tax','fixedassets','loans','ipc','costing','proc','budget','docs','reports','wflow'],
 sup:['proc','suppliers','inventory','docs','reports'],
 hr:['hr','payroll','hse','docs','reports']};
const RR=(n,mods,f)=>Object.assign({n:n,mods:mods==='all'?D.MODULES_DEF.map(m=>m[0]):[...new Set(['exec',...mods])]},f||{});
DATA.ROLES=[
 RR('Group CEO','all'), RR('Managing Director','all'), RR('General Manager','all'),
 RR('Quarry Manager',G.ops.concat(['sales','pricing','dispatch','inventory','proc']),{qry:1}),
 RR('Production Manager',['qsite','drill','extract','crush','products','stockpile','equip','fuel','maint','qc','hse','sync','reports','ai']),
 RR('Crushing Plant Manager',['crush','stockpile','products','equip','fuel','maint','qc','inventory','hse','sync','reports']),
 RR('Construction Project Manager',G.proj.concat(['extract','stockpile','products','equip','fleet','fuel','mrp']),{prj:1}),
 RR('Site Engineer',['projects','plan','site','boq','mrp','qc','inventory','hse','env','docs','reports'],{prj:1,mask:1}),
 RR('Quantity Surveyor',['projects','boq','ipc','costing','contracts','tenders','mrp','reports']),
 RR('Geologist',['geology','qsite','drill','stockpile','qc','reports']),
 RR('Surveyor',['geology','stockpile','site','rehab','reports']),
 RR('Blasting Supervisor',['drill','extract','qsite','hse','gate','sync','reports']),
 RR('Weighbridge Operator',['weigh','stockpile','dispatch','gate','comms','sync'],{mask:1}),
 RR('Dispatch Officer',['dispatch','sales','weigh','fleet','comms','stockpile','sync','reports']),
 RR('Sales Officer',G.com),
 RR('Fleet Manager',['fleet','fuel','maint','dispatch','equip','inventory','hse','reports']),
 RR('Driver',['dispatch','comms'],{mask:1,drv:1}),
 RR('Equipment Operator',['equip','extract','fuel','maint','hse'],{mask:1,op:1}),
 RR('Maintenance Manager',['maint','equip','fleet','inventory','proc','fuel','reports']),
 RR('Mechanic',['maint','equip','inventory','hse'],{mask:1}),
 RR('Stores Manager',G.sup.concat(['stockpile','fuel'])),
 RR('Procurement Officer',G.sup.concat(['costing','wflow'])),
 RR('Safety Officer',['hse','env','drill','gate','site','hr','reports']),
 RR('Environmental Officer',['env','rehab','hse','geology','reports']),
 RR('Laboratory Technician',['qc','products','stockpile','site','reports']),
 RR('Finance Manager',G.fin,{}), RR('Accountant',G.fin.concat(['wflow']),{}),
 RR('Cashier',['treasury','finance','sales','comms'],{mask:0}),
 RR('Credit Controller',['crm','sales','pricing','finance','reports']),
 RR('HR Manager',G.hr),
 RR('Payroll Officer',['payroll','hr','reports'],{pay:1}),
 RR('Internal Auditor',['exec','finance','treasury','tax','proc','inventory','docs','reports','wflow','setup','sync','admin'],{ro:1}),
 RR('System Administrator','all',{adm:1})];

/* ---------------- Quarry master records (spec §3) ---------------- */
const QUARRY_MASTERS=[
 {code:'NQ-SH',name:'Shai Hills Quarry',loc:'Shai Hills, Shai Osudoku, Greater Accra',gps:'5.8951° N, 0.0603° W',land:412,own:'Leased (Nowills Quarry Ltd)',lease:'25-yr land lease · L-2009-SH · expires 2034',ql:'Quarry licence QL-1187 (valid to 2027-03)',mp:'Mining permit MP-2200 (2026-12)',ep:'EPA permit EPA-GRA-2218 SH (2027-01)',op:'MinCom operating permit OP-7741 (2027-01)',mgr:'K. Boateng',geo:'Dr. A. Owusu',svy:'E. Larbi',pcap:'5,800 t/day',ccap:'4 × 220–280 tph crushers (rated 960 tph)',scap:'160,000 t across 8 piles',hours:'06:00–22:00 · 2 shifts · Sat optional',st:'Active',lon:-0.06,lat:5.895,
  areas:[['Pit A (Benches L1–L4)','active'],['Bench L4N — blasted 07 Aug','active'],['Primary crushing hall','active'],['Screening deck 1–3','active'],['Stockpile yard (8 piles)','active'],['Weighbridge WB-1/WB-2','active'],['Workshop','active'],['Fuel station (2×60k L)','active'],['Explosive magazine (licensed)','active'],['Maintenance yard','active'],['QA laboratory','active'],['Staff camp','active'],['Security post (gate A/B)','active'],['Administration office','active']]},
 {code:'NQ-BU',name:'Buoho Quarry',loc:'Buoho, Ashanti Region',gps:'6.7102° N, 1.6105° W',land:268,own:'Owned (freehold)',lease:'Freehold · deed GD-4412',ql:'Quarry licence QL-0912 (valid to 2026-09)','mp':'Mining permit MP-1990 (2026-09)',ep:'EPA permit BU-2219 (2026-08-31 — renewal filed)',op:'MinCom operating permit OP-6612 (2026-11)',mgr:'S. Adjei',geo:'M. Frimpong',svy:'K. Oduro',pcap:'3,200 t/day',ccap:'2 × 220 tph crushers (440 tph)',scap:'95,000 t across 5 piles',hours:'06:00–18:00 · day shift',st:'Active',lon:-1.61,lat:6.71,
  areas:[['Pit B (Benches B1–B2)','active'],['Crushing & screening plant','active'],['Stockpile yard (5 piles)','active'],['Weighbridge WB-3','active'],['Workshop','active'],['Fuel station (60k L)','active'],['Magazine','active'],['Laboratory','active'],['Security post','active'],['Admin office','active']]},
 {code:'NQ-NS',name:'Nsawam South Quarry',loc:'Nsawam, Eastern Region',gps:'5.8396° N, 0.3801° W',land:150,own:'Leased',lease:'12-yr lease · L-2018-NS · expires 2030',ql:'Quarry licence QL-2101 (2027-06)',mp:'Mining permit MP-3011 (2027-02)',ep:'EPA EIA NS-0661 (expires 2026-08-12 ⚠)',op:'MinCom operating permit OP-8102 (2027-04)',mgr:'D. Amoah',geo:'R. Asante',svy:'F. Hammond',pcap:'1,800 t/day',ccap:'1 × 180 tph crusher + mobile unit',scap:'48,000 t across 3 piles',hours:'06:00–18:00 · day shift',st:'Active',lon:-0.38,lat:5.84,
  areas:[['Pit N (Benches N1–N2)','active'],['Crusher + mobile screen','active'],['Stockpile yard','active'],['Weighbridge WB-4','active'],['Genset house','running'],['Fuel bowser point','active'],['Security post','active'],['Site office','active']]}];

/* ---------------- Geology & reserves (spec §4) ---------------- */
const RESERVES=[
 {block:'A-North',bench:'L4',rock:'Granite (fresh)',proven:2.4e6,probable:1.1e6,extracted:0.62e6,overburden:0.18e6,waste:0.09e6,grade:'ACV 21 · LAA 18 · SG 2.71',life:6.2},
 {block:'A-East',bench:'L3',rock:'Granite (weathered top)',proven:1.6e6,probable:0.9e6,extracted:0.41e6,overburden:0.31e6,waste:0.14e6,grade:'ACV 24 · LAA 21 · SG 2.66',life:4.1},
 {block:'B-South',bench:'B2',rock:'Granodiorite',proven:1.9e6,probable:0.7e6,extracted:0.29e6,overburden:0.22e6,waste:0.08e6,grade:'ACV 22 · LAA 19 · SG 2.68',life:7.6},
 {block:'B-West',bench:'B1',rock:'Granodiorite',proven:0.8e6,probable:0.4e6,extracted:0.12e6,overburden:0.09e6,waste:0.05e6,grade:'ACV 25 · LAA 23 · SG 2.64',life:3.2},
 {block:'N-1',bench:'N1',rock:'Gneiss',proven:1.1e6,probable:0.6e6,extracted:0.18e6,overburden:0.15e6,waste:0.07e6,grade:'ACV 23 · LAA 20 · SG 2.69',life:8.9},
 {block:'N-2',bench:'N2',rock:'Gneiss',proven:0.6e6,probable:0.3e6,extracted:0.04e6,overburden:0.12e6,waste:0.04e6,grade:'ACV 26 · LAA 24 · SG 2.63',life:6.8}];

/* ---------------- Products catalogue (spec §8) ---------------- */
const PCAT=[['PD-001','Aggregate 20mm (¾")','Crushed granite','20–25mm','A','t',2.68,195,15,'Road base · asphalt','SH/BU'],['PD-002','Aggregate 14mm (½")','Crushed granite','14–20mm','A','t',2.66,210,15,'Concrete production','SH/BU/NS'],['PD-003','Aggregate 10mm (⅜")','Crushed granite','10–14mm','A','t',2.64,225,15,'Concrete · block making','SH'],['PD-004','Chippings 5–10mm','Crushed granite','5–10mm','A','t',2.62,190,15,'Asphalt · surfacing','SH'],['PD-005','Quarry dust','Granite fines','0–5mm','B','t',2.55,95,15,'Block/paving · bedding','SH/BU'],['PD-006','Manufactured sand','Crushed fines','0–5mm (washed)','A','t',2.60,150,15,'Concrete · mortar','BU'],['PD-007','Crusher run 40mm D/L','Mixed graded','0–40mm','A','t',2.62,135,15,'Sub-base','SH/BU'],['PD-008','Base course GS','Graded stone','0–31.5mm','A','t',2.65,165,15,'Road base','SH'],['PD-009','Sub-base GSB','Graded stone','0–63mm','B','t',2.60,120,15,'Sub-base layers','All'],['PD-010','Ballast 40–75mm','Large graded','40–75mm','A','t',2.67,155,15,'Railway ballast/concrete','SH'],['PD-011','Hardcore','Mixed rock','75–150mm','B','m³','2.5 t/m³',98,15,'Site fill','SH'],['PD-012','Riprap 200–500kg','Armour','individual','A','piece','—',420,15,'Coastal/drainage protection','SH'],['PD-013','Boulders +500kg','Armour','individual','A','piece','—',560,15,'Sea defence','BU'],['PD-014','Rockfill','Blasted run','bulk','C','t',2.2,85,15,'Earthworks fill','All']].map(p=>({code:p[0],name:p[1],mat:p[2],size:p[3],grade:p[4],uom:p[5],dens:p[6],price:p[7],min:p[8],use:p[9],site:p[10],tax:'VAT 15% + levies 6%',stock:'Live',active:true }));

/* ---------------- Pricing matrix (spec §13) ---------------- */
const PRX=PCAT.slice(0,8).map(p=>({code:p.code,name:p.name,std:p.price,qry:p.price-8,del:p.price+28,dealer:p.price-15,contract:p.price-12,eff:'2026-08-01',exp:'2026-10-31',minSell:Math.round(p.price*0.9),ch:'—'}));

/* ---------------- Extraction orders (spec §6) ---------------- */
function extOrders(){const r=D.mulberry(7001);const rows=[];
 for(let i=0;i<16;i++){const pl=380+r()*420|0,ac=pl*(0.86+r()*0.26)|0;
 rows.push({no:'EX-'+(4410-i),bench:['L4N','L3E','B2S','B1W','N1'][i%5],rock:['Granite','Granite','Granodiorite','Granodiorite','Gneiss'][i%5],ex:'EX-'+['044','052','060'][i%3],ld:'LD-'+['118','122'][i%2],op:['J. Quartey','K. Nkrumah','Y. Frimpong'][i%3],st:'06:00',en:'14:00',hrs:8-(r()*1.2|0),loads:18+(r()*14|0),est:pl,act:ac,dest:i%4===0?'Waste dump':i%3===0?'ROM pad':'Crusher feed',fuel:Math.round(240+r()*160),down:Math.round(r()*90)/10,vr:Math.round((ac/pl-1)*1000)/10,sup:['E. Osei','K. Boateng','S. Adjei'][i%3]});}
 return rows;}

/* ---------------- Dispatch (spec §14) ---------------- */
const DSTAT=['Pending','Approved','Queued','Loading','Weighed','Dispatched','In transit','Delivered','Partially delivered','Returned','Cancelled'];
function dispatches(){const r=D.mulberry(7002);const rows=[];
 for(let i=0;i<22;i++){const s=DSTAT[Math.min(10,Math.floor(r()*11))];
 rows.push({no:'DSP-'+(8021-i),veh:'TRK-'+(1042+((i*149)%560)),drv:['A. Lamptey','K. Agyeman','S. Boateng','D. Mensah','P. Ansah'][i%5],cust:D.CLIENTS[i%10],prod:PCAT[i%10].name,dest:['Tema Port','P-2201 Km 44','Kumasi North','Kpone LP','Adenta Site'][i%5],eta:'2026-08-'+String(10+(i%4)).padStart(2,'0')+' '+String(7+i%10).padStart(2,'0')+':30',arr:s==='Delivered'?'2026-08-10 '+(10+i%8)+':45':'—',st:s,var:i%9===0?-'0.8 t':(i%7===0?'+0.4 t':'—'),pod:s==='Delivered'?'Signed + photo':(s==='In transit'?'—':'pending')});}
 return rows;}

/* ---------------- QC tests (spec §31) ---------------- */
function qcTests(){const r=D.mulberry(7003);const tests=['Sieve analysis','Aggregate crushing value','Aggregate impact value','Flakiness index','Elongation index','Specific gravity','Water absorption','Moisture content','Los Angeles abrasion','Dust content'];const lim=[['—','—'],['≤ 25','24 max'],['≤ 30','28 max'],['≤ 20%','18 max'],['≤ 25%',''],['2.6–2.75',''],['≤ 0.6%',''],['≤ 1.5%',''],['≤ 30','28 max'],['≤ 12%','']];
 return tests.map((t,i)=>{const v=[null,21.4,24.1,14.2,17.8,2.68,0.42,0.9,18.6,7.4][i];const pass=[true,true,true,true,true,true,true,true,true,true][i];
 return {id:'QT-'+(2201-i*2),test:t,sample:'SMP-8'+i+' · '+PCAT[i%6].name,spec:lim[i][0],result:v,unit:['','',' ','%','%','','%','%','','%'][i],pass:pass,by:'Lab Tech — G. Mensimah',date:'2026-08-'+(10-(i%4)),cert:'TC-26-0'+(41-i),stat:pass?(i%5?'Approved':'Released'):'Review'};});}

/* ---------------- Payroll register (spec §36) ---------------- */
function payrollRows(){const r=D.mulberry(7004);const names=[['EMP-2201','Kofi Annan Baah','Quarry worker','G3'],['EMP-2244','Ama Serwaa','Sales officer','G5'],['EMP-2266','Kwame Ofori','Plant operator','G4'],['EMP-2277','Esi Mensimah','Lab technician','G4'],['EMP-2288','Yaw Darko','Mechanic','G4'],['EMP-2299','Akosua Frimpong','Driver','G4'],['EMP-2311','Daniel Teye','Safety officer','G5'],['EMP-2324','Abigail Laryea','Accountant','G6'],['EMP-2337','Prince Nkrumah','Blaster','G5'],['EMP-2351','Gifty Owusu','HR officer','G5']];
 return names.map((n,i)=>{const basic=Math.round((2200+i%6*640+r()*400));const ot=Math.round(r()*480);const allow=Math.round(220+r()*560);const gross=basic+ot+allow;
 const paye=Math.round(gross*0.17);const ssEE=Math.round(gross*0.055);const ssER=Math.round(gross*0.13);const t2=Math.round(gross*0.05);const loan=i%4===0?Math.round(120+r()*260):0;
 return {id:n[0],name:n[1],cat:n[2],grade:n[3],basic:basic,ot:ot,allow:allow,gross:gross,paye:paye,ssEE:ssEE,ssER:ssER,t2:t2,loan:loan,net:gross-paye-ssEE-t2-loan,mode:i%5?'Bank — GCB':'MoMo',st:i%7?'Computed':'Review'};});}

/* ---------------- Fixed assets (spec §41) ---------------- */
const ASSETS=(()=>{const r=D.mulberry(7005);const cls=[['Land & quarry property','SH freehold parcels',18.4e6,0],['Buildings','HQ + site offices',6.2e6,0.04],['Crushers & plant','4 crushers, screens, conveyors',21.6e6,0.125],['Vehicles & trucks','64 fleet units',16.8e6,0.2],['Heavy machinery','Excavators, loaders, dozers, rigs',19.2e6,0.125],['Laboratory equipment','QA lab sets',0.9e6,0.15],['Office equipment & IT','Computers, furniture, network',1.4e6,0.25],['Generators & compressors','Gensets, air systems',2.1e6,0.175]];
 return cls.map((c,i)=>{const years=1+i%6;const dep=Math.min(c[2]*0.92,c[2]*c[3]*years);
 return {code:'FA-'+(1100+i*11),cls:c[0],desc:c[1],cost:Math.round(c[2]),rate:c[3],accdep:Math.round(dep),nbv:Math.round(c[2]-dep),loc:['Shai Hills','HQ Accra','Buoho','Tema Yard'][i%4],cond:['Good','Good','Fair','Good','Fair','Good','Good','Good'][i],ins:'Active',ver:'2026-06'};});})();

/* ---------------- Loans (spec §43) ---------------- */
const LOANS=[['LN-881','GCB term loan — crusher fleet',14.2e6,9.1e6,0.245,'2026-08-25','₵486,200/mo','Land title + debenture','Met'],
 ['LN-874','Ecobank equipment finance — Volvo EC480',3.4e6,1.2e6,0.265,'2026-08-28','₵118,400/mo','Asset financed','Met'],
 ['LN-866','Stanbic vehicle fleet facility',6.8e6,2.9e6,0.255,'2026-08-20','₵212,000/mo','Fleet + guarantors','Met'],
 ['LN-902','CalBank working-capital line',5.0e6,2.2e6,0.27,'On demand','Interest only','Floating charge','Watch'],
 ['LN-915','Hire-purchase — 8 Sinotruk tippers',4.1e6,3.0e6,0.285,'2026-08-22','₵132,600/mo','Vehicles','Met']].map(l=>({no:l[0],name:l[1],orig:l[2],bal:l[3],rate:l[4],next:l[5],inst:l[6],sec:l[7],cov:l[8]}));

/* ---------------- Tenders (spec §23) ---------------- */
const TENDERS=[['TD-118','NMT Trunk Road Rehab Lot 7','Ministry of Roads (via GHPA)','2026-08-21','Public Works Auth',6.4e6,'Bid security lodged','Pricing review','—'],
 ['TD-121','Tema Steelworks Internal Roads','Private — Tema Dev Co','2026-08-14','Direct invite',1.9e6,'Submitted 09 Aug','Awaiting result','—'],
 ['TD-109','Eastern Corridor Drainage Pkg 3','Govt — MLGRD','2026-07-28','Public tender',4.2e6,'Submitted','Won — LOI issued','Price aggressiveness worked'],
 ['TD-114','Kumasi Ring Rd Stabilisation','KMA / AshGold JV','2026-09-02','Site visit done',3.8e6,'Estimating','—','—'],
 ['TD-097','Ada Coastal Protection Armour','Volta Infra Group','2026-06-30','Public tender',7.4e6,'Submitted','Lost — 2.1% over winner','Re-check haulage+royalty basis'],
 ['TD-124','Nsawam Industrial Estate Roads','Private dev.','2026-08-30','Prequalified','—','Documents preparing','—','—']].map(t=>({no:t[0],name:t[1],cl:t[2],close:t[3],src:t[4],est:t[5],stat:t[6],res:t[7],ll:t[8]}));

/* ---------------- Hire contracts (spec §30) ---------------- */
const HIRES=[['HRC-312','Adenta Dev Co','GR-077 Grader + operator','Daily ₵2,800 · operator incl.','2026-07-27 → open','128 h MTD','₵9,400 invoiced','Active'],
 ['HRC-318','Goldline Civil Works','EX-044 Excavator (dry hire)','Weekly ₵14,500 · fuel customer','2026-08-03 → 30 Aug','—','₵14,500 paid wk1','Active'],
 ['HRC-321','Kpone Logistics Park','RL-099 Roller + operator','Hourly ₵380 · standby 40%','2026-08-05 → 12 Sep','96 h','On account','Active'],
 ['HRC-325','GH Karim & Partners','Water bowser WT-310','Daily ₵1,900 · fuel Nowills','2026-08-08 → open','—','Not yet invoiced','Active'],
 ['HRC-298','AshGold Contractors','LD-118 Loader + operator','Monthly ₵58,000 · OT ₵460/h','2026-05-04 → 04 Sep','212 h/mo avg','2 invoices pending','Active']].map(h=>({no:h[0],cust:h[1],what:h[2],rate:h[3],term:h[4],hrs:h[5],bill:h[6],st:h[7]}));

/* ---------------- Rehab parcels (spec §33) ---------------- */
const REHAB=[['RH-A1','SH North overburden dump',4.2,'Backfilled + topsoiled','Revegetation 60%','2026-11',420],['RH-A2','SH L1 worked-out bench',2.8,'Slope stabilised (27°)','Hydro-seeding done','2026-09',260],['RH-B1','BU historic borrow pit',1.9,'Backfilling 45%','—','2027-03',310],['RH-N1','NS topsoil stockpile area',1.1,'Topsoil replaced','Tree nursery planted 800','2026-10',120]].map(p=>({id:p[0],area:p[1],ha:p[2],stage:p[3],veg:p[4],due:p[5],prov:p[6]}));

/* ---------------- Gate log (spec §34) ---------------- */
function gateLog(){const r=D.mulberry(7006);const dir=['IN','OUT','OUT','IN','OUT'];const rows=[];
 for(let i=0;i<16;i++){const t=dir[i%5];const tv=r();
 rows.push({time:'0'+(6+((i*37)%16))+':'+String((i*23)%60).padStart(2,'0'),gate:['A','B'][i%2],dir:t,veh:'TRK-'+(1042+((i*149)%560)),who:['A. Lamptey','K. Agyeman','Contractor — Kanu','Visitor — RE Adenta','Supplier — GOIL'][i%5],what:PCAT[i%8].name,tick:'WB-902'+(10+i),anpr:'Matched',auth:t==='OUT'?('Gate pass GP-88'+(40+i)):'Delivery note',st:i===11?'Held — blacklist check':(i%2?'Cleared':'Cleared')});}
 return rows;}

/* ---------------- Communication log (spec §46) ---------------- */
function commsLog(){const r=D.mulberry(7007);const ev=['Order confirmation','Delivery notification','Invoice notification','Payment receipt','Credit-limit warning','Maintenance alert','Licence expiry','Safety incident','Stock reorder','Approval reminder'];
 const ch=['SMS','Email','WhatsApp','Push'];const rows=[];
 for(let i=0;i<18;i++){rows.push({time:'09:'+String(40-(i%40)).padStart(2,'0'),ev:ev[i%10],ch:ch[i%4],to:['GH Karim & Partners','E. Osei','TRK-1042 driver','J. Nowills','Procurement team','Goldline Civil'][i%6],stat:i%11===0?'Retry':(i%13===0?'Failed':'Delivered'),link:['SO-4412','DSP-8018','INV-99201','RCP-2218','—','WO-7871','EPA BU-2219','INC-0412','SKU SP-JW341','PO-26-1312'][i%10]});}
 return rows;}

/* ---------------- Sync nodes (spec §48) ---------------- */
const SYNC_NODES=[['Shai Hills HQ cluster','acc-erp-01','08:58 · healthy',0,0,0,'Master'],['WB-1 cabin node','wb1-sh','08:57',214,0,0,'Primary'],['WB-2 cabin node','wb2-sh','08:56',96,0,0,'Primary'],['Buoho site server','bu-01','08:41',57,0,1,'Review conflict'],['Nsawam tablet fleet ×6','ns-mob','08:44',31,0,0,'Primary'],['Tema Interchange node','tm-01','yesterday 21:12',0,12,0,'Offline 12h — auto-retry'],['Site engineers — mobile app ×24','mob-fleet','08:52',112,0,0,'Primary']].map(n=>({loc:n[0],id:n[1],last:n[2],up:n[3],queued:n[4],conf:n[5],st:n[6]}));

/* ---------------- Employment roles extended (spec §35 categories) — covered in HR module charts */

/* ---------------- Workflows & config for new modules ---------------- */
const S=(n,k,r,h)=>[n,k,r,h];
const WFNEW={
 qsite:[{n:'Permit & licence renewal',cat:'Compliance',stages:[S('Auto-flag 90/60/30 days','system','DMS',0),S('Prepare renewal pack','task','Quarry Administrator',240),S('Quarry Manager review','approval','Quarry Manager',24),S('Submission to regulator','task','Compliance Officer',48),S('Permit re-filed to DMS','system','DMS',0)]}],
 geology:[{n:'Reserve re-estimation',cat:'Governance',stages:[S('Drone + ground survey data','system','Survey ingest',72),S('Geologist interpretation & model','task','Geologist',72),S('Surveyor volume verification','task','Surveyor',24),S('QP sign-off (JORC-style)','approval','Qualified Person',48),S('Reserve register updated','system','ERP',0)]}],
 extract:[{n:'Extraction order fulfilment',cat:'Operations',stages:[S('Order raised against blast plan','task','Production Planner',1),S('Bench released & safe','system','Permit link',0),S('Load & haul executed','task','Operators',8),S('Supervisor shift sign-off','approval','Shift Supervisor',2),S('Post to crushing feed ledger','system','ERP',0)]}],
 products:[{n:'New product activation',cat:'Catalogue',stages:[S('Product master draft','task','Product Owner',4),S('QA spec & lab approval','approval','Laboratory Manager',12),S('Price list assignment','approval','Sales Manager',4),S('Activated for sale','system','ERP',0)]}],
 pricing:[{n:'Price change approval',cat:'Commercial',stages:[S('Change requested (effective-dated)','task','Sales Officer',2),S('Margin floor auto-check','system','Pricing engine',0),S('Sales Manager approval','approval','Sales Manager',4),S('CFO approval if below floor','approval','CFO',4),S('Published to price book','system','ERP',0)]}],
 dispatch:[{n:'Dispatch authorization',cat:'Logistics',stages:[S('Schedule built from orders','system','Dispatch engine',0),S('Vehicle & driver assignment','task','Dispatch Officer',1),S('Credit & stock validation','system','ERP',0),S('Gate release + ticket link','system','Gate',0)]}],
 comms:[{n:'Notification rule change',cat:'Governance',stages:[S('Template/rule edited','task','Template Owner',2),S('Test-fire to sandbox recipients','system','Comms hub',0),S('Admin approval','approval','System Administrator',4),S('Rule live','system','Comms hub',0)]}],
 hire:[{n:'Equipment-hire fulfilment',cat:'Commercial',stages:[S('Hire request + rate check','task','Hire Desk',2),S('Equipment availability confirm','system','Fleet board',0),S('Equipment Manager approval','approval','Equipment Manager',4),S('Mobilization + pre-inspection photos','task','Workshop',8),S('Billing schedule activated','system','AR',0)]}],
 boq:[{n:'BOQ revision / re-measure',cat:'Commercial',stages:[S('Import or edit items','task','Quantity Surveyor',8),S('Resource re-costing','system','Rates library',0),S('PM acceptance','approval','Project Manager',8),S('Version published to EVM','system','BOQ ledger',0)]}],
 plan:[{n:'Re-baseline schedule',cat:'Planning',stages:[S('Delay capture & reasons','task','Site Engineer',8),S('Critical-path recalculation','system','Scheduling engine',0),S('PM approval','approval','Project Manager',8),S('EOT claim attached if client-caused','decision','Contracts Manager',24),S('Baseline v+1 published','system','Planning service',0)]}],
 costing:[{n:'EAC re-forecast',cat:'Cost control',stages:[S('Cost-to-complete refresh','task','Project Controls',8),S('PM challenge & adjust','approval','Project Manager',8),S('Forecast posted to portfolio','system','EPM',0)]}],
 tenders:[{n:'Bid / no-bid & submission',cat:'Commercial',stages:[S('Opportunity logged & documents','task','Bid Coordinator',8),S('Site visit + query log','task','Estimating Team',48),S('Cost estimate + pricing review','task','Chief Estimator',72),S('Bid/no-bid committee','decision','Bid Committee',24),S('MD sign-off & submission','approval','Managing Director',12),S('Result + lessons learned','task','Bid Coordinator',48)]}],
 mrp:[{n:'Requirement to fulfilment',cat:'Planning',stages:[S('Net-requirement calculation','system','MRP engine',0),S('Planner firms plan (PR/transfer/issue)','task','Materials Planner',8),S('Procurement or quarry action','task','Procurement / Quarry',24),S('Delivery schedule locked','system','MRP engine',0)]}],
 payroll:[{n:'Payroll statutory return feed',cat:'Payroll',stages:[S('Payroll posted','system','Payroll',0),S('PAYE/SSNIT/Tier-2 returns generated','system','Tax engine',0),S('Accountant review','approval','Chief Accountant',4),S('Submit to GRA/SSNIT','system','Connectors',0)]}],
 rehab:[{n:'Closure provision review',cat:'Compliance',stages:[S('Annual model refresh','task','Environmental Manager',80),S('Third-party estimate update','task','Consultant',120),S('CFO provision sign-off','approval','CFO',24),S('Board disclosure note','system','FP&A',0)]}],
 qc:[{n:'Sample test & certification',cat:'Quality',stages:[S('Sample logged with chain-of-custody','task','Sampler',1),S('Lab test per spec','task','Laboratory Technician',24),S('Lab Manager review','approval','Laboratory Manager',8),S('Certificate issued / batch released','system','QA register',0)]}],
 gate:[{n:'Blacklist & incident review',cat:'Security',stages:[S('Incident/blacklist flag raised','system','ANPR/Gate',0),S('Security Manager review','task','Security Manager',4),S('Lift or sustain decision','decision','Security Manager',8),S('Gate enforcement updated','system','Access control',0)]}],
 fixedassets:[{n:'Asset lifecycle event',cat:'Assets',stages:[S('Event (acquire/transfer/dispose) raised','task','Asset Controller',4),S('Valuation & depreciation check','system','FA engine',0),S('FC approval per DoA','approval','Financial Controller',4),S('Register + GL posted','system','ERP',0)]}],
 loans:[{n:'Covenant monitoring',cat:'Treasury',stages:[S('Monthly covenant computation','system','Treasury',0),S('Treasurer review','task','Treasurer',8),S('CFO attestation to bank','approval','CFO',24),S('Certificate issued','system','Treasury',0)]}],
 sync:[{n:'Conflict resolution',cat:'System',stages:[S('Conflict detected at merge','system','Sync service',0),S('Supervisor resolution','task','Node Supervisor',8),S('Winner applied with preservation of loser copy','system','Sync service',0),S('Audit trail completed','system','Audit',0)]}]
};
Object.keys(WFNEW).forEach(m=>{ WFNEW[m].forEach(p=>{ p.stages=p.stages.filter(s2=>Array.isArray(s2)); }); });
Object.assign(D2.WF,WFNEW);

/* generic config for new modules */
const GN=(nn,fields)=>{return {nn:nn,sections:[['Module settings',fields]]};};
const F=(k,l,t,v,h)=>D2.CFG_T(k,l,t,v,h);
const CFGNEW={
 qsite:GN('NQ',[F('gpsLock','GPS-locked field capture (±20 m)','toggle',1),F('permitTrack','Permit auto-tracking + alerts','toggle',1)]),
 geology:GN('GEO',[F('reserveAlert','Reserve depletion alert (years)','number',2),F('surveyVar','Survey variance tolerance (%)','pct',5),F('model','Geological model standard','select','JORC-aligned|Internal')]),
 extract:GN('EXO',[F('loadTol','Estimated-vs-actual load tolerance (%)','pct',8),F('shiftClose','Shift close mandatory before posting','toggle',1)]),
 products:GN('PD',[F('priceBasis','Price stored','select','Tax-exclusive|Tax-inclusive'),F('marginFloor','Global margin floor (%)','pct',12),F('duplicate','Duplicate SKU check on create','toggle',1)]),
 pricing:GN('PX',[F('floor','Minimum selling price enforcement','toggle',1),F('effDating','Prices effective-dated (never overwrite)','toggle',1),F('hist','Price history immutable','toggle',1),F('unauthAlert','Alert on unauthorized price change','toggle',1)]),
 dispatch:GN('DSP',[F('podSize','POD photo required','toggle',1),F('etaMargin','ETA alert margin (min)','number',30),F('partials','Allow partial deliveries','toggle',1)]),
 comms:GN('MSG',[F('gateway','SMS/WhatsApp provider','text','mNotify · hub.mnotify.com'),F('dlr','Delivery receipts tracked','toggle',1),F('quiet','Quiet hours 21:00–06:00 (non-critical)','toggle',1)]),
 hire:GN('HRC',[F('mobFee','Default mobilization charge','currency',1800),F('standby','Standby rate (% of base)','pct',40),F('insp','Pre/post hire photo inspection','toggle',1)]),
 boq:GN('BOQ',[F('import','BOQ import format','select','Excel (standard template)|CSV|CESMM4 XML'),F('lock','Certified items locked','toggle',1)]),
 plan:GN('SCH',[F('cal','Project calendar','select','6-day Mon–Sat|7-day'),F('buffer','Schedule buffer (%)','pct',10),F('eotLink','Client-caused delays auto-draft EOT','toggle',1)]),
 costing:GN('ECO',[F('eac','EAC formula','select','CPI × SPI weighted|Budget at completion'),F('commit','Committed costs from open POs','toggle',1)]),
 tenders:GN('TD',[F('secVal','Bid security validity (days)','number',120),F('board','Approval board for > ₵5M bids','toggle',1)]),
 mrp:GN('MRP',[F('waste','Default waste allowance (%)','pct',4),F('lead','Supplier lead-time buffer (days)','number',3),F('firmWindow','Firming window (days)','number',14)]),
 payroll:GN('PAY',[F('cutoff','Attendance cutoff (day)','number',24),F('momo','MoMo payroll permitted','toggle',1),F('payslip','Self-service payslips','toggle',1),F('configNote','Statutory rules effective-dated & configurable','toggle',1)]),
 rehab:GN('RHB',[F('provReview','Provision review','select','Annual|Semi-annual'),F('treeTarget','Trees per hectare','number',300)]),
 qc:GN('QC',[F('retest','Retest on fail — duplicate sampling','toggle',1),F('chain','Chain-of-custody mandatory','toggle',1),F('calLab','Instrument calibration alert (days)','number',30)]),
 gate:GN('GATE',[F('anpr','ANPR deny-on-mismatch','toggle',1),F('blacklist','Blacklisted vehicles auto-hold','toggle',1),F('photo','Entry/exit photo capture','toggle',1)]),
 fixedassets:GN('FA',[F('capLimit','Capitalization threshold','currency',5000),F('verCycle','Physical verification cycle','select','Annual|Semi-annual'),F('tagging','QR asset tagging','toggle',1)]),
 loans:GN('LN',[F('covAlert','Covenant breach alert','toggle',1),F('remindDays','Installment reminder (days before)','number',5)]),
 sync:GN('SYN',[F('retry','Failed sync retry (min)','number',5),F('conflict','Conflict rule','select','Supervisor review|Server wins'),F('encrypt','Local DB encryption (AES-256)','toggle',1),F('deviceReg','Device registration required','toggle',1)])
};
Object.keys(CFGNEW).forEach(m=>{D2.CFG[m]=CFGNEW[m];});
/* legacy 'quarry' (production dashboard) content re-homed into Extraction */
if(D2.WF.quarry){ D2.WF.extract=(D2.WF.extract||[]).concat(D2.WF.quarry); delete D2.WF.quarry; }
if(D2.CFG.quarry){ D2.CFG.extract.sections=D2.CFG.extract.sections.concat(D2.CFG.quarry.sections); delete D2.CFG.quarry; }

window.DATA3={QUARRY_MASTERS,RESERVES,PCAT,PRX,DSTAT,extOrders,dispatches,qcTests,payrollRows,ASSETS,LOANS,TENDERS,HIRES,REHAB,gateLog,commsLog,SYNC_NODES};
})();
