Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(bulk-mileage): update bulk mileage #16539

Merged
merged 22 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface UpdateResponseError {
type: string
title: string
status: number
Errors: Array<{
thorkellmani marked this conversation as resolved.
Show resolved Hide resolved
lookupNo: number
warnSever: string
errorMess: string
permno: string
warningSerialNumber: string
}>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createUnionType } from '@nestjs/graphql'
import { VehicleMileageDetail } from '../getVehicleMileage.model'
import { VehiclesMileageUpdateError } from './vehicleMileageResponseError.model'

export const VehicleMileagePostResponse = createUnionType({
name: 'VehicleMileagePostResponse',
types: () => [VehicleMileageDetail, VehiclesMileageUpdateError] as const,
resolveType(value) {
if ('permno' in value && value.permno !== undefined) {
return VehicleMileageDetail
}

return VehiclesMileageUpdateError
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createUnionType } from '@nestjs/graphql'
import { VehicleMileagePutModel } from '../getVehicleMileage.model'
import { VehiclesMileageUpdateError } from './vehicleMileageResponseError.model'

export const VehicleMileagePutResponse = createUnionType({
name: 'VehicleMileagePutResponse',
types: () => [VehicleMileagePutModel, VehiclesMileageUpdateError] as const,
resolveType(value) {
if ('permno' in value && value.permno !== undefined) {
return VehicleMileagePutModel
}

return VehiclesMileageUpdateError
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Field, Int, ObjectType } from '@nestjs/graphql'
import GraphQLJSON from 'graphql-type-json'

@ObjectType()
export class VehiclesMileageUpdateError {
@Field()
message!: string

@Field(() => Int, { nullable: true })
code?: number

@Field(() => GraphQLJSON, { nullable: true })
error?: string
}
45 changes: 45 additions & 0 deletions libs/api/domains/vehicles/src/lib/resolvers/mileage.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import {
} from '@island.is/nest/feature-flags'
import { mileageDetailConstructor } from '../utils/helpers'
import { LOGGER_PROVIDER, type Logger } from '@island.is/logging'
import { VehicleMileagePostResponse } from '../models/v3/postVehicleMileageResponse.model'
import { VehiclesMileageUpdateError } from '../models/v3/vehicleMileageResponseError.model'
import { VehicleMileagePutResponse } from '../models/v3/putVehicleMileageResponse.model'

@UseGuards(IdsUserGuard, ScopesGuard, FeatureFlagGuard)
@FeatureFlag(Features.servicePortalVehicleMileagePageEnabled)
Expand Down Expand Up @@ -99,6 +102,48 @@ export class VehiclesMileageResolver {
return mileageDetailConstructor(res)
}

@Mutation(() => VehicleMileagePostResponse, {
name: 'vehicleMileagePostV2',
nullable: true,
})
@Audit()
async postVehicleMileageReadingV2(
@Args('input') input: PostVehicleMileageInput,
@CurrentUser() user: User,
) {
const res = await this.vehiclesService.postMileageReadingV2(user, {
...input,
mileage: Number(input.mileage ?? input.mileageNumber),
})

if (!res || res instanceof VehiclesMileageUpdateError) {
return res
}

return mileageDetailConstructor(res)
}

@Mutation(() => VehicleMileagePutResponse, {
name: 'vehicleMileagePutV2',
nullable: true,
})
@Audit()
async putVehicleMileageReadingV2(
@Args('input') input: PutVehicleMileageInput,
@CurrentUser() user: User,
) {
const res = await this.vehiclesService.putMileageReadingV2(user, {
...input,
mileage: Number(input.mileage ?? input.mileageNumber),
})

if (!res || res instanceof VehiclesMileageUpdateError) {
thorkellmani marked this conversation as resolved.
Show resolved Hide resolved
return res
}

return mileageDetailConstructor(res)
}

@ResolveField('canRegisterMileage', () => Boolean, {
nullable: true,
})
Expand Down
87 changes: 85 additions & 2 deletions libs/api/domains/vehicles/src/lib/services/vehicles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import {
VehicleDtoListPagedResponse,
PersidnoLookupResultDto,
CurrentVehiclesWithMilageAndNextInspDtoListPagedResponse,
ApiResponse,
} from '@island.is/clients/vehicles'
import {
CanregistermileagePermnoGetRequest,
GetMileageReadingRequest,
MileageReadingApi,
MileageReadingDto,
PostMileageReadingModel,
PutMileageReadingModel,
RequiresmileageregistrationPermnoGetRequest,
RootPostRequest,
RootPutRequest,
Expand All @@ -33,17 +35,22 @@ import {
GetVehiclesForUserInput,
GetVehiclesListV2Input,
} from '../dto/getVehiclesForUserInput'
import { VehicleMileageOverview } from '../models/getVehicleMileage.model'
import {
VehicleMileageDetail,
VehicleMileageOverview,
} from '../models/getVehicleMileage.model'
import isSameDay from 'date-fns/isSameDay'
import { mileageDetailConstructor } from '../utils/helpers'
import { handle404 } from '@island.is/clients/middlewares'
import { FetchError, handle404 } from '@island.is/clients/middlewares'
import { VehicleSearchCustomDto } from '../vehicles.type'
import { operatorStatusMapper } from '../utils/operatorStatusMapper'
import { VehiclesListInputV3 } from '../dto/vehiclesListInputV3'
import { VehiclesCurrentListResponse } from '../models/v3/currentVehicleListResponse.model'
import { isDefined } from '@island.is/shared/utils'
import { GetVehicleMileageInput } from '../dto/getVehicleMileageInput'
import { MileageRegistrationHistory } from '../models/v3/mileageRegistrationHistory.model'
import { VehiclesMileageUpdateError } from '../models/v3/vehicleMileageResponseError.model'
import { UpdateResponseError } from '../dto/updateResponseError.dto'

const ORIGIN_CODE = 'ISLAND.IS'
const LOG_CATEGORY = 'vehicle-service'
Expand Down Expand Up @@ -463,6 +470,82 @@ export class VehiclesService {
})
}

async postMileageReadingV2(
auth: User,
input: RootPostRequest['postMileageReadingModel'],
): Promise<PostMileageReadingModel | VehiclesMileageUpdateError | null> {
if (!input) return null

const isAllowed = await this.isAllowedMileageRegistration(
auth,
input.permno,
)
if (!isAllowed) {
this.logger.error(UNAUTHORIZED_OWNERSHIP_LOG, {
category: LOG_CATEGORY,
error: 'postMileageReading failed',
})
throw new ForbiddenException(UNAUTHORIZED_OWNERSHIP_LOG)
}

try {
const res = await this.getMileageWithAuth(auth).rootPostRaw({
postMileageReadingModel: input,
})

if (res.raw.status === 200) {
this.logger.info(
'Tried to post already existing mileage reading. Should use PUT',
)
return null
}
thorkellmani marked this conversation as resolved.
Show resolved Hide resolved

const value = await res.value()
return value
} catch (e) {
if (e instanceof FetchError && (e.status === 400 || e.status === 429)) {
const errorBody = e.body as UpdateResponseError
return {
code: e.status,
message: errorBody.Errors?.[0]?.errorMess || 'Unknown error',
}
} else throw e
}
}

async putMileageReadingV2(
auth: User,
input: RootPutRequest['putMileageReadingModel'],
): Promise<MileageReadingDto | VehiclesMileageUpdateError | null> {
if (!input) return null

const isAllowed = await this.isAllowedMileageRegistration(
auth,
input.permno,
)
if (!isAllowed) {
this.logger.error(UNAUTHORIZED_OWNERSHIP_LOG, {
category: LOG_CATEGORY,
error: 'putMileageReading failed',
})
throw new ForbiddenException(UNAUTHORIZED_OWNERSHIP_LOG)
}

try {
return this.getMileageWithAuth(auth).rootPut({
putMileageReadingModel: input,
})
} catch (e) {
if (e instanceof FetchError && (e.status === 400 || e.status === 429)) {
const errorBody = e.body as UpdateResponseError
return {
code: e.status,
message: errorBody.Errors?.[0]?.errorMess || 'Unknown error',
}
} else throw e
}
}

async canRegisterMileage(
auth: User,
input: CanregistermileagePermnoGetRequest,
Expand Down
6 changes: 5 additions & 1 deletion libs/service-portal/assets/src/lib/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,10 @@ export const vehicleMessage = defineMessages({
id: 'sp.vehicles:upload-failed',
defaultMessage: 'Upphleðsla mistókst',
},
wrongFileType: {
id: 'sp.vehicles:wrong-file-type',
defaultMessage: 'Vitlaus skráartýpa. Skrá verður að vera .csv eða .xslx',
},
thorkellmani marked this conversation as resolved.
Show resolved Hide resolved
errorWhileProcessing: {
id: 'sp.vehicles:error-while-processing',
defaultMessage: 'Villa við að meðhöndla skjal. Villur: ',
Expand Down Expand Up @@ -1026,7 +1030,7 @@ export const vehicleMessage = defineMessages({
},
fileUploadAcceptedTypes: {
id: 'sp.vehicles:file-upload-accepted-types',
defaultMessage: 'Tekið er við skjölum með endingu; .csv',
defaultMessage: 'Tekið er við skjölum með endingu; .csv, .xlsx',
},
dataAboutJob: {
id: 'sp.vehicles:data-about-job',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,32 @@ query vehiclesList($input: VehiclesListInputV3!) {
}

mutation postSingleVehicleMileage($input: PostVehicleMileageInput!) {
vehicleMileagePost(input: $input) {
permno
readDate
originCode
mileageNumber
internalId
vehicleMileagePostV2(input: $input) {
... on VehicleMileageDetail {
permno
readDate
originCode
mileageNumber
internalId
}
... on VehiclesMileageUpdateError {
code
message
}
}
}

mutation putSingleVehicleMileage($input: PutVehicleMileageInput!) {
vehicleMileagePut(input: $input) {
permno
internalId
mileageNumber
vehicleMileagePutV2(input: $input) {
... on VehicleMileagePutModel {
permno
internalId
mileageNumber
}
... on VehiclesMileageUpdateError {
code
message
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,41 @@
import { Button } from '@island.is/island-ui/core'
import { DropdownMenu } from '@island.is/island-ui/core'
import { useLocale } from '@island.is/localization'
thorkellmani marked this conversation as resolved.
Show resolved Hide resolved
import { vehicleMessage } from '../../lib/messages'
import { downloadFile } from '@island.is/service-portal/core'
import { useState } from 'react'

interface Props {
onError: (error: string) => void
}

const VehicleBulkMileageFileDownloader = ({ onError }: Props) => {
const { formatMessage } = useLocale()
const [isLoading, setIsLoading] = useState(false)

const downloadExampleFile = async () => {
setIsLoading(true)
const downloadExampleFile = async (type: 'csv' | 'xlsx') => {
try {
downloadFile(
`magnskraning_kilometrastodu_example`,
['permno', 'mileage'],
['bilnumer', 'kilometrastada'],
[
['ABC001', 10000],
['DEF002', 99999],
],
'csv',
type,
)
} catch (error) {
onError(error)
} finally {
setIsLoading(false)
}
}
thorkellmani marked this conversation as resolved.
Show resolved Hide resolved

return (
<Button
colorScheme="default"
icon="download"
iconType="outline"
size="default"
variant="utility"
onClick={downloadExampleFile}
loading={isLoading}
>
{formatMessage(vehicleMessage.downloadTemplate)}
</Button>
<DropdownMenu
icon="ellipsisHorizontal"
menuLabel={formatMessage(vehicleMessage.downloadTemplate)}
items={(['csv', 'xlsx'] as const).map((type) => ({
title: `.${type}`,
onClick: () => downloadExampleFile(type),
}))}
title={formatMessage(vehicleMessage.downloadTemplate)}
/>
)
}

Expand Down
Loading
Loading