Autosave: 20260413-132151

This commit is contained in:
Flatlogic Bot 2026-04-13 13:21:52 +00:00
parent c9732c5db8
commit da86c68992
16 changed files with 70 additions and 850 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@ -240,9 +240,19 @@ module.exports = class Payroll_runsDBApi {
output.payroll_line_items_payroll_run = await payroll_runs.getPayroll_line_items_payroll_run({
transaction
});
output.payroll_line_items_payroll_run = await Promise.all(
(await payroll_runs.getPayroll_line_items_payroll_run({
transaction
})).map(async (payrollLineItem) => {
const payrollLineItemOutput = payrollLineItem.get({ plain: true });
payrollLineItemOutput.employee = await payrollLineItem.getEmployee({
transaction,
});
return payrollLineItemOutput;
}),
);

View File

@ -5,6 +5,11 @@ const db = require('../db/models');
const { wrapAsync } = require('../helpers');
const { Op } = require('sequelize');
const getInclusiveDateRange = (startDate, endDate) => ({
start: new Date(`${startDate}T00:00:00.000Z`),
end: new Date(`${endDate}T23:59:59.999Z`),
});
router.post('/preview', passport.authenticate('jwt', { session: false }), wrapAsync(async (req, res) => {
const { startDate, endDate } = req.body;
@ -12,11 +17,13 @@ router.post('/preview', passport.authenticate('jwt', { session: false }), wrapAs
return res.status(400).send('startDate and endDate are required');
}
const { start, end } = getInclusiveDateRange(startDate, endDate);
// Find job logs in range that are not paid
const jobLogs = await db.job_logs.findAll({
where: {
work_date: {
[Op.between]: [new Date(startDate), new Date(endDate)]
[Op.between]: [start, end]
},
status: {
[Op.ne]: 'paid'
@ -85,11 +92,13 @@ router.post('/generate', passport.authenticate('jwt', { session: false }), wrapA
return res.status(400).send('startDate and endDate are required');
}
const { start, end } = getInclusiveDateRange(startDate, endDate);
// Find job logs
const jobLogs = await db.job_logs.findAll({
where: {
work_date: {
[Op.between]: [new Date(startDate), new Date(endDate)]
[Op.between]: [start, end]
},
status: {
[Op.ne]: 'paid'

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -218,52 +218,7 @@ export const loadColumns = async (
},
{
field: 'odometer_start',
headerName: 'OdometerStart',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'odometer_end',
headerName: 'OdometerEnd',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'job_address',
headerName: 'JobAddress',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'status',

View File

@ -84,28 +84,12 @@ const menuAside: MenuAsideItem[] = [
icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'UPDATE_USERS'
},
{
href: '/chemical_products/chemical_products-list',
label: 'Chemical products',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFlask' in icon ? icon['mdiFlask' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'UPDATE_USERS'
},
{
href: '/job_logs/job_logs-list',
label: 'All Job Logs',
icon: 'mdiClipboardTextClock' in icon ? icon['mdiClipboardTextClock' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'UPDATE_USERS'
},
{
href: '/job_chemical_usages/job_chemical_usages-list',
label: 'Job chemical usages',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFlaskOutline' in icon ? icon['mdiFlaskOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'UPDATE_USERS'
},
{
href: '/payroll_runs/payroll_runs-list',
label: 'Payroll runs',

View File

@ -32,9 +32,7 @@ const Dashboard = () => {
const [vehicles, setVehicles] = React.useState(loadingMessage);
const [pay_types, setPay_types] = React.useState(loadingMessage);
const [employee_pay_types, setEmployee_pay_types] = React.useState(loadingMessage);
const [chemical_products, setChemical_products] = React.useState(loadingMessage);
const [job_logs, setJob_logs] = React.useState(loadingMessage);
const [job_chemical_usages, setJob_chemical_usages] = React.useState(loadingMessage);
const [payroll_runs, setPayroll_runs] = React.useState(loadingMessage);
const [payroll_line_items, setPayroll_line_items] = React.useState(loadingMessage);
@ -58,8 +56,8 @@ const Dashboard = () => {
return;
}
const entities = ['users','roles','permissions','customers','vehicles','pay_types','employee_pay_types','chemical_products','job_logs','job_chemical_usages','payroll_runs','payroll_line_items',];
const fns = [setUsers,setRoles,setPermissions,setCustomers,setVehicles,setPay_types,setEmployee_pay_types,setChemical_products,setJob_logs,setJob_chemical_usages,setPayroll_runs,setPayroll_line_items,];
const entities = ['users','roles','permissions','customers','vehicles','pay_types','employee_pay_types','job_logs','payroll_runs','payroll_line_items',];
const fns = [setUsers,setRoles,setPermissions,setCustomers,setVehicles,setPay_types,setEmployee_pay_types,setJob_logs,setPayroll_runs,setPayroll_line_items,];
const requests = entities.map((entity, index) => {
@ -355,34 +353,6 @@ const Dashboard = () => {
</div>
</Link>}
{hasPermission(currentUser, 'READ_CHEMICAL_PRODUCTS') && <Link href={'/chemical_products/chemical_products-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Chemical products
</div>
<div className="text-3xl leading-tight font-semibold">
{chemical_products}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFlask' in icon ? icon['mdiFlask' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_JOB_LOGS') && <Link href={'/job_logs/job_logs-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
@ -411,34 +381,6 @@ const Dashboard = () => {
</div>
</Link>}
{hasPermission(currentUser, 'READ_JOB_CHEMICAL_USAGES') && <Link href={'/job_chemical_usages/job_chemical_usages-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Job chemical usages
</div>
<div className="text-3xl leading-tight font-semibold">
{job_chemical_usages}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFlaskOutline' in icon ? icon['mdiFlaskOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PAYROLL_RUNS') && <Link href={'/payroll_runs/payroll_runs-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}

View File

@ -270,7 +270,6 @@ const EditJob_logs = () => {
odometer_start: '',
@ -298,7 +297,6 @@ const EditJob_logs = () => {
odometer_end: '',
@ -320,7 +318,6 @@ const EditJob_logs = () => {
'job_address': '',
@ -875,146 +872,6 @@ const EditJob_logs = () => {
></Field>
</FormField>
<FormField
label="OdometerStart"
>
<Field
type="number"
name="odometer_start"
placeholder="OdometerStart"
/>
</FormField>
<FormField
label="OdometerEnd"
>
<Field
type="number"
name="odometer_end"
placeholder="OdometerEnd"
/>
</FormField>
<FormField
label="JobAddress"
>
<Field
name="job_address"
placeholder="JobAddress"
/>
</FormField>
<FormField label="Status" labelFor="status">
<Field name="status" id="status" component="select">

View File

@ -271,7 +271,6 @@ const EditJob_logsPage = () => {
odometer_start: '',
@ -299,7 +298,6 @@ const EditJob_logsPage = () => {
odometer_end: '',
@ -321,7 +319,6 @@ const EditJob_logsPage = () => {
'job_address': '',
@ -867,146 +864,6 @@ const EditJob_logsPage = () => {
></Field>
</FormField>
<FormField
label="OdometerStart"
>
<Field
type="number"
name="odometer_start"
placeholder="OdometerStart"
/>
</FormField>
<FormField
label="OdometerEnd"
>
<Field
type="number"
name="odometer_end"
placeholder="OdometerEnd"
/>
</FormField>
<FormField
label="JobAddress"
>
<Field
name="job_address"
placeholder="JobAddress"
/>
</FormField>
<FormField label="Status" labelFor="status">
<Field name="status" id="status" component="select">

View File

@ -34,8 +34,7 @@ const Job_logsTablesPage = () => {
const dispatch = useAppDispatch();
const [filters] = useState([{label: 'JobAddress', title: 'job_address'},{label: 'NotesToAdmin', title: 'notes_to_admin'},
{label: 'OdometerStart', title: 'odometer_start', number: 'true'},{label: 'OdometerEnd', title: 'odometer_end', number: 'true'},
const [filters] = useState([{label: 'NotesToAdmin', title: 'notes_to_admin'},
{label: 'HoursConducted', title: 'hours_conducted', number: 'true'},{label: 'ClientPaid', title: 'client_paid', number: 'true'},
{label: 'WorkDate', title: 'work_date', date: 'true'},

View File

@ -163,7 +163,6 @@ const initialValues = {
odometer_start: '',
@ -179,7 +178,6 @@ const initialValues = {
odometer_end: '',
@ -192,7 +190,6 @@ const initialValues = {
job_address: '',
@ -527,136 +524,6 @@ const Job_logsNew = () => {
<FormField label="Vehicle" labelFor="vehicle">
<Field name="vehicle" id="vehicle" component={SelectField} options={[]} itemRef={'vehicles'}></Field>
</FormField>
<FormField
label="OdometerStart"
>
<Field
type="number"
name="odometer_start"
placeholder="OdometerStart"
/>
</FormField>
<FormField
label="OdometerEnd"
>
<Field
type="number"
name="odometer_end"
placeholder="OdometerEnd"
/>
</FormField>
<FormField
label="JobAddress"
>
<Field
name="job_address"
placeholder="JobAddress"
/>
</FormField>
<FormField label="Status" labelFor="status">
<Field name="status" id="status" component="select">

View File

@ -34,8 +34,7 @@ const Job_logsTablesPage = () => {
const dispatch = useAppDispatch();
const [filters] = useState([{label: 'JobAddress', title: 'job_address'},{label: 'NotesToAdmin', title: 'notes_to_admin'},
{label: 'OdometerStart', title: 'odometer_start', number: 'true'},{label: 'OdometerEnd', title: 'odometer_end', number: 'true'},
const [filters] = useState([{label: 'NotesToAdmin', title: 'notes_to_admin'},
{label: 'HoursConducted', title: 'hours_conducted', number: 'true'},{label: 'ClientPaid', title: 'client_paid', number: 'true'},
{label: 'WorkDate', title: 'work_date', date: 'true'},

View File

@ -421,127 +421,6 @@ const Job_logsView = () => {
</div>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>OdometerStart</p>
<p>{job_logs?.odometer_start || 'No data'}</p>
</div>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>OdometerEnd</p>
<p>{job_logs?.odometer_end || 'No data'}</p>
</div>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>JobAddress</p>
<p>{job_logs?.job_address}</p>
</div>
<div className={'mb-4'}>
<p className={'block font-bold mb-2'}>Status</p>
<p>{job_logs?.status ?? 'No data'}</p>
@ -600,65 +479,6 @@ const Job_logsView = () => {
<>
<p className={'block font-bold mb-2'}>Job_chemical_usages JobLog</p>
<CardBox
className='mb-6 border border-gray-300 rounded overflow-hidden'
hasTable
>
<div className='overflow-x-auto'>
<table>
<thead>
<tr>
<th>QuantityUsed</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{job_logs.job_chemical_usages_job_log && Array.isArray(job_logs.job_chemical_usages_job_log) &&
job_logs.job_chemical_usages_job_log.map((item: any) => (
<tr key={item.id} onClick={() => router.push(`/job_chemical_usages/job_chemical_usages-view/?id=${item.id}`)}>
<td data-label="quantity_used">
{ item.quantity_used }
</td>
<td data-label="notes">
{ item.notes }
</td>
</tr>
))}
</tbody>
</table>
</div>
{!job_logs?.job_chemical_usages_job_log?.length && <div className={'text-center py-4'}>No data</div>}
</CardBox>
</>
<BaseDivider />
<BaseButton

View File

@ -1,4 +1,4 @@
import { mdiPencil, mdiPlus, mdiTrashCan } from '@mdi/js';
import { mdiPencil } from '@mdi/js';
import Head from 'next/head';
import React, { ReactElement, useEffect, useState } from 'react';
import CardBox from '../components/CardBox';
@ -8,7 +8,7 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton
import JobLogPayPreview from '../components/JobLogPayPreview';
import { getPageTitle } from '../config';
import { Field, Form, Formik, FieldArray } from 'formik';
import { Field, Form, Formik } from 'formik';
import FormField from '../components/FormField';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
@ -27,11 +27,13 @@ const LogWorkPage = () => {
useEffect(() => {
if (currentUser?.id) {
axios.get(`/employee_pay_types?employee=${currentUser.id}&active=true`)
axios
.get(`/employee_pay_types?employee=${currentUser.id}&active=true`)
.then((res) => {
if (res.data && res.data.rows) {
// Ensure we are extracting the inner pay_type relation from employee_pay_types
const payTypes = res.data.rows.map((row: any) => row.pay_type).filter(Boolean);
const payTypes = res.data.rows
.map((row: any) => row.pay_type)
.filter(Boolean);
setAssignedPayTypes(payTypes);
}
})
@ -40,7 +42,7 @@ const LogWorkPage = () => {
}, [currentUser]);
const initialValues = {
work_date: new Date().toISOString().slice(0, 16),
work_date: new Date().toISOString().slice(0, 10),
employee: currentUser?.id || '',
customer: '',
hours_conducted: '',
@ -48,12 +50,8 @@ const LogWorkPage = () => {
workersCompClass: '',
pay_type: '',
vehicle: '',
odometer_start: '',
odometer_end: '',
job_address: '',
status: 'submitted',
notes_to_admin: '',
chemical_usages: [],
};
const handleSubmit = async (data: any) => {
@ -72,10 +70,10 @@ const LogWorkPage = () => {
</SectionTitleLineWithButton>
<CardBox>
<Formik initialValues={initialValues} onSubmit={(values) => handleSubmit(values)}>
{({ values }) => (
{() => (
<Form>
<FormField label="Work Date">
<Field type="datetime-local" name="work_date" />
<Field type="date" name="work_date" />
</FormField>
<FormField label="Customer" labelFor="customer">
<Field name="customer" id="customer" placeholder="Enter customer name" />
@ -87,7 +85,14 @@ const LogWorkPage = () => {
<Field type="number" name="client_paid" placeholder="Amount" />
</FormField>
<FormField label="Worker's Comp Class" labelFor="workersCompClass">
<Field name="workersCompClass" id="workersCompClass" component={SelectField} options={[]} itemRef={"workers_comp_classes"} showField={"name"} />
<Field
name="workersCompClass"
id="workersCompClass"
component={SelectField}
options={[]}
itemRef={'workers_comp_classes'}
showField={'name'}
/>
</FormField>
<FormField label="Pay Type" labelFor="pay_type">
<Field name="pay_type" id="pay_type" as="select">
@ -101,68 +106,18 @@ const LogWorkPage = () => {
</FormField>
<JobLogPayPreview payTypeOptions={assignedPayTypes} />
<FormField label="Vehicle" labelFor="vehicle">
<Field name="vehicle" id="vehicle" component={SelectField} options={[]} itemRef={'vehicles'} />
</FormField>
<FormField label="Odometer Start">
<Field type="number" name="odometer_start" placeholder="Start" />
</FormField>
<FormField label="Odometer End">
<Field type="number" name="odometer_end" placeholder="End" />
</FormField>
<FormField label="Job Address">
<Field name="job_address" placeholder="Address" />
<Field
name="vehicle"
id="vehicle"
component={SelectField}
options={[]}
itemRef={'vehicles'}
/>
</FormField>
<FormField label="Notes to Admin" hasTextareaHeight>
<Field name="notes_to_admin" as="textarea" placeholder="Notes..." />
</FormField>
<BaseDivider />
<h3 className="text-lg font-bold mb-4">Chemical Usage (Optional)</h3>
<FieldArray name="chemical_usages">
{({ push, remove }) => (
<div>
{values.chemical_usages.map((usage, index) => (
<div key={index} className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4 items-end border p-4 rounded-lg bg-gray-50 dark:bg-slate-800">
<FormField label="Chemical Product" labelFor={`chemical_usages.${index}.chemical_product`}>
<Field
name={`chemical_usages.${index}.chemical_product`}
id={`chemical_usages.${index}.chemical_product`}
component={SelectField}
itemRef="chemical_products"
showField="name"
/>
</FormField>
<FormField label="Quantity Used" labelFor={`chemical_usages.${index}.quantity_used`}>
<Field
type="number"
name={`chemical_usages.${index}.quantity_used`}
id={`chemical_usages.${index}.quantity_used`}
placeholder="e.g. 5.5"
step="0.01"
/>
</FormField>
<div className="mb-4">
<BaseButton
type="button"
color="danger"
label="Remove"
icon={mdiTrashCan}
onClick={() => remove(index)}
/>
</div>
</div>
))}
<BaseButton
type="button"
color="success"
label="Add Chemical"
icon={mdiPlus}
onClick={() => push({ chemical_product: '', quantity_used: '' })}
/>
</div>
)}
</FieldArray>
<BaseDivider />
<BaseButtons>
<BaseButton type="submit" color="info" label="Submit Work Log" />
@ -180,4 +135,4 @@ LogWorkPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default LogWorkPage;
export default LogWorkPage;

View File

@ -30,10 +30,20 @@ const Payroll_runsView = () => {
const { id } = router.query;
function removeLastCharacter(str) {
console.log(str,`str`)
return str.slice(0, -1);
}
function formatEmployeeName(employee) {
if (!employee) return 'No data';
const fullName = [employee.firstName, employee.lastName]
.filter(Boolean)
.join(' ')
.trim();
return fullName || employee.email || 'No data';
}
useEffect(() => {
dispatch(fetch({ id }));
}, [dispatch, id]);
@ -292,72 +302,28 @@ const Payroll_runsView = () => {
<table>
<thead>
<tr>
<th>Employee</th>
<th>TotalHours</th>
<th>GrossPay</th>
<th>TotalCommissionBase</th>
<th>TotalClientPaid</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{payroll_runs.payroll_line_items_payroll_run && Array.isArray(payroll_runs.payroll_line_items_payroll_run) &&
payroll_runs.payroll_line_items_payroll_run.map((item: any) => (
<tr key={item.id} onClick={() => router.push(`/payroll_line_items/payroll_line_items-view/?id=${item.id}`)}>
<td data-label="employee">
{formatEmployeeName(item.employee)}
</td>
<td data-label="total_hours">
{ item.total_hours }
</td>
<td data-label="gross_pay">
{ item.gross_pay }
</td>
<td data-label="total_commission_base">
{ item.total_commission_base }
</td>
<td data-label="total_client_paid">
{ item.total_client_paid }
</td>
<td data-label="summary">
{ item.summary }
</td>
</tr>
))}
</tbody>