Skip to content

Commit

Permalink
Add DOB to client
Browse files Browse the repository at this point in the history
  • Loading branch information
georgehelman committed May 28, 2024
1 parent b689758 commit da09277
Show file tree
Hide file tree
Showing 12 changed files with 346 additions and 18 deletions.
34 changes: 32 additions & 2 deletions dear_petition/petition/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from dear_petition.petition.models import (
CIPRSRecord,
Contact,
Client,
Batch,
Offense,
OffenseRecord,
Expand Down Expand Up @@ -139,6 +140,7 @@ class Meta:
"zipcode",
"user",
"county",
"dob",
]

class ClientSerializer(ContactSerializer):
Expand All @@ -147,6 +149,24 @@ class ClientSerializer(ContactSerializer):
state = serializers.CharField(required=True)
zipcode = serializers.CharField(required=True)
user = serializers.PrimaryKeyRelatedField(read_only=True)
dob = serializers.DateField(required=False)

class Meta:
model = Client
fields = [
"pk",
"name",
"category",
"address1",
"address2",
"formatted_address",
"city",
"state",
"zipcode",
"user",
"county",
"dob",
]


class GeneratePetitionSerializer(serializers.Serializer):
Expand Down Expand Up @@ -288,7 +308,7 @@ class BatchSerializer(serializers.ModelSerializer):
petitions = PetitionSerializer(many=True, read_only=True)
generate_letter_errors = ValidationField(method_name='get_generate_errors_data', serializer=GenerateDocumentSerializer)
generate_summary_errors = ValidationField(method_name='get_generate_errors_data', serializer=GenerateDocumentSerializer)

def get_generate_errors_data(self, obj):
return {'batch': obj.pk}

Expand Down Expand Up @@ -316,9 +336,10 @@ class BatchDetailSerializer(serializers.ModelSerializer):
)
attorney = ContactSerializer(read_only=True)
client_id = serializers.PrimaryKeyRelatedField(
source='client', queryset=Contact.objects.filter(category='client'), write_only=True, required=False
source='client', queryset=Client.objects.all(), write_only=True, required=False
)
client = ClientSerializer(read_only=True)
client_errors = serializers.SerializerMethodField()

generate_letter_errors = ValidationField(method_name='get_generate_errors_data', serializer=GenerateDocumentSerializer)
generate_summary_errors = ValidationField(method_name='get_generate_errors_data', serializer=GenerateDocumentSerializer)
Expand All @@ -341,6 +362,7 @@ class Meta:
"client_id",
"generate_letter_errors",
"generate_summary_errors",
"client_errors"
]
read_only_fields = ["user", "pk", "date_uploaded", "records", "petitions"]

Expand All @@ -350,6 +372,14 @@ def get_petitions(self, instance):
batch=instance.pk,
).order_by("county", "jurisdiction")
return ParentPetitionSerializer(parent_petitions, many=True).data

def get_client_errors(self, instance):
errors = []
if not instance.client:
return errors
if not instance.client.dob:
errors.append("Date of birth missing. The petition generator will try its best to identify a date of birth from the records at time of petition creation.")
return errors


class MyInboxSerializer(serializers.ModelSerializer):
Expand Down
1 change: 1 addition & 0 deletions dear_petition/petition/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
router.register(r"offense", viewsets.OffenseViewSet)
router.register(r"offenserecord", viewsets.OffenseRecordViewSet)
router.register(r"contact", viewsets.ContactViewSet)
router.register(r"client", viewsets.ClientViewSet)
router.register(r"batch", viewsets.BatchViewSet)
router.register(r"petitions", viewsets.PetitionViewSet)
router.register(r"generatedpetition", viewsets.GeneratedPetitionViewSet)
Expand Down
9 changes: 9 additions & 0 deletions dear_petition/petition/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ def import_agencies(self, request):
resources.AgencyResource().import_data(dataset, raise_errors=True)
return Response({})

class ClientViewSet(ContactViewSet):
queryset = pm.Client.objects.all()
serializer_class = serializers.ClientSerializer

def get_queryset(self):
return pm.Client.objects.filter(user=self.request.user)

def perform_create(self, serializer):
serializer.save(user=self.request.user)


class BatchViewSet(viewsets.ModelViewSet):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Generated by Django 4.2.9 on 2024-05-26 17:02

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("petition", "0060_alter_contact_user"),
]

