fix(registratura): prevent duplicate numbers, add upload progress, submission lock, unified close/resolve, backdating support

- generateRegistryNumber: parse max existing number instead of counting entries
- addEntry: fetch fresh entries before generating number (race condition fix)
- Form: isSubmitting lock prevents double-click submission
- Form: uploadingCount tracks FileReader progress, blocks submit while uploading
- Form: submit button shows Loader2 spinner during save/upload
- CloseGuardDialog: added ClosureResolution selector (finalizat/aprobat-tacit/respins/retras/altele)
- ClosureBanner: displays resolution badge
- Types: ClosureResolution type, registrationDate field on RegistryEntry
- Date field renamed 'Data document' with tooltip explaining backdating
- Registry table shows '(înr. DATE)' when registrationDate differs from document date
This commit is contained in:
AI Assistant
2026-02-27 21:56:47 +02:00
parent db6662be39
commit 8042df481f
7 changed files with 403 additions and 185 deletions
@@ -1,7 +1,7 @@
import type { CompanyId } from '@/core/auth/types';
import type { RegistryEntry } from '../types';
import type { CompanyId } from "@/core/auth/types";
import type { RegistryEntry } from "../types";
const STORAGE_PREFIX = 'entry:';
const STORAGE_PREFIX = "entry:";
export interface RegistryStorage {
get<T>(key: string): Promise<T | null>;
@@ -10,7 +10,9 @@ export interface RegistryStorage {
list(): Promise<string[]>;
}
export async function getAllEntries(storage: RegistryStorage): Promise<RegistryEntry[]> {
export async function getAllEntries(
storage: RegistryStorage,
): Promise<RegistryEntry[]> {
const keys = await storage.list();
const entries: RegistryEntry[] = [];
for (const key of keys) {
@@ -23,42 +25,56 @@ export async function getAllEntries(storage: RegistryStorage): Promise<RegistryE
return entries;
}
export async function saveEntry(storage: RegistryStorage, entry: RegistryEntry): Promise<void> {
export async function saveEntry(
storage: RegistryStorage,
entry: RegistryEntry,
): Promise<void> {
await storage.set(`${STORAGE_PREFIX}${entry.id}`, entry);
}
export async function deleteEntry(storage: RegistryStorage, id: string): Promise<void> {
export async function deleteEntry(
storage: RegistryStorage,
id: string,
): Promise<void> {
await storage.delete(`${STORAGE_PREFIX}${id}`);
}
const COMPANY_PREFIXES: Record<CompanyId, string> = {
beletage: 'B',
'urban-switch': 'US',
'studii-de-teren': 'SDT',
group: 'G',
beletage: "B",
"urban-switch": "US",
"studii-de-teren": "SDT",
group: "G",
};
/**
* Generate company-specific registry number: B-0001/2026
* Uses the next sequential number for that company in that year.
* Uses the highest existing number + 1 for that company in that year.
* Parses actual numbers from entries to prevent duplicates.
*/
export function generateRegistryNumber(
company: CompanyId,
date: string,
existingEntries: RegistryEntry[]
_date: string,
existingEntries: RegistryEntry[],
): string {
const d = new Date(date);
const year = d.getFullYear();
const now = new Date();
const year = now.getFullYear();
const prefix = COMPANY_PREFIXES[company];
// Count existing entries for this company in this year
const sameCompanyYear = existingEntries.filter((e) => {
const entryYear = new Date(e.date).getFullYear();
return e.company === company && entryYear === year;
});
// Parse the numeric part from existing numbers for this company+year
// Pattern: PREFIX-NNNN/YYYY
const regex = new RegExp(`^${prefix}-(\\d+)/${year}$`);
let maxNum = 0;
const nextIndex = sameCompanyYear.length + 1;
const padded = String(nextIndex).padStart(4, '0');
for (const e of existingEntries) {
const match = e.number.match(regex);
if (match?.[1]) {
const num = parseInt(match[1], 10);
if (num > maxNum) maxNum = num;
}
}
const nextIndex = maxNum + 1;
const padded = String(nextIndex).padStart(4, "0");
return `${prefix}-${padded}/${year}`;
}