+>(({ className, ...props }, ref) => (
+ | [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+))
+TableCell.displayName = "TableCell"
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableCaption.displayName = "TableCaption"
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
new file mode 100644
index 000000000..0bf7b0928
--- /dev/null
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
@@ -0,0 +1,273 @@
+import { FarmHeader } from "@/components/custom/farm/farm-header"
+import { FarmTitle } from "@/components/custom/farm/farm-title"
+import { FertilizerForm } from "@/components/custom/fertilizer/form"
+import { FormSchema } from "@/components/custom/fertilizer/formschema"
+import { SidebarInset } from "@/components/ui/sidebar"
+import { getSession } from "@/lib/auth.server"
+import { handleActionError, handleLoaderError } from "@/lib/error"
+import { fdm } from "@/lib/fdm.server"
+import { extractFormValuesFromRequest } from "@/lib/form"
+import { zodResolver } from "@hookform/resolvers/zod"
+import {
+ getFarm,
+ getFarms,
+ getFertilizer,
+ getFertilizers,
+} from "@svenvw/fdm-core"
+import { updateFertilizerFromCatalogue } from "@svenvw/fdm-core"
+import { useEffect } from "react"
+import {
+ type ActionFunctionArgs,
+ type LoaderFunctionArgs,
+ data,
+ useLoaderData,
+} from "react-router"
+import { useRemixForm } from "remix-hook-form"
+import { dataWithSuccess } from "remix-toast"
+import type { z } from "zod"
+
+export async function loader({ request, params }: LoaderFunctionArgs) {
+ try {
+ // Get the farm id
+ const b_id_farm = params.b_id_farm
+ if (!b_id_farm) {
+ throw data("invalid: b_id_farm", {
+ status: 400,
+ statusText: "invalid: b_id_farm",
+ })
+ }
+
+ // Get the fertilizer id
+ const p_id = params.p_id
+ if (!p_id) {
+ throw data("invalid: p_id", {
+ status: 400,
+ statusText: "invalid: p_id",
+ })
+ }
+
+ // Get the session
+ const session = await getSession(request)
+
+ // Get details of farm
+ const farm = await getFarm(fdm, session.principal_id, b_id_farm)
+ if (!farm) {
+ throw data("not found: b_id_farm", {
+ status: 404,
+ statusText: "not found: b_id_farm",
+ })
+ }
+
+ // Get a list of possible farms of the user
+ const farms = await getFarms(fdm, session.principal_id)
+ if (!farms || farms.length === 0) {
+ throw data("not found: farms", {
+ status: 404,
+ statusText: "not found: farms",
+ })
+ }
+
+ const farmOptions = farms.map((farm) => {
+ return {
+ b_id_farm: farm.b_id_farm,
+ b_name_farm: farm.b_name_farm,
+ }
+ })
+
+ // Get selected fertilizer
+ const fertilizer = await getFertilizer(fdm, p_id)
+
+ // Get the available fertilizers
+ const fertilizers = await getFertilizers(
+ fdm,
+ session.principal_id,
+ b_id_farm,
+ )
+ const fertilizerOptions = fertilizers.map((fertilizer) => {
+ return {
+ p_id: fertilizer.p_id,
+ p_name_nl: fertilizer.p_name_nl,
+ }
+ })
+
+ // Set editable status
+ let editable = false
+ if (fertilizer.p_source === b_id_farm) {
+ editable = true
+ }
+
+ // Return user information from loader
+ return {
+ farm: farm,
+ b_id_farm: b_id_farm,
+ farmOptions: farmOptions,
+ fertilizerOptions: fertilizerOptions,
+ fertilizer: fertilizer,
+ editable: editable,
+ }
+ } catch (error) {
+ throw handleLoaderError(error)
+ }
+}
+
+/**
+ * Renders the layout for managing farm settings.
+ *
+ * This component displays a sidebar that includes the farm header, navigation options, and a link to farm fields.
+ * It also renders a main section containing the farm title, description, nested routes via an Outlet, and a notification toaster.
+ */
+export default function FarmFertilizerBlock() {
+ const loaderData = useLoaderData()
+ const fertilizer = loaderData.fertilizer
+
+ fertilizer.p_type = ""
+ if (fertilizer.p_type_manure) {
+ fertilizer.p_type = "manure"
+ } else if (fertilizer.p_type_compost) {
+ fertilizer.p_type = "compost"
+ } else if (fertilizer.p_type_mineral) {
+ fertilizer.p_type = "mineral"
+ }
+
+ const form = useRemixForm>({
+ mode: "onTouched",
+ resolver: zodResolver(FormSchema),
+ defaultValues: {
+ p_name_nl: fertilizer.p_name_nl,
+ p_type: fertilizer.p_type,
+ p_n_rt: fertilizer.p_n_rt,
+ p_n_wc: fertilizer.p_n_wc,
+ p_p_rt: fertilizer.p_p_rt,
+ p_k_rt: fertilizer.p_k_rt,
+ p_om: fertilizer.p_om,
+ p_eoc: fertilizer.p_eoc,
+ p_s_rt: fertilizer.p_s_rt,
+ p_ca_rt: fertilizer.p_ca_rt,
+ p_mg_rt: fertilizer.p_mg_rt,
+ },
+ })
+
+ useEffect(() => {
+ form.reset({
+ p_name_nl: fertilizer.p_name_nl,
+ p_type: fertilizer.p_type,
+ p_n_rt: fertilizer.p_n_rt,
+ p_n_wc: fertilizer.p_n_wc,
+ p_p_rt: fertilizer.p_p_rt,
+ p_k_rt: fertilizer.p_k_rt,
+ p_om: fertilizer.p_om,
+ p_eoc: fertilizer.p_eoc,
+ p_s_rt: fertilizer.p_s_rt,
+ p_ca_rt: fertilizer.p_ca_rt,
+ p_mg_rt: fertilizer.p_mg_rt,
+ })
+ }, [fertilizer, form.reset])
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export async function action({ request, params }: ActionFunctionArgs) {
+ try {
+ const b_id_farm = params.b_id_farm
+ const p_id = params.p_id
+
+ if (!b_id_farm) {
+ throw new Error("missing: b_id_farm")
+ }
+ if (!p_id) {
+ throw new Error("missing: p_id")
+ }
+
+ const session = await getSession(request)
+ const formValues = await extractFormValuesFromRequest(
+ request,
+ FormSchema,
+ )
+
+ const {
+ p_name_nl,
+ p_type,
+ p_n_rt,
+ p_n_wc,
+ p_p_rt,
+ p_k_rt,
+ p_om,
+ p_eoc,
+ p_s_rt,
+ p_ca_rt,
+ p_mg_rt,
+ } = formValues
+
+ let p_type_manure = false
+ let p_type_compost = false
+ let p_type_mineral = false
+ if (p_type === "manure") {
+ p_type_manure = true
+ }
+ if (p_type === "compost") {
+ p_type_compost = true
+ }
+ if (p_type === "mineral") {
+ p_type_mineral = true
+ }
+
+ const fertilizer = await getFertilizer(fdm, p_id)
+ const p_id_catalogue = fertilizer.p_id_catalogue
+
+ await updateFertilizerFromCatalogue(
+ fdm,
+ session.principal_id,
+ b_id_farm,
+ p_id_catalogue,
+ {
+ p_name_nl,
+ p_type_manure,
+ p_type_mineral,
+ p_type_compost,
+ p_n_rt,
+ p_n_wc,
+ p_p_rt,
+ p_k_rt,
+ p_om,
+ p_eoc,
+ p_s_rt,
+ p_ca_rt,
+ p_mg_rt,
+ },
+ )
+
+ return dataWithSuccess(
+ { result: "Data saved successfully" },
+ { message: "Meststof is bijgewerkt! 🎉" },
+ )
+ } catch (error) {
+ throw handleActionError(error)
+ }
+}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
new file mode 100644
index 000000000..cc27a759c
--- /dev/null
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers._index.tsx
@@ -0,0 +1,106 @@
+import { FarmHeader } from "@/components/custom/farm/farm-header"
+import { FarmTitle } from "@/components/custom/farm/farm-title"
+import {
+ columns,
+ type Fertilizer,
+} from "@/components/custom/fertilizer/columns"
+import { DataTable } from "@/components/custom/fertilizer/table"
+import { SidebarInset } from "@/components/ui/sidebar"
+import { getSession } from "@/lib/auth.server"
+import { handleLoaderError } from "@/lib/error"
+import { fdm } from "@/lib/fdm.server"
+import { getFarm, getFarms, getFertilizers } from "@svenvw/fdm-core"
+import { type LoaderFunctionArgs, data, useLoaderData } from "react-router"
+
+export async function loader({ request, params }: LoaderFunctionArgs) {
+ try {
+ // Get the farm id
+ const b_id_farm = params.b_id_farm
+ if (!b_id_farm) {
+ throw data("invalid: b_id_farm", {
+ status: 400,
+ statusText: "invalid: b_id_farm",
+ })
+ }
+
+ // Get the session
+ const session = await getSession(request)
+
+ // Get details of farm
+ const farm = await getFarm(fdm, session.principal_id, b_id_farm)
+ if (!farm) {
+ throw data("not found: b_id_farm", {
+ status: 404,
+ statusText: "not found: b_id_farm",
+ })
+ }
+
+ // Get a list of possible farms of the user
+ const farms = await getFarms(fdm, session.principal_id)
+ if (!farms || farms.length === 0) {
+ throw data("not found: farms", {
+ status: 404,
+ statusText: "not found: farms",
+ })
+ }
+
+ const farmOptions = farms.map((farm) => {
+ return {
+ b_id_farm: farm.b_id_farm,
+ b_name_farm: farm.b_name_farm,
+ }
+ })
+
+ // Get the available fertilizers
+ const fertilizers: Fertilizer[] = await getFertilizers(
+ fdm,
+ session.principal_id,
+ b_id_farm,
+ )
+
+ // Return user information from loader
+ return {
+ farm: farm,
+ b_id_farm: b_id_farm,
+ farmOptions: farmOptions,
+ fertilizers: fertilizers,
+ }
+ } catch (error) {
+ throw handleLoaderError(error)
+ }
+}
+
+/**
+ * Renders the layout for managing farm settings.
+ *
+ * This component displays a sidebar that includes the farm header, navigation options, and a link to farm fields.
+ * It also renders a main section containing the farm title, description, nested routes via an Outlet, and a notification toaster.
+ */
+export default function FarmFertilizersBlock() {
+ const loaderData = useLoaderData()
+
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.tsx
new file mode 100644
index 000000000..e503335d4
--- /dev/null
+++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.tsx
@@ -0,0 +1,283 @@
+import { FarmHeader } from "@/components/custom/farm/farm-header"
+import { FarmTitle } from "@/components/custom/farm/farm-title"
+import { FertilizerForm } from "@/components/custom/fertilizer/form"
+import { FormSchema } from "@/components/custom/fertilizer/formschema"
+import { SidebarInset } from "@/components/ui/sidebar"
+import { getSession } from "@/lib/auth.server"
+import { handleActionError, handleLoaderError } from "@/lib/error"
+import { fdm } from "@/lib/fdm.server"
+import { extractFormValuesFromRequest } from "@/lib/form"
+import { zodResolver } from "@hookform/resolvers/zod"
+import {
+ addFertilizerToCatalogue,
+ getFarm,
+ getFarms,
+ getFertilizer,
+ getFertilizers,
+} from "@svenvw/fdm-core"
+import { updateFertilizerFromCatalogue } from "@svenvw/fdm-core"
+import { UndoIcon } from "lucide-react"
+import { useEffect } from "react"
+import {
+ type ActionFunctionArgs,
+ type LoaderFunctionArgs,
+ data,
+ useLoaderData,
+} from "react-router"
+import { useRemixForm } from "remix-hook-form"
+import { dataWithSuccess, redirectWithSuccess } from "remix-toast"
+import type { z } from "zod"
+
+export async function loader({ request, params }: LoaderFunctionArgs) {
+ try {
+ // Get the farm id
+ const b_id_farm = params.b_id_farm
+ if (!b_id_farm) {
+ throw data("invalid: b_id_farm", {
+ status: 400,
+ statusText: "invalid: b_id_farm",
+ })
+ }
+
+ // Get the session
+ const session = await getSession(request)
+
+ // Get details of farm
+ const farm = await getFarm(fdm, session.principal_id, b_id_farm)
+ if (!farm) {
+ throw data("not found: b_id_farm", {
+ status: 404,
+ statusText: "not found: b_id_farm",
+ })
+ }
+
+ // Get a list of possible farms of the user
+ const farms = await getFarms(fdm, session.principal_id)
+ if (!farms || farms.length === 0) {
+ throw data("not found: farms", {
+ status: 404,
+ statusText: "not found: farms",
+ })
+ }
+
+ const farmOptions = farms.map((farm) => {
+ return {
+ b_id_farm: farm.b_id_farm,
+ b_name_farm: farm.b_name_farm,
+ }
+ })
+
+ // Get selected fertilizer
+ const fertilizer = {
+ p_source: b_id_farm,
+ p_name_nl: undefined,
+ p_type: undefined,
+ p_n_rt: undefined,
+ p_n_wc: undefined,
+ p_p_rt: undefined,
+ p_k_rt: undefined,
+ p_om: undefined,
+ p_eoc: undefined,
+ p_s_rt: undefined,
+ p_ca_rt: undefined,
+ p_mg_rt: undefined,
+ }
+
+ // Get the available fertilizers
+ const fertilizers = await getFertilizers(
+ fdm,
+ session.principal_id,
+ b_id_farm,
+ )
+ const fertilizerOptions = fertilizers.map((fertilizer) => {
+ return {
+ p_id: fertilizer.p_id,
+ p_name_nl: fertilizer.p_name_nl,
+ }
+ })
+
+ // Return user information from loader
+ return {
+ farm: farm,
+ b_id_farm: b_id_farm,
+ farmOptions: farmOptions,
+ fertilizerOptions: fertilizerOptions,
+ fertilizer: fertilizer,
+ }
+ } catch (error) {
+ throw handleLoaderError(error)
+ }
+}
+
+/**
+ * Renders the layout for managing farm settings.
+ *
+ * This component displays a sidebar that includes the farm header, navigation options, and a link to farm fields.
+ * It also renders a main section containing the farm title, description, nested routes via an Outlet, and a notification toaster.
+ */
+export default function FarmFertilizerBlock() {
+ const loaderData = useLoaderData()
+ const fertilizer = loaderData.fertilizer
+
+ const form = useRemixForm>({
+ mode: "onTouched",
+ resolver: zodResolver(FormSchema),
+ defaultValues: {
+ p_name_nl: fertilizer.p_name_nl,
+ p_type: fertilizer.p_type,
+ p_n_rt: fertilizer.p_n_rt,
+ p_n_wc: fertilizer.p_n_wc,
+ p_p_rt: fertilizer.p_p_rt,
+ p_k_rt: fertilizer.p_k_rt,
+ p_om: fertilizer.p_om,
+ p_eoc: fertilizer.p_eoc,
+ p_s_rt: fertilizer.p_s_rt,
+ p_ca_rt: fertilizer.p_ca_rt,
+ p_mg_rt: fertilizer.p_mg_rt,
+ },
+ })
+
+ useEffect(() => {
+ form.reset({
+ p_name_nl: fertilizer.p_name_nl,
+ p_type: fertilizer.p_type,
+ p_n_rt: fertilizer.p_n_rt,
+ p_n_wc: fertilizer.p_n_wc,
+ p_p_rt: fertilizer.p_p_rt,
+ p_k_rt: fertilizer.p_k_rt,
+ p_om: fertilizer.p_om,
+ p_eoc: fertilizer.p_eoc,
+ p_s_rt: fertilizer.p_s_rt,
+ p_ca_rt: fertilizer.p_ca_rt,
+ p_mg_rt: fertilizer.p_mg_rt,
+ })
+ }, [fertilizer, form.reset])
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export async function action({ request, params }: ActionFunctionArgs) {
+ try {
+ const b_id_farm = params.b_id_farm
+
+ if (!b_id_farm) {
+ throw new Error("missing: b_id_farm")
+ }
+
+ const session = await getSession(request)
+ const formValues = await extractFormValuesFromRequest(
+ request,
+ FormSchema,
+ )
+
+ const {
+ p_name_nl,
+ p_type,
+ p_n_rt,
+ p_n_wc,
+ p_p_rt,
+ p_k_rt,
+ p_om,
+ p_eoc,
+ p_s_rt,
+ p_ca_rt,
+ p_mg_rt,
+ } = formValues
+
+ let p_type_manure = false
+ let p_type_compost = false
+ let p_type_mineral = false
+ if (p_type === "manure") {
+ p_type_manure = true
+ }
+ if (p_type === "compost") {
+ p_type_compost = true
+ }
+ if (p_type === "mineral") {
+ p_type_mineral = true
+ }
+
+ await addFertilizerToCatalogue(fdm, session.principal_id, b_id_farm, {
+ p_name_nl,
+ p_type_manure,
+ p_type_mineral,
+ p_type_compost,
+ p_n_rt,
+ p_n_wc,
+ p_p_rt,
+ p_k_rt,
+ p_om,
+ p_eoc,
+ p_s_rt,
+ p_ca_rt,
+ p_mg_rt,
+ p_name_en: undefined,
+ p_description: undefined,
+ p_dm: undefined,
+ p_density: undefined,
+ p_a: undefined,
+ p_hc: undefined,
+ p_eom: undefined,
+ p_c_rt: undefined,
+ p_c_of: undefined,
+ p_c_if: undefined,
+ p_c_fr: undefined,
+ p_cn_of: undefined,
+ p_n_if: undefined,
+ p_n_of: undefined,
+ p_ne: undefined,
+ p_s_wc: undefined,
+ p_cu_rt: undefined,
+ p_zn_rt: undefined,
+ p_na_rt: undefined,
+ p_si_rt: undefined,
+ p_b_rt: undefined,
+ p_mn_rt: undefined,
+ p_ni_rt: undefined,
+ p_fe_rt: undefined,
+ p_mo_rt: undefined,
+ p_co_rt: undefined,
+ p_as_rt: undefined,
+ p_cd_rt: undefined,
+ pr_cr_rt: undefined,
+ p_cr_vi: undefined,
+ p_pb_rt: undefined,
+ p_hg_rt: undefined,
+ p_cl_rt: undefined,
+ })
+
+ return redirectWithSuccess("../fertilizers", {
+ message: "Meststof is toegevoegd! 🎉",
+ })
+ } catch (error) {
+ throw handleActionError(error)
+ }
+}
diff --git a/fdm-app/app/routes/farm.create._index.tsx b/fdm-app/app/routes/farm.create._index.tsx
index 3df2f86ae..41628384a 100644
--- a/fdm-app/app/routes/farm.create._index.tsx
+++ b/fdm-app/app/routes/farm.create._index.tsx
@@ -125,6 +125,13 @@ export async function action({ request }: ActionFunctionArgs) {
b_id_farm,
"srm",
)
+ // Enable catalogue with custom user fertilizers
+ await enableFertilizerCatalogue(
+ fdm,
+ session.principal_id,
+ b_id_farm,
+ b_id_farm,
+ )
await enableCultivationCatalogue(
fdm,
session.principal_id,
diff --git a/fdm-app/package.json b/fdm-app/package.json
index 00f758f18..9943e8ee3 100644
--- a/fdm-app/package.json
+++ b/fdm-app/package.json
@@ -37,6 +37,7 @@
"@sentry/vite-plugin": "^3.2.2",
"@svenvw/fdm-calculator": "workspace:^",
"@svenvw/fdm-core": "workspace:^",
+ "@tanstack/react-table": "^8.21.2",
"@turf/centroid": "^7.2.0",
"better-auth": "catalog:",
"class-variance-authority": "^0.7.1",
diff --git a/fdm-calculator/src/doses/get-dose-field.test.ts b/fdm-calculator/src/doses/get-dose-field.test.ts
index 66ebae170..edd976190 100644
--- a/fdm-calculator/src/doses/get-dose-field.test.ts
+++ b/fdm-calculator/src/doses/get-dose-field.test.ts
@@ -57,10 +57,7 @@ describe("getDoseForField", () => {
new Date(),
"lease",
)
- p_id_catalogue = `p_test_fertilizer_${Math.round(Math.random() * 1000)}`
- await addFertilizerToCatalogue(fdm, {
- p_id_catalogue: p_id_catalogue,
- p_source: "",
+ p_id_catalogue = await addFertilizerToCatalogue(fdm, principal_id, b_id_farm, {
p_name_nl: "",
p_name_en: "",
p_description: "",
diff --git a/fdm-core/src/catalogues.test.ts b/fdm-core/src/catalogues.test.ts
index a65e89365..d36ed5e42 100644
--- a/fdm-core/src/catalogues.test.ts
+++ b/fdm-core/src/catalogues.test.ts
@@ -591,7 +591,7 @@ describe("Catalogues syncing", () => {
.select()
.from(schema.fertilizersCatalogue)
- const srmCatalogueOriginal = getFertilizersCatalogue("srm")
+ const srmCatalogueOriginal = await getFertilizersCatalogue("srm")
expect(srmCatalogue.length).toBeGreaterThan(srmCatalogueOriginal.length)
const brpCatalogue = await fdm
@@ -599,7 +599,7 @@ describe("Catalogues syncing", () => {
.from(schema.cultivationsCatalogue)
expect(brpCatalogue.length).toBeGreaterThan(0)
- const brpCatalogueOriginal = getCultivationCatalogue("brp")
+ const brpCatalogueOriginal = await getCultivationCatalogue("brp")
expect(brpCatalogue.length).toBeGreaterThan(brpCatalogueOriginal.length)
})
@@ -614,6 +614,7 @@ describe("Catalogues syncing", () => {
})
.from(schema.fertilizersCatalogue)
.where(isNotNull(schema.fertilizersCatalogue.hash))
+ .orderBy(schema.fertilizersCatalogue.p_id_catalogue)
.limit(1)
expect(item[0].p_id_catalogue).toBeDefined()
@@ -672,6 +673,7 @@ describe("Catalogues syncing", () => {
})
.from(schema.cultivationsCatalogue)
.where(isNotNull(schema.cultivationsCatalogue.hash))
+ .orderBy(schema.cultivationsCatalogue.b_lu_catalogue)
.limit(1)
expect(item[0].b_lu_catalogue).toBeDefined()
diff --git a/fdm-core/src/catalogues.ts b/fdm-core/src/catalogues.ts
index eeb99e322..e628c642f 100644
--- a/fdm-core/src/catalogues.ts
+++ b/fdm-core/src/catalogues.ts
@@ -7,6 +7,8 @@ import { checkPermission } from "./authorization"
import {
getCultivationCatalogue,
getFertilizersCatalogue,
+ hashCultivation,
+ hashFertilizer,
} from "@svenvw/fdm-data"
/**
@@ -368,83 +370,101 @@ export async function isCultivationCatalogueEnabled(
* @returns A promise that resolves when the synchronization is complete.
*/
export async function syncCatalogues(fdm: FdmType): Promise {
- try {
- // Sync fertilizers catalogue (SRM)
- const srmCatalogue = getFertilizersCatalogue("srm")
- for (const srmItem of srmCatalogue) {
- const existingItem = await fdm
- .select()
- .from(schema.fertilizersCatalogue)
- .where(
- eq(
- schema.fertilizersCatalogue.p_id_catalogue,
- srmItem.p_id_catalogue,
- ),
- )
- .limit(1)
+ await syncFertilizerCatalogue(fdm)
+ await syncCultivationCatalogue(fdm)
+}
- if (existingItem.length === 0) {
- await fdm.insert(schema.fertilizersCatalogue).values(srmItem)
- console.log(
- `Inserted fertilizer catalogue item: ${srmItem.p_id_catalogue}`,
- )
- } else {
- // Update item if different
- if (srmItem.hash && srmItem.hash !== existingItem[0].hash) {
- await fdm
- .update(schema.fertilizersCatalogue)
- .set(srmItem)
- .where(
- eq(
- schema.fertilizersCatalogue.p_id_catalogue,
- srmItem.p_id_catalogue,
- ),
- )
- console.log(
- `Updated fertilizer catalogue item: ${srmItem.p_id_catalogue}`,
+async function syncFertilizerCatalogue(fdm: FdmType) {
+ const srmCatalogue = await getFertilizersCatalogue("srm")
+ await fdm.transaction(async (tx) => {
+ try {
+ for (const item of srmCatalogue) {
+ const hash = await hashFertilizer(item)
+ const existing = await tx
+ .select({ hash: schema.fertilizersCatalogue.hash })
+ .from(schema.fertilizersCatalogue)
+ .where(
+ eq(
+ schema.fertilizersCatalogue.p_id_catalogue,
+ item.p_id_catalogue,
+ ),
)
+ .limit(1)
+ if (existing.length === 0) {
+ //add the item if does not exist
+ await tx.insert(schema.fertilizersCatalogue).values({
+ ...item,
+ hash: hash,
+ })
+ } else {
+ // update the hash if it is undefined, null or different
+ if (
+ existing[0].hash === null ||
+ existing[0].hash === undefined ||
+ existing[0].hash !== hash
+ ) {
+ await tx
+ .update(schema.fertilizersCatalogue)
+ .set({ hash: hash })
+ .where(
+ eq(
+ schema.fertilizersCatalogue.p_id_catalogue,
+ item.p_id_catalogue,
+ ),
+ )
+ }
}
}
+ } catch (error) {
+ throw handleError(error, "Exception for syncFertilizerCatalogue")
}
+ })
+}
- // Sync cultivation catalogue (BRP)
- const brpCatalogue = getCultivationCatalogue("brp")
- for (const brpItem of brpCatalogue) {
- const existingItem = await fdm
- .select()
- .from(schema.cultivationsCatalogue)
- .where(
- eq(
- schema.cultivationsCatalogue.b_lu_catalogue,
- brpItem.b_lu_catalogue,
- ),
- )
- .limit(1)
+async function syncCultivationCatalogue(fdm: FdmType) {
+ const brpCatalogue = await getCultivationCatalogue("brp")
- if (existingItem.length === 0) {
- await fdm.insert(schema.cultivationsCatalogue).values(brpItem)
- console.log(
- `Inserted cultivation catalogue item: ${brpItem.b_lu_catalogue}`,
- )
- } else {
- // Update item if different
- if (brpItem.hash && brpItem.hash !== existingItem[0].hash) {
- await fdm
- .update(schema.cultivationsCatalogue)
- .set(brpItem)
- .where(
- eq(
- schema.cultivationsCatalogue.b_lu_catalogue,
- brpItem.b_lu_catalogue,
- ),
- )
- console.log(
- `Updated cultivation catalogue item: ${brpItem.b_lu_catalogue}`,
+ await fdm.transaction(async (tx) => {
+ try {
+ for (const item of brpCatalogue) {
+ const hash = await hashCultivation(item)
+ const existing = await tx
+ .select({ hash: schema.cultivationsCatalogue.hash })
+ .from(schema.cultivationsCatalogue)
+ .where(
+ eq(
+ schema.cultivationsCatalogue.b_lu_catalogue,
+ item.b_lu_catalogue,
+ ),
)
+ .limit(1)
+ if (existing.length === 0) {
+ //add the item if does not exist
+ await tx.insert(schema.cultivationsCatalogue).values({
+ ...item,
+ hash: hash,
+ })
+ } else {
+ // update the hash if it is undefined, null or different
+ if (
+ existing[0].hash === null ||
+ existing[0].hash === undefined ||
+ existing[0].hash !== hash
+ ) {
+ await tx
+ .update(schema.cultivationsCatalogue)
+ .set({ hash: hash })
+ .where(
+ eq(
+ schema.cultivationsCatalogue.b_lu_catalogue,
+ item.b_lu_catalogue,
+ ),
+ )
+ }
}
}
+ } catch (error) {
+ throw handleError(error, "Exception for syncCultivationCatalogue")
}
- } catch (err) {
- throw handleError(err, "Exception for syncCatalogues")
- }
+ })
}
diff --git a/fdm-core/src/cultivation.test.ts b/fdm-core/src/cultivation.test.ts
index da6702301..7dda3a37b 100644
--- a/fdm-core/src/cultivation.test.ts
+++ b/fdm-core/src/cultivation.test.ts
@@ -469,7 +469,6 @@ describe("Cultivation Data Model", () => {
let b_lu_catalogue: string
let p_id: string
let b_lu_source: string
- let p_source: string
beforeEach(async () => {
const farmName = "Test Farm"
@@ -492,13 +491,11 @@ describe("Cultivation Data Model", () => {
b_id_farm,
b_lu_source,
)
-
- p_source = "custom"
await enableFertilizerCatalogue(
fdm,
principal_id,
b_id_farm,
- p_source,
+ b_id_farm,
)
b_id = await addField(
@@ -544,63 +541,65 @@ describe("Cultivation Data Model", () => {
)
// Add fertilizer to catalogue (needed for fertilizer application)
- const p_id_catalogue = createId()
const p_name_nl = "Test Fertilizer"
const p_name_en = "Test Fertilizer (EN)"
const p_description = "This is a test fertilizer"
const p_acquiring_amount = 1000
const p_acquiring_date = new Date()
- await addFertilizerToCatalogue(fdm, {
- p_id_catalogue,
- p_source,
- p_name_nl,
- p_name_en,
- p_description,
- p_dm: 37,
- p_density: 20,
- p_om: 20,
- p_a: 30,
- p_hc: 40,
- p_eom: 50,
- p_eoc: 60,
- p_c_rt: 70,
- p_c_of: 80,
- p_c_if: 90,
- p_c_fr: 100,
- p_cn_of: 110,
- p_n_rt: 120,
- p_n_if: 130,
- p_n_of: 140,
- p_n_wc: 150,
- p_p_rt: 160,
- p_k_rt: 170,
- p_mg_rt: 180,
- p_ca_rt: 190,
- p_ne: 200,
- p_s_rt: 210,
- p_s_wc: 220,
- p_cu_rt: 230,
- p_zn_rt: 240,
- p_na_rt: 250,
- p_si_rt: 260,
- p_b_rt: 270,
- p_mn_rt: 280,
- p_ni_rt: 290,
- p_fe_rt: 300,
- p_mo_rt: 310,
- p_co_rt: 320,
- p_as_rt: 330,
- p_cd_rt: 340,
- pr_cr_rt: 350,
- p_cr_vi: 360,
- p_pb_rt: 370,
- p_hg_rt: 380,
- p_cl_rt: 390,
- p_type_manure: true,
- p_type_mineral: false,
- p_type_compost: false,
- })
+ const p_id_catalogue = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ {
+ p_name_nl,
+ p_name_en,
+ p_description,
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
p_id = await addFertilizer(
fdm,
diff --git a/fdm-core/src/db/migrations/0002_kind_cardiac.sql b/fdm-core/src/db/migrations/0002_kind_cardiac.sql
new file mode 100644
index 000000000..c65dcc3b4
--- /dev/null
+++ b/fdm-core/src/db/migrations/0002_kind_cardiac.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "fdm"."fertilizers_catalogue" RENAME COLUMN "p_cl_cr" TO "p_cl_rt";--> statement-breakpoint
+ALTER TABLE "fdm"."fertilizers_catalogue" ALTER COLUMN "p_name_nl" SET NOT NULL;
\ No newline at end of file
diff --git a/fdm-core/src/db/migrations/meta/0002_snapshot.json b/fdm-core/src/db/migrations/meta/0002_snapshot.json
new file mode 100644
index 000000000..041c7e4fe
--- /dev/null
+++ b/fdm-core/src/db/migrations/meta/0002_snapshot.json
@@ -0,0 +1,2428 @@
+{
+ "id": "dc114784-2d49-48eb-964a-cf4291916b83",
+ "prevId": "d46cbd5a-9e08-41a5-833f-5888c289c944",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "fdm.cultivation_catalogue_selecting": {
+ "name": "cultivation_catalogue_selecting",
+ "schema": "fdm",
+ "columns": {
+ "b_id_farm": {
+ "name": "b_id_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu_source": {
+ "name": "b_lu_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "cultivation_catalogue_selecting_b_id_farm_farms_b_id_farm_fk": {
+ "name": "cultivation_catalogue_selecting_b_id_farm_farms_b_id_farm_fk",
+ "tableFrom": "cultivation_catalogue_selecting",
+ "tableTo": "farms",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id_farm"
+ ],
+ "columnsTo": [
+ "b_id_farm"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.cultivation_ending": {
+ "name": "cultivation_ending",
+ "schema": "fdm",
+ "columns": {
+ "b_lu": {
+ "name": "b_lu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu_end": {
+ "name": "b_lu_end",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "cultivation_ending_b_lu_cultivations_b_lu_fk": {
+ "name": "cultivation_ending_b_lu_cultivations_b_lu_fk",
+ "tableFrom": "cultivation_ending",
+ "tableTo": "cultivations",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_lu"
+ ],
+ "columnsTo": [
+ "b_lu"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.cultivation_harvesting": {
+ "name": "cultivation_harvesting",
+ "schema": "fdm",
+ "columns": {
+ "b_id_harvesting": {
+ "name": "b_id_harvesting",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_id_harvestable": {
+ "name": "b_id_harvestable",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu": {
+ "name": "b_lu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu_harvest_date": {
+ "name": "b_lu_harvest_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "cultivation_harvesting_b_id_harvestable_harvestables_b_id_harvestable_fk": {
+ "name": "cultivation_harvesting_b_id_harvestable_harvestables_b_id_harvestable_fk",
+ "tableFrom": "cultivation_harvesting",
+ "tableTo": "harvestables",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id_harvestable"
+ ],
+ "columnsTo": [
+ "b_id_harvestable"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "cultivation_harvesting_b_lu_cultivations_b_lu_fk": {
+ "name": "cultivation_harvesting_b_lu_cultivations_b_lu_fk",
+ "tableFrom": "cultivation_harvesting",
+ "tableTo": "cultivations",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_lu"
+ ],
+ "columnsTo": [
+ "b_lu"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.cultivation_starting": {
+ "name": "cultivation_starting",
+ "schema": "fdm",
+ "columns": {
+ "b_id": {
+ "name": "b_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu": {
+ "name": "b_lu",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu_start": {
+ "name": "b_lu_start",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_sowing_amount": {
+ "name": "b_sowing_amount",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_sowing_method": {
+ "name": "b_sowing_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "cultivation_starting_b_id_fields_b_id_fk": {
+ "name": "cultivation_starting_b_id_fields_b_id_fk",
+ "tableFrom": "cultivation_starting",
+ "tableTo": "fields",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id"
+ ],
+ "columnsTo": [
+ "b_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "cultivation_starting_b_lu_cultivations_b_lu_fk": {
+ "name": "cultivation_starting_b_lu_cultivations_b_lu_fk",
+ "tableFrom": "cultivation_starting",
+ "tableTo": "cultivations",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_lu"
+ ],
+ "columnsTo": [
+ "b_lu"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.cultivations": {
+ "name": "cultivations",
+ "schema": "fdm",
+ "columns": {
+ "b_lu": {
+ "name": "b_lu",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_lu_catalogue": {
+ "name": "b_lu_catalogue",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "b_lu_idx": {
+ "name": "b_lu_idx",
+ "columns": [
+ {
+ "expression": "b_lu",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "cultivations_b_lu_catalogue_cultivations_catalogue_b_lu_catalogue_fk": {
+ "name": "cultivations_b_lu_catalogue_cultivations_catalogue_b_lu_catalogue_fk",
+ "tableFrom": "cultivations",
+ "tableTo": "cultivations_catalogue",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_lu_catalogue"
+ ],
+ "columnsTo": [
+ "b_lu_catalogue"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.cultivations_catalogue": {
+ "name": "cultivations_catalogue",
+ "schema": "fdm",
+ "columns": {
+ "b_lu_catalogue": {
+ "name": "b_lu_catalogue",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_lu_source": {
+ "name": "b_lu_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu_name": {
+ "name": "b_lu_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu_name_en": {
+ "name": "b_lu_name_en",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_harvestable": {
+ "name": "b_lu_harvestable",
+ "type": "b_lu_harvestable",
+ "typeSchema": "fdm",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_lu_hcat3": {
+ "name": "b_lu_hcat3",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_hcat3_name": {
+ "name": "b_lu_hcat3_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hash": {
+ "name": "hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "b_lu_catalogue_idx": {
+ "name": "b_lu_catalogue_idx",
+ "columns": [
+ {
+ "expression": "b_lu_catalogue",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.farms": {
+ "name": "farms",
+ "schema": "fdm",
+ "columns": {
+ "b_id_farm": {
+ "name": "b_id_farm",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_name_farm": {
+ "name": "b_name_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_businessid_farm": {
+ "name": "b_businessid_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_address_farm": {
+ "name": "b_address_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_postalcode_farm": {
+ "name": "b_postalcode_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "b_id_farm_idx": {
+ "name": "b_id_farm_idx",
+ "columns": [
+ {
+ "expression": "b_id_farm",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.fertilizer_acquiring": {
+ "name": "fertilizer_acquiring",
+ "schema": "fdm",
+ "columns": {
+ "b_id_farm": {
+ "name": "b_id_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_id": {
+ "name": "p_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_acquiring_amount": {
+ "name": "p_acquiring_amount",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_acquiring_date": {
+ "name": "p_acquiring_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "fertilizer_acquiring_b_id_farm_farms_b_id_farm_fk": {
+ "name": "fertilizer_acquiring_b_id_farm_farms_b_id_farm_fk",
+ "tableFrom": "fertilizer_acquiring",
+ "tableTo": "farms",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id_farm"
+ ],
+ "columnsTo": [
+ "b_id_farm"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "fertilizer_acquiring_p_id_fertilizers_p_id_fk": {
+ "name": "fertilizer_acquiring_p_id_fertilizers_p_id_fk",
+ "tableFrom": "fertilizer_acquiring",
+ "tableTo": "fertilizers",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "p_id"
+ ],
+ "columnsTo": [
+ "p_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.fertilizer_applying": {
+ "name": "fertilizer_applying",
+ "schema": "fdm",
+ "columns": {
+ "p_app_id": {
+ "name": "p_app_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_id": {
+ "name": "b_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_id": {
+ "name": "p_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_app_amount": {
+ "name": "p_app_amount",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_app_method": {
+ "name": "p_app_method",
+ "type": "p_app_method",
+ "typeSchema": "fdm",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_app_date": {
+ "name": "p_app_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "p_app_idx": {
+ "name": "p_app_idx",
+ "columns": [
+ {
+ "expression": "p_app_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fertilizer_applying_b_id_fields_b_id_fk": {
+ "name": "fertilizer_applying_b_id_fields_b_id_fk",
+ "tableFrom": "fertilizer_applying",
+ "tableTo": "fields",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id"
+ ],
+ "columnsTo": [
+ "b_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "fertilizer_applying_p_id_fertilizers_p_id_fk": {
+ "name": "fertilizer_applying_p_id_fertilizers_p_id_fk",
+ "tableFrom": "fertilizer_applying",
+ "tableTo": "fertilizers",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "p_id"
+ ],
+ "columnsTo": [
+ "p_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.fertilizer_catalogue_enabling": {
+ "name": "fertilizer_catalogue_enabling",
+ "schema": "fdm",
+ "columns": {
+ "b_id_farm": {
+ "name": "b_id_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_source": {
+ "name": "p_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "fertilizer_catalogue_enabling_b_id_farm_farms_b_id_farm_fk": {
+ "name": "fertilizer_catalogue_enabling_b_id_farm_farms_b_id_farm_fk",
+ "tableFrom": "fertilizer_catalogue_enabling",
+ "tableTo": "farms",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id_farm"
+ ],
+ "columnsTo": [
+ "b_id_farm"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.fertilizer_picking": {
+ "name": "fertilizer_picking",
+ "schema": "fdm",
+ "columns": {
+ "p_id": {
+ "name": "p_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_id_catalogue": {
+ "name": "p_id_catalogue",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_picking_date": {
+ "name": "p_picking_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "fertilizer_picking_p_id_fertilizers_p_id_fk": {
+ "name": "fertilizer_picking_p_id_fertilizers_p_id_fk",
+ "tableFrom": "fertilizer_picking",
+ "tableTo": "fertilizers",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "p_id"
+ ],
+ "columnsTo": [
+ "p_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "fertilizer_picking_p_id_catalogue_fertilizers_catalogue_p_id_catalogue_fk": {
+ "name": "fertilizer_picking_p_id_catalogue_fertilizers_catalogue_p_id_catalogue_fk",
+ "tableFrom": "fertilizer_picking",
+ "tableTo": "fertilizers_catalogue",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "p_id_catalogue"
+ ],
+ "columnsTo": [
+ "p_id_catalogue"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.fertilizers": {
+ "name": "fertilizers",
+ "schema": "fdm",
+ "columns": {
+ "p_id": {
+ "name": "p_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "p_id_idx": {
+ "name": "p_id_idx",
+ "columns": [
+ {
+ "expression": "p_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.fertilizers_catalogue": {
+ "name": "fertilizers_catalogue",
+ "schema": "fdm",
+ "columns": {
+ "p_id_catalogue": {
+ "name": "p_id_catalogue",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "p_source": {
+ "name": "p_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_name_nl": {
+ "name": "p_name_nl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p_name_en": {
+ "name": "p_name_en",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_description": {
+ "name": "p_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_dm": {
+ "name": "p_dm",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_density": {
+ "name": "p_density",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_om": {
+ "name": "p_om",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_a": {
+ "name": "p_a",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_hc": {
+ "name": "p_hc",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_eom": {
+ "name": "p_eom",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_eoc": {
+ "name": "p_eoc",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_c_rt": {
+ "name": "p_c_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_c_of": {
+ "name": "p_c_of",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_c_if": {
+ "name": "p_c_if",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_c_fr": {
+ "name": "p_c_fr",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_cn_of": {
+ "name": "p_cn_of",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_n_rt": {
+ "name": "p_n_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_n_if": {
+ "name": "p_n_if",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_n_of": {
+ "name": "p_n_of",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_n_wc": {
+ "name": "p_n_wc",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_p_rt": {
+ "name": "p_p_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_k_rt": {
+ "name": "p_k_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_mg_rt": {
+ "name": "p_mg_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_ca_rt": {
+ "name": "p_ca_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_ne": {
+ "name": "p_ne",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_s_rt": {
+ "name": "p_s_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_s_wc": {
+ "name": "p_s_wc",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_cu_rt": {
+ "name": "p_cu_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_zn_rt": {
+ "name": "p_zn_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_na_rt": {
+ "name": "p_na_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_si_rt": {
+ "name": "p_si_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_b_rt": {
+ "name": "p_b_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_mn_rt": {
+ "name": "p_mn_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_ni_rt": {
+ "name": "p_ni_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_fe_rt": {
+ "name": "p_fe_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_mo_rt": {
+ "name": "p_mo_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_co_rt": {
+ "name": "p_co_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_as_rt": {
+ "name": "p_as_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_cd_rt": {
+ "name": "p_cd_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_cr_rt": {
+ "name": "p_cr_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_cr_vi": {
+ "name": "p_cr_vi",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_pb_rt": {
+ "name": "p_pb_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_hg_rt": {
+ "name": "p_hg_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_cl_rt": {
+ "name": "p_cl_rt",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_type_manure": {
+ "name": "p_type_manure",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_type_mineral": {
+ "name": "p_type_mineral",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "p_type_compost": {
+ "name": "p_type_compost",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hash": {
+ "name": "hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "p_id_catalogue_idx": {
+ "name": "p_id_catalogue_idx",
+ "columns": [
+ {
+ "expression": "p_id_catalogue",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.field_acquiring": {
+ "name": "field_acquiring",
+ "schema": "fdm",
+ "columns": {
+ "b_id": {
+ "name": "b_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_id_farm": {
+ "name": "b_id_farm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_start": {
+ "name": "b_start",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_acquiring_method": {
+ "name": "b_acquiring_method",
+ "type": "b_acquiring_method",
+ "typeSchema": "fdm",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'unknown'"
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "field_acquiring_b_id_fields_b_id_fk": {
+ "name": "field_acquiring_b_id_fields_b_id_fk",
+ "tableFrom": "field_acquiring",
+ "tableTo": "fields",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id"
+ ],
+ "columnsTo": [
+ "b_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "field_acquiring_b_id_farm_farms_b_id_farm_fk": {
+ "name": "field_acquiring_b_id_farm_farms_b_id_farm_fk",
+ "tableFrom": "field_acquiring",
+ "tableTo": "farms",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id_farm"
+ ],
+ "columnsTo": [
+ "b_id_farm"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.field_discarding": {
+ "name": "field_discarding",
+ "schema": "fdm",
+ "columns": {
+ "b_id": {
+ "name": "b_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_end": {
+ "name": "b_end",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "field_discarding_b_id_fields_b_id_fk": {
+ "name": "field_discarding_b_id_fields_b_id_fk",
+ "tableFrom": "field_discarding",
+ "tableTo": "fields",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id"
+ ],
+ "columnsTo": [
+ "b_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.fields": {
+ "name": "fields",
+ "schema": "fdm",
+ "columns": {
+ "b_id": {
+ "name": "b_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_name": {
+ "name": "b_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_geometry": {
+ "name": "b_geometry",
+ "type": "geometry(Polygon,4326)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_id_source": {
+ "name": "b_id_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "b_id_idx": {
+ "name": "b_id_idx",
+ "columns": [
+ {
+ "expression": "b_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "b_geom_idx": {
+ "name": "b_geom_idx",
+ "columns": [
+ {
+ "expression": "b_geometry",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gist",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.harvestable_analyses": {
+ "name": "harvestable_analyses",
+ "schema": "fdm",
+ "columns": {
+ "b_id_harvestable_analysis": {
+ "name": "b_id_harvestable_analysis",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_lu_yield": {
+ "name": "b_lu_yield",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_n_harvestable": {
+ "name": "b_lu_n_harvestable",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_n_residue": {
+ "name": "b_lu_n_residue",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_p_harvestable": {
+ "name": "b_lu_p_harvestable",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_p_residue": {
+ "name": "b_lu_p_residue",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_k_harvestable": {
+ "name": "b_lu_k_harvestable",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_lu_k_residue": {
+ "name": "b_lu_k_residue",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "b_id_harvestable_analyses_idx": {
+ "name": "b_id_harvestable_analyses_idx",
+ "columns": [
+ {
+ "expression": "b_id_harvestable_analysis",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.harvestable_sampling": {
+ "name": "harvestable_sampling",
+ "schema": "fdm",
+ "columns": {
+ "b_id_harvestable": {
+ "name": "b_id_harvestable",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_id_harvestable_analysis": {
+ "name": "b_id_harvestable_analysis",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_sampling_date": {
+ "name": "b_sampling_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "harvestable_sampling_b_id_harvestable_harvestables_b_id_harvestable_fk": {
+ "name": "harvestable_sampling_b_id_harvestable_harvestables_b_id_harvestable_fk",
+ "tableFrom": "harvestable_sampling",
+ "tableTo": "harvestables",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id_harvestable"
+ ],
+ "columnsTo": [
+ "b_id_harvestable"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "harvestable_sampling_b_id_harvestable_analysis_harvestable_analyses_b_id_harvestable_analysis_fk": {
+ "name": "harvestable_sampling_b_id_harvestable_analysis_harvestable_analyses_b_id_harvestable_analysis_fk",
+ "tableFrom": "harvestable_sampling",
+ "tableTo": "harvestable_analyses",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id_harvestable_analysis"
+ ],
+ "columnsTo": [
+ "b_id_harvestable_analysis"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.harvestables": {
+ "name": "harvestables",
+ "schema": "fdm",
+ "columns": {
+ "b_id_harvestable": {
+ "name": "b_id_harvestable",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "b_id_harvestable_idx": {
+ "name": "b_id_harvestable_idx",
+ "columns": [
+ {
+ "expression": "b_id_harvestable",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.soil_analysis": {
+ "name": "soil_analysis",
+ "schema": "fdm",
+ "columns": {
+ "a_id": {
+ "name": "a_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "a_date": {
+ "name": "a_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "a_source": {
+ "name": "a_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "a_p_al": {
+ "name": "a_p_al",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "a_p_cc": {
+ "name": "a_p_cc",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "a_som_loi": {
+ "name": "a_som_loi",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_gwl_class": {
+ "name": "b_gwl_class",
+ "type": "b_gwl_class",
+ "typeSchema": "fdm",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_soiltype_agr": {
+ "name": "b_soiltype_agr",
+ "type": "b_soiltype_agr",
+ "typeSchema": "fdm",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm.soil_sampling": {
+ "name": "soil_sampling",
+ "schema": "fdm",
+ "columns": {
+ "b_id_sampling": {
+ "name": "b_id_sampling",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "b_id": {
+ "name": "b_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "a_id": {
+ "name": "a_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "b_depth": {
+ "name": "b_depth",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_sampling_date": {
+ "name": "b_sampling_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "b_sampling_geometry": {
+ "name": "b_sampling_geometry",
+ "type": "geometry(MultiPoint,4326)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated": {
+ "name": "updated",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "soil_sampling_b_id_fields_b_id_fk": {
+ "name": "soil_sampling_b_id_fields_b_id_fk",
+ "tableFrom": "soil_sampling",
+ "tableTo": "fields",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "b_id"
+ ],
+ "columnsTo": [
+ "b_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "soil_sampling_a_id_soil_analysis_a_id_fk": {
+ "name": "soil_sampling_a_id_soil_analysis_a_id_fk",
+ "tableFrom": "soil_sampling",
+ "tableTo": "soil_analysis",
+ "schemaTo": "fdm",
+ "columnsFrom": [
+ "a_id"
+ ],
+ "columnsTo": [
+ "a_id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm-authn.account": {
+ "name": "account",
+ "schema": "fdm-authn",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "schemaTo": "fdm-authn",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm-authn.rate_limit": {
+ "name": "rate_limit",
+ "schema": "fdm-authn",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm-authn.session": {
+ "name": "session",
+ "schema": "fdm-authn",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "schemaTo": "fdm-authn",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm-authn.user": {
+ "name": "user",
+ "schema": "fdm-authn",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "firstname": {
+ "name": "firstname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surname": {
+ "name": "surname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lang": {
+ "name": "lang",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "farm_active": {
+ "name": "farm_active",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm-authn.verification": {
+ "name": "verification",
+ "schema": "fdm-authn",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm-authz.audit": {
+ "name": "audit",
+ "schema": "fdm-authz",
+ "columns": {
+ "audit_id": {
+ "name": "audit_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "audit_timestamp": {
+ "name": "audit_timestamp",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "audit_origin": {
+ "name": "audit_origin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "principal_id": {
+ "name": "principal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_resource": {
+ "name": "target_resource",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_resource_id": {
+ "name": "target_resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granting_resource": {
+ "name": "granting_resource",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "granting_resource_id": {
+ "name": "granting_resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "allowed": {
+ "name": "allowed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration": {
+ "name": "duration",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "fdm-authz.role": {
+ "name": "role",
+ "schema": "fdm-authz",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "resource": {
+ "name": "resource",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "principal_id": {
+ "name": "principal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created": {
+ "name": "created",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted": {
+ "name": "deleted",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "role_idx": {
+ "name": "role_idx",
+ "columns": [
+ {
+ "expression": "resource",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "principal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "fdm.b_acquiring_method": {
+ "name": "b_acquiring_method",
+ "schema": "fdm",
+ "values": [
+ "owner",
+ "lease",
+ "unknown"
+ ]
+ },
+ "fdm.p_app_method": {
+ "name": "p_app_method",
+ "schema": "fdm",
+ "values": [
+ "slotted coulter",
+ "incorporation",
+ "injection",
+ "spraying",
+ "broadcasting",
+ "spoke wheel",
+ "pocket placement"
+ ]
+ },
+ "fdm.b_gwl_class": {
+ "name": "b_gwl_class",
+ "schema": "fdm",
+ "values": [
+ "II",
+ "IV",
+ "IIIb",
+ "V",
+ "VI",
+ "VII",
+ "Vb",
+ "-",
+ "Va",
+ "III",
+ "VIII",
+ "sVI",
+ "I",
+ "IIb",
+ "sVII",
+ "IVu",
+ "bVII",
+ "sV",
+ "sVb",
+ "bVI",
+ "IIIa"
+ ]
+ },
+ "fdm.b_lu_harvestable": {
+ "name": "b_lu_harvestable",
+ "schema": "fdm",
+ "values": [
+ "none",
+ "once",
+ "multiple"
+ ]
+ },
+ "fdm.b_soiltype_agr": {
+ "name": "b_soiltype_agr",
+ "schema": "fdm",
+ "values": [
+ "moerige_klei",
+ "rivierklei",
+ "dekzand",
+ "zeeklei",
+ "dalgrond",
+ "veen",
+ "loess",
+ "duinzand",
+ "maasklei"
+ ]
+ }
+ },
+ "schemas": {
+ "fdm": "fdm",
+ "fdm-authn": "fdm-authn",
+ "fdm-authz": "fdm-authz"
+ },
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/fdm-core/src/db/migrations/meta/_journal.json b/fdm-core/src/db/migrations/meta/_journal.json
index e56cc4fcc..e7fbe730c 100644
--- a/fdm-core/src/db/migrations/meta/_journal.json
+++ b/fdm-core/src/db/migrations/meta/_journal.json
@@ -15,6 +15,13 @@
"when": 1741267610502,
"tag": "0001_curved_proemial_gods",
"breakpoints": true
+ },
+ {
+ "idx": 2,
+ "version": "7",
+ "when": 1743420907290,
+ "tag": "0002_kind_cardiac",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/fdm-core/src/db/schema.ts b/fdm-core/src/db/schema.ts
index 43a700274..12473e11f 100644
--- a/fdm-core/src/db/schema.ts
+++ b/fdm-core/src/db/schema.ts
@@ -181,7 +181,7 @@ export const fertilizersCatalogue = fdmSchema.table(
{
p_id_catalogue: text().primaryKey(),
p_source: text().notNull(),
- p_name_nl: text(),
+ p_name_nl: text().notNull(),
p_name_en: text(),
p_description: text(),
p_dm: numericCasted(),
@@ -223,7 +223,7 @@ export const fertilizersCatalogue = fdmSchema.table(
p_cr_vi: numericCasted(),
p_pb_rt: numericCasted(),
p_hg_rt: numericCasted(),
- p_cl_cr: numericCasted(),
+ p_cl_rt: numericCasted(),
p_type_manure: boolean(),
p_type_mineral: boolean(),
p_type_compost: boolean(),
diff --git a/fdm-core/src/fertilizer.test.ts b/fdm-core/src/fertilizer.test.ts
index 51a383a12..2f050bdbb 100644
--- a/fdm-core/src/fertilizer.test.ts
+++ b/fdm-core/src/fertilizer.test.ts
@@ -22,17 +22,19 @@ import {
removeFertilizer,
removeFertilizerApplication,
updateFertilizerApplication,
+ updateFertilizerFromCatalogue,
} from "./fertilizer"
import { addField } from "./field"
+import {
+ disableFertilizerCatalogue,
+ enableFertilizerCatalogue,
+} from "./catalogues"
import { createId } from "./id"
-import { disableFertilizerCatalogue, enableFertilizerCatalogue } from "./catalogues"
describe("Fertilizer Data Model", () => {
let fdm: FdmServerType
- let p_id_catalogue: string
let principal_id: string
let b_id_farm: string
- let p_source: string
beforeEach(async () => {
const host = inject("host")
@@ -56,10 +58,7 @@ describe("Fertilizer Data Model", () => {
farmPostalCode,
)
- p_source = "custom"
- await enableFertilizerCatalogue(fdm, principal_id, b_id_farm, p_source)
-
- p_id_catalogue = createId()
+ await enableFertilizerCatalogue(fdm, principal_id, b_id_farm, b_id_farm)
})
afterAll(async () => {})
@@ -78,56 +77,59 @@ describe("Fertilizer Data Model", () => {
const p_name_nl = "Test Fertilizer"
const p_name_en = "Test Fertilizer (EN)"
const p_description = "This is a test fertilizer"
- await addFertilizerToCatalogue(fdm, {
- p_id_catalogue,
- p_source,
- p_name_nl,
- p_name_en,
- p_description,
- p_dm: 37,
- p_density: 20,
- p_om: 20,
- p_a: 30,
- p_hc: 40,
- p_eom: 50,
- p_eoc: 60,
- p_c_rt: 70,
- p_c_of: 80,
- p_c_if: 90,
- p_c_fr: 100,
- p_cn_of: 110,
- p_n_rt: 120,
- p_n_if: 130,
- p_n_of: 140,
- p_n_wc: 150,
- p_p_rt: 160,
- p_k_rt: 170,
- p_mg_rt: 180,
- p_ca_rt: 190,
- p_ne: 200,
- p_s_rt: 210,
- p_s_wc: 220,
- p_cu_rt: 230,
- p_zn_rt: 240,
- p_na_rt: 250,
- p_si_rt: 260,
- p_b_rt: 270,
- p_mn_rt: 280,
- p_ni_rt: 290,
- p_fe_rt: 300,
- p_mo_rt: 310,
- p_co_rt: 320,
- p_as_rt: 330,
- p_cd_rt: 340,
- pr_cr_rt: 350,
- p_cr_vi: 360,
- p_pb_rt: 370,
- p_hg_rt: 380,
- p_cl_rt: 390,
- p_type_manure: true,
- p_type_mineral: false,
- p_type_compost: false,
- })
+ const p_id_catalogue = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ {
+ p_name_nl,
+ p_name_en,
+ p_description,
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
const fertilizers = await getFertilizersFromCatalogue(
fdm,
@@ -139,7 +141,7 @@ describe("Fertilizer Data Model", () => {
(f) => f.p_id_catalogue === p_id_catalogue,
)
expect(fertilizer).toBeDefined()
- expect(fertilizer?.p_source).toBe(p_source)
+ expect(fertilizer?.p_source).toBe(b_id_farm)
expect(fertilizer?.p_name_nl).toBe(p_name_nl)
expect(fertilizer?.p_name_en).toBe(p_name_en)
expect(fertilizer?.p_description).toBe(p_description)
@@ -150,56 +152,59 @@ describe("Fertilizer Data Model", () => {
const p_name_nl = "Test Fertilizer"
const p_name_en = "Test Fertilizer (EN)"
const p_description = "This is a test fertilizer"
- await addFertilizerToCatalogue(fdm, {
- p_id_catalogue,
- p_source,
- p_name_nl,
- p_name_en,
- p_description,
- p_dm: 37,
- p_density: 20,
- p_om: 20,
- p_a: 30,
- p_hc: 40,
- p_eom: 50,
- p_eoc: 60,
- p_c_rt: 70,
- p_c_of: 80,
- p_c_if: 90,
- p_c_fr: 100,
- p_cn_of: 110,
- p_n_rt: 120,
- p_n_if: 130,
- p_n_of: 140,
- p_n_wc: 150,
- p_p_rt: 160,
- p_k_rt: 170,
- p_mg_rt: 180,
- p_ca_rt: 190,
- p_ne: 200,
- p_s_rt: 210,
- p_s_wc: 220,
- p_cu_rt: 230,
- p_zn_rt: 240,
- p_na_rt: 250,
- p_si_rt: 260,
- p_b_rt: 270,
- p_mn_rt: 280,
- p_ni_rt: 290,
- p_fe_rt: 300,
- p_mo_rt: 310,
- p_co_rt: 320,
- p_as_rt: 330,
- p_cd_rt: 340,
- pr_cr_rt: 350,
- p_cr_vi: 360,
- p_pb_rt: 370,
- p_hg_rt: 380,
- p_cl_rt: 390,
- p_type_manure: true,
- p_type_mineral: false,
- p_type_compost: false,
- })
+ const p_id_catalogue = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ {
+ p_name_nl,
+ p_name_en,
+ p_description,
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
const p_acquiring_amount = 1000
const p_acquiring_date = new Date()
@@ -222,56 +227,59 @@ describe("Fertilizer Data Model", () => {
const p_name_nl = "Test Fertilizer"
const p_name_en = "Test Fertilizer (EN)"
const p_description = "This is a test fertilizer"
- await addFertilizerToCatalogue(fdm, {
- p_id_catalogue,
- p_source,
- p_name_nl,
- p_name_en,
- p_description,
- p_dm: 37,
- p_density: 20,
- p_om: 20,
- p_a: 30,
- p_hc: 40,
- p_eom: 50,
- p_eoc: 60,
- p_c_rt: 70,
- p_c_of: 80,
- p_c_if: 90,
- p_c_fr: 100,
- p_cn_of: 110,
- p_n_rt: 120,
- p_n_if: 130,
- p_n_of: 140,
- p_n_wc: 150,
- p_p_rt: 160,
- p_k_rt: 170,
- p_mg_rt: 180,
- p_ca_rt: 190,
- p_ne: 200,
- p_s_rt: 210,
- p_s_wc: 220,
- p_cu_rt: 230,
- p_zn_rt: 240,
- p_na_rt: 250,
- p_si_rt: 260,
- p_b_rt: 270,
- p_mn_rt: 280,
- p_ni_rt: 290,
- p_fe_rt: 300,
- p_mo_rt: 310,
- p_co_rt: 320,
- p_as_rt: 330,
- p_cd_rt: 340,
- pr_cr_rt: 350,
- p_cr_vi: 360,
- p_pb_rt: 370,
- p_hg_rt: 380,
- p_cl_rt: 390,
- p_type_manure: true,
- p_type_mineral: false,
- p_type_compost: false,
- })
+ const p_id_catalogue = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ {
+ p_name_nl,
+ p_name_en,
+ p_description,
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
const p_acquiring_amount = 1000
const p_acquiring_date = new Date()
@@ -307,56 +315,59 @@ describe("Fertilizer Data Model", () => {
const p_name_nl = "Test Fertilizer"
const p_name_en = "Test Fertilizer (EN)"
const p_description = "This is a test fertilizer"
- await addFertilizerToCatalogue(fdm, {
- p_id_catalogue,
- p_source,
- p_name_nl,
- p_name_en,
- p_description,
- p_dm: 37,
- p_density: 20,
- p_om: 20,
- p_a: 30,
- p_hc: 40,
- p_eom: 50,
- p_eoc: 60,
- p_c_rt: 70,
- p_c_of: 80,
- p_c_if: 90,
- p_c_fr: 100,
- p_cn_of: 110,
- p_n_rt: 120,
- p_n_if: 130,
- p_n_of: 140,
- p_n_wc: 150,
- p_p_rt: 160,
- p_k_rt: 170,
- p_mg_rt: 180,
- p_ca_rt: 190,
- p_ne: 200,
- p_s_rt: 210,
- p_s_wc: 220,
- p_cu_rt: 230,
- p_zn_rt: 240,
- p_na_rt: 250,
- p_si_rt: 260,
- p_b_rt: 270,
- p_mn_rt: 280,
- p_ni_rt: 290,
- p_fe_rt: 300,
- p_mo_rt: 310,
- p_co_rt: 320,
- p_as_rt: 330,
- p_cd_rt: 340,
- pr_cr_rt: 350,
- p_cr_vi: 360,
- p_pb_rt: 370,
- p_hg_rt: 380,
- p_cl_rt: 390,
- p_type_manure: true,
- p_type_mineral: false,
- p_type_compost: false,
- })
+ const p_id_catalogue = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ {
+ p_name_nl,
+ p_name_en,
+ p_description,
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
const p_acquiring_amount = 1000
const p_acquiring_date = new Date()
@@ -385,7 +396,7 @@ describe("Fertilizer Data Model", () => {
fdm,
principal_id,
b_id_farm,
- p_source,
+ b_id_farm,
)
const fertilizersWithNoCatalogue =
@@ -395,6 +406,293 @@ describe("Fertilizer Data Model", () => {
})
})
+ describe("updateFertilizerFromCatalogue", () => {
+ let p_id_catalogue: string
+
+ beforeEach(async () => {
+ // Add a fertilizer to the catalogue
+ p_id_catalogue = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ {
+ p_name_nl: "Test Fertilizer",
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
+ })
+
+ it("should update an existing fertilizer in the catalogue", async () => {
+ const updatedProperties = {
+ p_name_nl: "Updated Test Fertilizer",
+ p_description: "This is an updated test fertilizer",
+ p_dm: 50,
+ }
+
+ await updateFertilizerFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ p_id_catalogue,
+ updatedProperties,
+ )
+
+ const fertilizers = await getFertilizersFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ )
+ const updatedFertilizer = fertilizers.find(
+ (f) => f.p_id_catalogue === p_id_catalogue,
+ )
+ expect(updatedFertilizer).toBeDefined()
+ expect(updatedFertilizer?.p_name_nl).toBe(
+ updatedProperties.p_name_nl,
+ )
+ expect(updatedFertilizer?.p_description).toBe(
+ updatedProperties.p_description,
+ )
+ expect(updatedFertilizer?.p_dm).toBe(updatedProperties.p_dm)
+ })
+
+ it("should throw an error if fertilizer does not exist in catalogue", async () => {
+ const nonExistingCatalogueId = createId()
+ const updatedProperties = {
+ p_name_nl: "Updated Test Fertilizer",
+ }
+
+ await expect(
+ updateFertilizerFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ nonExistingCatalogueId,
+ updatedProperties,
+ ),
+ ).rejects.toThrow("Exception for updateFertilizerFromCatalogue")
+ })
+
+ it("should update a fertilizer with a subset of properties", async () => {
+ const updatedProperties = {
+ p_name_nl: "Updated Name Only",
+ }
+
+ await updateFertilizerFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ p_id_catalogue,
+ updatedProperties,
+ )
+
+ const fertilizers = await getFertilizersFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ )
+ const updatedFertilizer = fertilizers.find(
+ (f) => f.p_id_catalogue === p_id_catalogue,
+ )
+ expect(updatedFertilizer).toBeDefined()
+ expect(updatedFertilizer?.p_name_nl).toBe(
+ updatedProperties.p_name_nl,
+ )
+ // Check that other properties remain unchanged
+ expect(updatedFertilizer?.p_description).toBe(
+ "This is a test fertilizer",
+ )
+ expect(updatedFertilizer?.p_dm).toBe(37)
+ })
+
+ it("should throw an error when updating with invalid principal ID", async () => {
+ const updatedProperties = {
+ p_name_nl: "Updated Test Fertilizer",
+ }
+ const invalidPrincipalId = "invalid-principal-id"
+
+ await expect(
+ updateFertilizerFromCatalogue(
+ fdm,
+ invalidPrincipalId,
+ b_id_farm,
+ p_id_catalogue,
+ updatedProperties,
+ ),
+ ).rejects.toThrow(
+ "Principal does not have permission to perform this action",
+ )
+ })
+ it("should update hash after updating a fertilizer", async () => {
+ const updatedProperties = {
+ p_name_nl: "Updated Test Fertilizer",
+ p_description: "This is an updated test fertilizer",
+ p_dm: 50,
+ }
+ const fertilizersBefore = await getFertilizersFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ )
+ const fertilizerBefore = fertilizersBefore.find(
+ (f) => f.p_id_catalogue === p_id_catalogue,
+ )
+ expect(fertilizerBefore).toBeDefined()
+ const hashBefore = fertilizerBefore?.hash
+ expect(hashBefore).toBeDefined()
+ await updateFertilizerFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ p_id_catalogue,
+ updatedProperties,
+ )
+ const fertilizersAfter = await getFertilizersFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ )
+ const fertilizerAfter = fertilizersAfter.find(
+ (f) => f.p_id_catalogue === p_id_catalogue,
+ )
+ expect(fertilizerAfter).toBeDefined()
+ const hashAfter = fertilizerAfter?.hash
+ expect(hashAfter).toBeDefined()
+
+ expect(hashBefore).not.toBe(hashAfter)
+ })
+ it("should throw an error if updating a fertilizer of another farm", async () => {
+ const farmName = "Test Farm 2"
+ const farmBusinessId = "98765"
+ const farmAddress = "456 Farm Lane"
+ const farmPostalCode = "54321"
+ const b_id_farm2 = await addFarm(
+ fdm,
+ principal_id,
+ farmName,
+ farmBusinessId,
+ farmAddress,
+ farmPostalCode,
+ )
+ await enableFertilizerCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm2,
+ b_id_farm2,
+ )
+
+ // Add a fertilizer to the catalogue
+ const p_id_catalogue2 = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm2,
+ {
+ p_name_nl: "Test Fertilizer 2",
+ p_name_en: "Test Fertilizer (EN) 2",
+ p_description: "This is a test fertilizer 2",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
+ const updatedProperties = {
+ p_name_nl: "Updated Test Fertilizer",
+ }
+ await expect(
+ updateFertilizerFromCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ p_id_catalogue2,
+ updatedProperties,
+ ),
+ ).rejects.toThrow("Exception for updateFertilizerFromCatalogue")
+ })
+ })
+
describe("Fertilizer Application", () => {
let b_id: string
let p_id: string
@@ -438,60 +736,62 @@ describe("Fertilizer Data Model", () => {
)
// Add fertilizer to catalogue
- p_id_catalogue = createId()
const p_name_nl = "Test Fertilizer"
const p_name_en = "Test Fertilizer (EN)"
const p_description = "This is a test fertilizer"
- await addFertilizerToCatalogue(fdm, {
- p_id_catalogue,
- p_source,
- p_name_nl,
- p_name_en,
- p_description,
- p_dm: 37,
- p_density: 20,
- p_om: 20,
- p_a: 30,
- p_hc: 40,
- p_eom: 50,
- p_eoc: 60,
- p_c_rt: 70,
- p_c_of: 80,
- p_c_if: 90,
- p_c_fr: 100,
- p_cn_of: 110,
- p_n_rt: 120,
- p_n_if: 130,
- p_n_of: 140,
- p_n_wc: 150,
- p_p_rt: 160,
- p_k_rt: 170,
- p_mg_rt: 180,
- p_ca_rt: 190,
- p_ne: 200,
- p_s_rt: 210,
- p_s_wc: 220,
- p_cu_rt: 230,
- p_zn_rt: 240,
- p_na_rt: 250,
- p_si_rt: 260,
- p_b_rt: 270,
- p_mn_rt: 280,
- p_ni_rt: 290,
- p_fe_rt: 300,
- p_mo_rt: 310,
- p_co_rt: 320,
- p_as_rt: 330,
- p_cd_rt: 340,
- pr_cr_rt: 350,
- p_cr_vi: 360,
- p_pb_rt: 370,
- p_hg_rt: 380,
- p_cl_rt: 390,
- p_type_manure: true,
- p_type_mineral: false,
- p_type_compost: false,
- })
+ const p_id_catalogue = await addFertilizerToCatalogue(
+ fdm,
+ principal_id,
+ b_id_farm,
+ {
+ p_name_nl,
+ p_name_en,
+ p_description,
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ pr_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ },
+ )
const p_acquiring_amount = 1000
const p_acquiring_date = new Date()
diff --git a/fdm-core/src/fertilizer.ts b/fdm-core/src/fertilizer.ts
index 259698197..7b46cadd0 100644
--- a/fdm-core/src/fertilizer.ts
+++ b/fdm-core/src/fertilizer.ts
@@ -1,4 +1,4 @@
-import { asc, desc, eq, inArray } from "drizzle-orm"
+import { and, asc, desc, eq, inArray } from "drizzle-orm"
import { createId } from "./id"
import { checkPermission } from "./authorization"
@@ -10,6 +10,7 @@ import type {
getFertilizerApplicationType,
getFertilizerType,
} from "./fertilizer.d"
+import { hashFertilizer } from "@svenvw/fdm-data"
/**
* Retrieves all fertilizers from the enabled catalogues for a farm.
@@ -72,9 +73,11 @@ export async function getFertilizersFromCatalogue(
}
/**
- * Adds a new fertilizer to the catalogue.
+ * Adds a new custom fertilizer to the catalogue of a farm.
*
* @param fdm The FDM instance providing the connection to the database. The instance can be created with {@link createFdmServer}.
+ * @param principal_id The ID of the principal making the request.
+ * @param b_id_farm The ID of the farm.
* @param properties The properties of the fertilizer to add.
* @returns A Promise that resolves when the fertilizer has been added.
* @throws If adding the fertilizer fails.
@@ -82,9 +85,9 @@ export async function getFertilizersFromCatalogue(
*/
export async function addFertilizerToCatalogue(
fdm: FdmType,
+ principal_id: PrincipalId,
+ b_id_farm: schema.farmsTypeInsert["b_id_farm"],
properties: {
- p_id_catalogue: schema.fertilizersCatalogueTypeInsert["p_id_catalogue"]
- p_source: schema.fertilizersCatalogueTypeInsert["p_source"]
p_name_nl: schema.fertilizersCatalogueTypeInsert["p_name_nl"]
p_name_en: schema.fertilizersCatalogueTypeInsert["p_name_en"]
p_description: schema.fertilizersCatalogueTypeInsert["p_description"]
@@ -127,15 +130,35 @@ export async function addFertilizerToCatalogue(
p_cr_vi: schema.fertilizersCatalogueTypeInsert["p_cr_vi"]
p_pb_rt: schema.fertilizersCatalogueTypeInsert["p_pb_rt"]
p_hg_rt: schema.fertilizersCatalogueTypeInsert["p_hg_rt"]
- p_cl_rt: schema.fertilizersCatalogueTypeInsert["p_cl_cr"]
+ p_cl_rt: schema.fertilizersCatalogueTypeInsert["p_cl_rt"]
p_type_manure: schema.fertilizersCatalogueTypeInsert["p_type_manure"]
p_type_mineral: schema.fertilizersCatalogueTypeInsert["p_type_mineral"]
p_type_compost: schema.fertilizersCatalogueTypeInsert["p_type_compost"]
},
-): Promise {
+): Promise {
try {
+ await checkPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ principal_id,
+ "addFertilizerToCatalogue",
+ )
+
+ const p_id_catalogue = createId()
+ const input: schema.fertilizersCatalogueTypeInsert = {
+ ...properties,
+ p_id_catalogue: p_id_catalogue,
+ p_source: b_id_farm,
+ hash: null,
+ }
+ input.hash = await hashFertilizer(input)
+
// Insert the farm in the db
- await fdm.insert(schema.fertilizersCatalogue).values(properties)
+ await fdm.insert(schema.fertilizersCatalogue).values(input)
+
+ return p_id_catalogue
} catch (err) {
throw handleError(err, "Exception for addFertilizerToCatalogue", {
properties,
@@ -238,6 +261,8 @@ export async function getFertilizer(
const fertilizer = await fdm
.select({
p_id: schema.fertilizers.p_id,
+ p_id_catalogue: schema.fertilizersCatalogue.p_id_catalogue,
+ p_source: schema.fertilizersCatalogue.p_source,
p_name_nl: schema.fertilizersCatalogue.p_name_nl,
p_name_en: schema.fertilizersCatalogue.p_name_en,
p_description: schema.fertilizersCatalogue.p_description,
@@ -245,6 +270,18 @@ export async function getFertilizer(
schema.fertilizerAcquiring.p_acquiring_amount,
p_acquiring_date: schema.fertilizerAcquiring.p_acquiring_date,
p_picking_date: schema.fertilizerPicking.p_picking_date,
+ p_dm: schema.fertilizersCatalogue.p_dm,
+ p_density: schema.fertilizersCatalogue.p_density,
+ p_om: schema.fertilizersCatalogue.p_om,
+ p_a: schema.fertilizersCatalogue.p_a,
+ p_hc: schema.fertilizersCatalogue.p_hc,
+ p_eom: schema.fertilizersCatalogue.p_eom,
+ p_eoc: schema.fertilizersCatalogue.p_eoc,
+ p_c_rt: schema.fertilizersCatalogue.p_c_rt,
+ p_c_of: schema.fertilizersCatalogue.p_c_of,
+ p_c_if: schema.fertilizersCatalogue.p_c_if,
+ p_c_fr: schema.fertilizersCatalogue.p_c_fr,
+ p_cn_of: schema.fertilizersCatalogue.p_cn_of,
p_n_rt: schema.fertilizersCatalogue.p_n_rt,
p_n_if: schema.fertilizersCatalogue.p_n_if,
p_n_of: schema.fertilizersCatalogue.p_n_of,
@@ -272,7 +309,10 @@ export async function getFertilizer(
p_cr_vi: schema.fertilizersCatalogue.p_cr_vi,
p_pb_rt: schema.fertilizersCatalogue.p_pb_rt,
p_hg_rt: schema.fertilizersCatalogue.p_hg_rt,
- p_cl_cr: schema.fertilizersCatalogue.p_cl_cr,
+ p_cl_rt: schema.fertilizersCatalogue.p_cl_rt,
+ p_type_manure: schema.fertilizersCatalogue.p_type_manure,
+ p_type_mineral: schema.fertilizersCatalogue.p_type_mineral,
+ p_type_compost: schema.fertilizersCatalogue.p_type_compost,
})
.from(schema.fertilizers)
.leftJoin(
@@ -301,6 +341,124 @@ export async function getFertilizer(
}
}
+/**
+ * Updates an existing fertilizer in the catalogue of a farm.
+ *
+ * @param fdm The FDM instance providing the connection to the database. The instance can be created with {@link createFdmServer}.
+ * @param principal_id The ID of the principal making the request.
+ * @param b_id_farm The ID of the farm.
+ * @param p_id_catalogue The ID of the fertilizer in the catalogue to update
+ * @param properties The properties of the fertilizer to update.
+ * @returns A Promise that resolves when the fertilizer has been updated.
+ * @throws If updating the fertilizer fails.
+ * @alpha
+ */
+export async function updateFertilizerFromCatalogue(
+ fdm: FdmType,
+ principal_id: PrincipalId,
+ b_id_farm: schema.farmsTypeInsert["b_id_farm"],
+ p_id_catalogue: schema.fertilizersCatalogueTypeInsert["p_id_catalogue"],
+ properties: Partial<{
+ p_name_nl: schema.fertilizersCatalogueTypeInsert["p_name_nl"]
+ p_name_en: schema.fertilizersCatalogueTypeInsert["p_name_en"]
+ p_description: schema.fertilizersCatalogueTypeInsert["p_description"]
+ p_dm: schema.fertilizersCatalogueTypeInsert["p_dm"]
+ p_density: schema.fertilizersCatalogueTypeInsert["p_density"]
+ p_om: schema.fertilizersCatalogueTypeInsert["p_om"]
+ p_a: schema.fertilizersCatalogueTypeInsert["p_a"]
+ p_hc: schema.fertilizersCatalogueTypeInsert["p_hc"]
+ p_eom: schema.fertilizersCatalogueTypeInsert["p_eom"]
+ p_eoc: schema.fertilizersCatalogueTypeInsert["p_eoc"]
+ p_c_rt: schema.fertilizersCatalogueTypeInsert["p_c_rt"]
+ p_c_of: schema.fertilizersCatalogueTypeInsert["p_c_of"]
+ p_c_if: schema.fertilizersCatalogueTypeInsert["p_c_if"]
+ p_c_fr: schema.fertilizersCatalogueTypeInsert["p_c_fr"]
+ p_cn_of: schema.fertilizersCatalogueTypeInsert["p_cn_of"]
+ p_n_rt: schema.fertilizersCatalogueTypeInsert["p_n_rt"]
+ p_n_if: schema.fertilizersCatalogueTypeInsert["p_n_if"]
+ p_n_of: schema.fertilizersCatalogueTypeInsert["p_n_of"]
+ p_n_wc: schema.fertilizersCatalogueTypeInsert["p_n_wc"]
+ p_p_rt: schema.fertilizersCatalogueTypeInsert["p_p_rt"]
+ p_k_rt: schema.fertilizersCatalogueTypeInsert["p_k_rt"]
+ p_mg_rt: schema.fertilizersCatalogueTypeInsert["p_mg_rt"]
+ p_ca_rt: schema.fertilizersCatalogueTypeInsert["p_ca_rt"]
+ p_ne: schema.fertilizersCatalogueTypeInsert["p_ne"]
+ p_s_rt: schema.fertilizersCatalogueTypeInsert["p_s_rt"]
+ p_s_wc: schema.fertilizersCatalogueTypeInsert["p_s_wc"]
+ p_cu_rt: schema.fertilizersCatalogueTypeInsert["p_cu_rt"]
+ p_zn_rt: schema.fertilizersCatalogueTypeInsert["p_zn_rt"]
+ p_na_rt: schema.fertilizersCatalogueTypeInsert["p_na_rt"]
+ p_si_rt: schema.fertilizersCatalogueTypeInsert["p_si_rt"]
+ p_b_rt: schema.fertilizersCatalogueTypeInsert["p_b_rt"]
+ p_mn_rt: schema.fertilizersCatalogueTypeInsert["p_mn_rt"]
+ p_ni_rt: schema.fertilizersCatalogueTypeInsert["p_ni_rt"]
+ p_fe_rt: schema.fertilizersCatalogueTypeInsert["p_fe_rt"]
+ p_mo_rt: schema.fertilizersCatalogueTypeInsert["p_mo_rt"]
+ p_co_rt: schema.fertilizersCatalogueTypeInsert["p_co_rt"]
+ p_as_rt: schema.fertilizersCatalogueTypeInsert["p_as_rt"]
+ p_cd_rt: schema.fertilizersCatalogueTypeInsert["p_cd_rt"]
+ p_cr_rt: schema.fertilizersCatalogueTypeInsert["p_cr_rt"]
+ p_cr_vi: schema.fertilizersCatalogueTypeInsert["p_cr_vi"]
+ p_pb_rt: schema.fertilizersCatalogueTypeInsert["p_pb_rt"]
+ p_hg_rt: schema.fertilizersCatalogueTypeInsert["p_hg_rt"]
+ p_cl_rt: schema.fertilizersCatalogueTypeInsert["p_cl_rt"]
+ p_type_manure: schema.fertilizersCatalogueTypeInsert["p_type_manure"]
+ p_type_mineral: schema.fertilizersCatalogueTypeInsert["p_type_mineral"]
+ p_type_compost: schema.fertilizersCatalogueTypeInsert["p_type_compost"]
+ }>,
+): Promise {
+ try {
+ await checkPermission(
+ fdm,
+ "farm",
+ "write",
+ b_id_farm,
+ principal_id,
+ "updateFertilizerFromCatalogue",
+ )
+
+ const existingFertilizer = await fdm
+ .select()
+ .from(schema.fertilizersCatalogue)
+ .where(
+ and(
+ eq(
+ schema.fertilizersCatalogue.p_id_catalogue,
+ p_id_catalogue,
+ ),
+ eq(schema.fertilizersCatalogue.p_source, b_id_farm),
+ ),
+ )
+ if (existingFertilizer.length === 0) {
+ throw new Error("Fertilizer does not exist in catalogue")
+ }
+ const updatedProperties = {
+ ...existingFertilizer[0],
+ ...properties,
+ hash: null,
+ }
+ updatedProperties.hash = await hashFertilizer(updatedProperties)
+
+ await fdm
+ .update(schema.fertilizersCatalogue)
+ .set(updatedProperties)
+ .where(
+ and(
+ eq(
+ schema.fertilizersCatalogue.p_id_catalogue,
+ p_id_catalogue,
+ ),
+ eq(schema.fertilizersCatalogue.p_source, b_id_farm),
+ ),
+ )
+ } catch (err) {
+ throw handleError(err, "Exception for updateFertilizerFromCatalogue", {
+ p_id_catalogue,
+ properties,
+ })
+ }
+}
+
/**
* Retrieves fertilizer details for a specified farm.
*
@@ -333,6 +491,8 @@ export async function getFertilizers(
const fertilizers = await fdm
.select({
p_id: schema.fertilizers.p_id,
+ p_id_catalogue: schema.fertilizersCatalogue.p_id_catalogue,
+ p_source: schema.fertilizersCatalogue.p_source,
p_name_nl: schema.fertilizersCatalogue.p_name_nl,
p_name_en: schema.fertilizersCatalogue.p_name_en,
p_description: schema.fertilizersCatalogue.p_description,
@@ -340,6 +500,18 @@ export async function getFertilizers(
schema.fertilizerAcquiring.p_acquiring_amount,
p_acquiring_date: schema.fertilizerAcquiring.p_acquiring_date,
p_picking_date: schema.fertilizerPicking.p_picking_date,
+ p_dm: schema.fertilizersCatalogue.p_dm,
+ p_density: schema.fertilizersCatalogue.p_density,
+ p_om: schema.fertilizersCatalogue.p_om,
+ p_a: schema.fertilizersCatalogue.p_a,
+ p_hc: schema.fertilizersCatalogue.p_hc,
+ p_eom: schema.fertilizersCatalogue.p_eom,
+ p_eoc: schema.fertilizersCatalogue.p_eoc,
+ p_c_rt: schema.fertilizersCatalogue.p_c_rt,
+ p_c_of: schema.fertilizersCatalogue.p_c_of,
+ p_c_if: schema.fertilizersCatalogue.p_c_if,
+ p_c_fr: schema.fertilizersCatalogue.p_c_fr,
+ p_cn_of: schema.fertilizersCatalogue.p_cn_of,
p_n_rt: schema.fertilizersCatalogue.p_n_rt,
p_n_if: schema.fertilizersCatalogue.p_n_if,
p_n_of: schema.fertilizersCatalogue.p_n_of,
@@ -367,7 +539,10 @@ export async function getFertilizers(
p_cr_vi: schema.fertilizersCatalogue.p_cr_vi,
p_pb_rt: schema.fertilizersCatalogue.p_pb_rt,
p_hg_rt: schema.fertilizersCatalogue.p_hg_rt,
- p_cl_cr: schema.fertilizersCatalogue.p_cl_cr,
+ p_cl_rt: schema.fertilizersCatalogue.p_cl_rt,
+ p_type_manure: schema.fertilizersCatalogue.p_type_manure,
+ p_type_mineral: schema.fertilizersCatalogue.p_type_mineral,
+ p_type_compost: schema.fertilizersCatalogue.p_type_compost,
})
.from(schema.fertilizers)
.leftJoin(
diff --git a/fdm-core/src/index.ts b/fdm-core/src/index.ts
index 0a641f877..6a184226e 100644
--- a/fdm-core/src/index.ts
+++ b/fdm-core/src/index.ts
@@ -25,6 +25,7 @@ export { addFarm, getFarm, getFarms, updateFarm } from "./farm"
export { addField, getField, getFields, updateField } from "./field"
export {
addFertilizerToCatalogue,
+ updateFertilizerFromCatalogue,
getFertilizersFromCatalogue,
addFertilizer,
removeFertilizer,
diff --git a/fdm-data/src/cultivations/catalogues/brp.ts b/fdm-data/src/cultivations/catalogues/brp.ts
index 0de3d2c36..34cfbbaac 100644
--- a/fdm-data/src/cultivations/catalogues/brp.ts
+++ b/fdm-data/src/cultivations/catalogues/brp.ts
@@ -1,8 +1,6 @@
import type { CatalogueCultivation, CatalogueCultivationItem } from "../d"
+import { hashCultivation } from "../hash"
import brp from "./brp.json"
-import xxhash from "xxhash-wasm"
-
-const { h32ToString } = await xxhash()
/**
* Retrieves the BRP (Basisregistratie Perceel) cultivation catalogue.
@@ -14,8 +12,8 @@ const { h32ToString } = await xxhash()
* @returns An array of cultivation catalogue entries conforming to the `CatalogueCultivation` type.
* @throws {Error} Throws an error if an invalid value is found for `b_lu_harvestable` in the JSON data.
*/
-export function getCatalogueBrp(): CatalogueCultivation {
- const catalogueBrp = brp.map((cultivation) => {
+export async function getCatalogueBrp(): Promise {
+ const catalogueBrpPromises = brp.map(async (cultivation) => {
// Validate b_lu_harvestable
const harvestable =
cultivation.b_lu_harvestable !== "once" &&
@@ -40,10 +38,11 @@ export function getCatalogueBrp(): CatalogueCultivation {
}
// Hash the item
- item.hash = h32ToString(JSON.stringify(item))
+ item.hash = await hashCultivation(item)
return item
})
+ const catalogueBrp = await Promise.all(catalogueBrpPromises)
return catalogueBrp
}
diff --git a/fdm-data/src/cultivations/hash.test.ts b/fdm-data/src/cultivations/hash.test.ts
new file mode 100644
index 000000000..992845bf0
--- /dev/null
+++ b/fdm-data/src/cultivations/hash.test.ts
@@ -0,0 +1,166 @@
+import { describe, it, expect } from "vitest"
+import { hashCultivation } from "./hash"
+import type { CatalogueCultivationItem } from "./d"
+
+describe("hashCultivation", () => {
+ it("should generate a hash for a cultivation item", async () => {
+ const cultivation: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id",
+ b_lu_name: "Test Cultivation",
+ b_lu_name_en: "Test Cultivation (EN)",
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const hash = await hashCultivation(cultivation)
+ expect(hash).toBeDefined()
+ expect(typeof hash).toBe("string")
+ expect(hash.length).toBeGreaterThan(0)
+ expect(hash).toBe("9e15c11b")
+ })
+
+ it("should generate different hashes for different cultivation items", async () => {
+ const cultivation1: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id-1",
+ b_lu_name: "Test Cultivation 1",
+ b_lu_name_en: "Test Cultivation (EN)",
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const cultivation2: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id-2",
+ b_lu_name: "Test Cultivation 2", // Different name
+ b_lu_name_en: "Test Cultivation (EN)",
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const hash1 = await hashCultivation(cultivation1)
+ const hash2 = await hashCultivation(cultivation2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+
+ it("should generate the same hash for identical cultivation items", async () => {
+ const cultivation1: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id-1",
+ b_lu_name: "Test Cultivation 1",
+ b_lu_name_en: "Test Cultivation (EN)",
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const cultivation2: CatalogueCultivationItem = {
+ ...cultivation1,
+ }
+
+ const hash1 = await hashCultivation(cultivation1)
+ const hash2 = await hashCultivation(cultivation2)
+
+ expect(hash1).toBe(hash2)
+ })
+
+ it("should generate different hashes when a string value changes", async () => {
+ const cultivation1: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id-1",
+ b_lu_name: "Test Cultivation 1",
+ b_lu_name_en: "Test Cultivation (EN)",
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const cultivation2: CatalogueCultivationItem = {
+ ...cultivation1,
+ b_lu_name: "Updated Test Cultivation Name",
+ }
+
+ const hash1 = await hashCultivation(cultivation1)
+ const hash2 = await hashCultivation(cultivation2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+ it("should generate different hashes when a null string value is changed", async () => {
+ const cultivation1: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id-1",
+ b_lu_name: "Test Cultivation 1",
+ b_lu_name_en: null,
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const cultivation2: CatalogueCultivationItem = {
+ ...cultivation1,
+ b_lu_name_en: "Test Cultivation (EN)",
+ }
+
+ const hash1 = await hashCultivation(cultivation1)
+ const hash2 = await hashCultivation(cultivation2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+
+ it("should generate different hashes when a non null string value is changed", async () => {
+ const cultivation1: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id-1",
+ b_lu_name: "Test Cultivation 1",
+ b_lu_name_en: "Test Cultivation (EN)",
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const cultivation2: CatalogueCultivationItem = {
+ ...cultivation1,
+ b_lu_hcat3: null,
+ }
+
+ const hash1 = await hashCultivation(cultivation1)
+ const hash2 = await hashCultivation(cultivation2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+
+ it("should generate different hashes when a enum value changes", async () => {
+ const cultivation1: CatalogueCultivationItem = {
+ b_lu_source: "brp",
+ b_lu_catalogue: "test-id-1",
+ b_lu_name: "Test Cultivation 1",
+ b_lu_name_en: "Test Cultivation (EN)",
+ b_lu_harvestable: "once",
+ b_lu_hcat3: "hcat3",
+ b_lu_hcat3_name: "hcat3 name",
+ hash: null,
+ }
+
+ const cultivation2: CatalogueCultivationItem = {
+ ...cultivation1,
+ b_lu_harvestable: "multiple",
+ }
+
+ const hash1 = await hashCultivation(cultivation1)
+ const hash2 = await hashCultivation(cultivation2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+})
diff --git a/fdm-data/src/cultivations/hash.ts b/fdm-data/src/cultivations/hash.ts
new file mode 100644
index 000000000..fca5be6f3
--- /dev/null
+++ b/fdm-data/src/cultivations/hash.ts
@@ -0,0 +1,25 @@
+import { ensureInitialized, h32ToString } from "../hash"
+import type { CatalogueCultivationItem } from "./d"
+
+export async function hashCultivation(cultivation: CatalogueCultivationItem) {
+ await ensureInitialized()
+ // Set hash to null for consistent hashing
+ cultivation.hash = null
+
+ // Remove all keys without a value
+ const filteredCultivation = Object.fromEntries(
+ Object.entries(cultivation).filter(
+ ([, value]) => value !== undefined && value !== null,
+ ),
+ )
+
+ // Sort keys to ensure consistent hash generation for identical objects
+ const sortedKeys = Object.keys(filteredCultivation).sort()
+ const sortedCultivation = sortedKeys.reduce>((obj, key) => {
+ obj[key] = cultivation[key as keyof typeof cultivation]
+ return obj
+ }, {})
+
+ const hash = h32ToString(JSON.stringify(sortedCultivation))
+ return hash
+}
diff --git a/fdm-data/src/cultivations/index.test.ts b/fdm-data/src/cultivations/index.test.ts
index 4f741413c..c79acb3bf 100644
--- a/fdm-data/src/cultivations/index.test.ts
+++ b/fdm-data/src/cultivations/index.test.ts
@@ -3,33 +3,33 @@ import { getCultivationCatalogue } from "./index"
import { getCatalogueBrp } from "./catalogues/brp"
describe("getCultivationCatalogue", () => {
- it("should return the BRP catalogue when catalogueName is 'brp'", () => {
- const expectedCatalogue = getCatalogueBrp()
- const actualCatalogue = getCultivationCatalogue("brp")
+ it("should return the BRP catalogue when catalogueName is 'brp'", async () => {
+ const expectedCatalogue = await getCatalogueBrp()
+ const actualCatalogue = await getCultivationCatalogue("brp")
expect(actualCatalogue).toEqual(expectedCatalogue)
})
- it("should throw an error when an invalid catalogueName is provided", () => {
- expect(() => getCultivationCatalogue("invalid-catalogue")).toThrowError(
- "catalogue invalid-catalogue is not recognized",
- )
+ it("should throw an error when an invalid catalogueName is provided", async () => {
+ await expect(
+ getCultivationCatalogue("invalid-catalogue"),
+ ).rejects.toThrowError("catalogue invalid-catalogue is not recognized")
})
- it("should return a non-empty array for 'brp' catalogue", () => {
- const catalogue = getCultivationCatalogue("brp")
+ it("should return a non-empty array for 'brp' catalogue", async () => {
+ const catalogue = await getCultivationCatalogue("brp")
expect(Array.isArray(catalogue)).toBe(true)
expect(catalogue.length).toBeGreaterThan(0)
})
- it("should check if all items in the brp catalogue have the correct source", () => {
- const catalogue = getCultivationCatalogue("brp")
+ it("should check if all items in the brp catalogue have the correct source", async () => {
+ const catalogue = await getCultivationCatalogue("brp")
for (const item of catalogue) {
expect(item.b_lu_source).toBe("brp")
}
})
- it("should check if all items in the brp catalogue have the correct b_lu_harvestable values", () => {
- const catalogue = getCultivationCatalogue("brp")
+ it("should check if all items in the brp catalogue have the correct b_lu_harvestable values", async () => {
+ const catalogue = await getCultivationCatalogue("brp")
for (const item of catalogue) {
expect(["once", "multiple", "none"]).toContain(
item.b_lu_harvestable,
@@ -39,8 +39,8 @@ describe("getCultivationCatalogue", () => {
})
describe("getCatalogueBrp", () => {
- it("should return an array of CatalogueCultivationItem", () => {
- const catalogue = getCatalogueBrp()
+ it("should return an array of CatalogueCultivationItem", async () => {
+ const catalogue = await getCatalogueBrp()
expect(Array.isArray(catalogue)).toBe(true)
for (const item of catalogue) {
expect(typeof item).toBe("object")
@@ -55,8 +55,8 @@ describe("getCatalogueBrp", () => {
}
})
- it("should return at least one item", () => {
- const catalogue = getCatalogueBrp()
+ it("should return at least one item", async () => {
+ const catalogue = await getCatalogueBrp()
expect(catalogue.length).toBeGreaterThan(0)
})
})
diff --git a/fdm-data/src/cultivations/index.ts b/fdm-data/src/cultivations/index.ts
index ff15bbe08..49ed8503f 100644
--- a/fdm-data/src/cultivations/index.ts
+++ b/fdm-data/src/cultivations/index.ts
@@ -11,21 +11,22 @@ import type { CatalogueCultivation, CatalogueCultivationName } from "./d"
* Currently supported names are: "brp".
* @returns An array of `CatalogueCultivationItem` objects representing the
* requested cultivation catalogue.
+ * @returns A Promise that resolves to an array of `CatalogueCultivationItem` objects.
* @throws {Error} Throws an error if the provided `catalogueName` is not
* recognized or supported.
*
* @example
* ```typescript
- * const brpCatalogue = getCultivationCatalogue("brp");
+ * const brpCatalogue = await getCultivationCatalogue("brp");
* console.log(brpCatalogue);
* ```
*/
-export function getCultivationCatalogue(
+export async function getCultivationCatalogue(
catalogueName: CatalogueCultivationName,
-): CatalogueCultivation {
+): Promise {
// Get the specified catalogue
if (catalogueName === "brp") {
- return getCatalogueBrp()
+ return await getCatalogueBrp()
}
throw new Error(`catalogue ${catalogueName} is not recognized`)
diff --git a/fdm-data/src/fertilizers/catalogues/srm.ts b/fdm-data/src/fertilizers/catalogues/srm.ts
index d5d09dcbb..701bf6751 100644
--- a/fdm-data/src/fertilizers/catalogues/srm.ts
+++ b/fdm-data/src/fertilizers/catalogues/srm.ts
@@ -1,8 +1,6 @@
import type { CatalogueFertilizer, CatalogueFertilizerItem } from "../d"
+import { hashFertilizer } from "../hash"
import srm from "./srm.json"
-import xxhash from "xxhash-wasm"
-
-const { h32ToString } = await xxhash()
/**
* Retrieves the SRM (Sluiting Regionale Kringlopen) fertilizer catalogue.
@@ -14,8 +12,8 @@ const { h32ToString } = await xxhash()
* @returns An array of fertilizer catalogue entries conforming to the
* `CatalogueFertilizer` type.
*/
-export function getCatalogueSrm(): CatalogueFertilizer {
- const catalogueSrm = srm.map((fertilizer) => {
+export async function getCatalogueSrm(): Promise {
+ const catalogueSrmPromises = srm.map(async (fertilizer) => {
const item: CatalogueFertilizerItem = {
p_source: "srm",
p_id_catalogue: fertilizer.p_id_catalogue,
@@ -67,7 +65,7 @@ export function getCatalogueSrm(): CatalogueFertilizer {
p_cr_vi: null,
p_pb_rt: null,
p_hg_rt: null,
- p_cl_cr: null,
+ p_cl_rt: null,
p_type_manure: fertilizer.p_type_manure,
p_type_mineral: fertilizer.p_type_mineral,
p_type_compost: fertilizer.p_type_compost,
@@ -75,10 +73,11 @@ export function getCatalogueSrm(): CatalogueFertilizer {
}
// Hash the item
- item.hash = h32ToString(JSON.stringify(item))
+ item.hash = await hashFertilizer(item)
return item
})
+ const catalogueSrm = await Promise.all(catalogueSrmPromises)
return catalogueSrm
}
diff --git a/fdm-data/src/fertilizers/d.ts b/fdm-data/src/fertilizers/d.ts
index 3322684cf..f5c3a3162 100644
--- a/fdm-data/src/fertilizers/d.ts
+++ b/fdm-data/src/fertilizers/d.ts
@@ -1,55 +1,55 @@
export type CatalogueFertilizerName = "srm"
export interface CatalogueFertilizerItem {
- p_source: CatalogueFertilizerName
+ p_source: CatalogueFertilizerName | string
p_id_catalogue: string
p_name_nl: string
- p_name_en: string | null
- p_description: string | null
- p_dm: number | null
- p_density: number | null
- p_om: number | null
- p_a: number | null
- p_hc: number | null
- p_eom: number | null
- p_eoc: number | null
- p_c_rt: number | null
- p_c_of: number | null
- p_c_if: number | null
- p_c_fr: number | null
- p_cn_of: number | null
- p_n_rt: number | null
- p_n_if: number | null
- p_n_of: number | null
- p_n_wc: number | null
- p_p_rt: number | null
- p_k_rt: number | null
- p_mg_rt: number | null
- p_ca_rt: number | null
- p_ne: number | null
- p_s_rt: number | null
- p_s_wc: number | null
- p_cu_rt: number | null
- p_zn_rt: number | null
- p_na_rt: number | null
- p_si_rt: number | null
- p_b_rt: number | null
- p_mn_rt: number | null
- p_ni_rt: number | null
- p_fe_rt: number | null
- p_mo_rt: number | null
- p_co_rt: number | null
- p_as_rt: number | null
- p_cd_rt: number | null
- p_cr_rt: number | null
- p_cr_vi: number | null
- p_pb_rt: number | null
- p_hg_rt: number | null
- p_cl_cr: number | null
- p_type_manure: boolean | null
- p_type_mineral: boolean | null
- p_type_compost: boolean | null
- hash: string | null
+ p_name_en?: string | null | undefined
+ p_description?: string | null | undefined
+ p_dm?: number | null
+ p_density?: number | null
+ p_om?: number | null
+ p_a?: number | null
+ p_hc?: number | null
+ p_eom?: number | null
+ p_eoc?: number | null
+ p_c_rt?: number | null
+ p_c_of?: number | null
+ p_c_if?: number | null
+ p_c_fr?: number | null
+ p_cn_of?: number | null
+ p_n_rt?: number | null
+ p_n_if?: number | null
+ p_n_of?: number | null
+ p_n_wc?: number | null
+ p_p_rt?: number | null
+ p_k_rt?: number | null
+ p_mg_rt?: number | null
+ p_ca_rt?: number | null
+ p_ne?: number | null
+ p_s_rt?: number | null
+ p_s_wc?: number | null
+ p_cu_rt?: number | null
+ p_zn_rt?: number | null
+ p_na_rt?: number | null
+ p_si_rt?: number | null
+ p_b_rt?: number | null
+ p_mn_rt?: number | null
+ p_ni_rt?: number | null
+ p_fe_rt?: number | null
+ p_mo_rt?: number | null
+ p_co_rt?: number | null
+ p_as_rt?: number | null
+ p_cd_rt?: number | null
+ p_cr_rt?: number | null
+ p_cr_vi?: number | null
+ p_pb_rt?: number | null
+ p_hg_rt?: number | null
+ p_cl_cr?: number | null
+ p_type_manure?: boolean | null
+ p_type_mineral?: boolean | null
+ p_type_compost?: boolean | null
+ hash?: string | null | undefined
}
export type CatalogueFertilizer = CatalogueFertilizerItem[]
diff --git a/fdm-data/src/fertilizers/hash.test.ts b/fdm-data/src/fertilizers/hash.test.ts
new file mode 100644
index 000000000..941442512
--- /dev/null
+++ b/fdm-data/src/fertilizers/hash.test.ts
@@ -0,0 +1,422 @@
+import { describe, it, expect } from "vitest"
+import { hashFertilizer } from "./hash"
+import type { CatalogueFertilizerItem } from "./d"
+
+describe("hashFertilizer", () => {
+ it("should generate a hash for a fertilizer item", async () => {
+ const fertilizer: CatalogueFertilizerItem = {
+ p_id_catalogue: "test-id",
+ p_source: "test-source",
+ p_name_nl: "Test Fertilizer",
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ p_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ }
+
+ const hash = await hashFertilizer(fertilizer)
+ expect(hash).toBeDefined()
+ expect(typeof hash).toBe("string")
+ expect(hash.length).toBeGreaterThan(0)
+ expect(hash).toBe("3852e767")
+ })
+
+ it("should generate different hashes for different fertilizer items", async () => {
+ const fertilizer1: CatalogueFertilizerItem = {
+ p_id_catalogue: "test-id-1",
+ p_source: "test-source",
+ p_name_nl: "Test Fertilizer 1",
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ p_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ }
+
+ const fertilizer2: CatalogueFertilizerItem = {
+ p_id_catalogue: "test-id-2",
+ p_source: "test-source",
+ p_name_nl: "Test Fertilizer 2", // Different name
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ p_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ }
+
+ const hash1 = await hashFertilizer(fertilizer1)
+ const hash2 = await hashFertilizer(fertilizer2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+
+ it("should generate the same hash for identical fertilizer items", async () => {
+ const fertilizer1: CatalogueFertilizerItem = {
+ p_id_catalogue: "test-id-1",
+ p_source: "test-source",
+ p_name_nl: "Test Fertilizer 1",
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ p_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ }
+
+ const fertilizer2: CatalogueFertilizerItem = {
+ ...fertilizer1,
+ }
+
+ const hash1 = await hashFertilizer(fertilizer1)
+ const hash2 = await hashFertilizer(fertilizer2)
+
+ expect(hash1).toBe(hash2)
+ })
+
+ it("should generate different hashes when a boolean value changes", async () => {
+ const fertilizer1: CatalogueFertilizerItem = {
+ p_id_catalogue: "test-id-1",
+ p_source: "test-source",
+ p_name_nl: "Test Fertilizer 1",
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ p_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ }
+
+ const fertilizer2: CatalogueFertilizerItem = {
+ ...fertilizer1,
+ p_type_manure: false,
+ }
+
+ const hash1 = await hashFertilizer(fertilizer1)
+ const hash2 = await hashFertilizer(fertilizer2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+ it("should generate different hashes when a numerical value changes", async () => {
+ const fertilizer1: CatalogueFertilizerItem = {
+ p_id_catalogue: "test-id-1",
+ p_source: "test-source",
+ p_name_nl: "Test Fertilizer 1",
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ p_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ }
+
+ const fertilizer2: CatalogueFertilizerItem = {
+ ...fertilizer1,
+ p_dm: 50,
+ }
+
+ const hash1 = await hashFertilizer(fertilizer1)
+ const hash2 = await hashFertilizer(fertilizer2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+ it("should generate different hashes when a string value changes", async () => {
+ const fertilizer1: CatalogueFertilizerItem = {
+ p_id_catalogue: "test-id-1",
+ p_source: "test-source",
+ p_name_nl: "Test Fertilizer 1",
+ p_name_en: "Test Fertilizer (EN)",
+ p_description: "This is a test fertilizer",
+ p_dm: 37,
+ p_density: 20,
+ p_om: 20,
+ p_a: 30,
+ p_hc: 40,
+ p_eom: 50,
+ p_eoc: 60,
+ p_c_rt: 70,
+ p_c_of: 80,
+ p_c_if: 90,
+ p_c_fr: 100,
+ p_cn_of: 110,
+ p_n_rt: 120,
+ p_n_if: 130,
+ p_n_of: 140,
+ p_n_wc: 150,
+ p_p_rt: 160,
+ p_k_rt: 170,
+ p_mg_rt: 180,
+ p_ca_rt: 190,
+ p_ne: 200,
+ p_s_rt: 210,
+ p_s_wc: 220,
+ p_cu_rt: 230,
+ p_zn_rt: 240,
+ p_na_rt: 250,
+ p_si_rt: 260,
+ p_b_rt: 270,
+ p_mn_rt: 280,
+ p_ni_rt: 290,
+ p_fe_rt: 300,
+ p_mo_rt: 310,
+ p_co_rt: 320,
+ p_as_rt: 330,
+ p_cd_rt: 340,
+ p_cr_rt: 350,
+ p_cr_vi: 360,
+ p_pb_rt: 370,
+ p_hg_rt: 380,
+ p_cl_rt: 390,
+ p_type_manure: true,
+ p_type_mineral: false,
+ p_type_compost: false,
+ }
+
+ const fertilizer2: CatalogueFertilizerItem = {
+ ...fertilizer1,
+ p_name_nl: "Updated Test Fertilizer Name",
+ }
+
+ const hash1 = await hashFertilizer(fertilizer1)
+ const hash2 = await hashFertilizer(fertilizer2)
+
+ expect(hash1).not.toBe(hash2)
+ })
+})
diff --git a/fdm-data/src/fertilizers/hash.ts b/fdm-data/src/fertilizers/hash.ts
new file mode 100644
index 000000000..4b7359e7b
--- /dev/null
+++ b/fdm-data/src/fertilizers/hash.ts
@@ -0,0 +1,25 @@
+import { ensureInitialized, h32ToString } from "../hash"
+import type { CatalogueFertilizerItem } from "./d"
+
+export async function hashFertilizer(fertilizer: CatalogueFertilizerItem) {
+ await ensureInitialized()
+ // Set hash to null for consistent hashing
+ fertilizer.hash = null
+
+ // Remove all keys without a value
+ const filteredFertilizer = Object.fromEntries(
+ Object.entries(fertilizer).filter(
+ ([, value]) => value !== undefined && value !== null,
+ ),
+ )
+
+ // Sort keys to ensure consistent hash generation for identical objects
+ const sortedKeys = Object.keys(filteredFertilizer).sort()
+ const sortedFertilizer = sortedKeys.reduce>((obj, key) => {
+ obj[key] = fertilizer[key as keyof typeof fertilizer]
+ return obj
+ }, {})
+
+ const hash = h32ToString(JSON.stringify(sortedFertilizer))
+ return hash
+}
diff --git a/fdm-data/src/fertilizers/index.test.ts b/fdm-data/src/fertilizers/index.test.ts
index 3c1b6969c..041b57547 100644
--- a/fdm-data/src/fertilizers/index.test.ts
+++ b/fdm-data/src/fertilizers/index.test.ts
@@ -3,36 +3,37 @@ import { getFertilizersCatalogue } from "./index"
import { getCatalogueSrm } from "./catalogues/srm"
describe("getFertilizersCatalogue", () => {
- it("should return the SRM catalogue when catalogueName is 'srm'", () => {
- const expectedCatalogue = getCatalogueSrm()
- const actualCatalogue = getFertilizersCatalogue("srm")
+ it("should return the SRM catalogue when catalogueName is 'srm'", async () => {
+ const expectedCatalogue = await getCatalogueSrm()
+ const actualCatalogue = await getFertilizersCatalogue("srm")
expect(actualCatalogue).toEqual(expectedCatalogue)
})
- it("should throw an error when an invalid catalogueName is provided", () => {
- expect(() => getFertilizersCatalogue("invalid-catalogue")).toThrowError(
- "catalogue invalid-catalogue is not recognized",
- )
+ it("should throw an error when an invalid catalogueName is provided", async () => {
+ await expect(
+ getFertilizersCatalogue("invalid-catalogue"),
+ ).rejects.toThrowError("catalogue invalid-catalogue is not recognized")
})
- it("should return a non-empty array for 'srm' catalogue", () => {
- const catalogue = getFertilizersCatalogue("srm")
+
+ it("should return a non-empty array for 'srm' catalogue", async () => {
+ const catalogue = await getFertilizersCatalogue("srm")
expect(Array.isArray(catalogue)).toBe(true)
expect(catalogue.length).toBeGreaterThan(0)
})
- it("should check if all items in the srm catalogue have the correct source", () => {
- const catalogue = getFertilizersCatalogue("srm")
+ it("should check if all items in the srm catalogue have the correct source", async () => {
+ const catalogue = await getFertilizersCatalogue("srm")
for (const item of catalogue) {
expect(item.p_source).toBe("srm")
}
})
})
-describe("getCatalogueSrm", () => {
+describe("getCatalogueSrm", async () => {
const originalSrm = require("./catalogues/srm.json")
- it("should return an array of CatalogueFertilizerItem", () => {
- const catalogue = getCatalogueSrm()
+ it("should return an array of CatalogueFertilizerItem", async () => {
+ const catalogue = await getCatalogueSrm()
expect(Array.isArray(catalogue)).toBe(true)
for (const item of catalogue) {
expect(typeof item).toBe("object")
@@ -80,7 +81,7 @@ describe("getCatalogueSrm", () => {
expect(item).toHaveProperty("p_cr_vi")
expect(item).toHaveProperty("p_pb_rt")
expect(item).toHaveProperty("p_hg_rt")
- expect(item).toHaveProperty("p_cl_cr")
+ expect(item).toHaveProperty("p_cl_rt")
expect(item).toHaveProperty("p_type_manure")
expect(item).toHaveProperty("p_type_mineral")
expect(item).toHaveProperty("p_type_compost")
@@ -88,8 +89,8 @@ describe("getCatalogueSrm", () => {
}
})
- it("should return at least one item", () => {
- const catalogue = getCatalogueSrm()
+ it("should return at least one item", async () => {
+ const catalogue = await getCatalogueSrm()
expect(catalogue.length).toBeGreaterThan(0)
})
})
diff --git a/fdm-data/src/fertilizers/index.ts b/fdm-data/src/fertilizers/index.ts
index 97381bec5..e84a4970d 100644
--- a/fdm-data/src/fertilizers/index.ts
+++ b/fdm-data/src/fertilizers/index.ts
@@ -11,21 +11,22 @@ import type { CatalogueFertilizer, CatalogueFertilizerName } from "./d"
* Currently supported names are: "srm".
* @returns An array of `CatalogueFertilizerItem` objects representing the
* requested fertilizer catalogue.
+ * @returns A Promise that resolves to an array of `CatalogueFertilizerItem` objects.
* @throws {Error} Throws an error if the provided `catalogueName` is not
* recognized or supported.
*
* @example
* ```typescript
- * const srmCatalogue = getFertilizersCatalogue("srm");
+ * const srmCatalogue = await getFertilizersCatalogue("srm");
* console.log(srmCatalogue);
* ```
*/
-export function getFertilizersCatalogue(
+export async function getFertilizersCatalogue(
catalogueName: CatalogueFertilizerName,
-): CatalogueFertilizer {
+): Promise {
// Get the specified catalogue
if (catalogueName === "srm") {
- return getCatalogueSrm()
+ return await getCatalogueSrm()
}
throw new Error(`catalogue ${catalogueName} is not recognized`)
}
diff --git a/fdm-data/src/hash.ts b/fdm-data/src/hash.ts
new file mode 100644
index 000000000..ca6bf60e4
--- /dev/null
+++ b/fdm-data/src/hash.ts
@@ -0,0 +1,15 @@
+import xxhash from "xxhash-wasm"
+
+// Initialize hash function lazily to avoid top-level await
+export let h32ToString: (input: string) => string
+let initPromise: Promise | null = null
+
+export function ensureInitialized() {
+ if (!initPromise) {
+ initPromise = xxhash().then((hash) => {
+ h32ToString = hash.h32ToString
+ return
+ })
+ }
+ return initPromise
+}
diff --git a/fdm-data/src/index.ts b/fdm-data/src/index.ts
index 1023c9216..3addf3e03 100644
--- a/fdm-data/src/index.ts
+++ b/fdm-data/src/index.ts
@@ -13,3 +13,5 @@
export { getFertilizersCatalogue } from "./fertilizers"
export { getCultivationCatalogue } from "./cultivations"
+export { hashFertilizer } from "./fertilizers/hash"
+export { hashCultivation } from "./cultivations/hash"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 845411d4a..ee5753534 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -121,6 +121,9 @@ importers:
'@svenvw/fdm-core':
specifier: workspace:^
version: link:../fdm-core
+ '@tanstack/react-table':
+ specifier: ^8.21.2
+ version: 8.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@turf/centroid':
specifier: ^7.2.0
version: 7.2.0
@@ -3644,6 +3647,17 @@ packages:
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
engines: {node: '>=14.16'}
+ '@tanstack/react-table@8.21.2':
+ resolution: {integrity: sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ react: '>=16.8'
+ react-dom: '>=16.8'
+
+ '@tanstack/table-core@8.21.2':
+ resolution: {integrity: sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==}
+ engines: {node: '>=12'}
+
'@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
@@ -13260,6 +13274,14 @@ snapshots:
dependencies:
defer-to-connect: 2.0.1
+ '@tanstack/react-table@8.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/table-core': 8.21.2
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@tanstack/table-core@8.21.2': {}
+
'@trysound/sax@0.2.0': {}
'@turf/centroid@7.2.0':
|