operations = [
migrations.CreateModel(
name="Client",
fields=[
(
"contact_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="petition.contact",
),
),
("dob", models.DateField(blank=True, null=True)),
],
bases=("petition.contact",),
),
migrations.AlterField(
model_name="generatedpetition",
name="form_type",
field=models.CharField(
choices=[
("AOC-CR-281", "AOC-CR-281"),
("AOC-CR-285", "AOC-CR-285"),
("AOC-CR-287", "AOC-CR-287"),
("AOC-CR-288", "AOC-CR-288"),
("AOC-CR-293", "AOC-CR-293"),
("AOC-CR-297", "AOC-CR-297"),
("AOC-CR-298", "AOC-CR-298"),
("Addendum 3B", "Addendum 3B"),
],
max_length=255,
),
),
migrations.AlterField(
model_name="petition",
name="agencies",
field=models.ManyToManyField(related_name="+", to="petition.contact"),
),
migrations.AlterField(
model_name="petition",
name="form_type",
field=models.CharField(
choices=[
("AOC-CR-281", "AOC-CR-281"),
("AOC-CR-285", "AOC-CR-285"),
("AOC-CR-287", "AOC-CR-287"),
("AOC-CR-288", "AOC-CR-288"),
("AOC-CR-293", "AOC-CR-293"),
("AOC-CR-297", "AOC-CR-297"),
("AOC-CR-298", "AOC-CR-298"),
("Addendum 3B", "Addendum 3B"),
],
max_length=255,
),
),
migrations.AlterField(
model_name="petitiondocument",
name="agencies",
field=models.ManyToManyField(related_name="+", to="petition.contact"),
),
migrations.AlterField(
model_name="petitiondocument",
name="form_type",
field=models.CharField(
choices=[
("AOC-CR-281", "AOC-CR-281"),
("AOC-CR-285", "AOC-CR-285"),
("AOC-CR-287", "AOC-CR-287"),
("AOC-CR-288", "AOC-CR-288"),
("AOC-CR-293", "AOC-CR-293"),
("AOC-CR-297", "AOC-CR-297"),
("AOC-CR-298", "AOC-CR-298"),
("Addendum 3B", "Addendum 3B"),
],
max_length=255,
),
),
migrations.AlterField(
model_name="batch",
name="client",
field=models.ForeignKey(
limit_choices_to={"category": "client"},
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="batches",
to="petition.client",
),
),
]
16 changes: 14 additions & 2 deletions dear_petition/petition/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from dear_petition.users.models import User
from dear_petition.users import constants as uc
from dear_petition.common.models import PrintableModelMixin
from dear_petition.petition.utils import resolve_dob
from . import constants as pc

from .constants import (
Expand Down Expand Up @@ -267,6 +268,7 @@ def get_queryset(self):
output_field=models.BooleanField(),
))
)


class Contact(PrintableModelMixin, models.Model):
name = models.CharField(max_length=512)
Expand Down Expand Up @@ -298,11 +300,21 @@ def __str__(self):

@classmethod
def get_sherriff_office_by_county(cls, county: str):
print(county)
qs = cls.agencies_with_sherriff_office.filter(county__iexact=county, is_sheriff_office=True)
if qs.count() > 1:
logger.error('Multiple agencies with sherriff department name detected. Picking first one...')
return qs.first() if qs.exists() else None


class Client(Contact):
dob = models.DateField(null=True, blank=True)

def save(self, *args, **kwargs):
# This is for backwards compatibility with existing data that pre-exists the multi-table inheritance paradigm
# TODO: Fully convert to multi-table inheritance paradigm
if self._state.adding:
self.category = "client"
super().save(*args, **kwargs)

class Batch(PrintableModelMixin, models.Model):

