+
{control}
-
+
>
);
}
diff --git a/app/client/src/features/licences/LicenceTrailers.jsx b/app/client/src/features/licences/LicenceTrailers.jsx
new file mode 100644
index 00000000..3f7af85a
--- /dev/null
+++ b/app/client/src/features/licences/LicenceTrailers.jsx
@@ -0,0 +1,253 @@
+/* eslint-disable */
+import React, { useEffect, useState } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import PropTypes from "prop-types";
+import { Link } from "react-router-dom";
+import {
+ Alert,
+ Container,
+ Spinner,
+ Table,
+ Row,
+ Col,
+ Button,
+ ButtonGroup,
+} from "react-bootstrap";
+import { startOfToday } from "date-fns";
+
+import SectionHeading from "../../components/SectionHeading";
+
+import { selectCreatedTrailer, createTrailer } from "../trailers/trailersSlice";
+import {
+ clearTrailerParameters,
+ setTrailerParameters,
+ fetchTrailerResults,
+ selectTrailerResults,
+ setTrailerSearchPage,
+ setTrailerFilterText,
+ clearTrailerFilterText,
+} from "../search/searchSlice";
+
+import { selectLicenceStatuses } from "../lookups/licenceStatusesSlice";
+
+import {
+ REQUEST_STATUS,
+ LICENCE_STATUS_TYPES,
+ COUNTRIES,
+ PROVINCES,
+ SYSTEM_ROLES,
+ TRAILERS_PATHNAME,
+} from "../../utilities/constants";
+
+import ErrorMessageRow from "../../components/ErrorMessageRow";
+
+import { selectCurrentUser } from "../../app/appSlice";
+import GenerateDairyTrailerInspection from "./GenerateDairyTrailerInspection";
+
+function formatResultRow(result) {
+ const url = `${TRAILERS_PATHNAME}/${result.dairyFarmTrailerId}`;
+ return (
+
+
+
+ {`${result.licenceNumber}-${result.licenceTrailerSeq}`}
+
+ |
+ {result.licenceStatus} |
+ {result.registrantLastFirst} |
+ {result.geographicalDivision} |
+
+ );
+}
+
+function navigateToSearchPage(dispatch, page) {
+ dispatch(setTrailerSearchPage(page));
+ dispatch(fetchTrailerResults());
+}
+
+export default function LicenceTrailers({ licence }) {
+ const dispatch = useDispatch();
+ const licenceStatuses = useSelector(selectLicenceStatuses);
+ const results = useSelector(selectTrailerResults);
+ const createdTrailer = useSelector(selectCreatedTrailer);
+ const currentUser = useSelector(selectCurrentUser);
+
+ const [debouncedActionTimeout, setDebouncedActionTimeout] = useState(null);
+
+ useEffect(() => {
+ dispatch(clearTrailerParameters());
+ dispatch(clearTrailerFilterText());
+ dispatch(
+ setTrailerParameters({ licenceNumber: licence.data.licenceNumber })
+ );
+ dispatch(fetchTrailerResults());
+ }, [dispatch]);
+
+ const handleFilterTextChange = (e) => {
+ const newFilterText = e.target.value;
+
+ if (debouncedActionTimeout) {
+ clearTimeout(debouncedActionTimeout);
+ }
+
+ const newTimeout = setTimeout(() => {
+ dispatch(setTrailerSearchPage(1));
+ dispatch(setTrailerFilterText(newFilterText));
+ dispatch(fetchTrailerResults());
+ }, 700);
+
+ setDebouncedActionTimeout(newTimeout);
+ };
+
+ function addTrailerOnClick() {
+ const payload = {
+ licenceId: licence.data.id,
+ licenceTypeId: licence.data.licenceTypeId,
+ licenceStatus: licenceStatuses.data.find(
+ (x) => x.code_description === LICENCE_STATUS_TYPES.ACTIVE
+ ).id,
+ country: COUNTRIES.CANADA,
+ province: PROVINCES.BC,
+ region: null,
+ regionalDistrict: null,
+ registrationDate: startOfToday(),
+ };
+ dispatch(createTrailer(payload));
+ }
+
+ const addTrailerButton = (
+
+ );
+
+ let control = null;
+ if (results.status === REQUEST_STATUS.PENDING) {
+ control = (
+
+
+ Searching...
+
+
+ );
+ } else if (results.status === REQUEST_STATUS.REJECTED) {
+ control = (
+
+
+ An error was encountered while retrieving results.
+
+
+ {results.error.code}: {results.error.description}
+
+
+ );
+ } else if (createdTrailer.status === REQUEST_STATUS.REJECTED) {
+ control = (
+
+
+ An error was encountered while creating a trailer.
+
+
+ {createdTrailer.error.code}: {createdTrailer.error.description}
+
+
+ );
+ } else if (
+ results.status === REQUEST_STATUS.FULFILLED &&
+ results.count === 0
+ ) {
+ control = (
+ <>
+
+ {currentUser.data.roleId !== SYSTEM_ROLES.READ_ONLY &&
+ currentUser.data.roleId !== SYSTEM_ROLES.INSPECTOR ? (
+
+ {addTrailerButton}
+
+ ) : null}
+ >
+ );
+ } else if (results.status === REQUEST_STATUS.FULFILLED && results.count > 0) {
+ control = (
+ <>
+
+
+
+ Trailer ID |
+ Trailer Status |
+ Name |
+ Division |
+
+
+ {results.data.map((result) => formatResultRow(result))}
+
+
+ {currentUser.data.roleId !== SYSTEM_ROLES.READ_ONLY &&
+ currentUser.data.roleId !== SYSTEM_ROLES.INSPECTOR ? (
+ {addTrailerButton}
+ ) : null}
+
+ Showing {results.data.length} of {results.count} entries
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
Trailers
+
+
+
+
+ {control}
+
+
Inspections Report
+
+
+
+ >
+ );
+}
+
+LicenceTrailers.propTypes = {
+ licence: PropTypes.object.isRequired,
+};
diff --git a/app/client/src/features/licences/ViewLicencePage.jsx b/app/client/src/features/licences/ViewLicencePage.jsx
index bc33b694..eeea0d30 100644
--- a/app/client/src/features/licences/ViewLicencePage.jsx
+++ b/app/client/src/features/licences/ViewLicencePage.jsx
@@ -3,7 +3,11 @@ import { useSelector, useDispatch } from "react-redux";
import { useParams, Redirect } from "react-router-dom";
import { Spinner, Alert } from "react-bootstrap";
-import { REQUEST_STATUS, SITES_PATHNAME } from "../../utilities/constants";
+import {
+ REQUEST_STATUS,
+ SITES_PATHNAME,
+ TRAILERS_PATHNAME,
+} from "../../utilities/constants";
import PageHeading from "../../components/PageHeading";
import RegistrantsViewEdit from "../registrants/RegistrantsViewEdit";
@@ -16,6 +20,7 @@ import { selectCreatedSite, clearCurrentSite } from "../sites/sitesSlice";
import LicenceDetailsViewEdit from "./LicenceDetailsViewEdit";
import LicenceHeader from "./LicenceHeader";
import LicenceSites from "./LicenceSites";
+import LicenceTrailers from "./LicenceTrailers";
import LicenceInventory from "./LicenceInventory";
import LicenceInventoryHistory from "./LicenceInventoryHistory";
import LicenceDairyTestInventory from "./LicenceDairyTestInventory";
@@ -30,11 +35,16 @@ import {
LICENCE_TYPE_ID_VETERINARY_DRUG,
LICENCE_TYPE_ID_MEDICATED_FEED,
LICENCE_TYPE_ID_DAIRY_FARM,
+ LICENCE_TYPE_ID_DAIRY_TANK_TRUCK,
} from "./constants";
import Comments from "../comments/Comments";
import "./ViewLicencePage.scss";
+import {
+ clearCurrentTrailer,
+ selectCreatedTrailer,
+} from "../trailers/trailersSlice";
export default function ViewLicencePage() {
const dispatch = useDispatch();
@@ -42,16 +52,21 @@ export default function ViewLicencePage() {
const licence = useSelector(selectCurrentLicence);
const createdSite = useSelector(selectCreatedSite);
+ const createdTrailer = useSelector(selectCreatedTrailer);
useEffect(() => {
dispatch(clearCurrentLicence());
dispatch(clearCurrentSite());
+ dispatch(clearCurrentTrailer());
dispatch(fetchLicence(id));
}, [dispatch, id]);
if (createdSite.status === REQUEST_STATUS.FULFILLED) {
return
;
}
+ if (createdTrailer.status === REQUEST_STATUS.FULFILLED) {
+ return
;
+ }
const associatedLicenceTypes = [
LICENCE_TYPE_ID_LIVESTOCK_DEALER,
@@ -65,7 +80,7 @@ export default function ViewLicencePage() {
const showAssociatedLicence =
licence.data &&
associatedLicenceTypes.find((x) => x === licence.data.licenceTypeId) !==
- undefined;
+ undefined;
let content;
if (licence.data) {
@@ -74,12 +89,17 @@ export default function ViewLicencePage() {
-
+
+ {licence.data.licenceTypeId === LICENCE_TYPE_ID_DAIRY_TANK_TRUCK ? (
+
+ ) : (
+
+ )}
{licence.data.licenceTypeId === LICENCE_TYPE_ID_DAIRY_FARM ? (
) : null}
{licence.data.licenceTypeId === LICENCE_TYPE_ID_GAME_FARM ||
- licence.data.licenceTypeId === LICENCE_TYPE_ID_FUR_FARM ? (
+ licence.data.licenceTypeId === LICENCE_TYPE_ID_FUR_FARM ? (
<>
diff --git a/app/client/src/features/reports/ReportDairyTrailerInspection.jsx b/app/client/src/features/reports/ReportDairyTrailerInspection.jsx
new file mode 100644
index 00000000..ce05a85d
--- /dev/null
+++ b/app/client/src/features/reports/ReportDairyTrailerInspection.jsx
@@ -0,0 +1,83 @@
+import React, { useEffect } from "react";
+import { useForm } from "react-hook-form";
+import { useSelector, useDispatch } from "react-redux";
+import { Row, Col, Form, Button } from "react-bootstrap";
+
+import DocGenDownloadBar from "../../components/DocGenDownloadBar";
+
+import {
+ startDairyTrailerInspectionJob,
+ generateReport,
+ fetchReportJob,
+ selectReportsJob,
+ completeReportJob,
+} from "./reportsSlice";
+
+import { REPORTS } from "../../utilities/constants";
+
+export default function ReportDairyTrailerInspection() {
+ const dispatch = useDispatch();
+
+ const job = useSelector(selectReportsJob);
+ const { pendingDocuments } = job;
+
+ const form = useForm({
+ reValidateMode: "onBlur",
+ });
+ const { register, watch } = form;
+
+ const watchLicenceNumber = watch("licenceNumber", null);
+
+ useEffect(() => {
+ if (job.id && job.type === REPORTS.DAIRY_TRAILER_INSPECTION) {
+ dispatch(fetchReportJob());
+
+ if (pendingDocuments?.length > 0) {
+ dispatch(generateReport(pendingDocuments[0].documentId));
+ } else {
+ dispatch(completeReportJob(job.id));
+ }
+ }
+ }, [pendingDocuments]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ const onGenerateReport = () => {
+ dispatch(
+ startDairyTrailerInspectionJob({
+ licenceNumber: watchLicenceNumber,
+ })
+ );
+ };
+
+ return (
+ <>
+
+
+ Licence Number
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+ReportDairyTrailerInspection.propTypes = {};
diff --git a/app/client/src/features/reports/Reports.jsx b/app/client/src/features/reports/Reports.jsx
index e5532bf8..e810b1bd 100644
--- a/app/client/src/features/reports/Reports.jsx
+++ b/app/client/src/features/reports/Reports.jsx
@@ -22,6 +22,7 @@ import ReportLicenceExpiry from "./ReportLicenceExpiry";
import { clearReportsJob } from "./reportsSlice";
import RenderOnRole from "../../components/RenderOnRole";
+import ReportDairyTrailerInspection from "./ReportDairyTrailerInspection";
export default function Reports() {
const dispatch = useDispatch();
@@ -75,6 +76,8 @@ export default function Reports() {
case REPORTS.LICENCE_EXPIRY:
control =
;
break;
+ case REPORTS.DAIRY_TRAILER_INSPECTION:
+ control =
;
default:
break;
}
@@ -177,6 +180,19 @@ export default function Reports() {
+
+
+
+
{
+ try {
+ if (payload && payload.licenceNumber) {
+ const licenceDetails = await Api.get(
+ `licences/number/${payload.licenceNumber}`
+ );
+ if (
+ licenceDetails &&
+ licenceDetails.data &&
+ licenceDetails.data.licenceType &&
+ licenceDetails.data.licenceType === "DAIRY TANK TRUCK"
+ ) {
+ const response = await Api.post(
+ `documents/reports/startJob/dairyTrailerInspection`,
+ payload
+ );
+ return response.data;
+ } else {
+ throw new Error(
+ "Invalid licence type for dairy trailer inspection report."
+ );
+ }
+ }
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
export const startProducersAnalysisRegionJob = createAsyncThunk(
"reports/startProducersAnalysisRegionJob",
async (_, thunkApi) => {
@@ -312,6 +346,9 @@ export const reportsSlice = createSlice({
[startApiaryHiveInspectionJob.pending]: pendingStartJobReducer,
[startApiaryHiveInspectionJob.fulfilled]: fulfilledStartJobReducer,
[startApiaryHiveInspectionJob.rejected]: rejectionStartJobReducer,
+ [startDairyTrailerInspectionJob.pending]: pendingStartJobReducer,
+ [startDairyTrailerInspectionJob.fulfilled]: fulfilledStartJobReducer,
+ [startDairyTrailerInspectionJob.rejected]: rejectionStartJobReducer,
[startProducersAnalysisRegionJob.pending]: pendingStartJobReducer,
[startProducersAnalysisRegionJob.fulfilled]: fulfilledStartJobReducer,
[startProducersAnalysisRegionJob.rejected]: rejectionStartJobReducer,
diff --git a/app/client/src/features/search/searchSlice.js b/app/client/src/features/search/searchSlice.js
index c2f4f59e..589136a4 100644
--- a/app/client/src/features/search/searchSlice.js
+++ b/app/client/src/features/search/searchSlice.js
@@ -15,6 +15,13 @@ export const selectSiteParameters = (state) => state.search.sites.parameters;
export const selectSiteResults = (state) => state.search.sites.results;
export const selectSiteFilterText = (state) => state.search.sites.filter;
+export const selectTrailerSearchType = (state) =>
+ state.search.trailers.searchType;
+export const selectTrailerParameters = (state) =>
+ state.search.trailers.parameters;
+export const selectTrailerResults = (state) => state.search.trailers.results;
+export const selectTrailerFilterText = (state) => state.search.trailers.filter;
+
export const selectInventoryHistorySearchType = (state) =>
state.search.inventoryHistory.searchType;
export const selectInventoryHistoryParameters = (state) =>
@@ -107,6 +114,27 @@ export const fetchSiteResults = createAsyncThunk(
}
);
+export const fetchTrailerResults = createAsyncThunk(
+ "search/fetchTrailerResults",
+ async (_, thunkApi) => {
+ try {
+ const parameters = selectTrailerParameters(thunkApi.getState());
+
+ const parsedParameters = {
+ ...parameters,
+ };
+
+ const response = await Api.get(`trailers/search`, parsedParameters);
+ return response.data;
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
export const fetchInventoryHistoryResults = createAsyncThunk(
"search/fetchInventoryHistoryResults",
async (_, thunkApi) => {
@@ -203,6 +231,17 @@ export const searchSlice = createSlice({
status: REQUEST_STATUS.IDLE,
},
},
+ trailers: {
+ searchType: SEARCH_TYPE.SIMPLE,
+ parameters: {},
+ results: {
+ data: undefined,
+ page: undefined,
+ count: undefined,
+ error: undefined,
+ status: REQUEST_STATUS.IDLE,
+ },
+ },
inventoryHistory: {
searchType: SEARCH_TYPE.SIMPLE,
parameters: {},
@@ -294,6 +333,37 @@ export const searchSlice = createSlice({
state.sites.parameters.filterText = undefined;
},
+ // Trailers
+ clearTrailerParameters: (state) => {
+ state.trailers.parameters = {};
+ state.trailers.searchType = SEARCH_TYPE.SIMPLE;
+ },
+ clearTrailerResults: (state) => {
+ state.trailers.results.data = undefined;
+ state.trailers.results.error = undefined;
+ state.trailers.results.status = REQUEST_STATUS.IDLE;
+ },
+ toggleTrailerSearchType: (state) => {
+ const currentSearchType = state.trailers.searchType;
+ state.trailers.searchType =
+ currentSearchType === SEARCH_TYPE.SIMPLE
+ ? SEARCH_TYPE.ADVANCED
+ : SEARCH_TYPE.SIMPLE;
+ },
+ setTrailerParameters: (state, action) => {
+ state.trailers.parameters = action.payload;
+ state.trailers.results.status = REQUEST_STATUS.IDLE;
+ },
+ setTrailerSearchPage: (state, action) => {
+ state.trailers.parameters.page = action.payload;
+ },
+ setTrailerFilterText: (state, action) => {
+ state.trailers.parameters.filterText = action.payload;
+ },
+ clearTrailerFilterText: (state) => {
+ state.trailers.parameters.filterText = undefined;
+ },
+
// Inventory History
clearInventoryHistoryParameters: (state) => {
state.inventoryHistory.parameters = {};
@@ -417,6 +487,24 @@ export const searchSlice = createSlice({
state.sites.results.status = REQUEST_STATUS.REJECTED;
},
+ // Trailers
+ [fetchTrailerResults.pending]: (state) => {
+ state.trailers.results.error = undefined;
+ state.trailers.results.status = REQUEST_STATUS.PENDING;
+ },
+ [fetchTrailerResults.fulfilled]: (state, action) => {
+ state.trailers.results.data = action.payload.results;
+ state.trailers.results.page = action.payload.page;
+ state.trailers.results.count = action.payload.count;
+ state.trailers.results.error = undefined;
+ state.trailers.results.status = REQUEST_STATUS.FULFILLED;
+ },
+ [fetchTrailerResults.rejected]: (state, action) => {
+ state.trailers.results.data = undefined;
+ state.trailers.results.error = action.payload;
+ state.trailers.results.status = REQUEST_STATUS.REJECTED;
+ },
+
// Inventory History
[fetchInventoryHistoryResults.pending]: (state) => {
state.inventoryHistory.results.error = undefined;
@@ -490,6 +578,14 @@ export const {
setSiteFilterText,
clearSiteFilterText,
+ clearTrailerParameters,
+ clearTrailerResults,
+ toggleTrailerSearchType,
+ setTrailerParameters,
+ setTrailerSearchPage,
+ setTrailerFilterText,
+ clearTrailerFilterText,
+
clearInventoryHistoryParameters,
clearInventoryHistoryResults,
toggleInventoryHistorySearchType,
diff --git a/app/client/src/features/sites/ViewSitePage.jsx b/app/client/src/features/sites/ViewSitePage.jsx
index f09d585f..1e80be8f 100644
--- a/app/client/src/features/sites/ViewSitePage.jsx
+++ b/app/client/src/features/sites/ViewSitePage.jsx
@@ -22,8 +22,17 @@ import RenderOnRole from "../../components/RenderOnRole";
import PageHeading from "../../components/PageHeading";
import SectionHeading from "../../components/SectionHeading";
-import { fetchSite, selectCurrentSite, clearCreatedSite, clearCurrentSite } from "./sitesSlice";
-import { fetchLicence, selectCurrentLicence, clearCurrentLicence } from "../licences/licencesSlice";
+import {
+ fetchSite,
+ selectCurrentSite,
+ clearCreatedSite,
+ clearCurrentSite,
+} from "./sitesSlice";
+import {
+ fetchLicence,
+ selectCurrentLicence,
+ clearCurrentLicence,
+} from "../licences/licencesSlice";
import SiteHeader from "./SiteHeader";
import LicenceDetailsView from "../licences/LicenceDetailsView";
@@ -33,6 +42,7 @@ import Comments from "../comments/Comments";
import "./ViewSitePage.scss";
import DairyTanksViewEdit from "./dairytanks/DairyTanksViewEdit";
+
import {
LICENCE_TYPE_ID_DAIRY_FARM,
LICENCE_TYPE_ID_APIARY,
diff --git a/app/client/src/features/trailerinspections/CreateTrailerInspectionPage.jsx b/app/client/src/features/trailerinspections/CreateTrailerInspectionPage.jsx
new file mode 100644
index 00000000..26880aee
--- /dev/null
+++ b/app/client/src/features/trailerinspections/CreateTrailerInspectionPage.jsx
@@ -0,0 +1,167 @@
+import React, { useEffect } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { Redirect, useHistory, useParams } from "react-router-dom";
+import { useForm } from "react-hook-form";
+import { Container, Form } from "react-bootstrap";
+import { startOfToday } from "date-fns";
+
+import { REQUEST_STATUS, TRAILERS_PATHNAME } from "../../utilities/constants";
+
+import ErrorMessageRow from "../../components/ErrorMessageRow";
+import PageHeading from "../../components/PageHeading";
+import SectionHeading from "../../components/SectionHeading";
+import SubmissionButtons from "../../components/SubmissionButtons";
+
+import {
+ selectCreatedInspection,
+ createTrailerInspection,
+ clearCreatedInspection,
+} from "./trailerInspectionsSlice";
+import { fetchTrailer, selectCurrentTrailer } from "../trailers/trailersSlice";
+import { fetchLicence, selectCurrentLicence } from "../licences/licencesSlice";
+import * as LicenceTypeConstants from "../licences/constants";
+
+import TrailerHeader from "../trailers/TrailerHeader";
+import TrailerDetailsView from "../trailers/TrailerDetailsView";
+import LicenceDetailsView from "../licences/LicenceDetailsView";
+import TrailerInspectionDetailsEdit from "./TrailerInspectionDetailsEdit";
+
+function submissionController(
+ licence,
+ trailer,
+ setError,
+ clearErrors,
+ dispatch
+) {
+ console.log("CreateTrailerInspectionPage");
+ const onSubmit = async (data) => {
+ switch (licence.data.licenceTypeId) {
+ case LicenceTypeConstants.LICENCE_TYPE_ID_DAIRY_TANK_TRUCK: {
+ const payload = {
+ ...data,
+ trailerId: trailer.data.id,
+ trailerNumber: trailer.data.trailerNumber,
+ inspectorId: data.inspectorId.length === 0 ? null : data.inspectorId,
+ inspectionComment:
+ data.inspectionComment.length === 0 ? null : data.inspectionComment,
+ };
+ dispatch(createTrailerInspection(payload));
+ break;
+ }
+ default:
+ break;
+ }
+ };
+
+ return { onSubmit };
+}
+
+const today = startOfToday();
+const initialFormValues = {
+ inspectionDate: today,
+ inspectorId: null,
+ inspectionComment: null,
+};
+
+export default function CreateTrailerInspectionPage() {
+ const history = useHistory();
+ const dispatch = useDispatch();
+
+ const { id } = useParams();
+
+ const trailer = useSelector(selectCurrentTrailer);
+ const licence = useSelector(selectCurrentLicence);
+ const inspection = useSelector(selectCreatedInspection);
+
+ const form = useForm({
+ reValidateMode: "onBlur",
+ });
+ const { handleSubmit, setValue, setError, clearErrors } = form;
+
+ useEffect(() => {
+ setValue("inspectionDate", today);
+
+ dispatch(clearCreatedInspection());
+
+ dispatch(fetchTrailer(id)).then((s) => {
+ dispatch(fetchLicence(s.payload.licenceId));
+ });
+ }, [dispatch]);
+
+ const onCancel = () => {
+ history.push(`${TRAILERS_PATHNAME}/${id}`);
+ };
+
+ const { onSubmit } = submissionController(
+ licence,
+ trailer,
+ setError,
+ clearErrors,
+ dispatch
+ );
+
+ const submitting = inspection.status === REQUEST_STATUS.PENDING;
+
+ let errorMessage = null;
+ if (inspection.status === REQUEST_STATUS.REJECTED) {
+ errorMessage = `${inspection.error.code}: ${inspection.error.description}`;
+ }
+
+ const submissionLabel = submitting ? "Submitting..." : "Create";
+
+ console.log(inspection);
+ if (inspection.status === REQUEST_STATUS.FULFILLED) {
+ return ;
+ }
+
+ let content;
+ if (trailer.data && licence.data) {
+ content = (
+ <>
+
+
+ License Details
+
+
+
+
+
+ Trailer Details
+
+
+
+
+ >
+ );
+ }
+
+ return (
+
+ Create Inspection
+ {content}
+ {trailer.data ? (
+
+ Inspection Details
+
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/app/client/src/features/trailerinspections/TrailerInspectionDetailsEdit.jsx b/app/client/src/features/trailerinspections/TrailerInspectionDetailsEdit.jsx
new file mode 100644
index 00000000..ecc67125
--- /dev/null
+++ b/app/client/src/features/trailerinspections/TrailerInspectionDetailsEdit.jsx
@@ -0,0 +1,84 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { Form, Row, Col } from "react-bootstrap";
+
+import CustomDatePicker from "../../components/CustomDatePicker";
+import SectionHeading from "../../components/SectionHeading";
+
+export default function TrailerInspectionDetailsEdit({
+ form,
+ initialValues,
+ trailer,
+}) {
+ console.log("TrailerInspectionDetailsEdit");
+ const {
+ setValue,
+ register,
+ formState: { errors },
+ } = form;
+
+ const handleFieldChange = (field) => {
+ return (value) => {
+ setValue(field, value);
+ };
+ };
+
+ return (
+ <>
+
+
+
+ Dairy Trailer ID
+
+
+
+
+
+
+
+
+ Inspector ID
+
+
+
+
+ Comments
+
+
+
+
+
+ >
+ );
+}
+
+TrailerInspectionDetailsEdit.propTypes = {
+ form: PropTypes.object.isRequired,
+ initialValues: PropTypes.object.isRequired,
+ trailer: PropTypes.object.isRequired,
+};
diff --git a/app/client/src/features/trailerinspections/TrailerInspectionDetailsView.jsx b/app/client/src/features/trailerinspections/TrailerInspectionDetailsView.jsx
new file mode 100644
index 00000000..80825e80
--- /dev/null
+++ b/app/client/src/features/trailerinspections/TrailerInspectionDetailsView.jsx
@@ -0,0 +1,49 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { Form, Row, Col } from "react-bootstrap";
+
+import SectionHeading from "../../components/SectionHeading";
+import VerticalField from "../../components/VerticalField";
+
+export default function TrailerInspectionDetailsView({ inspection, trailer }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ Comments
+
+
+
+
+
+ >
+ );
+}
+
+TrailerInspectionDetailsView.propTypes = {
+ inspection: PropTypes.object.isRequired,
+ trailer: PropTypes.object.isRequired,
+};
diff --git a/app/client/src/features/trailerinspections/TrailerInspectionDetailsViewEdit.jsx b/app/client/src/features/trailerinspections/TrailerInspectionDetailsViewEdit.jsx
new file mode 100644
index 00000000..8d525d98
--- /dev/null
+++ b/app/client/src/features/trailerinspections/TrailerInspectionDetailsViewEdit.jsx
@@ -0,0 +1,161 @@
+import React, { useEffect } from "react";
+import PropTypes from "prop-types";
+import { useDispatch, useSelector } from "react-redux";
+import { useForm } from "react-hook-form";
+import { Container, Form } from "react-bootstrap";
+import { startOfToday } from "date-fns";
+
+import {
+ LICENCE_MODE,
+ REQUEST_STATUS,
+ SYSTEM_ROLES,
+} from "../../utilities/constants.js";
+import { formatNumber } from "../../utilities/formatting.ts";
+import { parseAsDate } from "../../utilities/parsing.js";
+
+import ErrorMessageRow from "../../components/ErrorMessageRow.jsx";
+import SectionHeading from "../../components/SectionHeading.jsx";
+import SubmissionButtons from "../../components/SubmissionButtons.jsx";
+
+import {
+ updateTrailerInspection,
+ setCurrentInspectionModeToEdit,
+ setCurrentInspectionModeToView,
+} from "./trailerInspectionsSlice.js";
+
+import TrailerInspectionDetailsEdit from "./TrailerInspectionDetailsEdit.jsx";
+import TrailerInspectionDetailsView from "./TrailerInspectionDetailsView.jsx";
+
+import * as LicenceTypeConstants from "../licences/constants.js";
+
+import { selectCurrentUser } from "../../app/appSlice.js";
+
+export default function TrailerInspectionDetailsViewEdit({
+ inspection,
+ trailer,
+ licence,
+}) {
+ const { status, error, mode } = inspection;
+
+ const dispatch = useDispatch();
+
+ const currentUser = useSelector(selectCurrentUser);
+
+ const today = startOfToday();
+
+ const form = useForm({
+ reValidateMode: "onBlur",
+ });
+ const { handleSubmit, setValue } = form;
+
+ const initialFormValues = {
+ inspectionDate: inspection.data
+ ? parseAsDate(inspection.data.inspectionDate)
+ : today,
+ inspectorId: null,
+ inspectionComment: null,
+ };
+
+ useEffect(() => {}, [dispatch]);
+
+ useEffect(() => {
+ setValue("inspectionDate", new Date(inspection.data.inspectionDate));
+ setValue("inspectorId", formatNumber(inspection.data.inspectorId));
+ setValue("inspectionComment", inspection.data.inspectionComment);
+ }, [
+ setValue,
+ inspection.data.inspectionDate,
+ inspection.data.inspectorId,
+ inspection.data.inspectionComment,
+ mode,
+ ]);
+
+ if (mode === LICENCE_MODE.VIEW) {
+ const onEdit = () => {
+ dispatch(setCurrentInspectionModeToEdit());
+ };
+ return (
+
+
+ Inspection Details
+
+
+
+
+
+ );
+ }
+
+ const submitting = status === REQUEST_STATUS.PENDING;
+
+ let errorMessage = null;
+ if (status === REQUEST_STATUS.REJECTED) {
+ errorMessage = `${error.code}: ${error.description}`;
+ }
+
+ const submissionLabel = submitting ? "Saving..." : "Save";
+
+ const onSubmit = async (data) => {
+ switch (licence.data.licenceTypeId) {
+ case LicenceTypeConstants.LICENCE_TYPE_ID_DAIRY_TANK_TRUCK: {
+ const payload = {
+ ...data,
+ trailerId: trailer.data.id,
+ inspectorId: data.inspectorId.length === 0 ? null : data.inspectorId,
+ inspectionComment:
+ data.inspectionComment?.length === 0
+ ? null
+ : data.inspectionComment,
+ };
+
+ dispatch(
+ updateTrailerInspection({
+ inspection: payload,
+ id: inspection.data.id,
+ })
+ );
+ break;
+ }
+ default:
+ break;
+ }
+ };
+
+ const onCancel = () => {
+ dispatch(setCurrentInspectionModeToView());
+ };
+
+ return (
+
+ );
+}
+
+TrailerInspectionDetailsViewEdit.propTypes = {
+ inspection: PropTypes.object.isRequired,
+ trailer: PropTypes.object.isRequired,
+ licence: PropTypes.object.isRequired,
+};
diff --git a/app/client/src/features/trailerinspections/ViewTrailerInspectionPage.jsx b/app/client/src/features/trailerinspections/ViewTrailerInspectionPage.jsx
new file mode 100644
index 00000000..963b8a9e
--- /dev/null
+++ b/app/client/src/features/trailerinspections/ViewTrailerInspectionPage.jsx
@@ -0,0 +1,109 @@
+import React, { useEffect } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { useParams } from "react-router-dom";
+import { Spinner, Alert, Container } from "react-bootstrap";
+
+import { REQUEST_STATUS } from "../../utilities/constants";
+
+import PageHeading from "../../components/PageHeading";
+import SectionHeading from "../../components/SectionHeading";
+
+import {
+ fetchTrailerInspection,
+ selectCurrentInspection,
+ clearCreatedInspection,
+} from "./trailerInspectionsSlice";
+import { fetchTrailer, selectCurrentTrailer } from "../trailers/trailersSlice";
+import { fetchLicence, selectCurrentLicence } from "../licences/licencesSlice";
+
+import TrailerHeader from "../trailers/TrailerHeader";
+import LicenceDetailsView from "../licences/LicenceDetailsView";
+import TrailerDetailsView from "../trailers/TrailerDetailsView";
+import TrailerInspectionDetailsViewEdit from "./TrailerInspectionDetailsViewEdit";
+
+export default function ViewTrailerInspectionPage() {
+ const dispatch = useDispatch();
+ const { id } = useParams();
+ const inspection = useSelector(selectCurrentInspection);
+ const trailer = useSelector(selectCurrentTrailer);
+ const licence = useSelector(selectCurrentLicence);
+
+ useEffect(() => {
+ dispatch(clearCreatedInspection());
+
+ dispatch(fetchTrailerInspection(id)).then((inspectionRecord) =>
+ dispatch(fetchTrailer(inspectionRecord.payload.trailerId)).then(
+ (trailerRecord) =>
+ dispatch(fetchLicence(trailerRecord.payload.licenceId))
+ )
+ );
+ }, [dispatch, id]);
+
+ let content;
+ if (inspection.data && trailer.data && licence.data) {
+ content = (
+ <>
+
+
+ License Details
+
+
+
+
+
+ Trailer Details
+
+
+
+
+
+ >
+ );
+ } else if (
+ inspection.status === REQUEST_STATUS.IDLE ||
+ inspection.status === REQUEST_STATUS.PENDING ||
+ trailer.status === REQUEST_STATUS.IDLE ||
+ trailer.status === REQUEST_STATUS.PENDING ||
+ licence.status === REQUEST_STATUS.IDLE ||
+ licence.status === REQUEST_STATUS.PENDING
+ ) {
+ content = (
+
+ Loading...
+
+ );
+ } else {
+ content = (
+
+
+ An error was encountered while loading the inspection.
+
+ {inspection.error && (
+ {`${inspection.error.code}: ${inspection.error.description}`}
+ )}
+ {trailer.error && (
+ {`${trailer.error.code}: ${trailer.error.description}`}
+ )}
+ {licence.error && (
+ {`${licence.error.code}: ${licence.error.description}`}
+ )}
+
+ );
+ }
+
+ return (
+
+ View Inspection
+ {content}
+
+ );
+}
diff --git a/app/client/src/features/trailerinspections/trailerInspectionsSlice.js b/app/client/src/features/trailerinspections/trailerInspectionsSlice.js
new file mode 100644
index 00000000..03b48a27
--- /dev/null
+++ b/app/client/src/features/trailerinspections/trailerInspectionsSlice.js
@@ -0,0 +1,145 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+
+import Api, { ApiError } from "../../utilities/api.ts";
+import { REQUEST_STATUS, LICENCE_MODE } from "../../utilities/constants.js";
+
+export const fetchTrailerInspection = createAsyncThunk(
+ "trailerinspections/fetchTrailerInspection",
+ async (id, thunkApi) => {
+ try {
+ const response = await Api.get(`inspections/trailer/${id}`);
+ return response.data;
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
+export const createTrailerInspection = createAsyncThunk(
+ "trailerinspections/createTrailerInspection",
+ async (inspection, thunkApi) => {
+ try {
+ const response = await Api.post("inspections/trailer", inspection);
+ return response.data;
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
+export const updateTrailerInspection = createAsyncThunk(
+ "trailerinspections/updateTrailerInspection",
+ async ({ inspection, id }, thunkApi) => {
+ try {
+ const response = await Api.put(`inspections/trailer/${id}`, inspection);
+ return response.data;
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
+export const trailerInspectionsSlice = createSlice({
+ name: "trailerinspections",
+ initialState: {
+ createdInspection: {
+ data: undefined,
+ error: undefined,
+ status: REQUEST_STATUS.IDLE,
+ },
+ currentInspection: {
+ data: undefined,
+ error: undefined,
+ status: REQUEST_STATUS.IDLE,
+ mode: LICENCE_MODE.VIEW,
+ },
+ },
+ reducers: {
+ clearCreatedInspection: (state) => {
+ state.createdInspection.data = undefined;
+ state.createdInspection.error = undefined;
+ state.createdInspection.status = REQUEST_STATUS.IDLE;
+ },
+ clearCurrentInspection: (state) => {
+ state.currentInspection.data = undefined;
+ state.currentInspection.error = undefined;
+ state.currentInspection.status = REQUEST_STATUS.IDLE;
+ },
+ setCurrentInspectionModeToEdit: (state) => {
+ state.currentInspection.mode = LICENCE_MODE.EDIT;
+ },
+ setCurrentInspectionModeToView: (state) => {
+ state.currentInspection.mode = LICENCE_MODE.VIEW;
+ },
+ },
+ extraReducers: {
+ [createTrailerInspection.pending]: (state) => {
+ state.createdInspection.error = undefined;
+ state.createdInspection.status = REQUEST_STATUS.PENDING;
+ },
+ [createTrailerInspection.fulfilled]: (state, action) => {
+ state.createdInspection.data = action.payload;
+ state.createdInspection.error = undefined;
+ state.createdInspection.status = REQUEST_STATUS.FULFILLED;
+ },
+ [createTrailerInspection.rejected]: (state, action) => {
+ state.createdInspection.data = undefined;
+ state.createdInspection.error = action.payload;
+ state.createdInspection.status = REQUEST_STATUS.REJECTED;
+ },
+ [fetchTrailerInspection.pending]: (state) => {
+ state.currentInspection.error = undefined;
+ state.currentInspection.status = REQUEST_STATUS.PENDING;
+ },
+ [fetchTrailerInspection.fulfilled]: (state, action) => {
+ state.currentInspection.data = action.payload;
+ state.currentInspection.error = undefined;
+ state.currentInspection.status = REQUEST_STATUS.FULFILLED;
+ state.currentInspection.mode = LICENCE_MODE.VIEW;
+ },
+ [fetchTrailerInspection.rejected]: (state, action) => {
+ state.currentInspection.data = undefined;
+ state.currentInspection.error = action.payload;
+ state.currentInspection.status = REQUEST_STATUS.REJECTED;
+ },
+ [updateTrailerInspection.pending]: (state) => {
+ state.currentInspection.error = undefined;
+ state.currentInspection.status = REQUEST_STATUS.PENDING;
+ },
+ [updateTrailerInspection.fulfilled]: (state, action) => {
+ state.currentInspection.data = action.payload;
+ state.currentInspection.error = undefined;
+ state.currentInspection.status = REQUEST_STATUS.FULFILLED;
+ state.currentInspection.mode = LICENCE_MODE.VIEW;
+ },
+ [updateTrailerInspection.rejected]: (state, action) => {
+ state.currentInspection.error = action.payload;
+ state.currentInspection.status = REQUEST_STATUS.REJECTED;
+ },
+ },
+});
+
+export const selectCreatedInspection = (state) =>
+ state.trailerinspections.createdInspection;
+export const selectCurrentInspection = (state) =>
+ state.trailerinspections.currentInspection;
+
+const { actions, reducer } = trailerInspectionsSlice;
+
+export const {
+ clearCreatedInspection,
+ clearCurrentInspection,
+ setCurrentInspectionModeToEdit,
+ setCurrentInspectionModeToView,
+} = actions;
+
+export default reducer;
diff --git a/app/client/src/features/trailers/TrailerDetailsEdit.jsx b/app/client/src/features/trailers/TrailerDetailsEdit.jsx
new file mode 100644
index 00000000..9fb388d8
--- /dev/null
+++ b/app/client/src/features/trailers/TrailerDetailsEdit.jsx
@@ -0,0 +1,157 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { Form, Row, Col } from "react-bootstrap";
+import LicenceStatuses from "../lookups/LicenceStatuses";
+import CustomDatePicker from "../../components/CustomDatePicker";
+
+export default function TrailerDetailsEdit({ form, initialValues }) {
+ const {
+ setValue,
+ register,
+ formState: { errors },
+ } = form;
+
+ const handleFieldChange = (field) => {
+ return (value) => {
+ setValue(field, value);
+ };
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ Trailer #
+
+
+
+
+
+
+
+ Division
+
+
+
+
+
+ Serial No / VIN
+
+
+
+
+
+ Licence Plate #
+
+
+
+
+
+
+
+ Year
+
+
+
+
+
+ Make
+
+
+
+
+
+ Trailer Type
+
+
+
+
+
+
+
+ Capacity (L)
+
+
+
+
+
+ Compartments
+
+
+
+
+
+ >
+ );
+}
+
+TrailerDetailsEdit.propTypes = {
+ form: PropTypes.object.isRequired,
+ initialValues: PropTypes.object.isRequired,
+ licence: PropTypes.object,
+ mode: PropTypes.string.isRequired,
+};
diff --git a/app/client/src/features/trailers/TrailerDetailsView.jsx b/app/client/src/features/trailers/TrailerDetailsView.jsx
new file mode 100644
index 00000000..0c58d561
--- /dev/null
+++ b/app/client/src/features/trailers/TrailerDetailsView.jsx
@@ -0,0 +1,74 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { Row, Col } from "react-bootstrap";
+import { PatternFormat } from "react-number-format";
+
+import VerticalField from "../../components/VerticalField";
+import SectionHeading from "../../components/SectionHeading";
+
+export default function TrailerDetailsView({ trailer }) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+TrailerDetailsView.propTypes = {
+ trailer: PropTypes.object.isRequired,
+ licenceTypeId: PropTypes.number,
+};
+
+TrailerDetailsView.defaultProps = {
+ licenceTypeId: null,
+};
diff --git a/app/client/src/features/trailers/TrailerDetailsViewEdit.jsx b/app/client/src/features/trailers/TrailerDetailsViewEdit.jsx
new file mode 100644
index 00000000..ff80721f
--- /dev/null
+++ b/app/client/src/features/trailers/TrailerDetailsViewEdit.jsx
@@ -0,0 +1,182 @@
+import React, { useEffect } from "react";
+import PropTypes from "prop-types";
+import { useDispatch, useSelector } from "react-redux";
+import { useForm } from "react-hook-form";
+import { Container, Form } from "react-bootstrap";
+
+import {
+ LICENCE_MODE,
+ REQUEST_STATUS,
+ SYSTEM_ROLES,
+} from "../../utilities/constants";
+import { formatNumber } from "../../utilities/formatting";
+import { parseAsDate, parseAsInt } from "../../utilities/parsing";
+
+import ErrorMessageRow from "../../components/ErrorMessageRow";
+import SectionHeading from "../../components/SectionHeading";
+import SubmissionButtons from "../../components/SubmissionButtons";
+
+import { fetchLicenceStatuses } from "../lookups/licenceStatusesSlice";
+import {
+ setCurrentTrailerModeToEdit,
+ setCurrentTrailerModeToView,
+} from "../trailers/trailersSlice";
+
+import TrailerDetailsEdit from "./TrailerDetailsEdit";
+import TrailerDetailsView from "./TrailerDetailsView";
+
+import { selectCurrentUser } from "../../app/appSlice";
+import { formatDateString } from "../../utilities/formatting";
+
+import { updateTrailer } from "../trailers/trailersSlice";
+
+export default function TrailerDetailsViewEdit({ trailer, licence }) {
+ const { status, error, mode } = trailer;
+
+ const dispatch = useDispatch();
+
+ const currentUser = useSelector(selectCurrentUser);
+
+ useEffect(() => {
+ dispatch(fetchLicenceStatuses());
+ }, [dispatch]);
+
+ const form = useForm({
+ reValidateMode: "onBlur",
+ });
+ const { handleSubmit, setValue, setError } = form;
+
+ const initialFormValues = {
+ licenceStatus: null,
+ dateIssued: trailer.data ? parseAsDate(trailer.data.dateIssued) : null,
+ trailerNumber: null,
+ geographicalDivision: null,
+ serialNumberVIN: null,
+ licensePlate: null,
+ trailerYear: null,
+ trailerMake: null,
+ trailerType: null,
+ trailerCapacity: null,
+ trailerCompartments: null,
+ };
+
+ useEffect(() => {
+ setValue("licenceStatus", trailer.data.licenceStatusId);
+ setValue("dateIssued", formatDateString(trailer.data.dateIssued));
+ setValue("trailerNumber", trailer.data.trailerNumber);
+ setValue("geographicalDivision", trailer.data.geographicalDivision);
+ setValue("serialNumberVIN", trailer.data.serialNumberVIN);
+ setValue("licensePlate", trailer.data.licensePlate);
+ setValue("trailerYear", formatNumber(trailer.data.trailerYear));
+ setValue("trailerMake", trailer.data.trailerMake);
+ setValue("trailerType", trailer.data.trailerType);
+ setValue("trailerCapacity", formatNumber(trailer.data.trailerCapacity));
+ setValue(
+ "trailerCompartments",
+ formatNumber(trailer.data.trailerCompartments)
+ );
+ }, [
+ setValue,
+ trailer.data.licenceStatusId,
+ trailer.data.dateIssued,
+ trailer.data.trailerNumber,
+ trailer.data.geographicalDivision,
+ trailer.data.serialNumberVIN,
+ trailer.data.licensePlate,
+ trailer.data.trailerYear,
+ trailer.data.trailerMake,
+ trailer.data.trailerType,
+ trailer.data.trailerCapacity,
+ trailer.data.trailerCompartments,
+ mode,
+ ]);
+
+ if (mode === LICENCE_MODE.VIEW) {
+ const onEdit = () => {
+ dispatch(setCurrentTrailerModeToEdit());
+ };
+ return (
+
+
+ Trailer Details
+
+
+
+
+
+ );
+ }
+
+ const submitting = status === REQUEST_STATUS.PENDING;
+
+ let errorMessage = null;
+ if (status === REQUEST_STATUS.REJECTED) {
+ errorMessage = `${error.code}: ${error.description}`;
+ }
+
+ const submissionLabel = submitting ? "Saving..." : "Save";
+
+ const onSubmit = async (data) => {
+ let errorCount = 0;
+
+ // error checks
+
+ if (errorCount > 0) {
+ return;
+ }
+
+ const payload = {
+ ...data,
+ licenceId: parseAsInt(trailer.data.licenceId),
+ licenceStatus: parseAsInt(data.licenceStatus),
+ dateIssued: data.dateIssued ? data.dateIssued : null,
+ trailerNumber: data.trailerNumber,
+ geographicalDivision: data.geographicalDivision,
+ serialNumberVIN: data.serialNumberVIN,
+ licensePlate: data.licensePlate,
+ trailerYear: parseAsInt(data.trailerYear),
+ trailerMake: data.trailerMake,
+ trailerType: data.trailerType,
+ trailerCapacity: parseAsInt(data.trailerCapacity),
+ trailerCompartments: parseAsInt(data.trailerCompartments),
+ };
+
+ dispatch(updateTrailer({ trailer: payload, id: trailer.data.id }));
+ };
+
+ const onCancel = () => {
+ dispatch(setCurrentTrailerModeToView());
+ };
+
+ return (
+
+ );
+}
+
+TrailerDetailsViewEdit.propTypes = {
+ trailer: PropTypes.object.isRequired,
+};
diff --git a/app/client/src/features/trailers/TrailerHeader.jsx b/app/client/src/features/trailers/TrailerHeader.jsx
new file mode 100644
index 00000000..047c64d3
--- /dev/null
+++ b/app/client/src/features/trailers/TrailerHeader.jsx
@@ -0,0 +1,56 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { Container, Row } from "react-bootstrap";
+import { Link } from "react-router-dom";
+import { formatDateTimeString } from "../../utilities/formatting.ts";
+
+import HorizontalField from "../../components/HorizontalField";
+
+import { LICENSES_PATHNAME } from "../../utilities/constants";
+
+export default function TrailerHeader({ trailer, licence }) {
+ const url = `${LICENSES_PATHNAME}/${licence.id}`;
+
+ return (
+
+
+
+ {licence.licenceNumber}}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+TrailerHeader.propTypes = {
+ trailer: PropTypes.object.isRequired,
+ licence: PropTypes.object.isRequired,
+};
diff --git a/app/client/src/features/trailers/ViewTrailerPage.jsx b/app/client/src/features/trailers/ViewTrailerPage.jsx
new file mode 100644
index 00000000..19ff8a27
--- /dev/null
+++ b/app/client/src/features/trailers/ViewTrailerPage.jsx
@@ -0,0 +1,205 @@
+import React, { useEffect } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { Link, useParams, useHistory } from "react-router-dom";
+import {
+ Spinner,
+ Alert,
+ Container,
+ Table,
+ Row,
+ Col,
+ Button,
+} from "react-bootstrap";
+
+import {
+ REQUEST_STATUS,
+ SYSTEM_ROLES,
+ CREATE_TRAILER_INSPECTIONS_PATHNAME,
+ TRAILER_INSPECTIONS_PATHNAME,
+} from "../../utilities/constants";
+
+import RenderOnRole from "../../components/RenderOnRole";
+import PageHeading from "../../components/PageHeading";
+import SectionHeading from "../../components/SectionHeading";
+
+import {
+ fetchTrailer,
+ selectCurrentTrailer,
+ clearCreatedTrailer,
+ clearCurrentTrailer,
+} from "./trailersSlice";
+import {
+ fetchLicence,
+ selectCurrentLicence,
+ clearCurrentLicence,
+} from "../licences/licencesSlice";
+
+import TrailerHeader from "./TrailerHeader";
+import LicenceDetailsView from "../licences/LicenceDetailsView";
+import TrailerDetailsViewEdit from "./TrailerDetailsViewEdit";
+
+import Comments from "../comments/Comments";
+
+import "./ViewTrailerPage.scss";
+
+export default function ViewTrailerPage() {
+ const history = useHistory();
+ const dispatch = useDispatch();
+ const { id } = useParams();
+ const trailer = useSelector(selectCurrentTrailer);
+ const licence = useSelector(selectCurrentLicence);
+ useEffect(() => {
+ dispatch(clearCreatedTrailer());
+ dispatch(clearCurrentLicence());
+ dispatch(clearCurrentTrailer());
+
+ dispatch(fetchTrailer(id)).then((record) => {
+ dispatch(fetchLicence(record.payload.licenceId));
+ });
+ }, [dispatch, id]);
+
+ function formatInspectionsResultRow(result) {
+ const url = `${TRAILER_INSPECTIONS_PATHNAME}/${result.id}`;
+ const comment =
+ result?.inspectionComment?.length > 50
+ ? result.inspectionComment.substring(0, 50) + "..."
+ : result.inspectionComment;
+
+ return (
+
+ {result.inspectionDate} |
+ {result.inspectorId} |
+ {comment} |
+
+ View
+ |
+
+ );
+ }
+
+ function addInspectionOnClick() {
+ history.push(`${CREATE_TRAILER_INSPECTIONS_PATHNAME}/${id}`);
+ }
+
+ const addInspectionButton = (
+
+ );
+
+ let content;
+ if (trailer.data && licence.data) {
+ content = (
+ <>
+
+
+ License Details
+
+
+
+
+
+
+
+
+ Inspections
+
+ {trailer.data.inspections?.length > 0 ? (
+ <>
+
+
+
+ Inspection Date |
+ Inspector ID |
+ Comments |
+ |
+
+
+
+ {trailer.data.inspections.map((result) =>
+ formatInspectionsResultRow(result)
+ )}
+
+
+
+
+ {addInspectionButton}
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+ There are no inspections associated with this trailer.
+
+
+
+
+
+
+ {addInspectionButton}
+
+
+ >
+ )}
+
+
+
+
+ >
+ );
+ } else if (
+ trailer.status === REQUEST_STATUS.IDLE ||
+ trailer.status === REQUEST_STATUS.PENDING ||
+ licence.status === REQUEST_STATUS.IDLE ||
+ licence.status === REQUEST_STATUS.PENDING
+ ) {
+ content = (
+
+ Loading...
+
+ );
+ } else {
+ content = (
+
+
+ An error was encountered while loading the trailer.
+
+ {trailer.error && (
+ {`${trailer.error.code}: ${trailer.error.description}`}
+ )}
+ {licence.error && (
+ {`${licence.error.code}: ${licence.error.description}`}
+ )}
+
+ );
+ }
+
+ return (
+
+ View a Trailer Record
+ {content}
+
+ );
+}
diff --git a/app/client/src/features/trailers/ViewTrailerPage.scss b/app/client/src/features/trailers/ViewTrailerPage.scss
new file mode 100644
index 00000000..77181576
--- /dev/null
+++ b/app/client/src/features/trailers/ViewTrailerPage.scss
@@ -0,0 +1,3 @@
+header {
+ font-size: 95%;
+}
diff --git a/app/client/src/features/trailers/trailersSlice.js b/app/client/src/features/trailers/trailersSlice.js
new file mode 100644
index 00000000..3542b5eb
--- /dev/null
+++ b/app/client/src/features/trailers/trailersSlice.js
@@ -0,0 +1,143 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+
+import Api, { ApiError } from "../../utilities/api.ts";
+import { REQUEST_STATUS, LICENCE_MODE } from "../../utilities/constants";
+
+export const createTrailer = createAsyncThunk(
+ "trailers/createTrailer",
+ async (trailer, thunkApi) => {
+ try {
+ const response = await Api.post("trailers", trailer);
+ return response.data;
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
+export const updateTrailer = createAsyncThunk(
+ "trailers/updateTrailer",
+ async ({ trailer, id }, thunkApi) => {
+ try {
+ const response = await Api.put(`trailers/${id}`, trailer);
+ return response.data;
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
+export const fetchTrailer = createAsyncThunk(
+ "trailers/fetchTrailer",
+ async (id, thunkApi) => {
+ try {
+ const response = await Api.get(`trailers/${id}`);
+ return response.data;
+ } catch (error) {
+ if (error instanceof ApiError) {
+ return thunkApi.rejectWithValue(error.serialize());
+ }
+ return thunkApi.rejectWithValue({ code: -1, description: error.message });
+ }
+ }
+);
+
+export const trailersSlice = createSlice({
+ name: "trailers",
+ initialState: {
+ createdTrailer: {
+ data: undefined,
+ error: undefined,
+ status: REQUEST_STATUS.IDLE,
+ },
+ currentTrailer: {
+ data: undefined,
+ error: undefined,
+ status: REQUEST_STATUS.IDLE,
+ mode: LICENCE_MODE.VIEW,
+ },
+ },
+ reducers: {
+ clearCreatedTrailer: (state) => {
+ state.createdTrailer.data = undefined;
+ state.createdTrailer.error = undefined;
+ state.createdTrailer.status = REQUEST_STATUS.IDLE;
+ },
+ clearCurrentTrailer: (state) => {
+ state.currentTrailer.data = undefined;
+ state.currentTrailer.error = undefined;
+ state.currentTrailer.status = REQUEST_STATUS.IDLE;
+ },
+ setCurrentTrailerModeToEdit: (state) => {
+ state.currentTrailer.mode = LICENCE_MODE.EDIT;
+ },
+ setCurrentTrailerModeToView: (state) => {
+ state.currentTrailer.mode = LICENCE_MODE.VIEW;
+ },
+ },
+ extraReducers: {
+ [createTrailer.pending]: (state) => {
+ state.createdTrailer.error = undefined;
+ state.createdTrailer.status = REQUEST_STATUS.PENDING;
+ },
+ [createTrailer.fulfilled]: (state, action) => {
+ state.createdTrailer.data = action.payload;
+ state.createdTrailer.error = undefined;
+ state.createdTrailer.status = REQUEST_STATUS.FULFILLED;
+ },
+ [createTrailer.rejected]: (state, action) => {
+ state.createdTrailer.data = undefined;
+ state.createdTrailer.error = action.payload;
+ state.createdTrailer.status = REQUEST_STATUS.REJECTED;
+ },
+ [fetchTrailer.pending]: (state) => {
+ state.currentTrailer.error = undefined;
+ state.currentTrailer.status = REQUEST_STATUS.PENDING;
+ },
+ [fetchTrailer.fulfilled]: (state, action) => {
+ state.currentTrailer.data = action.payload;
+ state.currentTrailer.error = undefined;
+ state.currentTrailer.status = REQUEST_STATUS.FULFILLED;
+ state.currentTrailer.mode = LICENCE_MODE.VIEW;
+ },
+ [fetchTrailer.rejected]: (state, action) => {
+ state.currentTrailer.data = undefined;
+ state.currentTrailer.error = action.payload;
+ state.currentTrailer.status = REQUEST_STATUS.REJECTED;
+ },
+ [updateTrailer.pending]: (state) => {
+ state.currentTrailer.error = undefined;
+ state.currentTrailer.status = REQUEST_STATUS.PENDING;
+ },
+ [updateTrailer.fulfilled]: (state, action) => {
+ state.currentTrailer.data = action.payload;
+ state.currentTrailer.error = undefined;
+ state.currentTrailer.status = REQUEST_STATUS.FULFILLED;
+ state.currentTrailer.mode = LICENCE_MODE.VIEW;
+ },
+ [updateTrailer.rejected]: (state, action) => {
+ state.currentTrailer.error = action.payload;
+ state.currentTrailer.status = REQUEST_STATUS.REJECTED;
+ },
+ },
+});
+
+export const selectCreatedTrailer = (state) => state.trailers.createdTrailer;
+export const selectCurrentTrailer = (state) => state.trailers.currentTrailer;
+
+const { actions, reducer } = trailersSlice;
+
+export const {
+ clearCreatedTrailer,
+ clearCurrentTrailer,
+ setCurrentTrailerModeToEdit,
+ setCurrentTrailerModeToView,
+} = actions;
+
+export default reducer;
diff --git a/app/client/src/utilities/constants.js b/app/client/src/utilities/constants.js
index 6ed6970d..087078ca 100644
--- a/app/client/src/utilities/constants.js
+++ b/app/client/src/utilities/constants.js
@@ -15,6 +15,11 @@ export const CREATE_CONTACTS_PATHNAME = "/contacts/create";
export const INSPECTIONS_PATHNAME = "/inspections";
export const SEARCH_INSPECTIONS_PATHNAME = "/inspections/search";
export const CREATE_INSPECTIONS_PATHNAME = "/inspections/create";
+
+export const TRAILER_INSPECTIONS_PATHNAME = "/trailerinspections";
+// export const SEARCH_TRAILER_INSPECTIONS_PATHNAME = "/trailerinspections/search"; // unused
+export const CREATE_TRAILER_INSPECTIONS_PATHNAME = "/trailerinspections/create";
+
export const DOCUMENT_GENERATION_PATHNAME = "/documents";
export const REPORTS_PATHNAME = "/documents/reports";
export const SELECT_CERTIFICATES_PATHNAME = "/documents/certificates";
@@ -38,6 +43,9 @@ export const LICENSE_TYPES_ADMIN_PATHNAME = "/admin/license-types";
export const SITES_ADMIN_PATHNAME = "/admin/sites";
export const INSPECTIONS_ADMIN_PATHNAME = "/admin/inspections";
+export const TRAILERS_PATHNAME = "/trailers";
+export const CREATE_TRAILERS_PATHNAME = "/trailers/create"; // not used?
+
export const REQUEST_STATUS = {
IDLE: "idle",
PENDING: "pending",
@@ -144,6 +152,7 @@ export const REPORTS = {
DAIRY_TEST_THRESHOLD: "DAIRY_TEST_THRESHOLD",
LICENCE_LOCATION: "LICENCE_LOCATION",
LICENCE_EXPIRY: "LICENCE_EXPIRY",
+ DAIRY_TRAILER_INSPECTION: "TRAILER INSPECTION",
};
export const SYSTEM_ROLES = {
diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma
index 76b01c81..a774ec77 100644
--- a/app/prisma/schema.prisma
+++ b/app/prisma/schema.prisma
@@ -374,6 +374,7 @@ model mal_licence {
mal_registrant mal_registrant? @relation(fields: [primary_registrant_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "lic_rgst_fk")
mal_status_code_lu mal_status_code_lu @relation(fields: [status_code_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "lic_stat_fk")
mal_dairy_farm_test_result mal_dairy_farm_test_result[]
+ mal_dairy_farm_trailer mal_dairy_farm_trailer[]
mal_fur_farm_inventory mal_fur_farm_inventory[]
mal_game_farm_inventory mal_game_farm_inventory[]
mal_licence_comment mal_licence_comment[]
@@ -762,16 +763,56 @@ model mal_site {
}
model mal_status_code_lu {
- id Int @id @default(autoincrement())
- code_name String @unique(map: "mal_statcd_code_name_uk") @db.VarChar(50)
- code_description String? @db.VarChar(120)
- active_flag Boolean
- create_userid String @db.VarChar(63)
- create_timestamp DateTime @db.Timestamp(6)
- update_userid String @db.VarChar(63)
- update_timestamp DateTime @db.Timestamp(6)
- mal_licence mal_licence[]
- mal_site mal_site[]
+ id Int @id @default(autoincrement())
+ code_name String @unique(map: "mal_statcd_code_name_uk") @db.VarChar(50)
+ code_description String? @db.VarChar(120)
+ active_flag Boolean
+ create_userid String @db.VarChar(63)
+ create_timestamp DateTime @db.Timestamp(6)
+ update_userid String @db.VarChar(63)
+ update_timestamp DateTime @db.Timestamp(6)
+ mal_dairy_farm_trailer mal_dairy_farm_trailer[]
+ mal_licence mal_licence[]
+ mal_site mal_site[]
+}
+
+model mal_dairy_farm_trailer {
+ id Int @id @default(autoincrement())
+ licence_id Int
+ status_code_id Int
+ licence_trailer_seq Int?
+ date_issued DateTime? @db.Timestamp(6)
+ trailer_number String? @db.VarChar(50)
+ geographical_division String? @db.VarChar(50)
+ serial_number_vin String? @db.VarChar(50)
+ license_plate String? @db.VarChar(10)
+ trailer_year Int? @db.SmallInt
+ trailer_make String? @db.VarChar(50)
+ trailer_type String? @db.VarChar(50)
+ trailer_capacity Int?
+ trailer_compartments Int? @db.SmallInt
+ create_userid String @db.VarChar(63)
+ create_timestamp DateTime @db.Timestamp(6)
+ update_userid String @db.VarChar(63)
+ update_timestamp DateTime @db.Timestamp(6)
+ mal_licence mal_licence @relation(fields: [licence_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "dftr_licence_fk")
+ mal_status_code_lu mal_status_code_lu @relation(fields: [status_code_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "dftr_status_code_id_fk")
+ mal_dairy_farm_trailer_inspection mal_dairy_farm_trailer_inspection[]
+
+ @@unique([licence_id, licence_trailer_seq], map: "mal_dairy_farm_trailer_ukey")
+}
+
+model mal_dairy_farm_trailer_inspection {
+ id Int @id @default(autoincrement())
+ trailer_id Int
+ inspection_date DateTime? @db.Timestamp(6)
+ inspector_id String? @db.VarChar(128)
+ inspection_comments String? @db.VarChar(4000)
+ create_userid String @db.VarChar(63)
+ create_timestamp DateTime @db.Timestamp(6)
+ update_userid String @db.VarChar(63)
+ update_timestamp DateTime @db.Timestamp(6)
+ mal_dairy_farm_trailer mal_dairy_farm_trailer @relation(fields: [trailer_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "dftri_trailer_fk")
}
model mal_licence_summary_vw {
@@ -861,3 +902,59 @@ model mal_print_dairy_farm_tank_recheck_vw {
print_recheck_notice Boolean
recheck_notice_json Json
}
+
+model mal_dairy_farm_trailer_vw {
+ dairy_farm_trailer_id Int @unique
+ licence_id Int?
+ licence_number Int?
+ irma_number String? @db.VarChar(10)
+ licence_status String? @db.VarChar(50)
+ company_name String? @db.VarChar(200)
+ derived_licence_holder_name String?
+ registrant_last_first String? @db.VarChar
+ address String?
+ city String? @db.VarChar(35)
+ province String? @db.VarChar(4)
+ postal_code String? @db.VarChar(6)
+ registrant_primary_phone String? @db.VarChar(10)
+ registrant_secondary_phone String? @db.VarChar(10)
+ registrant_fax_number String? @db.VarChar(10)
+ registrant_email_address String? @db.VarChar(128)
+ issue_date DateTime? @db.Date
+ issue_date_display String?
+ licence_trailer_seq Int?
+ trailer_number String? @db.VarChar(50)
+ geographical_division String? @db.VarChar(50)
+ serial_number_vin String? @db.VarChar(50)
+ license_plate String? @db.VarChar(10)
+ trailer_year Int? @db.SmallInt
+ trailer_make String? @db.VarChar(50)
+ trailer_type String? @db.VarChar(50)
+ trailer_capacity Int?
+ trailer_compartments Int? @db.SmallInt
+}
+
+model mal_dairy_farm_trailer_inspection_vw {
+ dairy_farm_trailer_inspection_id Int @unique
+ dairy_farm_trailer_id Int
+ licence_id Int?
+ licence_number Int?
+ irma_number String? @db.VarChar(10)
+ licence_type String?
+ licence_status String?
+ company_name String?
+ licence_trailer_seq Int?
+ trailer_number String?
+ trailer_status String?
+ geographical_division String?
+ serial_number_vin String?
+ license_plate String?
+ trailer_year Int? @db.SmallInt
+ trailer_make String?
+ trailer_type String?
+ trailer_capacity Int?
+ trailer_compartments Int? @db.SmallInt
+ inspection_date DateTime?
+ inspector_id String?
+ inspection_comments String?
+}
\ No newline at end of file
diff --git a/app/prisma/views.prisma b/app/prisma/views.prisma
index dbe88b91..995d8ddd 100644
--- a/app/prisma/views.prisma
+++ b/app/prisma/views.prisma
@@ -86,3 +86,35 @@ model mal_print_dairy_farm_tank_recheck_vw {
print_recheck_notice Boolean
recheck_notice_json Json
}
+
+model mal_dairy_farm_trailer_vw {
+ dairy_farm_trailer_id Int @unique
+ licence_id Int?
+ licence_number Int?
+ irma_number String? @db.VarChar(10)
+ licence_status String? @db.VarChar(50)
+ company_name String? @db.VarChar(200)
+ derived_licence_holder_name String?
+ registrant_last_first String? @db.VarChar
+ address String?
+ city String? @db.VarChar(35)
+ province String? @db.VarChar(4)
+ postal_code String? @db.VarChar(6)
+ registrant_primary_phone String? @db.VarChar(10)
+ registrant_secondary_phone String? @db.VarChar(10)
+ registrant_fax_number String? @db.VarChar(10)
+ registrant_email_address String? @db.VarChar(128)
+ issue_date DateTime? @db.Date
+ issue_date_display String?
+ licence_trailer_seq Int?
+ trailer_number String? @db.VarChar(50)
+ geographical_division String? @db.VarChar(50)
+ serial_number_vin String? @db.VarChar(50)
+ license_plate String? @db.VarChar(10)
+ trailer_year Int? @db.SmallInt
+ trailer_make String? @db.VarChar(50)
+ trailer_type String? @db.VarChar(50)
+ trailer_capacity Int?
+ trailer_compartments Int? @db.SmallInt
+ trailer_active_flag Boolean?
+}
\ No newline at end of file
diff --git a/app/server/models/inspection.js b/app/server/models/inspection.js
index eaad1aa6..fa6b36c9 100644
--- a/app/server/models/inspection.js
+++ b/app/server/models/inspection.js
@@ -59,7 +59,35 @@ function convertApiaryInspectionToPhysicalModel(input) {
return output;
}
+function convertTrailerInspectionToLogicalModel(input) {
+ const output = {
+ id: input.id,
+ trailerId: input.trailer_id,
+ inspectionDate: formatDate(input.inspection_date),
+ inspectorId: input.inspector_id,
+ inspectionComment: input.inspection_comments,
+ };
+
+ return output;
+}
+
+function convertTrailerInspectionToPhysicalModel(input) {
+ const output = {
+ trailer_id: input.trailerId,
+ inspection_date: input.inspectionDate,
+ inspector_id: input.inspectorId,
+ inspection_comments: input.inspectionComment,
+ create_userid: input.createdBy,
+ create_timestamp: input.createdOn,
+ update_userid: input.updatedBy,
+ update_timestamp: input.updatedOn,
+ };
+ return output;
+}
+
module.exports = {
convertApiaryInspectionToLogicalModel,
convertApiaryInspectionToPhysicalModel,
+ convertTrailerInspectionToLogicalModel,
+ convertTrailerInspectionToPhysicalModel,
};
diff --git a/app/server/models/site.js b/app/server/models/site.js
index 3bcc1425..257ee8c0 100644
--- a/app/server/models/site.js
+++ b/app/server/models/site.js
@@ -60,9 +60,9 @@ function convertToLogicalModel(input) {
dairyTanks: input.mal_dairy_farm_tank
? input.mal_dairy_farm_tank.map((xref, index) => ({
- ...dairyTank.convertToLogicalModel(xref),
- key: index,
- }))
+ ...dairyTank.convertToLogicalModel(xref),
+ key: index,
+ }))
: null,
inspections: [],
@@ -152,8 +152,8 @@ function convertToPhysicalModel(input, update) {
input.region === null
? emptyRegion
: {
- connect: { id: input.region },
- },
+ connect: { id: input.region },
+ },
mal_status_code_lu: {
connect: { id: input.siteStatus },
},
@@ -161,8 +161,8 @@ function convertToPhysicalModel(input, update) {
input.regionalDistrict === null
? emptyRegionalDistrict
: {
- connect: { id: input.regionalDistrict },
- },
+ connect: { id: input.regionalDistrict },
+ },
address_line_1: input.addressLine1,
address_line_2: input.addressLine2,
city: input.city,
diff --git a/app/server/models/trailer.js b/app/server/models/trailer.js
new file mode 100644
index 00000000..14b55a64
--- /dev/null
+++ b/app/server/models/trailer.js
@@ -0,0 +1,119 @@
+const { formatDate } = require("../utilities/formatting");
+const { parseAsInt, parseAsDate } = require("../utilities/parsing");
+
+function convertToLogicalModel(input) {
+ const output = {
+ id: input.id,
+ licenceId: input.licence_id,
+
+ licenceNumber: input.licence_number,
+ irmaNumber: input.irma_number,
+ licenceStatus:
+ input.mal_status_code_lu == null
+ ? null
+ : input.mal_status_code_lu.code_description,
+ licenceStatusId: input.status_code_id,
+ companyName: input.company_name,
+ derivedLicenceHolderName: input.derived_licence_holder_name,
+ registrantLastFirst: input.registrant_last_first,
+ address: input.address,
+ city: input.city,
+ province: input.province,
+ postalCode: input.postal_code,
+ registrantPrimaryPhone: input.registrant_primary_phone,
+ registrantSecondaryPhone: input.registrant_secondary_phone,
+ registrantFaxNumber: input.registrant_fax_number,
+ registrantEmailAddress: input.registrant_email_address,
+ dateIssued: input.date_issued ? formatDate(input.date_issued) : "",
+ issueDateDisplay: input.issue_date_display,
+ trailerNumber: input.trailer_number,
+ licenceTrailerSeq: input.licence_trailer_seq,
+ geographicalDivision: input.geographical_division,
+ serialNumberVIN: input.serial_number_vin,
+ licensePlate: input.license_plate,
+ trailerYear: input.trailer_year,
+ trailerMake: input.trailer_make,
+ trailerType: input.trailer_type,
+ trailerCapacity: input.trailer_capacity,
+ trailerCompartments: input.trailer_compartments,
+ createdBy: input.create_userid,
+ createdOn: input.create_timestamp,
+ updatedBy: input.update_userid,
+ updatedOn: input.update_timestamp,
+
+ inspections: [],
+ };
+
+ return output;
+}
+
+function convertSearchResultToLogicalModel(input) {
+ const output = {
+ dairyFarmTrailerId: input.dairy_farm_trailer_id,
+ licenceId: input.licence_id,
+ licenceNumber: input.licence_number,
+ irmaNumber: input.irma_number,
+ licenceStatus: input.licence_status,
+ licenceStatusId: input.licence_status_id,
+ companyName: input.company_name,
+ derivedLicenceHolderName: input.derived_licence_holder_name,
+ registrantLastFirst: input.registrant_last_first,
+ address: input.address,
+ city: input.city,
+ province: input.province,
+ postalCode: input.postal_code,
+ registrantPrimaryPhone: input.registrant_primary_phone,
+ registrantSecondaryPhone: input.registrant_secondary_phone,
+ registrantFaxNumber: input.registrant_fax_number,
+ registrantEmailAddress: input.registrant_email_address,
+ dateIssued: input.date_issued,
+ dateIssuedDisplay: input.date_issued_display, // ?
+ licenceTrailerSeq: input.licence_trailer_seq,
+ trailerNumber: input.trailer_number,
+ licenceTrailerSeq: input.licence_trailer_seq,
+ geographicalDivision: input.geographical_division,
+ serialNumberVin: input.serial_number_vin,
+ licensePlate: input.license_plate,
+ trailerYear: input.trailer_year,
+ trailerMake: input.trailer_make,
+ trailerType: input.trailer_type,
+ trailerCapacity: input.trailer_capacity,
+ trailerCompartments: input.trailer_compartments,
+ };
+
+ return output;
+}
+
+function convertToPhysicalModel(input, update) {
+ const output = {
+ mal_licence: {
+ connect: { id: input.licenceId },
+ },
+ mal_status_code_lu: {
+ connect: { id: input.licenceStatus },
+ },
+ trailer_number: input.trailerNumber,
+ licence_trailer_seq: input.licenceTrailerSeq,
+ date_issued: parseAsDate(input.dateIssued),
+ geographical_division: input.geographicalDivision,
+ serial_number_vin: input.serialNumberVIN,
+ license_plate: input.licensePlate,
+ trailer_year: input.trailerYear,
+ trailer_make: input.trailerMake,
+ trailer_type: input.trailerType,
+ trailer_capacity: input.trailerCapacity,
+ trailer_compartments: input.trailerCompartments,
+ create_userid: input.createdBy,
+ create_timestamp: input.createdOn,
+ update_userid: input.updatedBy,
+ update_timestamp: input.updatedOn,
+ };
+
+ return output;
+}
+
+module.exports = {
+ convertToPhysicalModel,
+ convertSearchResultToLogicalModel,
+ convertToLogicalModel,
+};
diff --git a/app/server/routes/v1/documents.js b/app/server/routes/v1/documents.js
index 668b81e4..21006263 100644
--- a/app/server/routes/v1/documents.js
+++ b/app/server/routes/v1/documents.js
@@ -31,12 +31,18 @@ const certificateTemplateDir = path.join(
__dirname,
"../../static/templates/certificates"
);
-const renewalsTemplateDir = path.join(__dirname, "../../static/templates/notices");
+const renewalsTemplateDir = path.join(
+ __dirname,
+ "../../static/templates/notices"
+);
const dairyNoticeTemplateDir = path.join(
__dirname,
"../../static/templates/notices/dairy"
);
-const reportsTemplateDir = path.join(__dirname, "../../static/templates/reports");
+const reportsTemplateDir = path.join(
+ __dirname,
+ "../../static/templates/reports"
+);
// As templates are converted to base 64 for the first time they will be pushed to this for reuse
const templateBuffers = [];
@@ -61,7 +67,6 @@ cdogs.interceptors.request.use(
)
);
-
async function getPendingDocuments(jobId) {
const documents = await prisma.mal_print_job_output.findMany({
where: { document_binary: null, print_job_id: jobId },
@@ -286,6 +291,99 @@ async function generateCertificate(documentId) {
const template = templateBuffers.find(
(x) => x.templateFileName === templateFileName
);
+ // check if certificate is CARD (3 templates), if so, split into two arrays
+ if (
+ document.document_type === "CARD" &&
+ (document.licence_type === "BULK TANK MILK GRADER" ||
+ document.licence_type === "LIVESTOCK DEALER" ||
+ document.licence_type === "LIVESTOCK DEALER AGENT")
+ ) {
+ /**
+ * The Card templates have a table that is 2 columns by N rows, this function effectively combines
+ * those two columns into one so that we don't have to deal with bi-directional looping
+ * (which cdogs doesn't support yet) */
+
+ function combineEntries(licenceType, documentJson) {
+ let combinedJson = [];
+ switch (licenceType) {
+ case "BULK TANK MILK GRADER":
+ for (let i = 0; i < documentJson.length; i += 2) {
+ let combinedEntry = { ...documentJson[i] };
+ if (documentJson[i + 1]) {
+ combinedEntry.LicenceHolderName2 =
+ documentJson[i + 1].LicenceHolderName;
+ combinedEntry.LicenceHolderCompany2 =
+ documentJson[i + 1].LicenceHolderCompany;
+ combinedEntry.LicenceNumber2 = documentJson[i + 1].LicenceNumber;
+ combinedEntry.ExpiryDate2 = documentJson[i + 1].ExpiryDate;
+ combinedEntry.CardLabel2 = documentJson[i + 1].CardLabel;
+ } else {
+ combinedEntry.LicenceHolderName2 = null;
+ combinedEntry.LicenceHolderCompany2 = null;
+ combinedEntry.LicenceNumber2 = null;
+ combinedEntry.ExpiryDate2 = null;
+ combinedEntry.CardLabel2 = null;
+ }
+ combinedJson.push(combinedEntry);
+ }
+ return combinedJson;
+ case "LIVESTOCK DEALER AGENT":
+ for (let i = 0; i < documentJson.length; i += 2) {
+ let combinedEntry = { ...documentJson[i] };
+ if (documentJson[i + 1]) {
+ combinedEntry.CardType2 = documentJson[i + 1].CardType;
+ combinedEntry.LicenceHolderName2 =
+ documentJson[i + 1].LicenceHolderName;
+ combinedEntry.LastFirstName2 = documentJson[i + 1].LastFirstName;
+ combinedEntry.AgentFor2 = documentJson[i + 1].AgentFor;
+ combinedEntry.LicenceNumber2 = documentJson[i + 1].LicenceNumber;
+ combinedEntry.StartDate2 = documentJson[i + 1].StartDate;
+ combinedEntry.ExpiryDate2 = documentJson[i + 1].ExpiryDate;
+ } else {
+ combinedEntry.CardType2 = null;
+ combinedEntry.LicenceHolderName2 = null;
+ combinedEntry.LastFirstName2 = null;
+ combinedEntry.AgentFor2 = null;
+ combinedEntry.LicenceNumber2 = null;
+ combinedEntry.StartDate2 = null;
+ combinedEntry.ExpiryDate2 = null;
+ }
+ combinedJson.push(combinedEntry);
+ }
+ return combinedJson;
+ case "LIVESTOCK DEALER":
+ for (let i = 0; i < documentJson.length; i += 2) {
+ let combinedEntry = { ...documentJson[i] };
+ if (documentJson[i + 1]) {
+ combinedEntry.CardType2 = documentJson[i + 1].CardType;
+ combinedEntry.LicenceHolderCompany2 =
+ documentJson[i + 1].LicenceHolderCompany;
+ combinedEntry.LicenceNumber2 = documentJson[i + 1].LicenceNumber;
+ combinedEntry.StartDate2 = documentJson[i + 1].StartDate;
+ combinedEntry.ExpiryDate2 = documentJson[i + 1].ExpiryDate;
+ } else {
+ combinedEntry.CardType2 = null;
+ combinedEntry.LicenceHolderCompany2 = null;
+ combinedEntry.LicenceNumber2 = null;
+ combinedEntry.StartDate2 = null;
+ combinedEntry.ExpiryDate2 = null;
+ }
+ combinedJson.push(combinedEntry);
+ }
+ return combinedJson;
+ default:
+ return null;
+ }
+ }
+
+ let updatedJson = combineEntries(
+ document.licence_type,
+ document.document_json
+ );
+
+ document.document_json = updatedJson;
+ }
+
const generate = async () => {
const { data, status } = await cdogs.post(
"template/render",
@@ -363,7 +461,7 @@ async function startRenewalJob(licenceIds) {
},
};
- const [, , procedureResult,] = await prisma.$transaction([
+ const [, , procedureResult] = await prisma.$transaction([
// ensure selected licences have print_renewal set to true
prisma.mal_licence.updateMany({
where: licenceFilterCriteria,
@@ -463,7 +561,6 @@ async function generateRenewal(documentId) {
}
async function getQueuedDairyNotices(startDate, endDate) {
-
const andArray = [];
andArray.push({ recorded_date: { gte: new Date(startDate) } });
andArray.push({ recorded_date: { lte: new Date(endDate) } });
@@ -586,7 +683,6 @@ async function generateDairyNotice(documentId) {
return result;
}
-
async function getQueuedDairyTankNotices() {
return prisma.mal_print_dairy_farm_tank_recheck_vw.findMany({
where: {
@@ -730,6 +826,18 @@ async function startApiaryHiveInspectionJob(startDate, endDate) {
return { jobId, documents };
}
+async function startDairyTrailerInspectionJob(licenceNumber) {
+ const [procedureResult] = await prisma.$transaction([
+ prisma.$queryRawUnsafe(
+ `CALL mals_app.pr_generate_print_json_dairy_farm_trailer_inspection('${licenceNumber}', NULL)`
+ ),
+ ]);
+
+ const jobId = procedureResult[0].iop_print_job_id;
+ const documents = await getPendingDocuments(jobId);
+ return { jobId, documents };
+}
+
async function startProducersAnalysisRegionJob() {
const [procedureResult] = await prisma.$transaction([
prisma.$queryRawUnsafe(
@@ -862,7 +970,6 @@ async function startLicenceExpiryJob(startDate, endDate) {
async function generateReport(documentId) {
const document = await getDocument(documentId);
-
const templateFileName = getReportsTemplateName(document.document_type);
if (templateFileName === undefined) {
@@ -1208,6 +1315,24 @@ router.post(
}
);
+router.post(
+ "/reports/startJob/dairyTrailerInspection",
+ async (req, res, next) => {
+ const licenceNumber = parseAsInt(req.body.licenceNumber);
+
+ await startDairyTrailerInspectionJob(licenceNumber)
+ .then(({ jobId, documents }) => {
+ return res.send({
+ jobId,
+ documents,
+ type: REPORTS.DAIRY_TRAILER_INSPECTION,
+ });
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+ }
+);
+
router.post(
"/reports/startJob/producersAnalysisRegion",
async (req, res, next) => {
diff --git a/app/server/routes/v1/index.js b/app/server/routes/v1/index.js
index daceb284..dd86ebfb 100644
--- a/app/server/routes/v1/index.js
+++ b/app/server/routes/v1/index.js
@@ -1,13 +1,13 @@
-const router = require('express').Router();
+const router = require("express").Router();
const httpContext = require("express-http-context");
-const { currentUser } = require('../../middleware/authentication');
-
+const { currentUser } = require("../../middleware/authentication");
const userRouter = require("./user");
const licenceTypesRouter = require("./licenceTypes");
const licenceStatusesRouter = require("./licenceStatuses");
const licencesRouter = require("./licences");
const sitesRouter = require("./sites");
+const trailersRouter = require("./trailers");
const regionalDistrictsRouter = require("./regionalDistricts");
const regionsRouter = require("./regions");
const commentsRouter = require("./comments");
@@ -24,171 +24,182 @@ const roleValidation = require("../../middleware/roleValidation");
router.use(currentUser);
router.use((req, res, next) => {
- if (req.currentUser) {
- httpContext.set("currentUser", req.currentUser);
- }
- next();
+ if (req.currentUser) {
+ httpContext.set("currentUser", req.currentUser);
+ }
+ next();
});
// Base v1 Responder
-router.get('/', (_req, res) => {
- res.status(200).json({
- endpoints: [
- '/user',
- '/licence-types',
- '/licence-statuses',
- '/licences',
- '/sites',
- '/regional-districts',
- '/regions',
- '/config',
- '/comments',
- '/licence-species',
- '/slaughterhouse-species',
- '/documents',
- '/cities',
- '/dairyfarmtestthresholds',
- '/inspections',
- '/admin'
- ]
- });
+router.get("/", (_req, res) => {
+ res.status(200).json({
+ endpoints: [
+ "/user",
+ "/licence-types",
+ "/licence-statuses",
+ "/licences",
+ "/sites",
+ "/trailers",
+ "/regional-districts",
+ "/regions",
+ "/config",
+ "/comments",
+ "/licence-species",
+ "/slaughterhouse-species",
+ "/documents",
+ "/cities",
+ "/dairyfarmtestthresholds",
+ "/inspections",
+ "/admin",
+ ],
+ });
});
router.use("/user", userRouter);
router.use(
- "/licence-types",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- licenceTypesRouter
+ "/licence-types",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ licenceTypesRouter
+);
+router.use(
+ "/licence-statuses",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ licenceStatusesRouter
);
router.use(
- "/licence-statuses",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- licenceStatusesRouter
+ "/licences",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ licencesRouter
);
router.use(
- "/licences",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- licencesRouter
+ "/sites",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ sitesRouter
);
router.use(
- "/sites",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- sitesRouter
+ "/trailers",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ trailersRouter
);
router.use(
- "/regional-districts",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- regionalDistrictsRouter
+ "/regional-districts",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ regionalDistrictsRouter
);
router.use(
- "/regions",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- regionsRouter
+ "/regions",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ regionsRouter
);
router.use(
- "/comments",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- commentsRouter.router
+ "/comments",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ commentsRouter.router
);
router.use(
- "/licence-species",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- licenceSpeciesRouter
+ "/licence-species",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ licenceSpeciesRouter
);
router.use(
- "/slaughterhouse-species",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- slaughterhouseSpeciesRouter
+ "/slaughterhouse-species",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ slaughterhouseSpeciesRouter
);
router.use(
- "/documents",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- documentsRouter
+ "/documents",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ documentsRouter
);
router.use(
- "/cities",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- citiesRouter
+ "/cities",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ citiesRouter
);
router.use(
- "/dairyfarmtestthresholds",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- dairyFarmTestThresholdsRouter
+ "/dairyfarmtestthresholds",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ dairyFarmTestThresholdsRouter
);
router.use(
- "/inspections",
- roleValidation([
- constants.SYSTEM_ROLES.READ_ONLY,
- constants.SYSTEM_ROLES.USER,
- constants.SYSTEM_ROLES.INSPECTOR,
- constants.SYSTEM_ROLES.SYSTEM_ADMIN,
- ]),
- inspectionsRouter
+ "/inspections",
+ roleValidation([
+ constants.SYSTEM_ROLES.READ_ONLY,
+ constants.SYSTEM_ROLES.USER,
+ constants.SYSTEM_ROLES.INSPECTOR,
+ constants.SYSTEM_ROLES.SYSTEM_ADMIN,
+ ]),
+ inspectionsRouter
);
router.use(
- "/admin",
- roleValidation([constants.SYSTEM_ROLES.SYSTEM_ADMIN]),
- adminRouter
+ "/admin",
+ roleValidation([constants.SYSTEM_ROLES.SYSTEM_ADMIN]),
+ adminRouter
);
module.exports = router;
diff --git a/app/server/routes/v1/inspections.js b/app/server/routes/v1/inspections.js
index ef79ded5..ea9967e8 100644
--- a/app/server/routes/v1/inspections.js
+++ b/app/server/routes/v1/inspections.js
@@ -9,6 +9,7 @@ const inspection = require("../../models/inspection");
const router = express.Router();
const prisma = new PrismaClient();
+/*** Apiary Inspections ***/
async function findApiaryInspection(inspectionId) {
return prisma.mal_apiary_inspection.findUnique({
where: {
@@ -99,4 +100,96 @@ router.put("/apiary/:inspectionId(\\d+)", async (req, res, next) => {
.finally(async () => prisma.$disconnect());
});
+/*** Dairy Trailer Inspections ***/
+async function findTrailerInspection(inspectionId) {
+ return prisma.mal_dairy_farm_trailer_inspection.findUnique({
+ where: {
+ id: inspectionId,
+ },
+ });
+}
+
+async function createTrailerInspection(payload) {
+ return prisma.mal_dairy_farm_trailer_inspection.create({
+ data: payload,
+ });
+}
+
+async function updateTrailerInspection(inspectionId, payload) {
+ return prisma.mal_dairy_farm_trailer_inspection.update({
+ data: payload,
+ where: {
+ id: inspectionId,
+ },
+ });
+}
+
+router.get("/trailer/:inspectionId(\\d+)", async (req, res, next) => {
+ const inspectionId = parseInt(req.params.inspectionId, 10);
+
+ await findTrailerInspection(inspectionId)
+ .then((record) => {
+ if (record === null) {
+ return res.status(404).send({
+ code: 404,
+ description: "The requested trailer inspection could not be found.",
+ });
+ }
+
+ const payload = inspection.convertTrailerInspectionToLogicalModel(record);
+ return res.send(payload);
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
+router.post("/trailer", async (req, res, next) => {
+ console.log("create trailer route");
+ const now = new Date();
+
+ const payload = inspection.convertTrailerInspectionToPhysicalModel(
+ populateAuditColumnsCreate(req.body, now, now)
+ );
+
+ await createTrailerInspection(payload)
+ .then((record) => {
+ if (record === null) {
+ return res.status(404).send({
+ code: 404,
+ description: "Error while creating trailer inspection.",
+ });
+ }
+
+ const payload = inspection.convertTrailerInspectionToLogicalModel(record);
+ return res.send(payload);
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
+router.put("/trailer/:inspectionId(\\d+)", async (req, res, next) => {
+ const inspectionId = parseInt(req.params.inspectionId, 10);
+
+ const now = new Date();
+
+ const payload = inspection.convertTrailerInspectionToPhysicalModel(
+ populateAuditColumnsUpdate(req.body, now)
+ );
+
+ await updateTrailerInspection(inspectionId, payload)
+ .then((record) => {
+ if (record === null) {
+ return res.status(404).send({
+ code: 404,
+ description: "Error while updating inspection.",
+ });
+ }
+
+ const payload = inspection.convertTrailerInspectionToLogicalModel(record);
+ return res.send(payload);
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
module.exports = router;
diff --git a/app/server/routes/v1/licences.js b/app/server/routes/v1/licences.js
index 7e796439..8ae0a2b2 100644
--- a/app/server/routes/v1/licences.js
+++ b/app/server/routes/v1/licences.js
@@ -64,6 +64,43 @@ async function findLicence(licenceId) {
});
}
+async function findLicenceByNumber(licenceNumber) {
+ return prisma.mal_licence.findFirst({
+ where: {
+ licence_number: licenceNumber,
+ },
+ include: {
+ mal_licence_type_lu: true,
+ mal_region_lu: true,
+ mal_regional_district_lu: true,
+ mal_status_code_lu: true,
+ mal_licence_registrant_xref: {
+ select: {
+ mal_registrant: true,
+ },
+ },
+ mal_licence_parent_child_xref_mal_licenceTomal_licence_parent_child_xref_parent_licence_id: {
+ select: {
+ create_timestamp: true,
+ mal_licence_mal_licenceTomal_licence_parent_child_xref_child_licence_id: {
+ include: {
+ mal_licence_type_lu: true,
+ mal_status_code_lu: true,
+ mal_licence_registrant_xref: {
+ select: {
+ mal_registrant: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ mal_game_farm_inventory: true,
+ mal_fur_farm_inventory: true,
+ },
+ });
+}
+
async function findLicenceRegistrantXref(licenceId, registrantId) {
if (licenceId === undefined || registrantId === undefined) {
return null;
@@ -565,6 +602,25 @@ router.get("/:licenceId(\\d+)", async (req, res, next) => {
.finally(async () => prisma.$disconnect());
});
+router.get("/number/:licenceNumber(\\d+)", async (req, res, next) => {
+ const licenceNumber = parseInt(req.params.licenceNumber, 10);
+
+ await findLicenceByNumber(licenceNumber)
+ .then((record) => {
+ if (record === null) {
+ return res.status(404).send({
+ code: 404,
+ description: "The requested licence could not be found.",
+ });
+ }
+
+ const payload = licence.convertToLogicalModel(record);
+ return res.send(payload);
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
router.put("/:licenceId(\\d+)/associated", async (req, res, next) => {
const licenceId = parseInt(req.params.licenceId, 10);
@@ -948,8 +1004,10 @@ router.put("/dairyactions/:licenceId(\\d+)", async (req, res, next) => {
where: { id: req.body.thresholdId },
});
- if (req.body.thresholdId !== constants.DAIRY_TEST_THRESHOLD_IDS.FFA && // No FFA infractions
- req.body.value > threshold.upper_limit.toFixed(2)) {
+ if (
+ req.body.thresholdId !== constants.DAIRY_TEST_THRESHOLD_IDS.FFA && // No FFA infractions
+ req.body.value > threshold.upper_limit.toFixed(2)
+ ) {
const infractions = await prisma.mal_dairy_farm_test_infraction_lu.findMany(
{
where: { test_threshold_id: req.body.thresholdId },
@@ -983,13 +1041,17 @@ router.put("/dairyactions/:licenceId(\\d+)", async (req, res, next) => {
.length,
3
);
- } else if (req.body.thresholdId === constants.DAIRY_TEST_THRESHOLD_IDS.SCC) {
+ } else if (
+ req.body.thresholdId === constants.DAIRY_TEST_THRESHOLD_IDS.SCC
+ ) {
infractionCount = Math.min(
filteredResults.filter((x) => x.sccCorrespondenceDescription !== null)
.length,
3
);
- } else if (req.body.thresholdId === constants.DAIRY_TEST_THRESHOLD_IDS.CRY) {
+ } else if (
+ req.body.thresholdId === constants.DAIRY_TEST_THRESHOLD_IDS.CRY
+ ) {
infractionCount = Math.min(
filteredResults.filter((x) => x.cryCorrespondenceDescription !== null)
.length,
@@ -1172,8 +1234,8 @@ router.put("/:licenceId(\\d+)/registrants", async (req, res, next) => {
a.createTimestamp > b.createTimestamp
? 1
: b.createTimestamp > a.createTimestamp
- ? -1
- : 0
+ ? -1
+ : 0
);
let newPrimaryRegistrant = undefined;
@@ -1301,8 +1363,8 @@ router.post("/", async (req, res, next) => {
const newRegistrants = req.body.registrants
? req.body.registrants.filter(
- (r) => r && r.status === REGISTRANT_STATUS.NEW
- )
+ (r) => r && r.status === REGISTRANT_STATUS.NEW
+ )
: [];
req.body.companyName =
@@ -1346,8 +1408,8 @@ router.post("/", async (req, res, next) => {
a.createTimestamp > b.createTimestamp
? 1
: b.createTimestamp > a.createTimestamp
- ? -1
- : 0
+ ? -1
+ : 0
);
updatedRecordLogical.primaryRegistrantId = registrants[0].id;
diff --git a/app/server/routes/v1/sites.js b/app/server/routes/v1/sites.js
index 20a3026d..52936dc6 100644
--- a/app/server/routes/v1/sites.js
+++ b/app/server/routes/v1/sites.js
@@ -10,7 +10,6 @@ const dairyTank = require("../../models/dairyTank");
const inspection = require("../../models/inspection");
const constants = require("../../utilities/constants");
-
const router = express.Router();
const prisma = new PrismaClient();
@@ -93,23 +92,59 @@ function getSearchFilter(params) {
}
if (params.filterText) {
- if (params.filterText === 'ACT' || params.filterText === 'INA') {
+ if (params.filterText === "ACT" || params.filterText === "INA") {
andArray.push({
site_status: {
contains: params.filterText,
- mode: "insensitive"
- }
- })
+ mode: "insensitive",
+ },
+ });
} else {
andArray.push({
OR: [
- { site_id_pk: { equals: isNaN(parseInt(params.filterText, 10)) ? 0 : parseInt(params.filterText, 10) } }, // unfortunately site id has to be an exact match
- { apiary_site_id_display: { contains: params.filterText, mode: "insensitive" } },
- { registrant_last_name: { contains: params.filterText, mode: "insensitive" } },
- { registrant_first_name: { contains: params.filterText, mode: "insensitive" } },
- { site_address_line_1: { contains: params.filterText, mode: "insensitive" } },
- { licence_region_name: { contains: params.filterText, mode: "insensitive" } },
- { licence_regional_district_name: { contains: params.filterText, mode: "insensitive" } },
+ {
+ site_id_pk: {
+ equals: isNaN(parseInt(params.filterText, 10))
+ ? 0
+ : parseInt(params.filterText, 10),
+ },
+ }, // unfortunately site id has to be an exact match
+ {
+ apiary_site_id_display: {
+ contains: params.filterText,
+ mode: "insensitive",
+ },
+ },
+ {
+ registrant_last_name: {
+ contains: params.filterText,
+ mode: "insensitive",
+ },
+ },
+ {
+ registrant_first_name: {
+ contains: params.filterText,
+ mode: "insensitive",
+ },
+ },
+ {
+ site_address_line_1: {
+ contains: params.filterText,
+ mode: "insensitive",
+ },
+ },
+ {
+ licence_region_name: {
+ contains: params.filterText,
+ mode: "insensitive",
+ },
+ },
+ {
+ licence_regional_district_name: {
+ contains: params.filterText,
+ mode: "insensitive",
+ },
+ },
],
});
}
@@ -299,14 +334,15 @@ router.post("/search/export", async (req, res, next) => {
"Site ID,Registrant Name,Company Name,Licence Number,City,Region,District,Next Inspection Date\n";
const values = results
.map((x) => {
- return `${x.apiarySiteIdDisplay ? x.apiarySiteIdDisplay : x.siteId
- },${formatValue(x.registrantLastName)},${formatValue(
- x.registrantCompanyName
- )},${formatValue(x.licenceNumber)},${formatValue(
- x.licenceCity
- )},${formatValue(x.licenceRegion)},${formatValue(
- x.licenceDistrict
- )},${formatValue(x.nextInspectionDate)}`;
+ return `${
+ x.apiarySiteIdDisplay ? x.apiarySiteIdDisplay : x.siteId
+ },${formatValue(x.registrantLastName)},${formatValue(
+ x.registrantCompanyName
+ )},${formatValue(x.licenceNumber)},${formatValue(
+ x.licenceCity
+ )},${formatValue(x.licenceRegion)},${formatValue(
+ x.licenceDistrict
+ )},${formatValue(x.nextInspectionDate)}`;
})
.join("\n");
const payload = columnHeaders.concat(values);
diff --git a/app/server/routes/v1/trailers.js b/app/server/routes/v1/trailers.js
new file mode 100644
index 00000000..12a89a5c
--- /dev/null
+++ b/app/server/routes/v1/trailers.js
@@ -0,0 +1,231 @@
+const express = require("express");
+const { PrismaClient } = require("@prisma/client");
+
+const {
+ populateAuditColumnsCreate,
+ populateAuditColumnsUpdate,
+} = require("../../utilities/auditing");
+const trailer = require("../../models/trailer");
+const inspection = require("../../models/inspection");
+const constants = require("../../utilities/constants");
+
+const router = express.Router();
+const prisma = new PrismaClient();
+
+function getSearchFilter(params) {
+ let filter = {};
+
+ const andArray = [];
+ const licenceTypeId = parseInt(params.licenceType, 10);
+ const licenceNumber = parseInt(params.licenceNumber, 10);
+
+ if (!Number.isNaN(licenceTypeId)) {
+ andArray.push({ licence_type_id: licenceTypeId });
+ }
+
+ if (!Number.isNaN(licenceNumber)) {
+ andArray.push({ licence_number: licenceNumber });
+ }
+
+ filter = {
+ AND: andArray,
+ };
+
+ return filter;
+}
+
+async function countTrailers(params) {
+ const filter = getSearchFilter(params);
+ return prisma.mal_dairy_farm_trailer_vw.count({
+ where: filter,
+ });
+}
+
+async function searchTrailers(params, skip, take) {
+ const filter = getSearchFilter(params);
+ return prisma.mal_dairy_farm_trailer_vw.findMany({
+ where: filter,
+ skip,
+ take,
+ });
+}
+
+async function findTrailersByLicenceId(licenceId) {
+ return prisma.mal_dairy_farm_trailer.findMany({
+ where: {
+ licence_id: licenceId,
+ },
+ include: {
+ mal_status_code_lu: true,
+ mal_licence: true,
+ },
+ });
+}
+
+async function findTrailer(trailerId) {
+ return prisma.mal_dairy_farm_trailer.findUnique({
+ where: {
+ id: trailerId,
+ },
+ include: {
+ mal_status_code_lu: true,
+ },
+ });
+}
+
+async function updateTrailer(trailerId, payload) {
+ return prisma.mal_dairy_farm_trailer.update({
+ data: payload,
+ where: {
+ id: trailerId,
+ },
+ include: {
+ mal_status_code_lu: true,
+ },
+ });
+}
+
+async function createTrailer(payload) {
+ return prisma.mal_dairy_farm_trailer.create({
+ data: payload,
+ });
+}
+
+// Search for trailers (will always be by licence id)
+router.get("/search", async (req, res, next) => {
+ let { page } = req.query;
+ if (page) {
+ page = parseInt(page, 10);
+ } else {
+ page = 1;
+ }
+
+ const size = 20;
+ const skip = (page - 1) * size;
+
+ const params = req.query;
+
+ await searchTrailers(params, skip, size)
+ .then(async (records) => {
+ if (records === null) {
+ return res.status(404).send({
+ code: 404,
+ description: "The requested trailer could not be found.",
+ });
+ }
+
+ const results = records.map((record) =>
+ trailer.convertSearchResultToLogicalModel(record)
+ );
+
+ const count = await countTrailers(params);
+
+ const payload = {
+ results,
+ page,
+ count,
+ };
+
+ return res.send(payload);
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
+// Get trailer by id
+router.get("/:trailerId(\\d+)", async (req, res, next) => {
+ const trailerId = parseInt(req.params.trailerId, 10);
+
+ await findTrailer(trailerId)
+ .then(async (record) => {
+ if (record === null) {
+ return res.status(404).send({
+ code: 404,
+ description: "The requested trailer could not be found.",
+ });
+ }
+
+ const payload = trailer.convertToLogicalModel(record);
+
+ // Grab inspections since they aren't linked by FKs
+ if (payload.licenceTrailerSeq !== null) {
+ const trailerInspections = await prisma.mal_dairy_farm_trailer_inspection.findMany(
+ {
+ where: {
+ trailer_id: payload.id,
+ },
+ }
+ );
+
+ payload.inspections = trailerInspections.map((xref, index) => ({
+ ...inspection.convertTrailerInspectionToLogicalModel(xref),
+ key: index,
+ }));
+ }
+
+ return res.send(payload);
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
+// Update Trailer
+router.put("/:trailerId(\\d+)", async (req, res, next) => {
+ const trailerId = parseInt(req.params.trailerId, 10);
+ const now = new Date();
+ const trailerPayload = trailer.convertToPhysicalModel(
+ populateAuditColumnsUpdate(req.body, now),
+ true
+ );
+
+ await updateTrailer(trailerId, trailerPayload)
+ .then(async (record) => {
+ if (record === null) {
+ return res.status(404).send({
+ code: 404,
+ description: "The requested trailer could not be found.",
+ });
+ }
+
+ const payload = trailer.convertToLogicalModel(record);
+ return res.send(payload);
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
+// Create Trailer
+router.post("/", async (req, res, next) => {
+ const now = new Date();
+ const data = req.body;
+
+ if (data.licenceTypeId === constants.LICENCE_TYPE_ID_DAIRY_TANK_TRUCK) {
+ const trailers = await findTrailersByLicenceId(data.licenceId);
+ if (trailers === null || trailers === undefined || trailers.length === 0) {
+ data.licenceTrailerSeq = 100;
+ } else {
+ const high = Math.max.apply(
+ Math,
+ trailers.map(function (o) {
+ return o.licence_trailer_seq;
+ })
+ );
+ const next = high + 1;
+ data.licenceTrailerSeq = next;
+ }
+ }
+
+ const trailerPayload = trailer.convertToPhysicalModel(
+ populateAuditColumnsCreate(data, now, now),
+ false
+ );
+
+ await createTrailer(trailerPayload)
+ .then(async (record) => {
+ return res.send({ id: record.id });
+ })
+ .catch(next)
+ .finally(async () => prisma.$disconnect());
+});
+
+module.exports = router;
diff --git a/app/server/server.js b/app/server/server.js
index 1d57586c..de2e013c 100644
--- a/app/server/server.js
+++ b/app/server/server.js
@@ -1,7 +1,7 @@
require("dotenv").config();
const express = require("express");
-const Problem = require('api-problem');
+const Problem = require("api-problem");
const httpContext = require("express-http-context");
const path = require("path");
const cookieParser = require("cookie-parser");
@@ -9,14 +9,14 @@ const logger = require("morgan");
const helmet = require("helmet");
const cors = require("cors");
-const appRouter = require('./routes/v1');
-const { Error, Log, getGitRevision } = require('./utilities/util');
+const appRouter = require("./routes/v1");
+const { Error, Log, getGitRevision } = require("./utilities/util");
const apiRouter = express.Router();
const state = {
gitRev: getGitRevision(),
ready: false,
- shutdown: false
+ shutdown: false,
};
const app = express();
@@ -43,7 +43,7 @@ app.use(
"*.oidc.gov.bc.ca",
"oidc.gov.bc.ca",
"loginproxy.gov.bc.ca",
- "*.loginproxy.gov.bc.ca/"
+ "*.loginproxy.gov.bc.ca/",
],
},
},
@@ -56,9 +56,11 @@ app.use(function (req, res, next) {
next();
});
-app.use(cors({
- origin: true // Set true to dynamically set Access-Control-Allow-Origin based on Origin
-}));
+app.use(
+ cors({
+ origin: true, // Set true to dynamically set Access-Control-Allow-Origin based on Origin
+ })
+);
app.use(logger("dev"));
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ extended: false, limit: "50mb" }));
@@ -66,7 +68,7 @@ app.use(cookieParser());
app.use(httpContext.middleware);
// Skip if running tests
-if (process.env.NODE_ENV !== 'test') {
+if (process.env.NODE_ENV !== "test") {
// Initialize connections and exit if unsuccessful
initializeConnections();
}
@@ -74,9 +76,9 @@ if (process.env.NODE_ENV !== 'test') {
// Block requests until service is ready
app.use((_req, res, next) => {
if (state.shutdown) {
- new Problem(503, { details: 'Server is shutting down' }).send(res);
+ new Problem(503, { details: "Server is shutting down" }).send(res);
} else if (!state.ready) {
- new Problem(503, { details: 'Server is not ready' }).send(res);
+ new Problem(503, { details: "Server is not ready" }).send(res);
} else {
next();
}
@@ -92,11 +94,11 @@ apiRouter.use("/v1/config", (req, res) => {
const response = {
environment: process.env.ENVIRONMENT_LABEL || "dev",
nodeVersion: process.version,
- version: process.env.npm_package_version
+ version: process.env.npm_package_version,
};
return res.send(response);
});
-apiRouter.use('/v1', appRouter);
+apiRouter.use("/v1", appRouter);
// Root level Router
app.use(/(\/api)?/, apiRouter);
@@ -139,12 +141,12 @@ app.use(function handleError(error, req, res, next) {
});
// Graceful shutdown support
-process.on('SIGTERM', shutdown);
-process.on('SIGINT', shutdown);
-process.on('SIGUSR1', shutdown);
-process.on('SIGUSR2', shutdown);
-process.on('exit', () => {
- Log('Exiting...');
+process.on("SIGTERM", shutdown);
+process.on("SIGINT", shutdown);
+process.on("SIGUSR1", shutdown);
+process.on("SIGUSR2", shutdown);
+process.on("exit", () => {
+ Log("Exiting...");
});
/**
@@ -152,10 +154,10 @@ process.on('exit', () => {
* Cleans up connections in this application.
*/
function cleanup() {
- Log('Service no longer accepting traffic');
+ Log("Service no longer accepting traffic");
state.shutdown = true;
- Log('Cleaning up...');
+ Log("Cleaning up...");
// Wait 10 seconds max before hard exiting
setTimeout(() => process.exit(), 10000);
@@ -166,7 +168,7 @@ function cleanup() {
* Shuts down this application after at least 3 seconds.
*/
function shutdown() {
- Log('Received kill signal. Shutting down...');
+ Log("Received kill signal. Shutting down...");
// Wait 3 seconds before starting cleanup
if (!state.shutdown) setTimeout(cleanup, 3000);
}
@@ -179,19 +181,17 @@ function shutdown() {
function initializeConnections() {
try {
// Empty block
- }
- catch (error) {
- Error('Connection initialization failure');
+ } catch (error) {
+ Error("Connection initialization failure");
Error(error.message);
if (!state.ready) {
process.exitCode = 1;
shutdown();
}
- }
- finally {
+ } finally {
state.ready = true;
if (state.ready) {
- Log('Service ready to accept traffic');
+ Log("Service ready to accept traffic");
}
}
}
diff --git a/app/server/static/templates/certificates/Bulk-Tank-Milk-Grader-Card.docx b/app/server/static/templates/certificates/Bulk-Tank-Milk-Grader-Card.docx
index cc21831b..3a16c908 100644
Binary files a/app/server/static/templates/certificates/Bulk-Tank-Milk-Grader-Card.docx and b/app/server/static/templates/certificates/Bulk-Tank-Milk-Grader-Card.docx differ
diff --git a/app/server/static/templates/certificates/Dairy-Tank-Truck.docx b/app/server/static/templates/certificates/Dairy-Tank-Truck.docx
new file mode 100644
index 00000000..750c2dd4
Binary files /dev/null and b/app/server/static/templates/certificates/Dairy-Tank-Truck.docx differ
diff --git a/app/server/static/templates/certificates/Livestock-Dealer-Agent-Card.docx b/app/server/static/templates/certificates/Livestock-Dealer-Agent-Card.docx
index d812fae9..dbda3b0f 100644
Binary files a/app/server/static/templates/certificates/Livestock-Dealer-Agent-Card.docx and b/app/server/static/templates/certificates/Livestock-Dealer-Agent-Card.docx differ
diff --git a/app/server/static/templates/certificates/Livestock-Dealer-Card.docx b/app/server/static/templates/certificates/Livestock-Dealer-Card.docx
index 27821519..78807f81 100644
Binary files a/app/server/static/templates/certificates/Livestock-Dealer-Card.docx and b/app/server/static/templates/certificates/Livestock-Dealer-Card.docx differ
diff --git a/app/server/static/templates/notices/Renewal_DairyTankTruck_Template.docx b/app/server/static/templates/notices/Renewal_DairyTankTruck_Template.docx
new file mode 100644
index 00000000..7e3b8c95
Binary files /dev/null and b/app/server/static/templates/notices/Renewal_DairyTankTruck_Template.docx differ
diff --git a/app/server/static/templates/reports/Dairy_Trailer_Inspection_Template.xlsx b/app/server/static/templates/reports/Dairy_Trailer_Inspection_Template.xlsx
new file mode 100644
index 00000000..6b6c2ca8
Binary files /dev/null and b/app/server/static/templates/reports/Dairy_Trailer_Inspection_Template.xlsx differ
diff --git a/app/server/utilities/constants.js b/app/server/utilities/constants.js
index 804fc50d..bac122b7 100644
--- a/app/server/utilities/constants.js
+++ b/app/server/utilities/constants.js
@@ -99,6 +99,7 @@ module.exports = Object.freeze({
DAIRY_FARM_QUALITY: "DAIRY_FARM_QUALITY",
DAIRY_FARM_TANK: "DAIRY_FARM_TANK",
DAIRY_TEST_THRESHOLD: "DAIRY_TEST_THRESHOLD",
+ DAIRY_TRAILER_INSPECTION: "TRAILER INSPECTION",
LICENCE_LOCATION: "LICENCE_LOCATION",
LICENCE_EXPIRY: "LICENCE_EXPIRY",
},
diff --git a/app/server/utilities/documents.js b/app/server/utilities/documents.js
index 6bac2233..5ad59215 100644
--- a/app/server/utilities/documents.js
+++ b/app/server/utilities/documents.js
@@ -71,6 +71,8 @@ function getCertificateTemplateName(documentType, licenceType) {
return "Veterinary-Drug-Outlet";
case "DISPENSER":
return "Veterinary-Drug-Dispenser";
+ case "DAIRY TANK TRUCK":
+ return "Dairy-Tank-Truck";
default:
return undefined;
}
@@ -110,6 +112,8 @@ function getRenewalTemplateName(documentType, licenceType) {
return "Renewal_VetDrugLicence_Template";
case "DISPENSER":
return "Renewal_VetDrugDispenser_Template";
+ case "DAIRY TANK TRUCK":
+ return "Renewal_DairyTankTruck_Template"; // not implemented
default:
return undefined;
}
@@ -192,6 +196,8 @@ function getReportsTemplateName(documentType) {
return "LicenceType_Location_Template";
case constants.REPORTS.LICENCE_EXPIRY:
return "Licence_Expiry_Species_NoSpecies_Template";
+ case constants.REPORTS.DAIRY_TRAILER_INSPECTION:
+ return "Dairy_Trailer_Inspection_Template";
default:
return null;
}