Skip to content

Commit

Permalink
notifications apres prise en charge svi
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaudambro committed Oct 23, 2024
1 parent 383e20a commit cb75663
Show file tree
Hide file tree
Showing 16 changed files with 332 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getUserFromCookie } from "~/services/auth.server";
import type { ExtractLoaderData } from "~/services/extract-loader-data";
import { CarcasseType, Prisma, UserRoles, type Carcasse } from "@prisma/client";
import dayjs from "dayjs";
import sendNotificationToUser from "~/services/notifications.server";

export async function action(args: ActionFunctionArgs) {
const { request, params } = args;
Expand Down Expand Up @@ -199,15 +200,57 @@ export async function action(args: ActionFunctionArgs) {
}
}

console.log({ nextCarcasse });

const updatedCarcasse = await prisma.carcasse.update({
where: {
numero_bracelet: existingCarcasse.numero_bracelet,
},
data: nextCarcasse,
});

if (!existingCarcasse.svi_carcasse_saisie.length && updatedCarcasse.svi_carcasse_saisie.length) {
const [examinateurInitial, premierDetenteur] = await prisma.fei
.findUnique({
where: {
numero: existingCarcasse.fei_numero,
},
include: {
FeiExaminateurInitialUser: true,
FeiPremierDetenteurUser: true,
},
})
.then((fei) => {
return [fei?.FeiExaminateurInitialUser, fei?.FeiPremierDetenteurUser];
});

const email = [
`Carcasse de ${existingCarcasse.espece}`,
`Nombre d'animaux\u00A0: ${existingCarcasse.nombre_d_animaux}`,
`Numéro d'identification\u00A0: ${existingCarcasse.numero_bracelet}`,
`Décision de saisie\u00A0: ${updatedCarcasse.svi_carcasse_saisie.join(" - ")}`,
`Motifs de saisie\u00A0:\n${updatedCarcasse.svi_carcasse_saisie_motif.map((motif) => ` -> ${motif}`).join("\n")}`,
`Commentaire\u00A0:\n${updatedCarcasse.svi_carcasse_commentaire}`,
`Rendez-vous sur Zacharie pour consulter le détail de la carcasse : https://zacharie.agriculture.gouv.fr/carcasse-svi/${existingCarcasse.fei_numero}/${existingCarcasse.numero_bracelet}`,
];

sendNotificationToUser({
user: examinateurInitial!,
title: `Une carcasse de ${existingCarcasse.espece} a été saisie`,
body: `Motif de saisie: ${updatedCarcasse.svi_carcasse_saisie_motif.join(", ")}`,
email: email.join("\n"),
notificationLogAction: `CARCASSE_SAISIE_${existingCarcasse.numero_bracelet}`,
});

if (premierDetenteur?.id !== examinateurInitial?.id) {
sendNotificationToUser({
user: premierDetenteur!,
title: `Une carcasse de ${existingCarcasse.espece} a été saisie`,
body: `Motif de saisie: ${updatedCarcasse.svi_carcasse_saisie_motif.join(", ")}`,
email: email.join("\n"),
notificationLogAction: `CARCASSE_SAISIE_${existingCarcasse.numero_bracelet}`,
});
}
}