Expand All @@ -321,7 +333,7 @@ class Batch(PrintableModelMixin, models.Model):
on_delete=models.SET_NULL,
)
client = models.ForeignKey(
Contact,
Client,
related_name="batches",
null=True,
limit_choices_to={"category": "client"},
Expand Down
7 changes: 2 additions & 5 deletions dear_petition/petition/types/adult_felonies.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@ def get_offense_records(batch, jurisdiction=""):
qs = OffenseRecord.objects.filter(offense__ciprs_record__batch=batch)
if jurisdiction:
qs = qs.filter(offense__jurisdiction=jurisdiction)
dob = resolve_dob(qs)
if not dob:
return qs # We can't determine this petition type without the date of birth
query = build_query(dob)
query = build_query()
qs = qs.filter(query)
return qs.select_related("offense__ciprs_record__batch")


def build_query(dob):
def build_query():
action = Q(action=pc.CONVICTED)
severity = Q(severity__iexact=pc.SEVERITIES.FELONY)
today = timezone.now().date()
Expand Down
7 changes: 2 additions & 5 deletions dear_petition/petition/types/adult_misdemeanors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@ def get_offense_records(batch, jurisdiction=""):
qs = OffenseRecord.objects.filter(offense__ciprs_record__batch=batch)
if jurisdiction:
qs = qs.filter(offense__jurisdiction=jurisdiction)
dob = resolve_dob(qs)
if not dob:
return qs # We can't determine this petition type without the date of birth
query = build_query(dob)
query = build_query()
qs = qs.filter(query)
return qs.select_related("offense__ciprs_record__batch")


def build_query(dob):
def build_query():
action = Q(action=pc.CONVICTED)
severity = Q(severity__iexact=pc.SEVERITIES.MISDEMEANOR)
today = timezone.now().date()
Expand Down
2 changes: 1 addition & 1 deletion dear_petition/petition/types/underaged_convictions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def get_offense_records(batch, jurisdiction=""):

dob = resolve_dob(qs)
if not dob:
return qs # We can't determine this petition type without the date of birth
return OffenseRecord.objects.none() # We can't determine this petition type without the date of birth

if jurisdiction:
qs = qs.filter(offense__ciprs_record__jurisdiction=jurisdiction)
Expand Down
34 changes: 34 additions & 0 deletions src/components/elements/Input/FormDateInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useController } from 'react-hook-form';
import { AnimatePresence } from 'framer-motion';
import { InputWrapper, InputStyled, ActualInputStyled, InputErrors } from './Input.styled';

const FormDateInput = ({ className, label, errors, inputProps, ...restProps }) => {
const { field, fieldState } = useController(inputProps);
const { error: inputError } = fieldState;
const error = inputError ? (
<p>Invalid date</p>
) : (
// eslint-disable-next-line react/no-array-index-key
errors?.map((errMsg, i) => <p key={`${i}${errMsg}`}>{errMsg}</p>)
);
return (
<InputWrapper className={className}>
<InputStyled>{label}</InputStyled>
<ActualInputStyled type="date" {...field} {...restProps} />
{error && (
<AnimatePresence>
<InputErrors
initial={{ opacity: 0, y: -25 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: '50' }}
positionTransition
>
{error}
</InputErrors>
</AnimatePresence>
)}
</InputWrapper>
);
};

export default FormDateInput;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import AutocompleteInput from '../../../elements/Input/AutocompleteInput';
import { useLazySearchClientsQuery, useUpdateBatchMutation, useUpdateContactMutation } from '../../../../service/api';
import Button, { ModalButton } from '../../../elements/Button';
import { useModalContext } from '../../../elements/Button/ModalButton';
import { CreateContact } from '../../../../features/CreateContact';
import { CreateClient } from '../../../../features/CreateClient';

const TextInput = styled(Input)`
input {
Expand All @@ -27,7 +27,7 @@ export const CreateClientModal = ({ onCreate }) => {
const { closeModal } = useModalContext();
return (
<div className="w-[550px] px-40 py-20" ref={modalElement}>
<CreateContact onClose={closeModal} category="client" onSubmitSuccess={(submitData) => onCreate(submitData)} />
<CreateClient onClose={closeModal} category="client" onSubmitSuccess={(submitData) => onCreate(submitData)} />
</div>
);
};
Expand Down Expand Up @@ -57,7 +57,7 @@ export default function PetitionerInput({ petitioner, errors, onClearError }) {
const [editErrors, setEditErrors] = useState({});

const [petitionerData, setPetitionerData] = useState(getPetitionerData(petitioner));
const { name, ...address } = petitionerData;
const { name, dob, ...address } = petitionerData;

const addError = (key, error) => setEditErrors((prev) => ({ ...prev, [key]: [error] }));
const clearError = (key) => setEditErrors((prev) => ({ ...prev, [key]: [] }));
Expand Down Expand Up @@ -202,6 +202,14 @@ export default function PetitionerInput({ petitioner, errors, onClearError }) {
onClearError={onClearError}
disabled={!isEditing}
/>
<TextInput
label="Date of Birth"
value={dob}
onChange={(e) => setPetitionerData((prev) => ({ ...prev, name: e.target.value }))}
errors={isEditing && editErrors.name}
onClearError={onClearError}
disabled={!isEditing}
/>
<AddressInput
address={address}
setAddress={setPetitionerData}
Expand Down
Loading

0 comments on commit da09277

Please sign in to comment.