v1.6.5: fix attendance unique constraint, schema sync, seed script

- Change attendance idx_attendance_user_date from unique to index (allow multiple shifts per day)
- Reset migrations to single baseline init migration
- Add seed script with admin user (admin/admin)
- Update CLAUDE.md with migration workflow documentation
- Various frontend fixes (queries, pages, hooks)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-05-17 16:21:34 +02:00
parent 1db5060c42
commit cd6d3daf43
75 changed files with 1748 additions and 1169 deletions

View File

@@ -11,11 +11,17 @@ import SortIcon from "../components/SortIcon";
import useTableSort from "../hooks/useTableSort";
import useModalLock from "../hooks/useModalLock";
import AdminDatePicker from "../components/AdminDatePicker";
import { companySettingsOptions } from "../lib/queries/settings";
import {
companySettingsOptions,
type CompanySettingsData,
} from "../lib/queries/settings";
import { supplierListOptions } from "../lib/queries/common";
import {
receivedInvoiceListOptions,
receivedInvoiceStatsOptions,
type ReceivedInvoice,
type ReceivedStats,
type CurrencyAmount,
} from "../lib/queries/invoices";
import { Skeleton } from "boneyard-js/react";
import ReceivedInvoicesFixture from "../fixtures/ReceivedInvoicesFixture";
@@ -48,37 +54,6 @@ const MONTH_NAMES = [
"prosinec",
];
interface CurrencyAmount {
amount: number;
currency: string;
}
interface ReceivedInvoice {
id: number;
supplier_name: string;
invoice_number: string;
amount: number;
currency: string;
vat_rate: number;
issue_date: string;
due_date: string;
notes: string;
status: string;
file_name?: string;
created_at: string;
}
interface ReceivedStats {
total_month: CurrencyAmount[];
total_month_czk: number | null;
vat_month: CurrencyAmount[];
vat_month_czk: number | null;
unpaid: CurrencyAmount[];
unpaid_czk: number | null;
unpaid_count: number;
month_count: number;
}
interface UploadMeta {
supplier_name: string;
invoice_number: string;
@@ -133,14 +108,9 @@ function formatCzkWithDetail(
return { value: formatMultiCurrency(amounts), detail: null };
}
interface CompanySettings {
default_currency: string;
default_vat_rate: number;
available_currencies: string[];
available_vat_rates: number[];
}
function emptyMeta(settings: CompanySettings | null | undefined): UploadMeta {
function emptyMeta(
settings: CompanySettingsData | null | undefined,
): UploadMeta {
return {
supplier_name: "",
invoice_number: "",
@@ -182,9 +152,7 @@ export default function ReceivedInvoices({
const prevYear = useRef(statsYear);
const { data: supplierNames = [] } = useQuery(supplierListOptions());
const companySettings = useQuery(companySettingsOptions()).data as unknown as
| CompanySettings
| undefined;
const companySettings = useQuery(companySettingsOptions()).data;
// List query — auto-refetches when filters change
const listQuery = useQuery(
@@ -203,11 +171,11 @@ export default function ReceivedInvoices({
);
// Derive list data from query (paginatedJsonQuery returns { data, pagination })
const invoices = (listQuery.data?.data ?? []) as ReceivedInvoice[];
const invoices = listQuery.data?.data ?? [];
if (listQuery.data || statsQuery.data) hasLoadedOnce.current = true;
// Derive stats from query
const stats = (statsQuery.data as unknown as ReceivedStats) ?? null;
const stats = statsQuery.data ?? null;
// Trigger slide animation when stats data changes
useEffect(() => {
@@ -345,8 +313,10 @@ export default function ReceivedInvoices({
setUploadMeta([]);
setUploadErrors({});
queryClient.invalidateQueries({
queryKey: ["invoices", "received"],
queryKey: ["invoices"],
});
queryClient.invalidateQueries({ queryKey: ["suppliers"] });
queryClient.invalidateQueries({ queryKey: ["company-settings"] });
} else {
alert.error(data.error || "Chyba při nahrávání");
}
@@ -416,8 +386,10 @@ export default function ReceivedInvoices({
setEditOpen(false);
setEditInvoice(null);
queryClient.invalidateQueries({
queryKey: ["invoices", "received"],
queryKey: ["invoices"],
});
queryClient.invalidateQueries({ queryKey: ["suppliers"] });
queryClient.invalidateQueries({ queryKey: ["company-settings"] });
} else {
alert.error(data.error || "Chyba při ukládání");
}
@@ -445,8 +417,10 @@ export default function ReceivedInvoices({
alert.success(data.message || "Faktura byla smazána");
setDeleteConfirm({ show: false, invoice: null });
queryClient.invalidateQueries({
queryKey: ["invoices", "received"],
queryKey: ["invoices"],
});
queryClient.invalidateQueries({ queryKey: ["suppliers"] });
queryClient.invalidateQueries({ queryKey: ["company-settings"] });
} else {
alert.error(data.error || "Chyba při mazání");
}
@@ -491,8 +465,10 @@ export default function ReceivedInvoices({
if (data.success) {
alert.success("Faktura označena jako uhrazená");
queryClient.invalidateQueries({
queryKey: ["invoices", "received"],
queryKey: ["invoices"],
});
queryClient.invalidateQueries({ queryKey: ["suppliers"] });
queryClient.invalidateQueries({ queryKey: ["company-settings"] });
} else {
alert.error(data.error || "Nepodařilo se změnit stav");
}