Skip to content

Commit

Permalink
696 bugs and features (#620)
Browse files Browse the repository at this point in the history
* Fix Deploy bug with Sheltered animals.

* Add all Visit Notes to DA Summary and PDF.

* Fix minor text bug.

* Updates printed SR details
Closes #611

* Sorts animals by id for printing many schedules
Closes #615

* Add "Permission" to Forced Entry

* Fix visit notes in PDF.

* Fix incident bugs.

---------

Co-authored-by: Mike Parks <[email protected]>
Co-authored-by: Trevor Skaggs <[email protected]>
  • Loading branch information
3 people authored Jul 30, 2023
1 parent 41a01bd commit 9c3f770
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 47 deletions.
8 changes: 4 additions & 4 deletions evac/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ class AssignedRequestDispatchSerializer(serializers.ModelSerializer):
service_request_object = DispatchServiceRequestSerializer(source='service_request', required=False, read_only=True)
visit_note = VisitNoteSerializer(required=False, read_only=True)
owner_contact = OwnerContactSerializer(required=False, read_only=True)
previous_visit = serializers.SerializerMethodField()
visit_notes = serializers.SerializerMethodField()

def get_previous_visit(self, obj):
def get_visit_notes(self, obj):
#TODO: this triggers one request per SR
if VisitNote.objects.filter(assigned_request__service_request=obj.service_request).exclude(assigned_request=obj).exists():
return VisitNoteSerializer(VisitNote.objects.filter(assigned_request__service_request=obj.service_request).exclude(assigned_request=obj).latest('date_completed')).data
return None
return VisitNoteSerializer(VisitNote.objects.filter(assigned_request__service_request=obj.service_request).exclude(assigned_request=obj), many=True).data
return []

class Meta:
model = AssignedRequest
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/IncidentForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ const IncidentForm = ({ id }) => {
.test('required-check', 'Name already in use.',
function(value) {
// Check against slug for dupes.
if (names.includes(value.trim().toLowerCase().replaceAll(' ','-').match(/[a-zA-Z0-9-]+/g)[0])) {
if (data.name !== value && names.includes(value.trim().toLowerCase().replaceAll(' ','-').match(/[a-zA-Z0-9-]+/g)[0])) {
return false;
}
return true;
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/Shelterly.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ function Shelterly() {

const path = window.location.pathname;

const style = state.user && path !== '/' && !path.includes('/incident') && !path.includes('/reset_password') ? {position:"absolute", marginLeft:"335px"} : {position:"absolute", maxWidth:"100%"};
const style = state.user && path !== '/' && !path.includes('/incident/') && !path.includes('/reset_password') ? {position:"absolute", marginLeft:"335px"} : {position:"absolute", maxWidth:"100%"};

return (
<ThemeProvider theme={theme}>
<Container fluid>
<Row>
{state.user && path !== '/' && !path.includes('/incident') && !path.includes('/reset_password') ?
{state.user && path !== '/' && !path.includes('/incident/') && !path.includes('/reset_password') ?
<span>
<Sidebar state={state} dispatch={dispatch} removeCookie={removeCookie} />
</span>
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/animals/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ async function printAnimalCareSchedule (animal = {}) {
};

async function printAllAnimalCareSchedules (animals = []) {
const pdf = await buildAnimalCareScheduleDoc(animals);
// sort animals by id
const sortedAnimals = [...animals].sort((a,b) => a.id - b.id);

const pdf = await buildAnimalCareScheduleDoc(sortedAnimals);
pdf.fileName = `Shelterly-Animal-Care-Schedules-${moment().format(DATE_FORMAT)}`;
return pdf.saveFile();
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ export const finishedMarkerIcon = new L.DivIcon({
// Counts the number of size/species matches for a service request by status.
export const countMatches = (service_request) => {
var matches = {};
var status_matches = {'REPORTED':{}, 'SHELTERED':{}, 'REPORTED (EVAC REQUESTED)':{}, 'REPORTED (SIP REQUESTED)':{}, 'SHELTERED IN PLACE':{}, 'UNABLE TO LOCATE':{}};
var status_matches = {'REPORTED':{}, 'REPORTED (EVAC REQUESTED)':{}, 'REPORTED (SIP REQUESTED)':{}, 'SHELTERED IN PLACE':{}, 'UNABLE TO LOCATE':{}};

service_request.animals.forEach((animal) => {
if (['REPORTED', 'SHELTERED', 'REPORTED (EVAC REQUESTED)', 'REPORTED (SIP REQUESTED)', 'SHELTERED IN PLACE', 'UNABLE TO LOCATE'].indexOf(animal.status) > -1) {
if (['REPORTED', 'REPORTED (EVAC REQUESTED)', 'REPORTED (SIP REQUESTED)', 'SHELTERED IN PLACE', 'UNABLE TO LOCATE'].indexOf(animal.status) > -1) {
if (!matches[[animal.species]]) {
matches[[animal.species]] = 1;
}
Expand Down
28 changes: 26 additions & 2 deletions frontend/src/dispatch/Dispatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { faQuestionCircle as faQuestionCircleDuo, faChevronCircleDown, faChevronCircleUp } from '@fortawesome/pro-duotone-svg-icons';
import { faHomeAlt as faHomeAltReg } from '@fortawesome/pro-regular-svg-icons';
import { faCircleBolt, faHomeAlt } from '@fortawesome/pro-solid-svg-icons';
import Map, { countMatches, prettyText, reportedMarkerIcon, reportedEvacMarkerIcon, reportedSIPMarkerIcon, SIPMarkerIcon, UTLMarkerIcon, finishedMarkerIcon } from "../components/Map";
import Map, { prettyText, reportedMarkerIcon, reportedEvacMarkerIcon, reportedSIPMarkerIcon, SIPMarkerIcon, UTLMarkerIcon, finishedMarkerIcon } from "../components/Map";
import Header from "../components/Header";
import Scrollbar from '../components/Scrollbars';
import { SystemErrorContext } from '../components/SystemError';
Expand All @@ -26,6 +26,30 @@ function Dispatch({ incident }) {
const [showActive, setShowActive] = useState(true);
const [showPreplanned, setShowPreplanned] = useState(true);

// Counts the number of size/species matches for a service request by status.
const countMatches = (animal_dict) => {
var matches = {};
var status_matches = {'REPORTED':{}, 'SHELTERED':{}, 'REPORTED (EVAC REQUESTED)':{}, 'REPORTED (SIP REQUESTED)':{}, 'SHELTERED IN PLACE':{}, 'UNABLE TO LOCATE':{}};

Object.keys(animal_dict).forEach((animal) => {
if (['REPORTED', 'SHELTERED', 'REPORTED (EVAC REQUESTED)', 'REPORTED (SIP REQUESTED)', 'SHELTERED IN PLACE', 'UNABLE TO LOCATE'].indexOf(animal_dict[animal]['status']) > -1) {
if (!matches[[animal_dict[animal]['species']]]) {
matches[[animal_dict[animal]['species']]] = 1;
}
else {
matches[[animal_dict[animal]['species']]] += 1;
}
if (!status_matches[animal_dict[animal]['status']][[animal_dict[animal]['species']]]) {
status_matches[animal_dict[animal]['status']][[animal_dict[animal]['species']]] = 1;
}
else {
status_matches[animal_dict[animal]['status']][[animal_dict[animal]['species']]] += 1;
}
}
});
return [matches, status_matches]
}

// Hook for initializing data.
useEffect(() => {
let unmounted = false;
Expand All @@ -49,7 +73,7 @@ function Dispatch({ incident }) {
response.data.forEach((dispatch_assignment, index) => {
let sr_dict = {}
for (const assigned_request of dispatch_assignment.assigned_requests) {
const matches = countMatches(assigned_request.service_request_object)[0];
const matches = countMatches(assigned_request.animals)[0];
sr_dict[assigned_request.service_request_object.id] = {id:assigned_request.service_request_object.id, matches:matches, latitude:assigned_request.service_request_object.latitude, longitude:assigned_request.service_request_object.longitude, full_address:assigned_request.service_request_object.full_address};
bounds.push([assigned_request.service_request_object.latitude, assigned_request.service_request_object.longitude]);
}
Expand Down
25 changes: 10 additions & 15 deletions frontend/src/dispatch/DispatchSummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ function DispatchSummary({ id, incident }) {
responseType: 'blob',
}).then(res => {
fileDownload(res.data, 'DAR-' + data.id + '.geojson');
console.log(res);
}).catch(err => {
console.log(err);
})
}

Expand Down Expand Up @@ -413,7 +411,7 @@ function DispatchSummary({ id, incident }) {
>
<FontAwesomeIcon icon={faCalendarDay} className="ml-1 fa-move-up" size="sm" />
</OverlayTrigger> : ""}
&nbsp;| {Object.values(assigned_request.animals).filter(animal => ['REPORTED', 'REPORTED (EVAC REQUESTED)', 'REPORTED (SIP REQUESTED)'].includes(animal.status)).length === 0 ? "Completed" : <span style={{textTransform:"capitalize"}}>{assigned_request.service_request_object.status}</span>} {assigned_request.visit_note ? <Moment format="[ on ]l">{assigned_request.visit_note.date_completed}</Moment> : ""}
&nbsp;| {Object.values(assigned_request.animals).filter(animal => ['REPORTED', 'REPORTED (EVAC REQUESTED)', 'REPORTED (SIP REQUESTED)', 'SHELTERED IN PLACE', 'UNABLE TO LOCATE'].includes(animal.status)).length === 0 ? "Completed" : <span style={{textTransform:"capitalize"}}>{assigned_request.service_request_object.status}</span>} {assigned_request.visit_note ? <Moment format="[ on ]l">{assigned_request.visit_note.date_completed}</Moment> : ""}
</h4>
</Card.Title>
<hr style={{marginBottom:"7px"}}/>
Expand Down Expand Up @@ -541,28 +539,25 @@ function DispatchSummary({ id, incident }) {
</ListGroup.Item>
))}
</ListGroup>
{assigned_request.previous_visit ?
{assigned_request.visit_note ?
<span>
<hr/>
<ListGroup variant="flush" style={{marginTop:"-13px", marginBottom:"-13px"}}>
<h4 className="mt-2" style={{marginBottom:"-2px"}}>Previous Visit: <Link href={"/" + incident + "/dispatch/summary/" + assigned_request.previous_visit.dispatch_assignment} className="text-link" style={{textDecoration:"none", color:"white"}}><Moment format="L">{assigned_request.previous_visit.date_completed}</Moment></Link></h4>
<h4 className="mt-2" style={{marginBottom:"-2px"}}>Visit Notes</h4>
<ListGroup.Item>
{assigned_request.previous_visit.notes || "No information available."}
{assigned_request.visit_note.notes || "No information available."}
</ListGroup.Item>
</ListGroup>
</span>
: "" }
{assigned_request.visit_note ?
<span>
<hr/>
<ListGroup variant="flush" style={{marginTop:"-13px", marginBottom:"-13px"}}>
<h4 className="mt-2" style={{marginBottom:"-2px"}}>Visit Notes</h4>
<ListGroup.Item key={assigned_request.visit_note.id}>
{assigned_request.visit_note.notes || "No information available."}
{assigned_request.visit_notes.length > 0 ? <h4 className="mt-2" style={{marginBottom:"-2px"}}>Previous Visit Notes</h4> : ""}
{assigned_request.visit_notes.map(visit_note =>
<ListGroup variant="flush" style={{marginBottom:"-13px"}} key={visit_note.id}>
<ListGroup.Item key={visit_note.id}>
<Link href={"/" + incident + "/dispatch/summary/" + visit_note.dispatch_assignment} className="text-link" style={{textDecoration:"none", color:"white"}}><Moment format="L">{visit_note.date_completed}</Moment></Link>: {visit_note.notes || "No information available."}
</ListGroup.Item>
</ListGroup>
</span>
: ""}
) || "None"}
</Card.Body>
</Card>
</Row>
Expand Down
39 changes: 30 additions & 9 deletions frontend/src/dispatch/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ const buildDispatchResolutionsDoc = (drs = []) => {

// forced entry
pdf.drawWrappedText({
text: `Forced Entry: ${assigned_request.visit_note?.forced_entry ? 'Yes' : 'No'}`
text: `Forced Entry Permission: ${assigned_request.visit_note?.forced_entry ? 'Yes' : 'No'}`
});

// key at staging (key provided)
Expand Down Expand Up @@ -215,6 +215,8 @@ const buildDispatchResolutionsDoc = (drs = []) => {
pdf.drawHRule();
}

pdf.setDocumentFontSize();

assigned_request.service_request_object.animals.filter(animal => Object.keys(assigned_request.animals).includes(String(animal.id))).forEach((animal) => {
// if very little page is left that would cause a weird break between the header, manually page break now
const estimatedReleaseSectionHeight = 106;
Expand Down Expand Up @@ -244,7 +246,7 @@ const buildDispatchResolutionsDoc = (drs = []) => {
pdf.drawList({
listItems: animalRow,
listStyle: 'inline',
bottomPadding: 10
bottomPadding: 5
});

pdf.setDocumentFontSize({ size: 10 });
Expand Down Expand Up @@ -318,19 +320,38 @@ const buildDispatchResolutionsDoc = (drs = []) => {
],
bottomPadding: 26
})
pdf.drawWrappedText({
text: 'Visit Notes:'
});

if (assigned_request.visit_note?.notes) {
pdf.drawWrappedText({
text: `${
text: `Visit Notes: ${
(assigned_request.visit_note?.notes && assigned_request.visit_note?.notes) || ''
}`
});
pdf.drawHRule();
}
else {
pdf.drawWrappedText({
text: `Visit Notes:`,
});
pdf.drawPad(-15);
pdf.drawTextArea({ rows: 4 });
}

if (assigned_request.visit_notes.length > 0) {
pdf.drawWrappedText({
text: `Previous Visit Notes`,
fontSize: 12
});
assigned_request.visit_notes.forEach((visit_note) => {
pdf.drawWrappedText({
text: `${moment(visit_note.date_completed).format(
'MMMM Do'
)}: ${(visit_note?.notes && visit_note?.notes) || 'No information available.'}`
});
pdf.drawHRule();
})
}
pdf.drawPad(-15);
pdf.drawTextArea({ rows: 4 });
pdf.drawCheckBoxLine({ label: 'Forced Entry' });
pdf.drawCheckBoxLine({ label: 'Forced Entry Permission' });

// owners contacted
if (assigned_request.service_request_object.owners.length) {
Expand Down
62 changes: 53 additions & 9 deletions frontend/src/hotline/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ function buildServiceRequestsDoc(srs = []) {
bottomPadding: 10
});

// forced entry
pdf.drawWrappedText({
text: `Forced Entry Permission: ${
data.assigned_requests?.find?.(
(ar) => ar.visit_note?.forced_entry === true
)
? 'Yes'
: 'No'
}`,
});

// follow up date
pdf.drawWrappedText({ text: `Followup Date: ${data.followup_date ? new Date(data.followup_date)?.toLocaleDateString?.() : 'Not set'}` });

Expand All @@ -60,6 +71,22 @@ function buildServiceRequestsDoc(srs = []) {

pdf.drawHRule();

// visit notes
data.assigned_requests?.forEach?.(({ visit_note }, i) => {
if (i === 0) {
pdf.drawSectionHeader({ text: 'Visit Notes' });
pdf.drawPad(20);
}

pdf.drawWrappedText({
text: `***${moment(visit_note.date_completed).format('MMMM Do')}:***${visit_note.forced_entry ? ' (Forced Entry)' : ''} ${
(visit_note?.notes && visit_note?.notes) || ''
}`,
});
});

pdf.drawHRule();

// contacts
pdf.drawSectionHeader({ text: `Contacts` });

Expand Down Expand Up @@ -122,15 +149,31 @@ function buildServiceRequestsDoc(srs = []) {
lastYPosBeforeDraw = pdf.getLastYPositionWithBuffer({ buffer: 0 });
}

let animalStatus;

switch (animal.status) {
case 'REPORTED (SIP REQUESTED)':
animalStatus = 'Reported (SIP Requested)';
break;
default:
animalStatus = `${capitalize(animal.status.toLowerCase(), { proper: true })}`;
}

const animalInfoList = [
`ID: A#${animal.id}`,
`Status: ${capitalize(animal.status.toLowerCase(), { proper: true })}`,
`Status: ${animalStatus}`,
`Name: ${animal.name || 'Unknown'}`,
`Species: ${capitalize(animal.species)}`,
`Sex: ${capitalize(animal.sex|| 'Unknown')}`,
`Age: ${capitalize(animal.age || 'Unknown')}`,
`Size: ${capitalize(animal.size || 'Unknown')}`,
`Primary Color: ${capitalize(animal.pcolor || 'N/A')}, Secondary Color: ${capitalize(animal.scolor || 'N/A')}`
`Primary Color: ${capitalize(animal.pcolor || 'N/A')}, Secondary Color: ${capitalize(animal.scolor || 'N/A')}`,
`Fixed: ${capitalize(animal.fixed || 'Unknown')}`,
`Aggressive: ${capitalize(animal.aggressive || 'Unknown')}`,
`ACO Required: ${capitalize(animal.aco_required || 'Unknown')}`,
`Confined: ${capitalize(animal.confined || 'Unknown')}`,
`Injured: ${capitalize(animal.injured || 'Unknown')}`,
`Last Seen: ${animal.last_seen ? moment(animal.last_seen).format('MMMM Do YYYY HH:mm') : 'Unknown'}`
];

pdf.drawTextList({
Expand All @@ -142,12 +185,10 @@ function buildServiceRequestsDoc(srs = []) {
pdf.drawPad();

// breed / description (color_notes)
if (animal.color_notes) {
pdf.drawWrappedText({
text: `Breed / Description: ${animal.color_notes}`,
linePadding: 0
});
}
pdf.drawWrappedText({
text: `Breed / Description: ${animal.color_notes}`,
linePadding: 0
});

// medical notes
if (animal.medical_notes) {
Expand Down Expand Up @@ -205,7 +246,10 @@ function printAllServiceRequests(srs = []) {
}

const printSrAnimalCareSchedules = async (animals = [], srId = 0) => {
const pdf = await buildAnimalCareScheduleDoc(animals);
// sort animals by id
const sortedAnimals = [...animals].sort((a,b) => a.id - b.id);

const pdf = await buildAnimalCareScheduleDoc(sortedAnimals);
pdf.fileName = `Shelterly-SR-Animal-Care-Schedules-${srId.toString().padStart(3, 0)}-${moment().format(DATE_FORMAT)}`;
return pdf.saveFile();
};
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/people/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,10 @@ async function printAllOwnersDetails(owners = []) {
}

const printOwnerAnimalCareSchedules = async (animals = [], ownerId = 0) => {
const pdf = await buildAnimalCareScheduleDoc(animals);
// sort animals by id
const sortedAnimals = [...animals].sort((a,b) => a.id - b.id);

const pdf = await buildAnimalCareScheduleDoc(sortedAnimals);
pdf.fileName = `Shelterly-Owner-Animal-Care-Schedules-${ownerId.toString().padStart(4, 0)}-${moment().format(DATE_FORMAT)}`;
return pdf.saveFile();
};
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/shelter/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import { DATE_FORMAT } from "../constants";
export { printOwnerDetails, printAllOwnersDetails } from "../people/Utils";

async function printAnimalCareSchedules(animals = [], id = 0, type = "Intake") {
const pdf = await buildAnimalCareScheduleDoc(animals);
// sort animals by id
const sortedAnimals = [...animals].sort((a,b) => a.id - b.id);

const pdf = await buildAnimalCareScheduleDoc(sortedAnimals);
pdf.fileName = `Shelterly-${type}-Animal-Care-Schedules-${id
.toString()
.padStart(4, 0)}-${moment().format(DATE_FORMAT)}`;
Expand Down

0 comments on commit 9c3f770

Please sign in to comment.