Loading Pierre Closets...
Team Time Tracker

Who are you?

Tap your name to sign in

Pierre Closets
Sign out

Live status

Live locations


Location history


Edit & delete audit log

All time entries that were manually edited or deleted by employees.

Custom pay period
📊 Session data usage
0KB
fetched this session
0
DB requests
0KB
avg per request
Pro plan: 250GB/month · 250,000MB total
📈 Monthly data usage
Open Settings to load usage data
🏗️ Job names
`); w.document.close(); } async function exportPayrollDirect(from,to){ if(!from||!to){toast('Select a date range first');return;} try{ var fTs=parseLocalDate(from).getTime(); var tTs=parseLocalDate(to+'T23:59:59').getTime(); var fromLabel=new Date(from).toLocaleDateString([],{month:'long',day:'numeric',year:'numeric'}); var toLabel=new Date(to).toLocaleDateString([],{month:'long',day:'numeric',year:'numeric'}); var all=await SBget('time_log','order=ts.desc&ts=gte.'+fTs+'&ts=lte.'+tTs+'&duration_ms=not.is.null&limit=2000'); var otw=ps.otWeek||40,byE2={}; emps.forEach(function(e){byE2[e.name]={hrs:0,shopHrs:0,fieldHrs:0,pr:e.pay_rate||0,role:e.is_admin?'Admin':(e.role||'Shop'),actualRole:e.role||'Shop',man:0,dayEntries:[],annualLeave:0,sickLeave:0,holOffHrs:0,holOffPay:0,holBtnWorkedHrs:0,holBtnWorkedPay:0};}); // Use job entries for periods after cutoff, day entries before var allJob2=(all||[]).filter(function(l){return isJobEntry(l);}); var hasJobByEmp2={};allJob2.forEach(function(l){hasJobByEmp2[l.employee_name]=true;}); var csvEntries=allJob2.concat((all||[]).filter(function(l){return isDayEntry(l)&&!hasJobByEmp2[l.employee_name];})); csvEntries.forEach(function(l){ if(!byE2[l.employee_name]){var ed=getE(l.employee_name);byE2[l.employee_name]={hrs:0,pr:ed.pay_rate||0,role:ed.role||'',man:0,dayEntries:[]};} byE2[l.employee_name].hrs+=l.duration_ms/3600000; byE2[l.employee_name].dayEntries.push({ts:l.ts,duration_ms:l.duration_ms}); if(l.is_manual)byE2[l.employee_name].man++; if(l.type==='job'){if(l.task==='Installation'||l.task==='Delivery')byE2[l.employee_name].fieldHrs+=(l.duration_ms||0)/3600000;else byE2[l.employee_name].shopHrs+=(l.duration_ms||0)/3600000;} }); var totalReg=0,totalOT=0,totalPay=0; var empRows=[]; Object.keys(byE2).filter(function(n){return byE2[n].hrs>0;}).sort().forEach(function(n){ var d=byE2[n]; var wkCalc=calcWeeklyOT(d.dayEntries||[],otw); var reg=wkCalc.reg,ot=wkCalc.ot,rp=reg*d.pr,op=ot*d.pr*1.5; totalReg+=reg;totalOT+=ot;totalPay+=rp+op; var lhrs=(d.annualLeave||0)+(d.sickLeave||0); var lp=lhrs*d.pr; var holHrs=(d.holOffHrs||0)+(d.holBtnWorkedHrs||0); var holPay=(d.holOffPay||0)+(d.holBtnWorkedPay||0); empRows.push([n,d.actualRole||d.role,reg.toFixed(2),ot.toFixed(2),(d.hrs).toFixed(2),d.shopHrs.toFixed(2),d.fieldHrs.toFixed(2),'$'+d.pr.toFixed(2),'$'+rp.toFixed(2),'$'+op.toFixed(2),lhrs.toFixed(2),'$'+lp.toFixed(2),holHrs.toFixed(2),'$'+holPay.toFixed(2),'$'+(rp+op+lp+holPay).toFixed(2),d.man>0?d.man+' manual':'']); }); var rows=[ ['PIERRE CLOSETS — PAYROLL SUMMARY','','','','','','','','',''], ['Pay period:',fromLabel+' — '+toLabel,'','','','','','','',''], ['Generated:',new Date().toLocaleDateString([],{month:'long',day:'numeric',year:'numeric',hour:'2-digit',minute:'2-digit'}),'','','','','','','',''], ['','','','','','','','','',''], ['Employee','Role','Reg Hrs','OT Hrs','Worked Hrs','Shop Hrs','Install Hrs','Pay Rate','Reg Pay','OT Pay','Leave Hrs','Leave Pay','Holiday Hrs','Holiday Pay','Total Pay','Notes'] ]; empRows.forEach(function(r){rows.push(r);}); rows.push(['','','','','','','','','','']); rows.push(['TOTALS','',totalReg.toFixed(2),totalOT.toFixed(2),(totalReg+totalOT).toFixed(2),'','','','$'+totalPay.toFixed(2),'']); dlCSV(rows,'payroll-'+from+'-to-'+to+'.csv'); setTimeout(function(){ window.open('https://sheets.new','_blank'); toast('CSV saved — import it into the new Sheet'); },600); }catch(err){sy('e');toast('Export error: '+err.message);console.error(err);} }