Leandro Hernan Rojas 6e61b7b598
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 10m12s
Update Expeditions Print
2025-09-04 18:15:15 -03:00

316 lines
11 KiB
Plaintext

@using System
@using System.Globalization
@using System.Text.Json
@using System.Collections.Generic
@model Domain.Dtos.Stock.ExpeditionDto
@{
Layout = null;
var ci = CultureInfo.GetCultureInfo("es-AR");
CultureInfo.CurrentCulture = ci;
CultureInfo.CurrentUICulture = ci;
// Parseo seguro del snapshot (claves EXACTAS del JSON: Professional, Institution, Patient, SurgeryDate)
SurgerySnapshot snap;
if (string.IsNullOrWhiteSpace(Model.ExtrainfoJson))
{
snap = new SurgerySnapshot();
}
else
{
try
{
snap = JsonSerializer.Deserialize<SurgerySnapshot>(Model.ExtrainfoJson) ?? new SurgerySnapshot();
}
catch
{
snap = new SurgerySnapshot();
}
}
var reprintText = Model.Printcount > 0 ? (" — Reimpresión " + Model.Printcount) : "";
}
@functions {
// Mapear 1:1 con tu JSON de ExtrainfoJson MODELO ORTOPEDIA
public class SurgerySnapshot
{
public string? Professional { get; set; } // "PECHERVSKY PABLO GUSTAVO"
public string? Institution { get; set; } // "CORPORACION MEDICA LABORAL S.A."
public string? Patient { get; set; } // "ALEXIS LASTRA"
public DateTime? SurgeryDate { get; set; } // "2025-06-10T03:00:00"
}
public static string FQty(decimal q) => q.ToString("G29", CultureInfo.InvariantCulture);
// DateTime?
public static string FDate(DateTime? d) => d.HasValue ? d.Value.ToString("dd/MM/yyyy") : string.Empty;
// DateOnly? (por si tus Items usan DateOnly? en Expiration)
public static string FDate(DateOnly? d) => d.HasValue ? d.Value.ToString("dd/MM/yyyy") : string.Empty;
}
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8" />
<title>Nota de Entrega @Model.Expeditionnumber</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
@@page { size: A4; margin: 10mm 9mm 10mm 9mm; }
html, body { font-family: Arial, Helvetica, sans-serif; font-size: 12px; color: #000; }
.sheet { width: 100%; }
.header-legal { display: grid; grid-template-columns: 1.6fr 1fr; gap: 8px; align-items: start; }
.company { line-height: 1.15; }
.doc-title { text-align: right; }
.doc-title h1 { font-size: 20px; margin: 0 0 2px 0; letter-spacing: .5px; }
.doc-title .num { font-weight: bold; }
.doc-title .date { margin-top: 2px; }
.status-line {
display: flex;
justify-content: space-between; /* izquierda / derecha en la misma línea */
align-items: baseline;
margin: 6px 0 8px;
font-weight: 600;
}
.status-line .left {
font-weight: 600;
}
.status-line .right {
font-weight: 400;
text-align: right;
}
.hr { border-bottom: 1px solid #000; margin: 6px 0; }
.snapshot { page-break-inside: avoid; margin-top: 6px; }
.snapshot table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.snapshot td { padding: 2px 4px; vertical-align: top; }
.snapshot .lbl { width: 16%; font-weight: 600; }
.snapshot .val { width: 34%; word-break: break-word; }
.items { margin-top: 8px; }
.items table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.items thead th { border-bottom: 1px solid #000; padding: 4px 4px; text-align: center; }
.items tbody td { padding: 3px 4px; vertical-align: top; }
.col-reg { width: 4%; text-align: center; }
.col-code { width: 18%; text-align: center; }
.col-desc { width: 36%; text-align: left; }
.col-batch { width: 12%; text-align: center; }
.col-exp { width: 10%; text-align: center; }
.col-serial { width: 12%; word-break: break-word; text-align: center; }
.col-qty { width: 8%; text-align: right; text-align: center; }
.totals { margin-top: 6px; display: flex; justify-content: space-between; }
.observ { margin-top: 8px; min-height: 36px; border: 1px dashed #999; padding: 6px; }
.footer { margin-top: 10px; font-size: 11px; text-align: center; border-top: 1px solid #000; padding-top: 6px; }
.muted { color: #111; }
/* ===== MODO COMPACT ===== */
.compact {
font-size: 11px;
}
/* texto general un toque más chico */
.compact .status-line {
margin: 4px 0 6px;
}
.compact .doc-title h1 {
font-size: 18px;
margin: 0 0 0;
}
.compact .snapshot td {
padding: 1px 3px;
}
/* bloque clínico más apretado */
.compact .items table {
font-size: 10px;
}
/* texto de filas más chico */
.compact .items thead th {
padding: 2px 3px;
line-height: 1.05;
background: #eaeaea;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.compact .items tbody td {
padding: 2px 3px; /* ↓ padding vertical */
line-height: 1.05; /* ↓ altura de línea */
}
/* Bordes más finos para no sumar alto visual (algunos engines aceptan 0.6px) */
.compact .items thead th,
.compact .items tbody td {
border: 0.6px solid #000;
}
/* Tabla con bordes tipo grilla */
.items table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.items thead th, .items tbody td {
border: 1px solid #000;
}
/* Repetir encabezado y pie en cada página */
.items thead {
display: table-header-group;
}
.items tfoot {
display: table-footer-group;
}
/* Fila “marco” en el pie (línea inferior por página) */
.items tfoot .page-frame-row td {
border: 1px solid #000; /* dibuja línea inferior y laterales */
height: 0;
padding: 0;
line-height: 0; /* que no coma espacio visual */
}
/* Encabezados con fondo gris */
.items thead th {
background: #eaeaea; /* gris suave */
color: #000;
font-weight: 700;
}
.avoid-break { page-break-inside: avoid; }
</style>
</head>
<body>
<div class="sheet compact">
<!-- Encabezado / identidad -->
<div class="header-legal">
<div class="company">
@if (!string.IsNullOrWhiteSpace(Model.LogoBase64))
{
<img src="data:image/png;base64,@Model.LogoBase64" alt="Logo" style="height:48px; margin-bottom:0;" />
}
<div style="font-size: 10px; margin-top: 0;">
Segurola 1885<br />
C.P. (C1407AOK) - Capital Federal<br/>
Email: ventas@biodec.net<br />
Urgencias: 15-2155-9380 * 15-5909-4987 * 15-5909-4892
</div><br />
<div class="muted">Mercadería en préstamo</div><br />
</div>
<div class="doc-title">
<h1>Nota de Expedición </h1>
<div class="num">EXPEDICIÓN: @Model.Expeditionnumber</div>
<div class="date">Fecha: @Model.Issuedate.ToString("dd/MM/yyyy")</div>
</div>
</div>
<!-- Estado / auditoría -->
<div class="status-line">
<div class="left">
Estado:
@((string.IsNullOrWhiteSpace(Model.StatusLabel) ? "DESCONOCIDO" : Model.StatusLabel) + reprintText)
</div>
<div class="right">Origen: @(Model.LocationName ?? "-")</div>
</div>
<div class="hr"></div>
<!-- Institución / Profesional / Paciente / Fecha de cirugía (desde ExtrainfoJson) -->
<section class="snapshot">
<table>
<tr>
<td class="lbl">Institución:</td>
<td class="val">@snap.Institution</td>
<td class="lbl">Profesional:</td>
<td class="val">@snap.Professional</td>
</tr>
<tr>
<td class="lbl">Paciente:</td>
<td class="val">@snap.Patient</td>
<td class="lbl">Fecha CX:</td>
<td class="val">@FDate(snap.SurgeryDate)</td>
</tr>
</table>
</section>
<div class="hr"></div>
<!-- Detalle de ítems -->
<section class="items">
<table>
<thead>
<tr>
<th class="col-reg">#</th>
<th class="col-code">Código</th>
<th class="col-desc">Descripción</th>
<th class="col-batch">Lote</th>
<th class="col-exp">Venc.</th>
<th class="col-serial">Serie</th>
<th class="col-qty">Cant.</th>
</tr>
</thead>
<tfoot>
<tr class="page-frame-row">
<td colspan="7"></td>
</tr>
</tfoot>
<tbody>
@if (Model.Items != null && Model.Items.Count > 0)
{
int repeat = 100; // ← poné 1 para desactivar el stress-test
for (int r = 0; r < repeat; r++)
{
foreach (var it in Model.Items)
{
<tr>
<td class="col-reg">@r</td>
<td class="col-code">@it.FactoryCode</td>
<td class="col-desc">@it.ProductName</td>
<td class="col-batch">@it.Batch</td>
<td class="col-exp">@FDate(it.Expiration)</td>
<td class="col-serial">@it.Serial</td>
<td class="col-qty">@FQty(it.Quantity)</td>
</tr>
}
}
}
else
{
<tr><td colspan="7" style="text-align:center; padding:6px;">Sin ítems</td></tr>
}
</tbody>
</table>
</section>
<!-- Totales y observaciones -->
<div class="totals">
<div><strong>Renglones:</strong> @Model.TotalItems</div>
<div><strong>Unidades:</strong> @FQty(Model.TotalQuantity)</div>
</div>
<div class="observ">
<strong>Observaciones:</strong> @(Model.Observations ?? string.Empty)
</div>
<!-- Pie -->
<div class="footer">
La mercadería viaja por cuenta y riesgo del comprador.
</div>
</div>
</body>
</html>