diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 5ff11a78228ca0b41391eb9512252edfb4c459cb..ad3e60bf4a48f4680879a02d95555feb3028f70c 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -17,6 +17,8 @@ "roleStartDate": "From", "roleEndDate": "To", "comment": "Comment", + "contact": "Contact person", + "searchable": "Available in search?", "email": "E-mail", "fullName": "Full name", "mobilePhone": "Mobile phone", @@ -25,14 +27,21 @@ "countryCallingCode": "Country code" }, "sponsor": { - "contactInfo": "Contact information", - "roleInfo": "Guest role- and period information", + "addRole": "Add role", "roleInfoText": "You can change the start and end dates for the role.", "choose": "Choose", "details": "Details", "modifyEnd": "Change end date", - "endNow": "End role", - "overviewGuest": "Guest overview" + "endNow": "End role" + }, + "guestInfo": { + "contactInfo": "Contact information", + "roleInfoHead": "Roles and periods", + "roleInfoBody": "You can only change roles that you have given" + }, + "guest": { + "headerText": "Add new role and period.", + "bodyText": "Here you can add a new role to the same guest" }, "register": { "registerHeading": "Register new guest", diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json index 4b2b4ae26ee9f88e176106e32ff593fa2bb02d76..41828b97a9f79695f3254220e07477295699658d 100644 --- a/frontend/public/locales/nb/common.json +++ b/frontend/public/locales/nb/common.json @@ -17,6 +17,8 @@ "roleStartDate": "Fra", "roleEndDate": "Til", "comment": "Kommentar", + "contact": "Kontaktperson", + "searchable": "Synlig i søk?", "email": "E-post", "fullName": "Fullt navn", "mobilePhone": "Mobilnummer", @@ -25,14 +27,21 @@ "countryCallingCode": "Landkode" }, "sponsor": { - "contactInfo": "Kontaktinformasjon", - "roleInfo": "Gjesterolle- og periodeinformasjon", + "addRole": "Legg til rolle", "roleInfoText": "Her kan du endre på start- og sluttdato for gjesterollen eller avslutte perioden", "choose": "Velg", "details": "Detaljer", "modifyEnd": "Endre sluttdato", - "endNow": "Avslutt rolle", - "overviewGuest": "Oversikt over gjest" + "endNow": "Avslutt rolle" + }, + "guestInfo": { + "contactInfo": "Kontaktinformasjon", + "roleInfoHead": "Roller og perioder", + "roleInfoBody": "Du kan bare endre på gjesteroller som du er vert for" + }, + "guest": { + "headerText": "Legg til ny rolle og periode", + "bodyText": "Her kan du legge til en ny rolle på samme gjest" }, "register": { "registerHeading": "Registrer ny gjest", diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json index 23ff342f4192359ebd8760f491d4d24b3fd326ad..af1b8aac7174d127b2ae632e014d7c24eec139f4 100644 --- a/frontend/public/locales/nn/common.json +++ b/frontend/public/locales/nn/common.json @@ -18,6 +18,8 @@ "roleStartDate": "Frå", "roleEndDate": "Til", "comment": "Kommentar", + "contact": "Kontaktperson", + "searchable": "Synleg i søk?", "email": "E-post", "fullName": "Fullt namn", "mobilePhone": "Mobilnummer", @@ -26,14 +28,21 @@ "countryCallingCode": "Landkode" }, "sponsor": { - "contactInfo": "Kontaktinformasjon", - "roleInfo": "Gjesterolle- og periodeinformasjon", + "addRole": "Legg til role", "roleInfoText": "Her kan du endre på start- og sluttdato for gjesterollen eller avslutte perioden", "choose": "Velg", "details": "Detaljer", "modifyEnd": "Endre sluttdato", - "endNow": "Avslutt rolle", - "overviewGuest": "Oversikt over gjest" + "endNow": "Avslutt rolle" + }, + "guestInfo": { + "contactInfo": "Kontaktinformasjon", + "roleInfoHead": "Roller og perioder", + "roleInfoBody": "Du kan bare endre på gjesteroller som du er vert for" + }, + "guest": { + "headerText": "Legg til ny rolle og periode", + "bodyText": "Her kan du legge til en ny rolle på samme gjest" }, "register": { "registerHeading": "Registrer ny gjest", diff --git a/frontend/src/hooks/useOus/index.tsx b/frontend/src/hooks/useOus/index.tsx index 64c12dba296b0065935a9415892ecd6c21f672e7..01459176acb55d3eeada3c9271b7ffc640d6b0b8 100644 --- a/frontend/src/hooks/useOus/index.tsx +++ b/frontend/src/hooks/useOus/index.tsx @@ -22,5 +22,25 @@ function useOus(): OuData[] { return ous } +export const enSort = (a: OuData, b: OuData) => { + if (a.en > b.en) { + return 1 + } + if (b.en > a.en) { + return -1 + } + return 0 +} + +export const nbSort = (a: OuData, b: OuData) => { + if (a.nb > b.nb) { + return 1 + } + if (b.nb > a.nb) { + return -1 + } + return 0 +} + export type { OuData } export default useOus diff --git a/frontend/src/hooks/useRoleTypes/index.tsx b/frontend/src/hooks/useRoleTypes/index.tsx index 7e915decd861bba6c78ac923dd85a524c4717f89..0a61d01311038623c0769937dbbfc6e6703fc21c 100644 --- a/frontend/src/hooks/useRoleTypes/index.tsx +++ b/frontend/src/hooks/useRoleTypes/index.tsx @@ -5,6 +5,7 @@ type RoleTypeData = { identifier: string name_en: string name_nb: string + max_days: number } function useRoleTypes(): RoleTypeData[] { diff --git a/frontend/src/routes/components/sponsorInfoButtons.tsx b/frontend/src/routes/components/sponsorInfoButtons.tsx index 65c1d5e8ce44c8bd50d03be2f2d41162d63bd3b6..ce50eb18df2b7452bd7c5c2d215de70ec2bbad73 100644 --- a/frontend/src/routes/components/sponsorInfoButtons.tsx +++ b/frontend/src/routes/components/sponsorInfoButtons.tsx @@ -2,14 +2,16 @@ import { IconButton, Theme, Box } from '@mui/material' import { Link } from 'react-router-dom' import PersonOutlineRoundedIcon from '@mui/icons-material/PersonOutlineRounded' import ArrowBackIcon from '@mui/icons-material/ArrowBack' -import { useTranslation } from 'react-i18next' interface SponsorInfoButtonsProps { to: string + name: string } -export default function SponsorInfoButtons({ to }: SponsorInfoButtonsProps) { - const { t } = useTranslation(['common']) +export default function SponsorInfoButtons({ + to, + name, +}: SponsorInfoButtonsProps) { return ( <Box sx={{ @@ -46,7 +48,7 @@ export default function SponsorInfoButtons({ to }: SponsorInfoButtonsProps) { typography: 'caption', }} > - {t('sponsor.overviewGuest')} + {name} </Box> </Box> </Box> diff --git a/frontend/src/routes/sponsor/guestInfo/index.tsx b/frontend/src/routes/sponsor/guest/guestInfo/index.tsx similarity index 62% rename from frontend/src/routes/sponsor/guestInfo/index.tsx rename to frontend/src/routes/sponsor/guest/guestInfo/index.tsx index ea8dad32642d6d381082de3b8d5258c3aa4acfbf..139e128465ec003b0200749ad59cc37980185557 100644 --- a/frontend/src/routes/sponsor/guestInfo/index.tsx +++ b/frontend/src/routes/sponsor/guest/guestInfo/index.tsx @@ -1,4 +1,3 @@ -import React, { useEffect, useState } from 'react' import { Link, useParams } from 'react-router-dom' import Page from 'components/page' @@ -13,10 +12,9 @@ import { TableRow, Paper, } from '@mui/material' -import { Guest, Role, FetchedRole } from 'interfaces' +import { Guest, Role } from 'interfaces' import SponsorInfoButtons from 'routes/components/sponsorInfoButtons' import { format } from 'date-fns' -import { parseRole } from 'utils' type GuestInfoParams = { pid: string @@ -25,14 +23,15 @@ interface RoleLineProps { role: Role pid: string } +type GuestInfoProps = { + guest: Guest + roles: Role[] +} const RoleLine = ({ role, pid }: RoleLineProps) => { const [t, i18n] = useTranslation('common') return ( - <TableRow - key={role.id} - sx={{ '&:last-child td, &:last-child th': { border: 0 } }} - > + <TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> <TableCell align="left"> {i18n.language === 'en' ? role.name_en : role.name_nb} </TableCell> @@ -56,60 +55,19 @@ const RoleLine = ({ role, pid }: RoleLineProps) => { ) } -export default function GuestInfo() { +export default function GuestInfo({ guest, roles }: GuestInfoProps) { const { pid } = useParams<GuestInfoParams>() const [t] = useTranslation(['common']) - const [guestInfo, setGuest] = useState<Guest>({ - pid: '', - first: '', - last: '', - email: '', - fnr: '', - mobile: '', - active: false, - registered: false, - verified: false, - roles: [], - }) - const [roles, setRoles] = useState<Role[]>([]) - - const getPerson = async (id: string) => { - try { - const response = await fetch(`/api/ui/v1/person/${id}`) - const rjson = await response.json() - if (response.ok) { - setGuest({ - pid: rjson.pid, - first: rjson.first, - last: rjson.last, - email: rjson.email, - mobile: rjson.mobile, - fnr: rjson.fnr, - active: rjson.active, - registered: rjson.registered, - verified: rjson.verified, - roles: rjson.roles, - }) - setRoles(rjson.roles.map((role: FetchedRole) => parseRole(role))) - } - } catch (error) { - console.error(error) - } - } - - useEffect(() => { - getPerson(pid) - }, []) return ( <Page> - <SponsorInfoButtons to="/sponsor" /> - <h4>{t('sponsor.contactInfo')}</h4> + <SponsorInfoButtons to="/sponsor" name={`${guest.first} ${guest.last}`} /> + <h4>{t('guestInfo.contactInfo')}</h4> <TableContainer component={Paper}> <Table sx={{ minWidth: 650 }} aria-label="simple table"> <TableHead sx={{ backgroundColor: 'primary.light' }}> <TableRow> - <TableCell align="left">{t('sponsor.contactInfo')}</TableCell> + <TableCell align="left">{t('guestInfo.contactInfo')}</TableCell> <TableCell /> </TableRow> </TableHead> @@ -117,25 +75,36 @@ export default function GuestInfo() { <TableRow> <TableCell align="left">{t('input.fullName')}</TableCell> <TableCell align="left"> - {`${guestInfo.first} ${guestInfo.last}`} + {`${guest.first} ${guest.last}`} </TableCell> </TableRow> <TableRow> <TableCell align="left">{t('input.email')}</TableCell> - <TableCell align="left">{guestInfo.email}</TableCell> + <TableCell align="left">{guest.email}</TableCell> </TableRow> <TableRow> <TableCell align="left">{t('input.nationalIdNumber')}</TableCell> - <TableCell align="left">{guestInfo.fnr}</TableCell> + <TableCell align="left">{guest.fnr}</TableCell> </TableRow> <TableRow> <TableCell align="left">{t('input.mobilePhone')}</TableCell> - <TableCell align="left">{guestInfo.mobile}</TableCell> + <TableCell align="left">{guest.mobile}</TableCell> </TableRow> </TableBody> </Table> </TableContainer> - <h4>{t('sponsor.roleInfo')}</h4> + <h4>{t('guestInfo.roleInfoHead')}</h4> + <h5> + {t('guestInfo.roleInfoBody')} + <Button + variant="contained" + component={Link} + to={`/sponsor/guest/${pid}/newrole`} + > + {t('sponsor.addRole')} + </Button> + </h5> + <TableContainer component={Paper}> <Table sx={{ minWidth: 650 }} aria-label="simple table"> <TableHead sx={{ backgroundColor: 'primary.light' }}> @@ -148,7 +117,7 @@ export default function GuestInfo() { </TableHead> <TableBody> {roles.map((role) => ( - <RoleLine pid={pid} role={role} /> + <RoleLine key={role.id} pid={pid} role={role} /> ))} </TableBody> </Table> diff --git a/frontend/src/routes/sponsor/guestRoleInfo/index.tsx b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx similarity index 90% rename from frontend/src/routes/sponsor/guestRoleInfo/index.tsx rename to frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx index fed60946e6e9cb66f1af5518f2e2f2d1582609d4..6445c4ac23c5778e3b2707eba9865ce5a389cf42 100644 --- a/frontend/src/routes/sponsor/guestRoleInfo/index.tsx +++ b/frontend/src/routes/sponsor/guest/guestRoleInfo/index.tsx @@ -12,16 +12,17 @@ import { TextField, } from '@mui/material' import Page from 'components/page' -import { Guest } from 'interfaces' +import { Guest, Role } from 'interfaces' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' import SponsorInfoButtons from 'routes/components/sponsorInfoButtons' import { DatePicker } from '@mui/lab' import { Controller, SubmitHandler, useForm } from 'react-hook-form' -import { submitJsonOpts } from '../../../utils' +import { submitJsonOpts } from '../../../../utils' interface GuestRoleInfoProps { - guests: Guest[] + guest: Guest + roles: Role[] } const endPeriodPost = (id: string, data: { end_date: Date }) => { const payload = { @@ -56,15 +57,12 @@ type RoleFormData = { end_date: Date } -export default function GuestRoleInfo({ guests }: GuestRoleInfoProps) { +export default function GuestRoleInfo({ guest, roles }: GuestRoleInfoProps) { const { pid, id } = useParams<GuestRoleInfoParams>() const [t, i18n] = useTranslation('common') // Find the role info relevant for this page - const guestInfo = guests.filter((guest) => guest.pid.toString() === pid)[0] - const roleInfo = guestInfo.roles.filter( - (role) => role.id.toString() === id - )[0] + const roleInfo = roles.filter((role) => role.id.toString() === id)[0] // Prepare min and max date values const today = new Date() @@ -106,10 +104,12 @@ export default function GuestRoleInfo({ guests }: GuestRoleInfoProps) { const { control, handleSubmit } = useForm() const onSubmit = handleSubmit(submit) - return ( <Page> - <SponsorInfoButtons to={`/sponsor/guest/${pid}`} /> + <SponsorInfoButtons + to={`/sponsor/guest/${pid}`} + name={`${guest.first} ${guest.last}`} + /> <h4>{t('sponsor.roleInfoText')}</h4> <form onSubmit={onSubmit}> <TableContainer component={Paper}> @@ -139,9 +139,7 @@ export default function GuestRoleInfo({ guests }: GuestRoleInfoProps) { render={({ field: { onChange, value } }) => ( <DatePicker mask="____-__-__" - disabled={ - roleInfo.start_date.getDate() <= today.getDate() - } + disabled={roleInfo.start_date <= today} label={t('input.roleStartDate')} value={value} minDate={today} @@ -162,7 +160,7 @@ export default function GuestRoleInfo({ guests }: GuestRoleInfoProps) { <DatePicker mask="____-__-__" label={t('input.roleEndDate')} - disabled={roleInfo.end_date.getDate() < today.getDate()} + disabled={roleInfo.end_date < today} minDate={today} maxDate={todayPlusMaxDays} value={value} diff --git a/frontend/src/routes/sponsor/guest/index.tsx b/frontend/src/routes/sponsor/guest/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..81152184d3bc670acc3b3ffed86b7dedc8972310 --- /dev/null +++ b/frontend/src/routes/sponsor/guest/index.tsx @@ -0,0 +1,73 @@ +import { FetchedRole, Guest, Role } from 'interfaces' +import { useEffect, useState } from 'react' +import { Route, useParams } from 'react-router-dom' +import { parseRole } from 'utils' +import GuestInfo from './guestInfo' +import GuestRoleInfo from './guestRoleInfo' +import NewGuestRole from './newGuestRole' + +type GuestInfoParams = { + pid: string +} + +function GuestRoutes() { + const { pid } = useParams<GuestInfoParams>() + + const [guestInfo, setGuest] = useState<Guest>({ + pid: '', + first: '', + last: '', + email: '', + fnr: '', + mobile: '', + active: false, + registered: false, + verified: false, + roles: [], + }) + const [roles, setRoles] = useState<Role[]>([]) + + const getPerson = async (id: string) => { + try { + const response = await fetch(`/api/ui/v1/person/${id}`) + const rjson = await response.json() + if (response.ok) { + setGuest({ + pid: rjson.pid, + first: rjson.first, + last: rjson.last, + email: rjson.email, + mobile: rjson.mobile, + fnr: rjson.fnr, + active: rjson.active, + registered: rjson.registered, + verified: rjson.verified, + roles: rjson.roles, + }) + setRoles(rjson.roles.map((role: FetchedRole) => parseRole(role))) + } + } catch (error) { + console.error(error) + } + } + + useEffect(() => { + getPerson(pid) + }, []) + + return ( + <> + <Route path="/sponsor/guest/:pid/roles/:id"> + <GuestRoleInfo guest={guestInfo} roles={roles} /> + </Route> + <Route exact path="/sponsor/guest/:pid/newrole"> + <NewGuestRole guest={guestInfo} /> + </Route> + <Route exact path="/sponsor/guest/:pid"> + <GuestInfo guest={guestInfo} roles={roles} /> + </Route> + </> + ) +} + +export default GuestRoutes diff --git a/frontend/src/routes/sponsor/guest/newGuestRole/index.tsx b/frontend/src/routes/sponsor/guest/newGuestRole/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2cf41c37ebcf0651aec1033c21fde36c3f42f28b --- /dev/null +++ b/frontend/src/routes/sponsor/guest/newGuestRole/index.tsx @@ -0,0 +1,274 @@ +import { DatePicker } from '@mui/lab' +import { addDays } from 'date-fns/fp' +import { + Checkbox, + Button, + Select, + FormControl, + InputLabel, + MenuItem, + Stack, + TextField, + SelectChangeEvent, + FormControlLabel, +} from '@mui/material' +import Page from 'components/page' +import { format } from 'date-fns' +import useOus, { enSort, nbSort, OuData } from 'hooks/useOus' +import useRoleTypes, { RoleTypeData } from 'hooks/useRoleTypes' +import { Guest } from 'interfaces' +import { useState } from 'react' +import { Controller, useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { Link, useParams } from 'react-router-dom' +import SponsorInfoButtons from 'routes/components/sponsorInfoButtons' +import { submitJsonOpts } from 'utils' + +type AddRoleFormData = { + orgunit: number + type: string + end_date: Date + start_date?: Date + contact_person_unit?: string + comments?: string + available_in_search?: boolean +} +type AddRolePayload = { + orgunit: number + person: string + type: string + end_date: string + start_date?: string + contact_person_unit?: string + comments?: string + available_in_search?: boolean +} + +type GuestInfoParams = { + pid: string +} + +interface NewGuestRoleProps { + guest: Guest +} + +const postRole = (formData: AddRoleFormData, pid: string) => { + const payload: AddRolePayload = { + orgunit: formData.orgunit, + person: pid, + type: formData.type, + end_date: format(formData.end_date as Date, 'yyyy-MM-dd'), + } + if (formData.start_date) { + payload.start_date = format(formData.start_date as Date, 'yyyy-MM-dd') + } + if (formData.contact_person_unit) { + payload.contact_person_unit = formData.contact_person_unit + } + if (formData.comments) { + payload.comments = formData.comments + } + if (formData.available_in_search) { + payload.available_in_search = formData.available_in_search + } + + console.log('submitting', JSON.stringify(payload)) + fetch('/api/ui/v1/role', submitJsonOpts('POST', payload)) + .then((res) => { + if (!res.ok) { + console.log('result', res) + return null + } + console.log('result', res) + return res.text() + }) + .then((result) => { + if (result !== null) { + console.log('result', result) + } + }) + .catch((error) => { + console.log('error', error) + }) +} + +function NewGuestRole({ guest }: NewGuestRoleProps) { + const { + register, + control, + handleSubmit, + formState: { errors }, + setValue, + getValues, + } = useForm<AddRoleFormData>() + + const { pid } = useParams<GuestInfoParams>() + const onSubmit = handleSubmit(() => { + postRole(getValues(), pid) + }) + + const ous = useOus() + const roleTypes = useRoleTypes() + const [ouChoice, setOuChoice] = useState<string>('') + const [roleTypeChoice, setRoleTypeChoice] = useState<string>('') + const [t, i18n] = useTranslation('common') + const today = new Date() + + const todayPlusMaxDays = () => { + if (roleTypeChoice) { + const role = roleTypes.filter( + (rt) => rt.id.toString() === roleTypeChoice.toString() + )[0] + return addDays(role.max_days)(today) + } + return addDays(0)(today) + } + + const roleTypeSort = () => (a: RoleTypeData, b: RoleTypeData) => { + if (i18n.language === 'en') { + return a.name_nb.localeCompare(b.name_nb) + } + return a.name_en.localeCompare(b.name_en) + } + // Handling choices in menus + const handleRoleTypeChange = (event: SelectChangeEvent) => { + setValue('type', event.target.value) + setRoleTypeChoice(event.target.value) + } + const handleOuChange = (event: SelectChangeEvent) => { + if (event.target.value) { + setOuChoice(event.target.value) + setValue('orgunit', parseInt(event.target.value, 10)) + } + } + // Functions for menu items + const rolesToItem = (roleType: RoleTypeData) => ( + <MenuItem key={roleType.id.toString()} value={roleType.id}> + {i18n.language === 'en' ? roleType.name_en : roleType.name_nb} + </MenuItem> + ) + const ouToItem = (ou: OuData) => ( + <MenuItem key={ou.id.toString()} value={ou.id}> + {i18n.language === 'en' ? ou.en : ou.nb} ({ou.id}) + </MenuItem> + ) + + return ( + <Page> + <SponsorInfoButtons + to={`/sponsor/guest/${pid}`} + name={`${guest.first} ${guest.last}`} + /> + <h3>{t('guest.headerText')}</h3> + <h4>{t('guest.bodyText')}</h4> + <form onSubmit={onSubmit}> + <Stack spacing={2}> + <FormControl> + <InputLabel id="ou-select-label">{t('input.roleType')}</InputLabel> + <Select + id="roletype-select" + defaultValue="" + value={roleTypeChoice} + error={!!errors.type} + label={t('input.roleType')} + onChange={handleRoleTypeChange} + > + {roleTypes.sort(roleTypeSort()).map((rt) => rolesToItem(rt))} + </Select> + </FormControl> + + <FormControl> + <InputLabel id="ou-select-label">{t('common:ou')}</InputLabel> + <Select + labelId="ou-select-label" + id="ou-select-label" + defaultValue="" + value={ouChoice.toString()} + label={t('common:ou')} + onChange={handleOuChange} + > + {ous.length > 0 ? ( + ous + .sort(i18n.language === 'en' ? enSort : nbSort) + .map((ou) => ouToItem(ou)) + ) : ( + <></> + )} + </Select> + </FormControl> + <Controller + name="start_date" + control={control} + defaultValue={today} + render={({ field }) => ( + <DatePicker + mask="____-__-__" + label={t('input.roleStartDate')} + disabled={!roleTypeChoice} + value={field.value} + minDate={today} + maxDate={todayPlusMaxDays()} + inputFormat="yyyy-MM-dd" + onChange={(value) => { + field.onChange(value) + }} + renderInput={(params) => <TextField {...params} />} + /> + )} + /> + <Controller + name="end_date" + control={control} + defaultValue={today} + render={({ field }) => ( + <DatePicker + mask="____-__-__" + label={t('input.roleEndDate')} + disabled={!roleTypeChoice} + value={field.value} + minDate={today} + maxDate={todayPlusMaxDays()} + inputFormat="yyyy-MM-dd" + onChange={(value) => { + field.onChange(value) + }} + renderInput={(params) => <TextField {...params} />} + /> + )} + /> + + <TextField + id="contact" + label={t('input.contact')} + multiline + rows={5} + {...register('contact_person_unit')} + /> + <TextField + id="comments" + label={t('input.comment')} + multiline + rows={5} + {...register('comments')} + /> + <FormControlLabel + control={ + <Checkbox + id="available_in_search" + {...register('available_in_search')} + /> + } + label={t('input.searchable')} + /> + <Button variant="contained" type="submit"> + {t('button.save')} + </Button> + <Button component={Link} to={`/sponsor/guest/${pid}`}> + {t('button.cancel')} + </Button> + </Stack> + </form> + </Page> + ) +} +export default NewGuestRole diff --git a/frontend/src/routes/sponsor/index.tsx b/frontend/src/routes/sponsor/index.tsx index 1df6b21dc7f462e8dfc369de0f765c732191ab89..d070e2b28d5d4cef3fb69234c66d4c6335ebd40f 100644 --- a/frontend/src/routes/sponsor/index.tsx +++ b/frontend/src/routes/sponsor/index.tsx @@ -1,11 +1,10 @@ -import React, { useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { Route } from 'react-router-dom' import FrontPage from 'routes/sponsor/frontpage' -import GuestInfo from 'routes/sponsor/guestInfo' -import GuestRoleInfo from 'routes/sponsor/guestRoleInfo' import { FetchedGuest, Guest } from 'interfaces' import { parseRole } from 'utils' +import GuestRoutes from './guest' function Sponsor() { const [guests, setGuests] = useState<Guest[]>([]) @@ -44,11 +43,8 @@ function Sponsor() { return ( <> - <Route path="/sponsor/guest/:pid/roles/:id"> - <GuestRoleInfo guests={guests} /> - </Route> - <Route exact path="/sponsor/guest/:pid"> - <GuestInfo /> + <Route path="/sponsor/guest/:pid"> + <GuestRoutes /> </Route> <Route exact path="/sponsor"> <FrontPage guests={guests} /> diff --git a/frontend/src/routes/sponsor/register/stepPersonForm.tsx b/frontend/src/routes/sponsor/register/stepPersonForm.tsx index 9c45ce7d8ca66eb7e27e4c02b2a68944b217581e..bbfd2431a8f3b5a200e865dd451bd7f203a08fa4 100644 --- a/frontend/src/routes/sponsor/register/stepPersonForm.tsx +++ b/frontend/src/routes/sponsor/register/stepPersonForm.tsx @@ -11,7 +11,7 @@ import { } from '@mui/material' import { Controller, SubmitHandler, useForm } from 'react-hook-form' import { DatePicker } from '@mui/lab' -import React, { +import { forwardRef, Ref, useEffect, @@ -21,7 +21,7 @@ import React, { import { useTranslation } from 'react-i18next' import { RegisterFormData } from './formData' import { PersonFormMethods } from './personFormMethods' -import useOus, { OuData } from '../../../hooks/useOus' +import useOus, { enSort, nbSort } from '../../../hooks/useOus' import useRoleTypes, { RoleTypeData } from '../../../hooks/useRoleTypes' import { isValidEmail } from '../../../utils' @@ -51,26 +51,6 @@ const StepPersonForm = forwardRef( return a.name_en.localeCompare(b.name_en) } - const enSort = (a: OuData, b: OuData) => { - if (a.en > b.en) { - return 1 - } - if (b.en > a.en) { - return -1 - } - return 0 - } - - const nbSort = (a: OuData, b: OuData) => { - if (a.nb > b.nb) { - return 1 - } - if (b.nb > a.nb) { - return -1 - } - return 0 - } - const submit: SubmitHandler<RegisterFormData> = (data) => { nextHandler(data) } diff --git a/frontend/src/routes/sponsor/register/stepRegistration.tsx b/frontend/src/routes/sponsor/register/stepRegistration.tsx index a93b29c9d16d52c50aa965f9de4edbcd4a8769a3..8e4bd716e21d118d6f5c2dfdafad4f01ffe38319 100644 --- a/frontend/src/routes/sponsor/register/stepRegistration.tsx +++ b/frontend/src/routes/sponsor/register/stepRegistration.tsx @@ -71,7 +71,7 @@ export default function StepRegistration() { ? null : format(formData.role_end as Date, 'yyyy-MM-dd'), comments: formData.comment, - orgunit_id: formData.ou_id, + orgunit: formData.ou_id, }, } diff --git a/greg/api/serializers/person.py b/greg/api/serializers/person.py index 93958a212eb20b22d59cab74bd0db91b384bc5e0..16d362f73b8813d14638fbcb3ca0addce87e8bd4 100644 --- a/greg/api/serializers/person.py +++ b/greg/api/serializers/person.py @@ -15,8 +15,8 @@ class RoleSerializer(serializers.ModelSerializer): "id", "start_date", "end_date", - "sponsor_id", - "orgunit_id", + "sponsor", + "orgunit", "created", "updated", "type", diff --git a/greg/migrations/0011_role_remove_id_suffix.py b/greg/migrations/0011_role_remove_id_suffix.py new file mode 100644 index 0000000000000000000000000000000000000000..8af30b8ccef16ec30d0fc3330f96042140e57663 --- /dev/null +++ b/greg/migrations/0011_role_remove_id_suffix.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.8 on 2021-10-26 14:43 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('greg', '0010_roletype_max_days'), + ] + + operations = [ + migrations.RenameField( + model_name='role', + old_name='orgunit_id', + new_name='orgunit', + ), + migrations.RenameField( + model_name='role', + old_name='sponsor_id', + new_name='sponsor', + ), + ] diff --git a/greg/models.py b/greg/models.py index f0c4876fe57409693afd5e189ad75f37d9cd8d57..a57558b7d2c8f1e436aeaaba6b0418ffe6f287f2 100644 --- a/greg/models.py +++ b/greg/models.py @@ -203,7 +203,7 @@ class Role(BaseModel): type = models.ForeignKey( "RoleType", on_delete=models.PROTECT, related_name="persons" ) - orgunit_id = models.ForeignKey( + orgunit = models.ForeignKey( "OrganizationalUnit", on_delete=models.PROTECT, related_name="unit_person_role" ) # The start date can be null for people that are already @@ -213,7 +213,7 @@ class Role(BaseModel): contact_person_unit = models.TextField(blank=True) comments = models.TextField(blank=True) available_in_search = models.BooleanField(default=False) - sponsor_id = models.ForeignKey( + sponsor = models.ForeignKey( "Sponsor", on_delete=models.PROTECT, related_name="sponsor_role" ) diff --git a/greg/tests/api/test_person.py b/greg/tests/api/test_person.py index 9a09193c4f98c5ac9135afd24bf4bf641273ae10..791bd9372de61a6f170d4fe41335576b62b93189 100644 --- a/greg/tests/api/test_person.py +++ b/greg/tests/api/test_person.py @@ -55,8 +55,8 @@ def role_data_guest( "type": "Test Guest", "start_date": "2021-06-10", "end_date": "2021-08-10", - "sponsor_id": sponsor_bar.id, - "orgunit_id": unit_foo.id, + "sponsor": sponsor_bar.id, + "orgunit": unit_foo.id, } @@ -131,8 +131,8 @@ def test_add_role( "type": "visiting_professor", "start_date": "2021-06-10", "end_date": "2021-08-10", - "sponsor_id": "1", - "orgunit_id": "1", + "sponsor": "1", + "orgunit": "1", } response = client.post(url, role_data) @@ -425,8 +425,8 @@ def test_add_duplicate_role_fails(client, person_foo: Person, role_person_foo): "type": role_person_foo.type.identifier, "start_date": role_person_foo.start_date, "end_date": role_person_foo.end_date, - "sponsor_id": role_person_foo.sponsor_id.id, - "orgunit_id": role_person_foo.unit_id, + "sponsor": role_person_foo.sponsor.id, + "orgunit": role_person_foo.unit_id, } response = client.post(url, role_data) # If the role cannot be create the return code is 400 @@ -453,8 +453,8 @@ def test_filter_active_includes_person_with_active_role( person=person_foo, type=role_type_test_guest, end_date=date_today + datetime.timedelta(days=1), - sponsor_id=sponsor_guy, - orgunit_id=unit_foo, + sponsor=sponsor_guy, + orgunit=unit_foo, ) url = reverse("v1:person-list") @@ -491,8 +491,8 @@ def test_filter_active_value_false( person=person_foo, type=role_type_test_guest, end_date=date_today - datetime.timedelta(days=1), - sponsor_id=sponsor_guy, - orgunit_id=unit_foo, + sponsor=sponsor_guy, + orgunit=unit_foo, ) url = reverse("v1:person-list") diff --git a/greg/tests/conftest.py b/greg/tests/conftest.py index 1998517c8c42c728e25053a4c150606aef750146..1650c63b2a715b0e0db8bc6385afa8b538cc2141 100644 --- a/greg/tests/conftest.py +++ b/greg/tests/conftest.py @@ -152,8 +152,8 @@ def role_person_foo( type=role_type_test_guest, start_date="2021-08-02", end_date="2021-08-06", - sponsor_id=sponsor_guy, - orgunit_id=unit_foo, + sponsor=sponsor_guy, + orgunit=unit_foo, ) return Role.objects.get(id=role.id) diff --git a/greg/tests/models/test_person.py b/greg/tests/models/test_person.py index 396b0f0e5e3f1fe994ca70732e470cf7cda364f8..39be76df70748589eaaaf9752b2b6f84bc537ff1 100644 --- a/greg/tests/models/test_person.py +++ b/greg/tests/models/test_person.py @@ -98,14 +98,14 @@ def test_add_multiple_roles_to_person( role_with( person=person, type=role_type_foo, - orgunit_id=ou, - sponsor_id=Sponsor.objects.create(feide_id="foosponsor@uio.no"), + orgunit=ou, + sponsor=Sponsor.objects.create(feide_id="foosponsor@uio.no"), ) role_with( person=person, type=role_type_bar, - orgunit_id=ou, - sponsor_id=Sponsor.objects.create(feide_id="barsponsor@uio.no"), + orgunit=ou, + sponsor=Sponsor.objects.create(feide_id="barsponsor@uio.no"), ) assert person.roles.count() == 2 diff --git a/greg/tests/test_expire_role.py b/greg/tests/test_expire_role.py index 77ca940cf723736b657488e7975c003d7d25e307..0eda7855840c161104dade3cf64ed9cc71542197 100644 --- a/greg/tests/test_expire_role.py +++ b/greg/tests/test_expire_role.py @@ -33,12 +33,12 @@ def role( return Role.objects.create( person=person, type=role_type_bar, - orgunit_id=unit_foo, + orgunit=unit_foo, start_date="2020-03-05", end_date=datetime.today() + timedelta(days=30), contact_person_unit="Contact Person", available_in_search=True, - sponsor_id=sponsor_guy, + sponsor=sponsor_guy, ) diff --git a/greg/tests/test_notifications.py b/greg/tests/test_notifications.py index ad1475887036ff6ea785e2df5f3a5ad8598e5e42..290fb5cf0142ff4416ff6acee9d2145cd3511db8 100644 --- a/greg/tests/test_notifications.py +++ b/greg/tests/test_notifications.py @@ -73,8 +73,8 @@ def test_role_add_notification( type=role_type_foo, start_date="2021-05-06", end_date="2021-10-20", - orgunit_id=org_unit_bar, - sponsor_id=sponsor, + orgunit=org_unit_bar, + sponsor=sponsor, ) notifications = Notification.objects.filter(object_type="Role") assert len(notifications) == 1 @@ -97,8 +97,8 @@ def test_role_update_notification( type=role_type_foo, start_date="2021-05-06", end_date="2021-10-20", - orgunit_id=org_unit_bar, - sponsor_id=sponsor, + orgunit=org_unit_bar, + sponsor=sponsor, ) assert len(person.roles.all()) == 1 person_role = person.roles.all()[0] @@ -125,8 +125,8 @@ def test_role_delete_notification( type=role_type_foo, start_date="2021-05-06", end_date="2021-10-20", - orgunit_id=org_unit_bar, - sponsor_id=sponsor, + orgunit=org_unit_bar, + sponsor=sponsor, ) assert len(person.roles.all()) == 1 person_role = person.roles.all()[0] diff --git a/gregui/api/serializers/invitation.py b/gregui/api/serializers/invitation.py index b1dcda9e88b24dee3cc8f1930fdc4c3a9f5d4c49..18aa892677febf3360f76e90bdd1e70460ed2dca 100644 --- a/gregui/api/serializers/invitation.py +++ b/gregui/api/serializers/invitation.py @@ -5,13 +5,13 @@ from django.utils import timezone from rest_framework import serializers from greg.models import Invitation, InvitationLink, Person, Role, Identity -from gregui.api.serializers.role import RoleSerializerUi +from gregui.api.serializers.role import InviteRoleSerializerUi from gregui.models import GregUserProfile class InviteGuestSerializer(serializers.ModelSerializer): email = serializers.EmailField(required=True) - role = RoleSerializerUi(required=True) + role = InviteRoleSerializerUi(required=True) uuid = serializers.UUIDField(read_only=True) def create(self, validated_data): @@ -30,7 +30,7 @@ class InviteGuestSerializer(serializers.ModelSerializer): source="greg", ) role_data["person"] = person - role_data["sponsor_id"] = user.sponsor + role_data["sponsor"] = user.sponsor role = Role.objects.create(**role_data) invitation = Invitation.objects.create(role=role) InvitationLink.objects.create( diff --git a/gregui/api/serializers/role.py b/gregui/api/serializers/role.py index 26f3db97864d23fdbc4465e23b1d24a4289f5b5a..722c8e8f4a71f8387d525093d766c778263b8b60 100644 --- a/gregui/api/serializers/role.py +++ b/gregui/api/serializers/role.py @@ -1,49 +1,104 @@ import datetime from rest_framework import serializers +from rest_framework.exceptions import ValidationError +from rest_framework.validators import UniqueTogetherValidator from greg.models import Role class RoleSerializerUi(serializers.ModelSerializer): - def update(self, instance, validated_data): - # validate new date is after old date, and old date has not passed + """Serializer for the Role model with validation of various fields""" + + class Meta: + model = Role + fields = [ + "orgunit", + "start_date", + "type", + "end_date", + "contact_person_unit", + "comments", + "available_in_search", + "person", + ] + validators = [ + UniqueTogetherValidator( + queryset=Role.objects.all(), + fields=["person", "type", "orgunit", "start_date", "end_date"], + ) + ] + + def validate_start_date(self, start_date): today = datetime.date.today() - new_start = validated_data.get("start_date") - new_end = validated_data.get("end_date") - if new_start: - new_start = datetime.datetime.strptime( - validated_data["start_date"], "%Y-%m-%d" - ).date() - if new_start < today: - raise serializers.ValidationError("Start date cannot be in the past") - if instance.start_date < today and new_start: - raise serializers.ValidationError( - "Role has started, cannot change start date" - ) - instance.start_date = new_start - if new_end: - new_end = datetime.datetime.strptime( - validated_data["end_date"], "%Y-%m-%d" - ).date() - if new_end < today: - raise serializers.ValidationError("End date cannot be in the past") - if instance.end_date < today and new_end: + # New start dates cannot be in the past + if start_date < today: + raise serializers.ValidationError("Start date cannot be in the past") + + return start_date + + def validate_end_date(self, end_date): + """Ensure rules for end_date are followed""" + today = datetime.date.today() + if end_date < today: + raise serializers.ValidationError("End date cannot be in the past") + if self.instance and self.instance.end_date < today: + raise serializers.ValidationError("Role has ended, cannot change end date") + return end_date + + def validate_orgunit(self, unit): + """Enforce rules related to orgunit""" + sponsor = self.context["sponsor"] + units = sponsor.units.all() + # Restrict to a sponsor's own units + if not units or unit not in units: + raise ValidationError( + "A sponsor can only make changes to roles at units they are sponsors for." + ) + # If we are updating an existing roles, we must be the sponsor of the role + if self.instance and self.instance.sponsor != sponsor: + raise ValidationError("You can only edit your own roles.") + return unit + + def validate(self, attrs): + """Validate things that need access to multiple fields""" + # Ensure end date is not further into the future than the role type allows + today = datetime.date.today() + if self.instance: + max_days = today + datetime.timedelta(days=self.instance.type.max_days) + else: + max_days = today + datetime.timedelta(days=attrs["type"].max_days) + if attrs["end_date"] > max_days: + raise serializers.ValidationError( + f"New end date too far into the future for this type. Must be before {max_days.strftime('%Y-%m-%d')}" + ) + # Ensure end date is after start date if start date is set + if self.instance: + start_date = attrs.get("start_date") or self.instance.start_date + end_date = attrs.get("end_date") or self.instance.end_date + if start_date and end_date < start_date: raise serializers.ValidationError( - "Role has ended, cannot change end date" + "End date cannot be before start date." ) - max_days = today + datetime.timedelta(days=instance.type.max_days) - if new_end > max_days: + else: + if attrs.get("start_date") and attrs["end_date"] < attrs["start_date"]: raise serializers.ValidationError( - f"New end date too far into the future. Must be before {max_days.strftime('%Y-%m-%d')}" + "End date cannot be before start date." ) - instance.end_date = new_end + return attrs + + +class InviteRoleSerializerUi(RoleSerializerUi): + """ + Serializer for the role part of an invite. - return instance + This one exists so that we don't have to specify the person argument on the role. + Simply reuses all the logic of the parent class except requiring the person field. + """ class Meta: model = Role fields = [ - "orgunit_id", + "orgunit", "start_date", "type", "end_date", diff --git a/gregui/api/serializers/roletype.py b/gregui/api/serializers/roletype.py index 5f1857d69d2ceb8a9e980d9041a99a56386c7ed5..d2adecea1231870408f67ac3d48631f5e4215322 100644 --- a/gregui/api/serializers/roletype.py +++ b/gregui/api/serializers/roletype.py @@ -6,4 +6,4 @@ from greg.models import RoleType class RoleTypeSerializerUi(ModelSerializer): class Meta: model = RoleType - fields = ("id", "identifier", "name_en", "name_nb") + fields = ("id", "identifier", "name_en", "name_nb", "max_days") diff --git a/gregui/api/urls.py b/gregui/api/urls.py index db1d56dbedfee7dafec148cde4f959575ef5d864..af6ff1ffdd2ee35a2284878d3147d0fc77fe1b2a 100644 --- a/gregui/api/urls.py +++ b/gregui/api/urls.py @@ -8,10 +8,12 @@ from gregui.api.views.invitation import ( InvitedGuestView, ) from gregui.api.views.person import PersonSearchView, PersonView +from gregui.api.views.role import RoleInfoViewSet from gregui.api.views.roletypes import RoleTypeViewSet from gregui.api.views.unit import UnitsViewSet router = DefaultRouter(trailing_slash=False) +router.register(r"role", RoleInfoViewSet, basename="role") urlpatterns = router.urls urlpatterns += [ diff --git a/gregui/api/views/invitation.py b/gregui/api/views/invitation.py index 98a0bd8e1839fa49675a4d3598b37b3d8b680a71..5ffca10acd3f5643e16a82654153a29fa12fea66 100644 --- a/gregui/api/views/invitation.py +++ b/gregui/api/views/invitation.py @@ -30,7 +30,7 @@ class CreateInvitationView(CreateAPIView): "last_name": "sss", "date_of_birth": null, "role": { - "orgunit_id": 1, + "orgunit": 1, "start_date": null, "type": 1, "end_date": "2021-12-15", @@ -58,16 +58,15 @@ class CreateInvitationView(CreateAPIView): """ sponsor_user = GregUserProfile.objects.get(user=request.user) serializer = self.serializer_class( - data=request.data, context={"request": request} + data=request.data, + context={"request": request, "sponsor": sponsor_user.sponsor}, ) serializer.is_valid(raise_exception=True) - - # TODO: check that sponsor has access to OU person = serializer.save() invitationlink = InvitationLink.objects.filter( - invitation__role__person=person.id, - invitation__role__sponsor_id=sponsor_user.sponsor, + invitation__role__person_id=person.id, + invitation__role__sponsor_id=sponsor_user.sponsor_id, ) # TODO: send email to invited guest print(invitationlink) @@ -133,7 +132,7 @@ class InvitedGuestView(GenericAPIView): invite_link = InvitationLink.objects.get(uuid=invite_id) role = invite_link.invitation.role person = role.person - sponsor = role.sponsor_id + sponsor = role.sponsor # If the user is not logged in then tell the client to take him through the manual registration process session_type = ( @@ -165,8 +164,8 @@ class InvitedGuestView(GenericAPIView): "last_name": sponsor.last_name, }, "role": { - "ou_name_nb": role.orgunit_id.name_nb, - "ou_name_en": role.orgunit_id.name_en, + "ou_name_nb": role.orgunit.name_nb, + "ou_name_en": role.orgunit.name_en, "role_name_nb": role.type.name_nb, "role_name_en": role.type.name_en, "start": role.start_date, diff --git a/gregui/api/views/person.py b/gregui/api/views/person.py index edf0239f1ba71026ae331db29b29a04b5e6c9e10..e5953a28e325c22f537781295d4d8c813010d4d5 100644 --- a/gregui/api/views/person.py +++ b/gregui/api/views/person.py @@ -29,13 +29,16 @@ class PersonView(APIView): "email": person.private_email and person.private_email.value, "mobile": person.private_mobile and person.private_mobile.value, "fnr": person.fnr and "".join((person.fnr.value[:-5], "*****")), + "active": person.is_registered and person.is_verified, + "registered": person.is_registered, + "verified": person.is_verified, "roles": [ { "id": role.id, "name_nb": role.type.name_nb, "name_en": role.type.name_en, - "ou_nb": role.orgunit_id.name_nb, - "ou_en": role.orgunit_id.name_en, + "ou_nb": role.orgunit.name_nb, + "ou_en": role.orgunit.name_en, "start_date": role.start_date, "end_date": role.end_date, "max_days": role.type.max_days, diff --git a/gregui/api/views/role.py b/gregui/api/views/role.py index 47b2fbf959b557c2bbebd85425050bd8d4281966..7424b2858c5d60b845c3d5eb8146491cd41a7e74 100644 --- a/gregui/api/views/role.py +++ b/gregui/api/views/role.py @@ -1,30 +1,46 @@ from django.db import transaction -from rest_framework import status +from rest_framework import serializers, status from rest_framework.authentication import BasicAuthentication, SessionAuthentication -from rest_framework.generics import GenericAPIView +from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from greg.models import Role from greg.permissions import IsSponsor from gregui.api.serializers.role import RoleSerializerUi -from gregui.api.serializers.roletype import RoleTypeSerializerUi +from gregui.models import GregUserProfile -class RoleInfoView(GenericAPIView): +class RoleInfoViewSet(ModelViewSet): queryset = Role.objects.all() authentication_classes = [SessionAuthentication, BasicAuthentication] permission_classes = [IsAuthenticated, IsSponsor] serializer_class = RoleSerializerUi - def patch(self, request, id): - role = Role.objects.get(id=id) - + def partial_update(self, request, pk): + role = Role.objects.get(pk=pk) + sponsor = GregUserProfile.objects.get(user=self.request.user).sponsor with transaction.atomic(): serializer = self.serializer_class( - instance=role, data=request.data, partial=True + instance=role, + data=request.data, + partial=True, + context={"sponsor": sponsor}, ) serializer.is_valid(raise_exception=True) - instance = serializer.update(role, request.data) + instance = serializer.update(role, serializer.validated_data) instance.save() return Response(status=status.HTTP_200_OK) + + def create(self, request): + sponsor = GregUserProfile.objects.get(user=self.request.user).sponsor + with transaction.atomic(): + serializer = self.serializer_class( + data=request.data, + context={ + "sponsor": sponsor, + }, + ) + serializer.is_valid(raise_exception=True) + serializer.save(sponsor=sponsor) + return Response(status=status.HTTP_201_CREATED) diff --git a/gregui/api/views/userinfo.py b/gregui/api/views/userinfo.py index 3a4a0d4133499c4301042d43929cfa977de8bd87..980cbd31454413298678b420e06bfa3d17b0f490 100644 --- a/gregui/api/views/userinfo.py +++ b/gregui/api/views/userinfo.py @@ -68,16 +68,16 @@ class UserInfoView(APIView): { "roles": [ { - "ou_name_nb": role.orgunit_id.name_nb, - "ou_name_en": role.orgunit_id.name_en, + "ou_name_nb": role.orgunit.name_nb, + "ou_name_en": role.orgunit.name_en, "role_name_nb": role.type.name_nb, "role_name_en": role.type.name_en, "start": role.start_date, "end": role.end_date, "comments": role.comments, "sponsor": { - "first_name": role.sponsor_id.first_name, - "last_name": role.sponsor_id.last_name, + "first_name": role.sponsor.first_name, + "last_name": role.sponsor.last_name, }, } for role in roles.all() @@ -105,16 +105,16 @@ class UserInfoView(APIView): "passport": passports and passports.value, "roles": [ { - "ou_name_nb": role.orgunit_id.name_nb, - "ou_name_en": role.orgunit_id.name_en, + "ou_name_nb": role.orgunit.name_nb, + "ou_name_en": role.orgunit.name_en, "role_name_nb": role.type.name_nb, "role_name_en": role.type.name_en, "start": role.start_date, "end": role.end_date, "comments": role.comments, "sponsor": { - "first_name": role.sponsor_id.first_name, - "last_name": role.sponsor_id.last_name, + "first_name": role.sponsor.first_name, + "last_name": role.sponsor.last_name, }, } for role in person.roles.all() diff --git a/gregui/tests/api/test_invite_guest.py b/gregui/tests/api/test_invite_guest.py index 55a962e330cf5bd92fbbf94f511da9f41ecfec73..87554ec7edbb03b6325945abc06e478e469ba6dc 100644 --- a/gregui/tests/api/test_invite_guest.py +++ b/gregui/tests/api/test_invite_guest.py @@ -1,3 +1,4 @@ +import datetime import pytest from rest_framework import status from rest_framework.reverse import reverse @@ -14,9 +15,13 @@ def test_invite_guest(client, user_sponsor, unit_foo, role_type_foo): "last_name": "Bar", "email": "test@example.com", "role": { - "start_date": "2019-08-06", - "end_date": "2019-08-10", - "orgunit_id": unit_foo.id, + "start_date": ( + datetime.datetime.today() + datetime.timedelta(days=1) + ).strftime("%Y-%m-%d"), + "end_date": ( + datetime.datetime.today() + datetime.timedelta(days=10) + ).strftime("%Y-%m-%d"), + "orgunit": unit_foo.id, "type": role_type_foo.id, }, } diff --git a/gregui/tests/conftest.py b/gregui/tests/conftest.py index f4cd467c2d60ed3447e0dcf2e15f2cb57276e8d2..1bd22154f3a68dc909a2eebb94a8a65aee17f9bc 100644 --- a/gregui/tests/conftest.py +++ b/gregui/tests/conftest.py @@ -190,8 +190,8 @@ def person(person_foo_data) -> Person: def role(person, sponsor_guy, unit_foo, role_type_foo) -> Role: role = Role.objects.create( person=person, - sponsor_id=sponsor_guy, - orgunit_id=unit_foo, + sponsor=sponsor_guy, + orgunit=unit_foo, end_date="2050-10-15", type_id=role_type_foo.id, ) diff --git a/gregui/urls.py b/gregui/urls.py index 5ef2297a90af140a512a0917078599f447da4e49..cc6fa884e2da3bc3a1884dcb83b4382a0f0ac9f9 100644 --- a/gregui/urls.py +++ b/gregui/urls.py @@ -5,7 +5,6 @@ from django.urls import path from django.urls.resolvers import URLResolver from gregui.api import urls as api_urls -from gregui.api.views.role import RoleInfoView from gregui.api.views.userinfo import UserInfoView from gregui.views import OusView, GuestInfoView from . import views @@ -23,5 +22,4 @@ urlpatterns: List[URLResolver] = [ path("api/ui/v1/userinfo/", UserInfoView.as_view()), # type: ignore path("api/ui/v1/ous/", OusView.as_view()), path("api/ui/v1/guests/", GuestInfoView.as_view()), - path("api/ui/v1/role/<int:id>", RoleInfoView.as_view()), ] diff --git a/gregui/views.py b/gregui/views.py index 15e6e264ce87afe18682107efe00ee6980cb6daa..4721cccb27e0426fa2874796671bb62d1b55dbaa 100644 --- a/gregui/views.py +++ b/gregui/views.py @@ -113,8 +113,8 @@ class GuestInfoView(APIView): "id": role.id, "name_nb": role.type.name_nb, "name_en": role.type.name_en, - "ou_nb": role.orgunit_id.name_nb, - "ou_en": role.orgunit_id.name_en, + "ou_nb": role.orgunit.name_nb, + "ou_en": role.orgunit.name_en, "start_date": role.start_date, "end_date": role.end_date, "max_days": role.type.max_days, @@ -123,7 +123,7 @@ class GuestInfoView(APIView): ], } for person in Person.objects.filter( - roles__sponsor_id=user.sponsor + roles__sponsor=user.sponsor ).distinct() ] }