Skip to content

Commit e9fb183

Browse files
committed
fix(admin-settings): address 5 bot review comments on GIP/active-year flow
1 parent 4c86b4f commit e9fb183

4 files changed

Lines changed: 36 additions & 27 deletions

File tree

packages/app/src/modules/admin/settings/CampaignDeadlinesForm.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,13 @@ function GipPublicationReadOnly({ value }: { value: string | null }) {
276276
</span>
277277
</label>
278278
<input
279+
aria-readonly="true"
279280
className="fr-input"
281+
defaultValue={value ?? "Non disponible"}
280282
id="settings-gipPublicationDate"
283+
key={value ?? "empty"}
281284
readOnly
282285
type="text"
283-
value={value ?? "Non disponible"}
284286
/>
285287
</div>
286288
);

packages/app/src/server/api/routers/adminSettings.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ export const adminSettingsRouter = createTRPCRouter({
108108
decl2JointEvaluationDeadline: input.decl2JointEvaluationDeadline,
109109
};
110110

111+
// `gipPublicationDate` is written exclusively by the SUIT CSV import,
112+
// so we never touch it here — neither on insert (defaults to NULL)
113+
// nor on conflict (the `set` object below omits it by construction).
111114
await ctx.db.insert(campaignDeadlines).values(values).onConflictDoUpdate({
112115
target: campaignDeadlines.year,
113116
set: values,

packages/app/src/server/db/getGlobalSettings.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@ export const GLOBAL_SETTINGS_ID = 1;
1818
* Wrapped in React `cache()` to deduplicate calls within a single request.
1919
*/
2020
export const getActiveCampaignYear = cache(async (): Promise<number> => {
21-
// `.date` columns are serialised as "YYYY-MM-DD" strings, safe to compare lexicographically.
22-
const today = new Date().toISOString().slice(0, 10);
21+
// `.date` columns are stored as "YYYY-MM-DD" in Europe/Paris civil time, so
22+
// we compare them against the local calendar date. `toISOString()` would
23+
// yield a UTC date and flip the result across midnight for anyone not on
24+
// UTC (e.g. Paris in summer is UTC+2 → "tomorrow" for ~2h each night).
25+
// `sv-SE` locale renders dates as ISO "YYYY-MM-DD" in local time.
26+
const today = new Date().toLocaleDateString("sv-SE");
2327

2428
const rows = await db
2529
.select({ year: campaignDeadlines.year })

packages/app/src/server/services/gipMds.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type { DB } from "~/server/db";
77
import { campaignDeadlines, companies, gipMdsData } from "~/server/db/schema";
88
import { fetchCompanyBySiren } from "./weez";
99

10+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
11+
1012
/**
1113
* CSV metadata extracted from the first 2 lines of a GIP MDS file.
1214
* Line 1: destinataire;projet;horodatage;date_debut;date_fin;nb_lignes
@@ -233,30 +235,28 @@ export async function importGipCsvToDb(
233235
})),
234236
);
235237

236-
// Record the SUIT `horodatage` as the GIP publication date on the
237-
// matching campaign-deadlines row. Admins cannot edit this field — the
238-
// import is the single source of truth.
239-
if (/^\d{4}-\d{2}-\d{2}$/.test(metadata.publicationDate)) {
240-
await tx
241-
.insert(campaignDeadlines)
242-
.values({
243-
year,
244-
gipPublicationDate: metadata.publicationDate,
245-
// Non-null columns need a safe placeholder: the import-time
246-
// write only touches gipPublicationDate on an existing row,
247-
// so these fallbacks only matter when no campaign has been
248-
// configured yet.
249-
decl1ModificationDeadline: `${year}-06-01`,
250-
decl1JustificationDeadline: `${year}-06-01`,
251-
decl1JointEvaluationDeadline: `${year}-08-01`,
252-
decl2ModificationDeadline: `${year}-12-01`,
253-
decl2JustificationDeadline: `${year}-12-01`,
254-
decl2JointEvaluationDeadline: `${year + 1}-02-01`,
255-
})
256-
.onConflictDoUpdate({
257-
target: campaignDeadlines.year,
258-
set: { gipPublicationDate: metadata.publicationDate },
259-
});
238+
// Record the SUIT `horodatage` as the GIP publication date — but only
239+
// on an EXISTING `campaign_deadline` row. We never synthesise a row
240+
// with placeholder deadlines here: admins must configure the campaign
241+
// first (decl1/decl2 deadlines are NOT NULL and those values must be
242+
// decided by a human, not made up by the import).
243+
if (!ISO_DATE_RE.test(metadata.publicationDate)) {
244+
console.warn(
245+
`[gip-mds/import] Ignoring invalid horodatage "${metadata.publicationDate}" for year ${year}`,
246+
);
247+
return;
248+
}
249+
250+
const updated = await tx
251+
.update(campaignDeadlines)
252+
.set({ gipPublicationDate: metadata.publicationDate })
253+
.where(eq(campaignDeadlines.year, year))
254+
.returning({ year: campaignDeadlines.year });
255+
256+
if (updated.length === 0) {
257+
console.warn(
258+
`[gip-mds/import] No campaign_deadline row for year ${year} — gipPublicationDate not stored. Ask an admin to configure deadlines first.`,
259+
);
260260
}
261261
});
262262

0 commit comments

Comments
 (0)