return json({
ok: true,
data: {
Expand Down
44 changes: 44 additions & 0 deletions api-remix/app/routes/api.fei.$fei_numero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ export async function action(args: ActionFunctionArgs) {
nextFei.svi_signed_at = new Date(formData.get(Prisma.FeiScalarFieldEnum.svi_signed_at) as string);
}
}
if (formData.has(Prisma.FeiScalarFieldEnum.svi_assigned_at)) {
if (formData.get(Prisma.FeiScalarFieldEnum.svi_assigned_at) === "") {
nextFei.svi_assigned_at = null;
} else {
nextFei.svi_assigned_at = new Date(formData.get(Prisma.FeiScalarFieldEnum.svi_assigned_at) as string);
}
}
if (formData.has(Prisma.FeiScalarFieldEnum.svi_entity_id)) {
nextFei.svi_entity_id = (formData.get(Prisma.FeiScalarFieldEnum.svi_entity_id) as string) || null;
}
Expand All @@ -228,6 +235,43 @@ export async function action(args: ActionFunctionArgs) {
data: nextFei,
});

if (existingFei.fei_next_owner_role !== UserRoles.SVI && savedFei.fei_next_owner_role === UserRoles.SVI) {
// this is the end of the FEI
// send notification to examinateur initial
const feiWithSvi = await prisma.fei.update({
where: { numero: feiNumero },
data: {
svi_entity_id: savedFei.fei_next_owner_entity_id,
svi_assigned_at: new Date(),
},
});
const examinateurInitial = await prisma.user.findUnique({ where: { id: feiWithSvi.examinateur_initial_user_id! } });
sendNotificationToUser({
user: examinateurInitial!,
title: `La fiche du ${feiWithSvi.date_mise_a_mort?.toLocaleDateString()} est prise en charge par l'ETG`,
body: `Les carcasses vont être inspectées par le Service Vétérinaire. Si une carcasse est saisie, vous serez notifié. Vous ne serez pas notifié pour les carcasses acceptées.`,
email: `Les carcasses vont être inspectées par le Service Vétérinaire. Si une carcasse est saisie, vous serez notifié. Vous ne serez pas notifié pour les carcasses acceptées.`,
notificationLogAction: `FEI_ASSIGNED_TO_${feiWithSvi.fei_next_owner_role}_${feiWithSvi.numero}`,
});
if (feiWithSvi.examinateur_initial_user_id !== feiWithSvi.premier_detenteur_user_id) {
const premierDetenteur = await prisma.user.findUnique({ where: { id: feiWithSvi.premier_detenteur_user_id! } });
sendNotificationToUser({
user: premierDetenteur!,
title: `La fiche du ${feiWithSvi.date_mise_a_mort?.toLocaleDateString()} est prise en charge par l'ETG`,
body: `Les carcasses vont être inspectées par le Service Vétérinaire. Si une carcasse est saisie, vous serez notifié. Vous ne serez pas notifié pour les carcasses acceptées.`,
email: `Les carcasses vont être inspectées par le Service Vétérinaire. Si une carcasse est saisie, vous serez notifié. Vous ne serez pas notifié pour les carcasses acceptées.`,
notificationLogAction: `FEI_ASSIGNED_TO_${savedFei.fei_next_owner_role}_${savedFei.numero}`,
});
}
return json({
ok: true,
data: {
fei: JSON.parse(JSON.stringify(feiWithSvi)) as SerializeFrom<Fei>,
},
error: "",
});
}

if (formData.get(Prisma.FeiScalarFieldEnum.fei_next_owner_user_id)) {
const nextOwnerId = formData.get(Prisma.FeiScalarFieldEnum.fei_next_owner_user_id) as string;
if (nextOwnerId !== user.id) {
Expand Down
68 changes: 34 additions & 34 deletions api-remix/app/services/capture.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
import * as Sentry from "@sentry/node";
import { ScopeContext } from "@sentry/types";

interface Context {
extra?: Record<string, unknown>;
[key: string]: unknown;
}
// Extend ScopeContext to make all properties optional
interface OptionalScopeContext extends Partial<ScopeContext> {}

type ErrorType = Error | string;
export const capture = (err: Error, context: OptionalScopeContext = {}): void => {
if (process.env.NODE_ENV === "development") {
console.log("capture", err, context);
}

export function capture(err: ErrorType, context: Context | string): void {
let parsedContext: Context;
if (!context) {
Sentry.captureException(err);
return;
}

if (import.meta.env.DEV) {
return console.log("capture", err, context);
try {
context = JSON.parse(JSON.stringify(context)); // deep copy context
} catch (e) {
console.error("Error parsing context", e);
return;
}
if (typeof context === "string") {
parsedContext = JSON.parse(context);
} else {
parsedContext = JSON.parse(JSON.stringify(context));

// @ts-expect-error Property 'status' does not exist on type 'unknown'
if (context?.extra?.response?.status === 401) {
return;
}

if (parsedContext.extra && typeof parsedContext.extra === "object") {
try {
const newExtra: Record<string, string> = {};
for (const [extraKey, extraValue] of Object.entries(parsedContext.extra)) {
if (typeof extraValue === "string") {
newExtra[extraKey] = extraValue;
} else {
const extraValueObj = extraValue as Record<string, unknown>;
if (extraValueObj && "password" in extraValueObj) {
extraValueObj.password = "******";
}
newExtra[extraKey] = JSON.stringify(extraValueObj);
}
}
parsedContext.extra = newExtra;
} catch (e) {
Sentry.captureMessage(String(e), parsedContext);
if (context.extra) {
const newExtra: Record<string, string> = {};
for (const extraKey of Object.keys(context.extra)) {
newExtra[extraKey] =
typeof context.extra[extraKey] === "string" ? context.extra[extraKey] : JSON.stringify(context.extra[extraKey]);
}
context.extra = newExtra;
}

if (typeof err === "string") {
Sentry.captureMessage(err, parsedContext);
if (Sentry && err) {
if (typeof err === "string") {
Sentry.captureMessage(err, context);
} else {
Sentry.captureException(err, context);
}
} else {
Sentry.captureException(err, parsedContext);
console.log("capture", err, JSON.stringify(context));
}
}
};
14 changes: 14 additions & 0 deletions api-remix/cron/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// import feiEnfing from "./jobs/feiEnding";
// import { setupCronJob } from "./utils";

// await Promise.resolve().then(
// async () =>
// await setupCronJob({
// name: "Pollens",
// // There is no known rule for the update of the data. It is updated when the data is available.
// // Data is valid from the day of issue until 7 days later
// cronTime: "5 0/4 * * *", // every day starting at 00:05 every 4 hours
// job: feiEnfing,
// runOnInit: true,
// }),
// );
91 changes: 91 additions & 0 deletions api-remix/cron/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as cron from "cron";
import { capture } from "~/services/capture";
import { prisma } from "~/db/prisma.server";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
/*
*
*
Launch Cron Job function
In order to prevent the same cron to be launched twice, we use a cronJob table in the database.
If a cron is already running, we don't launch it again.
*/
type TaskFn = () => Promise<void>;

export async function launchCronJob(name: string, job: TaskFn): Promise<boolean> {
try {
const cronJobAlreadyExisting = await prisma.cronJob.findUnique({
where: {
unique_key: `${dayjs().utc().format("YYYY-MM-DD:HH:mm")}_${name}`,
},
});
if (cronJobAlreadyExisting) {
capture(new Error(`Cron job ${name} already existing`), {
level: "error",
});
return false;
}
const cronJob = await prisma.cronJob.create({
data: {
unique_key: `${dayjs().utc().format("YYYY-MM-DD:HH:mm")}_${name}`,
name,
active: true,
},
});

await job();

await prisma.cronJob.update({
where: {
id: cronJob.id,
},
data: {
active: false,
},
});
return true;
} catch (cronError: any) {
capture(cronError, { level: "error", extra: { name } });
}
return false;
}

/*
*
*
Setpup Cron Job function
Each cron job has the same parameters:
- we launch it on init
- we start it (all are activated)
- we set the timezone to Europe/Paris
*/

interface SetupCronJob {
cronTime: string;
name: string;
job: TaskFn;
runOnInit: boolean;
}

export async function setupCronJob({ cronTime, name, job, runOnInit = true }: SetupCronJob): Promise<boolean> {
return await new Promise((resolve) => {
cron.CronJob.from({
cronTime,
onTick: async function () {
const cronStarted = await launchCronJob(name, job);
console.log(`${name}: next run at ${cron.sendAt(cronTime).toISO()}`);
resolve(cronStarted);
},
runOnInit,
start: true,
timeZone: "Europe/Paris",
});
if (!runOnInit) {
resolve(true);
}
});
}
1 change: 1 addition & 0 deletions api-remix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"bcryptjs": "^2.4.3",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cron": "^3.1.7",
"cross-env": "^7.0.3",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- AlterTable
ALTER TABLE "Fei" ADD COLUMN "svi_received_at" TIMESTAMP(3);

-- CreateTable
CREATE TABLE "CronJob" (
"id" TEXT NOT NULL,
"unique_key" TEXT NOT NULL,
"name" TEXT NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "CronJob_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "CronJob_unique_key_key" ON "CronJob"("unique_key");
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
Warnings:
- You are about to drop the column `svi_received_at` on the `Fei` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Fei" DROP COLUMN "svi_received_at",
ADD COLUMN "svi_assigned_at" TIMESTAMP(3);
10 changes: 10 additions & 0 deletions api-remix/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ model Fei {
premier_detenteur_date_depot_quelque_part DateTime?
premier_detenteur_depot_entity_id String?
premier_detenteur_depot_sauvage String?
svi_assigned_at DateTime?
svi_entity_id String?
svi_user_id String?
svi_carcasses_saisies Int?
Expand Down Expand Up @@ -368,3 +369,12 @@ model NotificationLog {
@@index([user_id, action])
}

model CronJob {
id String @id @default(uuid())
unique_key String @unique
name String
active Boolean @default(true)
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
}
Loading

0 comments on commit cb75663

Please sign in to comment.