Backend: Zapisnik o preuzimanju (pickup form)
GitHub Issue: #193
Last modified with commit 374306c on
by Aljaž.
Backend task for creating and saving the pickup record (POST /drivepark/records, POST /drivepark/records/{recordId}/pickup) – check the API spec and DB structure, save drafts, complete the pickup with signatures, PDF and e-mail, fill the vehicle usage overview, implement, and verify against the spec.
Docs: Zapisnik o preuzimanju (pickup form)
Spec: POST /drivepark/records (createDriveParkRecord), POST /drivepark/records/{recordId}/pickup (saveDriveParkRecordPickup), DriveParkRecordPickupRequest, DriveParkRecordSigning, DriveParkRecordDetail, DriveParkRecordConflict in api-docs/openapi.yaml
DB: DriveParkRecords, DriveParkRecordEquipment in api/database/pending_schema.sql (-- DRIVE PARK --)
This is the first Zapisnici endpoint, so it also includes the shared setup for records: the blob container, signature storage, PDF generation and the e-mail.
1. Double check API spec and DB structure
- Every docs field of step 1 is in
DriveParkRecordPickupRequestand DriveParkRecords:- Podaci o vozilu – Registarska oznaka (
vehicleId); Marka, Tip, Broj šasije, Boja are not sent and not stored, they are read from Vehicles. - Oprema vozila –
equipment(added items only; the 2 fixed items are in code). - Podaci o korisniku – 10 optional fields (
company…driverLicenseValidUntil). - Rok korištenja –
usageFrom,usageTo,returnByTime,returnLocation,specialArrangement. - Stanje vozila pri preuzimanju –
pickupMileageKm,pickupDate,pickupTime,pickupBatteryPercent(0–100),pickupFuelLevel(text),pickupDamage.
- Podaci o vozilu – Registarska oznaka (
- Mandatory for completion: Od, Do, Vozilo treba vratiti najkasnije do, Km stanje, Datum and Vrijeme preuzimanja, the terms checkbox and both signatures. Check the docs and the spec list the same fields. Mjesto povratka is optional (Figma marks it mandatory on the return screens; docs comment).
- Draft: only
vehicleIdis required (docs comment: “Recommended: Registarska oznaka is required for a draft”). Confirm with the project manager. - “Vozilo treba vratiti najkasnije do”: stored as
TIME(0), APIHH:mm. Figma shows a whole-hour field (“14 h”); docs mark it as a conflict for the assignee. Confirm whether minutes are allowed. - Neispravno vehicle: the spec only rejects deleted / archived vehicles
(
vehicle_unavailable). The docs comment recommends Neispravno vehicles can’t be selected. Decide and update the spec. - Several open records per vehicle are allowed. Confirm.
- Validation not decided in the docs: e-mail format (the spec has
format: email), Vrijedi do before the pickup date (docs recommend a warning only). Confirm. - Prefill from a reservation: done by the frontend from
getDriveParkReservation(contactgoes to E-mail if it contains “@”, otherwise Telefon). The record keeps no link to the reservation. - PDF and e-mail: the PDF layout and the e-mail subject/text are not specified yet (docs: “Yet to be specified”). The Opći uvjeti text is in the docs (transcribed, to be proofread); decide how it is attached (a static PDF file in the API, or rendered).
- E-mail status:
PickupEmailStatusrecords pending / sent / failed; there is no retry. Confirm whether a retry (e.g. likeEventInviteeEmailTimerFunction) is needed. - Signatures: PNG data URLs in the request, stored as blobs, returned as data URLs. Decide a size limit (→ 400).
- Vehicle “Zauzeto”: the inventory docs say a vehicle is Zauzeto when it is “reserved or
picked up in Vozni park”, but
isOccupiedonly reads VehicleReservations. A Preuzeto record without a reservation (records aren’t linked to reservations) doesn’t make the vehicle Zauzeto. Decide whether a Preuzeto record should count. - Roles and permissions:
drivepark.access. - Is the
DriveParkRecordDetailresponse enough for the next step (back to the previous view with a toast; the frontend keeps the record id for photos)?
2. Implementation
- Roles and permissions: require
drivepark.access→ 403. - Blob storage: a
driveparkcontainer viaBlobStorageServicefor signatures, PDFs and photos; blob names per record (e.g.records/{recordId}/…). -
createDriveParkRecord: check the vehicle (exists, not deleted, not archived → 409vehicle_unavailable), insert the record and the equipment rows (OrderIndex) in one transaction. Withsigning, complete it as below. -
saveDriveParkRecordPickup: record must be Draft (PickupCompletedAt IS NULL) → else 409record_not_draft. Put the check in theUPDATE’sWHEREand handle 0 rows (a parallel completion). Replace all pickup fields, replace the equipment rows (one multi-row insert). - Completion (with
signing): validate the mandatory fields → 400validation_errorwith the missing fields inmessage; upload both signatures; generate the PDF (reuse the approach ofPdfTemplateService); setPickupCompletedAt,PickupCompletedBy, the blob columns andPickupEmailStatus(pending, or NULL without an e-mail). After the commit, send the PDF and the Opći uvjeti withBrevoEmailServiceand setsent/failed. A failed e-mail doesn’t fail the request. If the transaction fails, delete the uploaded blobs. - Validation → 400
validation_error:usageTo≥usageFrom, battery 0–100, mileage ≥ 0, lengths,HH:mm. - Response 201 / 200 with
DriveParkRecordDetail(computedstatus). - Vehicle usage overview: fill
drivepark-rental(“Vozni park - aktivni najam”) entries inGetVehicleUsageAsyncfrom completed pickups: name = fullName or company, fromPickupDatetoReturnDate(orUsageTowhile not returned). - isOccupied (“Zauzeto”): if confirmed in step 1, add “has a Preuzeto record” to
IsOccupiedByFutureReservationSql.
3. Verify implementation against spec
- Routes, methods, request and response shapes match
createDriveParkRecordandsaveDriveParkRecordPickup. - 400
validation_error, 404, 409record_not_draft, 409vehicle_unavailablecan be produced; no undocumented ones. - Draft with only
vehicleId→ 201, statusdraft. Completing without signatures or without Km stanje → 400. Complete → statuspicked-up, PDF downloadable, e-mail sent. - Completing without an e-mail → 200,
pickupEmailStatusnull, nothing sent. - Saving the pickup of a Preuzeto record → 409. Changing the vehicle’s color afterwards
shows the new color in
getDriveParkRecordbut doesn’t change the stored PDF. - Two parallel completions of the same draft → one 200, one 409.
- BrandManager → 403.
Done when
A Manager of Vozni park can fill in the pickup form, save it as a draft, sign it, and the user gets the signed record by e-mail.