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

feat: able to transfer vehicle to business #1829

Merged
merged 2 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 20 additions & 1 deletion apps/api/src/controllers/business/BusinessController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ import {
} from "@snailycad/schemas";
import { BadRequest, NotFound } from "@tsed/exceptions";
import { prisma } from "lib/data/prisma";
import { type User, EmployeeAsEnum, MiscCadSettings, WhitelistStatus, cad } from "@prisma/client";
import {
type User,
EmployeeAsEnum,
MiscCadSettings,
WhitelistStatus,
cad,
Prisma,
} from "@prisma/client";
import { validateSchema } from "lib/data/validate-schema";
import { UsePermissions, Permissions } from "middlewares/use-permissions";
import type * as APITypes from "@snailycad/types/api";
Expand Down Expand Up @@ -72,6 +79,18 @@ export class BusinessController {
return { ownedBusinesses, joinedBusinesses, joinableBusinesses };
}

@Get("/search")
async searchBusinesses(@QueryParams("query") query: string) {
const where: Prisma.BusinessWhereInput = query
? { name: { contains: query, mode: "insensitive" } }
: {};
const joinableBusinesses = await prisma.business.findMany({
where,
});

return joinableBusinesses;
}

@Get("/business/:id")
async getBusinessById(
@Context("user") user: User,
Expand Down
57 changes: 42 additions & 15 deletions apps/api/src/controllers/citizen/vehicles-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { getLastOfArray, manyToManyHelper } from "lib/data/many-to-many";
import { AllowedFileExtension, allowedFileExtensions } from "@snailycad/config";
import { getImageWebPPath } from "~/lib/images/get-image-webp-path";
import fs from "node:fs/promises";
import { ExtendedNotFound } from "~/exceptions/extended-not-found";

@Controller("/vehicles")
@UseBeforeEach(IsAuth)
Expand Down Expand Up @@ -484,25 +485,51 @@ export class VehiclesController {
throw new NotFound("vehicleNotFound");
}

const newOwner = await prisma.citizen.findFirst({
where: {
AND: [{ id: data.ownerId }, { NOT: { id: String(vehicle.citizenId) } }],
},
});
if (data.businessId) {
const business = await prisma.business.findUnique({
where: { id: data.businessId },
});

if (!newOwner) {
throw new NotFound("newOwnerNotFound");
if (!business) {
throw new ExtendedNotFound({ business: "businessNotFound" });
}

const updatedVehicle = await prisma.registeredVehicle.update({
where: { id: vehicle.id },
data: {
Business: { connect: { id: data.businessId } },
},
});

return updatedVehicle;
}

const updatedVehicle = await prisma.registeredVehicle.update({
where: { id: vehicle.id },
data: {
citizenId: newOwner.id,
userId: newOwner.userId,
},
});
if (data.ownerId) {
const newOwner = await prisma.citizen.findFirst({
where: {
AND: [{ id: data.ownerId }, { NOT: { id: String(vehicle.citizenId) } }],
},
});

return updatedVehicle;
if (!newOwner) {
throw new NotFound("newOwnerNotFound");
}

const updatedVehicle = await prisma.registeredVehicle.update({
where: { id: vehicle.id },
data: {
citizenId: newOwner.id,
userId: newOwner.userId,
},
});

return updatedVehicle;
}

throw new ExtendedBadRequest({
ownerId: "ownerIdOrBusinessIdRequired",
businessId: "ownerIdOrBusinessIdRequired",
});
}

@Delete("/:id")
Expand Down
6 changes: 3 additions & 3 deletions apps/client/locales/en/citizen.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
"trimLevels": "Trim Levels",
"image": "Image",
"transferVehicleInfo": "Transfer your <span>{model}</span> to a new owner. This means you will lose ownership of this vehicle and will be unable to manage it further.",
"alert_deleteVehicle": "Are you sure you want to delete this vehicle? This action cannot be undone."
"alert_deleteVehicle": "Are you sure you want to delete this vehicle? This action cannot be undone.",
"transferToBusiness": "Transfer to business"
},
"Weapons": {
"model": "Model",
Expand Down Expand Up @@ -156,12 +157,11 @@
"LawBook": {
"lawBook": "Law Book",
"warningApplicable": "Warning Applicable",
"noPenalCodes": "No penal code has been registered",
"noPenalCodes": "No penal code has been registered",
"warningNotApplicable": "Warning Not Applicable",
"fines": "Fines",
"bail": "Bail",
"jailTime": "Jail Time",
"isPrimary": "Is Primary"
}
}

Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { Loader, Button, TextField, FormRow } from "@snailycad/ui";
import {
Loader,
Button,
TextField,
FormRow,
SwitchField,
AsyncListSearchField,
Item,
} from "@snailycad/ui";
import { Modal } from "components/modal/Modal";
import { useModal } from "state/modalState";
import { Form, Formik } from "formik";
import useFetch from "lib/useFetch";
import { useTranslations } from "use-intl";
import type { RegisteredVehicle } from "@snailycad/types";
import type { Business, RegisteredVehicle } from "@snailycad/types";
import { ModalIds } from "types/modal-ids";
import { handleValidate } from "lib/handleValidate";
import { TRANSFER_VEHICLE_SCHEMA } from "@snailycad/schemas";
Expand Down Expand Up @@ -37,6 +45,10 @@ export function TransferVehicleModal({ onTransfer, vehicle }: Props) {

const validate = handleValidate(TRANSFER_VEHICLE_SCHEMA);
const INITIAL_VALUES = {
transferType: "citizen" as "citizen" | "business",
businessName: "",
businessId: "",

ownerId: "",
name: "",
};
Expand All @@ -49,27 +61,65 @@ export function TransferVehicleModal({ onTransfer, vehicle }: Props) {
className="w-[750px]"
>
<Formik validate={validate} initialValues={INITIAL_VALUES} onSubmit={onSubmit}>
{({ isValid }) => (
{({ isValid, values, errors, setValues, setFieldValue }) => (
<Form>
<p className="my-2 mb-5">
{t("transferVehicleInfo", {
{t.rich("transferVehicleInfo", {
model: vehicle.model.value.value,
span: (children) => <span className="font-medium">{children}</span>,
})}
</p>

<SwitchField
onChange={(isSelected) =>
setFieldValue("transferType", isSelected ? "business" : "citizen")
}
>
{t("transferToBusiness")}
</SwitchField>

<FormRow>
<TextField label={t("plate")} isDisabled defaultValue={vehicle.plate} />
<TextField label={t("model")} isDisabled defaultValue={vehicle.model.value.value} />
</FormRow>

<CitizenSuggestionsField
autoFocus
allowsCustomValue
label={t("owner")}
fromAuthUserOnly={false}
labelFieldName="name"
valueFieldName="ownerId"
/>
{values.transferType === "business" ? (
<AsyncListSearchField<Business>
className="w-full"
setValues={({ localValue, node }) => {
const labelValue =
typeof localValue !== "undefined" ? { businessName: localValue } : {};
const valueField = node?.value ? { businessId: node.key as string } : {};

setValues({ ...values, ...labelValue, ...valueField });
}}
localValue={values.businessName}
errorMessage={errors.businessId}
label="Business"
selectedKey={values.businessId}
fetchOptions={{
apiPath: (query) => `/businesses/search?query=${query}`,
filterTextRequired: true,
}}
>
{(item) => {
return (
<Item key={item.id} textValue={item.name}>
{item.name}
</Item>
);
}}
</AsyncListSearchField>
) : (
<CitizenSuggestionsField
autoFocus
allowsCustomValue
label={t("owner")}
fromAuthUserOnly={false}
labelFieldName="name"
valueFieldName="ownerId"
/>
)}

<footer className="mt-5 flex justify-end">
<Button
Expand Down
3 changes: 2 additions & 1 deletion packages/schemas/src/citizen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export const VEHICLE_SCHEMA = z.object({
});

export const TRANSFER_VEHICLE_SCHEMA = z.object({
ownerId: z.string().min(2).max(255),
ownerId: z.string().max(255).nullish(),
businessId: z.string().max(255).nullish(),
});

export const DELETE_VEHICLE_SCHEMA = z.object({
Expand Down
Loading