633d05e330
- Estructura nueva: README maestro, bitacora/ (REGISTRO, PENDIENTES, plantillas), propuesta/, fuentes/; material superado a _archivado/ - Propuesta v1.0: MVP BIND-first con emisión asistida MXN/USD (dry-run + confirmación, timbra PAC de BIND), 112-136 h / $67,200-$81,600 + IVA, stack .NET 10 + EF Core + Angular 21 + PostgreSQL 17 sobre Azure - Bitácora: historial de correos + 2 llamadas (incl. revisión 4-jun) y pendientes - Prototipo y diagrama actualizados a v1.0; precios de Azure verificados - Archivo ajeno (proyecto EOS) retirado del repo Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
/**
|
|
* Helpers para construir queries OData que entiende el API de BIND.
|
|
*
|
|
* Sintaxis observable en la doc oficial:
|
|
* /api/Products?$filter=ID eq guid'bbe2cc0c-...'&$skip=0&$top=50&$orderby=Name asc
|
|
*
|
|
* Mantengo el builder muy chico — sólo lo que el MVP necesita.
|
|
*/
|
|
|
|
export type ODataFilter = string;
|
|
|
|
export interface ODataQuery {
|
|
filter?: ODataFilter;
|
|
top?: number;
|
|
skip?: number;
|
|
orderby?: string;
|
|
select?: string[];
|
|
count?: boolean;
|
|
}
|
|
|
|
export function buildQueryString(q: ODataQuery): string {
|
|
const parts: string[] = [];
|
|
if (q.filter) parts.push(`$filter=${encodeURIComponent(q.filter)}`);
|
|
if (typeof q.top === "number") parts.push(`$top=${q.top}`);
|
|
if (typeof q.skip === "number") parts.push(`$skip=${q.skip}`);
|
|
if (q.orderby) parts.push(`$orderby=${encodeURIComponent(q.orderby)}`);
|
|
if (q.select?.length) parts.push(`$select=${encodeURIComponent(q.select.join(","))}`);
|
|
if (q.count) parts.push(`$count=true`);
|
|
return parts.length ? `?${parts.join("&")}` : "";
|
|
}
|
|
|
|
/**
|
|
* Pequeño helper para escribir filtros legibles. No es un parser OData;
|
|
* solo escapa comillas simples y envuelve guids.
|
|
*
|
|
* Ejemplos:
|
|
* eq("ID", guid("bbe2...")) -> "ID eq guid'bbe2...'"
|
|
* eq("Status", "issued") -> "Status eq 'issued'"
|
|
* and(eq("Status","issued"), gt("Total", 1000))
|
|
*/
|
|
export const guid = (id: string): string => `guid'${id.replaceAll("'", "''")}'`;
|
|
export const str = (s: string): string => `'${s.replaceAll("'", "''")}'`;
|
|
export const eq = (field: string, value: string | number | boolean): string =>
|
|
`${field} eq ${formatValue(value)}`;
|
|
export const ne = (field: string, value: string | number | boolean): string =>
|
|
`${field} ne ${formatValue(value)}`;
|
|
export const gt = (field: string, value: string | number): string =>
|
|
`${field} gt ${formatValue(value)}`;
|
|
export const lt = (field: string, value: string | number): string =>
|
|
`${field} lt ${formatValue(value)}`;
|
|
export const ge = (field: string, value: string | number): string =>
|
|
`${field} ge ${formatValue(value)}`;
|
|
export const le = (field: string, value: string | number): string =>
|
|
`${field} le ${formatValue(value)}`;
|
|
export const and = (...parts: string[]): string => parts.join(" and ");
|
|
export const or = (...parts: string[]): string => `(${parts.join(" or ")})`;
|
|
|
|
function formatValue(v: string | number | boolean): string {
|
|
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
// Si ya viene formateado como guid'...' o '...' (string OData), respétalo.
|
|
if (/^(guid'.*'|'.*')$/.test(v)) return v;
|
|
return str(v);
|
|
}
|