diff --git a/api/main.ts b/api/main.ts index 32619da..fe77bab 100644 --- a/api/main.ts +++ b/api/main.ts @@ -1383,8 +1383,9 @@ app.get("/v1/badge-jobs/:id/pdf", ...requireCoreAuth, requirePermission("documen { job_id: id }, { route }, ); - if (!jobEnv.ok) return respondRpc(c, jobEnv); - const pdfPath = jobEnv.data?.job?.pdf_path || badgeJobPdfKey(id); + const pdfPath = jobEnv.ok + ? (jobEnv.data?.job?.pdf_path || badgeJobPdfKey(id)) + : badgeJobPdfKey(id); const bytes = await getObject(pdfPath); c.header("Content-Type", "application/pdf"); c.header("Content-Disposition", `attachment; filename="gafetes-${id}.pdf"`); diff --git a/api/pdf.ts b/api/pdf.ts index 6a40a3b..e19bd4c 100644 --- a/api/pdf.ts +++ b/api/pdf.ts @@ -101,16 +101,34 @@ export async function generateBadgePdf( workerIds: number[], ): Promise { const project = await db.prepare( - "SELECT name, code, theme_id, logo_left_path, logo_right_path FROM projects WHERE id = ?", + `SELECT p.name, p.code, p.theme_id, p.logo_left_path, p.logo_right_path, p.tenant_id, + COALESCE(NULLIF(c.nombre_comercial, ''), NULLIF(c.name, '')) AS company_name + FROM projects p + LEFT JOIN companies c ON c.id = p.company_id + WHERE p.id = ?`, ).get(projectId) as { name: string; code: string; theme_id: string; logo_left_path: string | null; logo_right_path: string | null; + tenant_id?: number | null; + company_name?: string | null; } | undefined; if (!project) throw new Error("Proyecto no encontrado"); + let companyName = String(project.company_name || "").trim(); + if (!companyName && project.tenant_id) { + const principal = await db.prepare( + `SELECT COALESCE(NULLIF(nombre_comercial, ''), name) AS company_name + FROM companies + WHERE tenant_id = ? AND kind = 'principal' + ORDER BY id + LIMIT 1`, + ).get(project.tenant_id) as { company_name?: string } | undefined; + companyName = String(principal?.company_name || "").trim(); + } + const workers = await db.prepare( `SELECT w.id, w.first_name, w.middle_name, w.last_name_p, w.last_name_m, w.curp, w.nss, w.blood_type, w.position, @@ -156,7 +174,7 @@ export async function generateBadgePdf( const photo = await embedMaybe(pdf, await loadCurrentPhoto(db, w.id)); const qrPng = await badgeQrPng(w.curp); const qrImg = await pdf.embedPng(qrPng); - drawFront(page, font, fontBold, x, topY, w, photo, logoL, logoR); + drawFront(page, font, fontBold, x, topY, w, photo, logoL, logoR, companyName); drawBack(page, font, fontBold, x, botY, w, qrImg, project.name, project.code); } } @@ -164,6 +182,22 @@ export async function generateBadgePdf( return await pdf.save(); } +function drawFittedLeft( + page: PDFPage, + font: PDFFont, + text: string, + x: number, + y: number, + maxWidth: number, + size: number, + color = rgb(0, 0, 0), +) { + if (!text) return; + let s = size; + while (s > 5 && font.widthOfTextAtSize(text, s) > maxWidth) s -= 0.4; + page.drawText(text, { x, y, size: s, font, color }); +} + function drawFront( page: PDFPage, font: PDFFont, @@ -174,6 +208,7 @@ function drawFront( photo: PDFImage | null, logoL: PDFImage | null, logoR: PDFImage | null, + companyName: string, ) { page.drawRectangle({ x, @@ -193,12 +228,15 @@ function drawFront( height: logoL.height * scale, }); } else { - page.drawText("Construcciones Arctec", { - x: x + 6, - y: y + CARD_H - 22, - size: 7, - font: fontBold, - }); + drawFittedLeft( + page, + fontBold, + companyName.toUpperCase(), + x + 6, + y + CARD_H - 22, + CARD_W / 2 - 10, + 7, + ); } if (logoR) { const scale = Math.min((CARD_W / 2 - 8) / logoR.width, (logoH - 4) / logoR.height); @@ -208,14 +246,6 @@ function drawFront( width: logoR.width * scale, height: logoR.height * scale, }); - } else { - page.drawText("Zendala", { - x: x + CARD_W / 2 + 8, - y: y + CARD_H - 22, - size: 9, - font: fontBold, - color: rgb(0.1, 0.25, 0.55), - }); } const photoW = 3.5 * CM; diff --git a/db/core/changelog-master.xml b/db/core/changelog-master.xml index a00bad9..5c4f997 100644 --- a/db/core/changelog-master.xml +++ b/db/core/changelog-master.xml @@ -41,6 +41,7 @@ + diff --git a/db/core/changesets/032-fn-badge-job-get.sql b/db/core/changesets/032-fn-badge-job-get.sql new file mode 100644 index 0000000..f67b447 --- /dev/null +++ b/db/core/changesets/032-fn-badge-job-get.sql @@ -0,0 +1,80 @@ +--liquibase formatted sql +-- PANELS · core · fn_badge_job_get: ORDER BY del subquery de personas + +--changeset panel:core-032-fn-badge-job-get-people-order splitStatements:false +CREATE OR REPLACE FUNCTION core.fn_badge_job_get(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_job_id bigint := COALESCE( + NULLIF(payload->>'job_id', '')::bigint, + NULLIF(payload->>'id', '')::bigint + ); + v_job jsonb; + v_people jsonb; +BEGIN + IF v_job_id IS NULL THEN + RETURN core.rpc_err( + 'VALIDATION', + 'fn_badge_job_get: job_id es obligatorio', + jsonb_build_object('fn', 'fn_badge_job_get', 'field', 'job_id') + ); + END IF; + + SELECT to_jsonb(x) + INTO v_job + FROM ( + SELECT + j.*, + p.code AS project_code, + p.name AS project_name + FROM badge_jobs j + JOIN projects p ON p.id = j.project_id + WHERE j.id = v_job_id + ) x; + + IF v_job IS NULL THEN + RETURN core.rpc_err( + 'NOT_FOUND', + format('fn_badge_job_get: job de gafetes id=%s no encontrado', v_job_id), + jsonb_build_object('fn', 'fn_badge_job_get', 'job_id', v_job_id) + ); + END IF; + + SELECT COALESCE(jsonb_agg(to_jsonb(p) ORDER BY p.last_name_p, p.first_name), '[]'::jsonb) + INTO v_people + FROM ( + SELECT + bp.job_id, + bp.worker_id, + bp.delivered, + bp.delivered_at, + w.first_name, + w.last_name_p, + w.last_name_m, + w.position + FROM badge_job_people bp + JOIN workers w ON w.id = bp.worker_id + WHERE bp.job_id = v_job_id::integer + ) p; + + RETURN core.rpc_ok( + jsonb_build_object('job', v_job, 'people', v_people), + format('Job de gafetes #%s cargado: %s persona(s) en el lote', + v_job_id, jsonb_array_length(v_people)), + jsonb_build_object( + 'fn', 'fn_badge_job_get', + 'job_id', v_job_id, + 'people_count', jsonb_array_length(v_people) + ) + ); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_badge_job_get', SQLSTATE, SQLERRM); +END; +$$; + +--changeset panel:core-032-fn-badge-job-get-grant endDelimiter:; splitStatements:true +GRANT EXECUTE ON FUNCTION core.fn_badge_job_get(jsonb) TO panels_core_app; diff --git a/web-panel/components/BadgeCard.vue b/web-panel/components/BadgeCard.vue index c9b6659..8e55885 100644 --- a/web-panel/components/BadgeCard.vue +++ b/web-panel/components/BadgeCard.vue @@ -2,7 +2,7 @@