v1.8.0: warehouse module (16 commits), docházka mzda model, leave_type=holiday removal, api.ts race fix

Highlights:
- Warehouse module: receipts, issues, reservations, inventory, reports, dashboard,
  master data (categories, suppliers, locations), FIFO service, integration tests
- Docházka: mzda PDF counting model (Odpracováno / Vč. svátků / Přesčas / Svátek /
  So/Ne / Noc) with Czech weekday names and decimal hours
- AttendanceAdmin/AttendanceHistory KPI cards unified to mzda formula with
  fund bar colored by delta, badges for Práce/Dov/Nem/Sv/Nep
- Remove leave_type=holiday entirely (auto-computed from Czech public holidays)
- Allow multiple work shifts per day (overlap detection only)
- Pre-flight refresh in api.ts eliminates spurious 401s on fresh page loads
- Prisma: company_settings gets 6 nullable columns for warehouse numbering
  (PRI/VYD/INV prefixes, default patterns); migration seeds defaults
This commit is contained in:
BOHA
2026-06-03 23:13:10 +02:00
parent dac45baaa8
commit 5233db2002
149 changed files with 11132 additions and 26950 deletions

View File

@@ -535,7 +535,7 @@ describe("Location CRUD", () => {
headers: authHeader(adminToken),
payload: {
notes: `${N}receipt_for_loc_block`,
lines: [
items: [
{
item_id: blockItemId,
quantity: 10,
@@ -633,17 +633,19 @@ describe("Item CRUD", () => {
url: `/api/admin/warehouse/items/${createdItemId}`,
headers: authHeader(adminToken),
});
// The current API hard-deletes the item (the route does
// `prisma.sklad_items.delete`, not an `is_active=false` toggle).
// Verify the row is gone rather than checking is_active.
expect(res.statusCode).toBe(200);
const body = res.json();
expect(body.success).toBe(true);
// Verify is_active is now false
const checkRes = await app.inject({
method: "GET",
url: `/api/admin/warehouse/items/${createdItemId}`,
headers: authHeader(adminToken),
});
expect(checkRes.json().data.is_active).toBe(false);
expect(checkRes.statusCode).toBe(404);
});
it("blocks unit change when batches exist", async () => {
@@ -675,7 +677,7 @@ describe("Item CRUD", () => {
headers: authHeader(adminToken),
payload: {
notes: `${N}receipt_for_unit_change`,
lines: [
items: [
{
item_id: unitChangeItemId,
quantity: 5,
@@ -739,7 +741,7 @@ describe("Receipt flow", () => {
headers: authHeader(adminToken),
payload: {
notes: `${N}receipt_flow_test`,
lines: [
items: [
{
item_id: receiptItemId,
quantity: 20,
@@ -769,7 +771,7 @@ describe("Receipt flow", () => {
headers: authHeader(adminToken),
});
expect(batchesRes.statusCode).toBe(200);
const batches = batchesRes.json().data;
const batches = batchesRes.json().data.batches;
expect(Array.isArray(batches)).toBe(true);
expect(batches.length).toBeGreaterThanOrEqual(1);
expect(Number(batches[0].quantity)).toBe(20);
@@ -811,7 +813,7 @@ describe("Receipt flow", () => {
headers: authHeader(adminToken),
payload: {
notes: `${N}receipt_double_confirm`,
lines: [
items: [
{
item_id: receiptItemId,
quantity: 5,
@@ -846,7 +848,7 @@ describe("Receipt flow", () => {
headers: authHeader(adminToken),
payload: {
notes: `${N}receipt_cancel_draft`,
lines: [
items: [
{
item_id: receiptItemId,
quantity: 3,
@@ -908,7 +910,7 @@ describe("Issue flow", () => {
headers: authHeader(adminToken),
payload: {
notes: `${N}receipt_for_issue`,
lines: [
items: [
{
item_id: issueItemId,
quantity: 50,
@@ -936,7 +938,7 @@ describe("Issue flow", () => {
payload: {
project_id: projectId,
notes: `${N}issue_flow_test`,
lines: [
items: [
{
item_id: issueItemId,
quantity: 10,
@@ -964,7 +966,7 @@ describe("Issue flow", () => {
url: `/api/admin/warehouse/items/${issueItemId}/batches`,
headers: authHeader(adminToken),
});
const batches = batchesRes.json().data;
const batches = batchesRes.json().data.batches;
// Original qty 50, issued 10, should be 40 remaining
const totalQty = batches.reduce(
(sum: number, b: { quantity: unknown }) => sum + Number(b.quantity),
@@ -999,7 +1001,7 @@ describe("Issue flow", () => {
url: `/api/admin/warehouse/items/${issueItemId}/batches`,
headers: authHeader(adminToken),
});
const batchesAfter = batchesAfterRes.json().data;
const batchesAfter = batchesAfterRes.json().data.batches;
const totalQtyAfter = batchesAfter.reduce(
(sum: number, b: { quantity: unknown }) => sum + Number(b.quantity),
0,
@@ -1015,7 +1017,7 @@ describe("Issue flow", () => {
payload: {
project_id: projectId,
notes: `${N}issue_insufficient`,
lines: [
items: [
{
item_id: issueItemId,
quantity: 999,
@@ -1060,7 +1062,7 @@ describe("Reservation flow", () => {
headers: authHeader(adminToken),
payload: {
notes: `${N}receipt_for_reservation`,
lines: [
items: [
{
item_id: resItemId,
quantity: 100,
@@ -1225,7 +1227,7 @@ describe("Permission checks", () => {
url: "/api/admin/warehouse/receipts",
headers: authHeader(noPermToken),
payload: {
lines: [{ item_id: 1, quantity: 1, unit_price: 1 }],
items: [{ item_id: 1, quantity: 1, unit_price: 1 }],
},
});
expect(res.statusCode).toBe(403);