@asteby/metacore-runtime-react 49.0.0 → 49.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/dist/action-modal-dispatcher.d.ts +18 -1
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +66 -11
- package/dist/business/document-editor-model.d.ts +6 -1
- package/dist/business/document-editor-model.d.ts.map +1 -1
- package/dist/business/document-editor-model.js +48 -7
- package/dist/business/document-editor-panels.d.ts +18 -0
- package/dist/business/document-editor-panels.d.ts.map +1 -1
- package/dist/business/document-editor-panels.js +9 -0
- package/dist/business/document-editor.d.ts +7 -1
- package/dist/business/document-editor.d.ts.map +1 -1
- package/dist/business/document-editor.js +72 -26
- package/dist/calendar-date.d.ts +12 -0
- package/dist/calendar-date.d.ts.map +1 -1
- package/dist/calendar-date.js +36 -0
- package/dist/document-form-dialog.d.ts +23 -2
- package/dist/document-form-dialog.d.ts.map +1 -1
- package/dist/document-form-dialog.js +49 -8
- package/dist/dynamic-crud-page.d.ts +7 -0
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +5 -4
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/record-mutation-events.d.ts +12 -0
- package/dist/record-mutation-events.d.ts.map +1 -1
- package/dist/record-mutation-events.js +18 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/document-editor-model.test.ts +27 -0
- package/src/__tests__/document-editor.test.tsx +60 -0
- package/src/__tests__/document-form-create-model.test.tsx +143 -0
- package/src/__tests__/field-default-today.test.tsx +116 -0
- package/src/__tests__/invoice-document-editor.test.tsx +267 -0
- package/src/__tests__/r5-self-label-settled.test.ts +57 -0
- package/src/action-modal-dispatcher.tsx +78 -11
- package/src/business/document-editor-model.ts +50 -7
- package/src/business/document-editor-panels.tsx +49 -0
- package/src/business/document-editor.tsx +249 -166
- package/src/calendar-date.ts +36 -0
- package/src/document-form-dialog.tsx +72 -8
- package/src/dynamic-crud-page.tsx +20 -2
- package/src/index.ts +8 -0
- package/src/record-mutation-events.ts +23 -0
- package/src/types.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 49.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4fca0af: DocumentEditor a pantalla completa para documentos con renglones (factura, cotización, pedido, OC).
|
|
8
|
+
|
|
9
|
+
- **Pantalla completa:** DocumentFormDialog abre el editor de un tipo `layout: "editor"` con renglones de venta o compra en un diálogo que ocupa la pantalla (máx. 1440 px). En pantallas anchas va a dos columnas: captura a la izquierda; totales, revisión y vista previa a la derecha. NC y cobro/REP siguen en el diálogo de 960 px.
|
|
10
|
+
- **Divulgación progresiva:** lo obligatorio que el tipo declara queda a la vista aunque sea de extensión (`fiscal_data.metodo_pago`, `fiscal_data.forma_pago` de la factura); las extensiones que llegan del metadata siguen plegadas en «Opciones fiscales».
|
|
11
|
+
- **Tarjeta de la contraparte:** sus filas usan las etiquetas del formulario del modelo de la contraparte (`/metadata/modal/<party.model>`): «RFC», «Régimen fiscal», «Uso CFDI» en vez de `tax_id` o `regimen_receptor`.
|
|
12
|
+
- **«Cargar desde…»:** con varias fuentes, botones por tipo de origen (venta, OT, cotización) en lugar de un `<select>`. Un origen sin dato de impuesto (p. ej. una OT de taller) toma la tasa de la org, como un renglón nuevo.
|
|
13
|
+
- **Vista previa local:** un tipo con renglones y sin acción `preview` del servidor ofrece una vista previa plegada del documento (encabezado, renglones y totales) sin crear borrador.
|
|
14
|
+
- i18n es/en: `documentEditor.source_kind`, `documentEditor.preview`, `documentEditor.advanced`.
|
|
15
|
+
|
|
16
|
+
- 4fca0af: Retest Pitsline r5 (nota de crédito y acciones):
|
|
17
|
+
|
|
18
|
+
- «Crear desde»: un renglón de origen sin descripción propia (InvoiceItem solo
|
|
19
|
+
lleva el producto) se nombra con la etiqueta del producto que resuelve el
|
|
20
|
+
host, y un renglón con producto ya no exige texto para guardar. El fallo de
|
|
21
|
+
`source-lines` (404, 400/500 de un host sin la ruta) cae a la relación.
|
|
22
|
+
- `ActionModalDispatcher` anuncia la mutación ahora y otra vez cuando el
|
|
23
|
+
servidor asienta lo derivado (`emitRecordMutationSettled`,
|
|
24
|
+
`RECORD_MUTATION_SETTLE_DELAYS`): la lista y la pestaña REP se refrescan
|
|
25
|
+
tras timbrar o registrar un pago.
|
|
26
|
+
- Un selector sembrado con el id de la propia fila muestra su folio
|
|
27
|
+
(«Factura a abonar: FAC-00015») y no el UUID.
|
|
28
|
+
|
|
29
|
+
## 49.1.0
|
|
30
|
+
|
|
31
|
+
### Minor Changes
|
|
32
|
+
|
|
33
|
+
- eee67cb: Los campos fecha de un formulario declarativo (document_forms, acciones y asistentes) pueden declarar `default: "$today"` (o `"today"`) y nacen con el día de hoy en la zona horaria de la organización (`OrgRuntimeProvider`), no en la del navegador ni en UTC.
|
|
34
|
+
|
|
35
|
+
- `buildFieldDefaults` lee el `default` del manifest además del `defaultValue` del host. Antes los defaults escalares de los campos de document_forms se ignoraban.
|
|
36
|
+
- Un `$token` desconocido nunca se siembra en un campo fecha: el campo queda vacío y no se manda un literal que el backend rechaza.
|
|
37
|
+
- Nuevos exports: `todayInZone`, `isTodayToken`, `resolveFieldDefault`.
|
|
38
|
+
|
|
39
|
+
Retest r4 de Pitsline: FAC-00014 se guardó con fecha 26/27 sep. `invoice_date` era obligatoria y no tenía default, y el calendario abre en el mes actual con los últimos días del mes anterior en la primera fila.
|
|
40
|
+
|
|
41
|
+
- 40cf304: `document_forms.types[].create_model`: un tipo puede delegar su alta a otro modelo (p. ej. `customers.Invoice`). «Crear» no pinta un formulario: llama a `onDelegateCreate({ model, type })` del host, que abre la página de ese modelo con su alta abierta.
|
|
42
|
+
|
|
43
|
+
- `DocumentFormDialog` y `DynamicCRUDPage` aceptan `onDelegateCreate`. Sin él, los tipos delegados no se ofrecen: una vista acotada a uno de ellos sigue sin «Crear», como hasta ahora.
|
|
44
|
+
- Nuevos exports: `delegatedCreate(forms)`, `withoutDelegatedTypes(forms)` y el tipo `DelegatedCreate`.
|
|
45
|
+
|
|
46
|
+
Retest r4 de Pitsline: Documentos fiscales → Facturas se había quedado sin «Crear» porque FiscalDocument no tiene formulario de factura. La factura nace en customers y se timbra después.
|
|
47
|
+
|
|
3
48
|
## 49.0.0
|
|
4
49
|
|
|
5
50
|
### Minor Changes
|
|
@@ -89,7 +89,24 @@ export declare const FEDERATED_ACTION_MODAL_TIMEOUT_MS = 20000;
|
|
|
89
89
|
* other failure (500, network) -> the server's own message and cause. Unlike
|
|
90
90
|
* the toast it stays on screen until the next attempt. */
|
|
91
91
|
export declare function actionErrorBannerMessage(err: unknown, fields: readonly ActionFieldDef[] | undefined, t: Translate, language?: string): string;
|
|
92
|
-
|
|
92
|
+
/** Context a declared field default is resolved against. */
|
|
93
|
+
export interface FieldDefaultContext {
|
|
94
|
+
/** Org IANA timezone: `$today` is the org's day, not the browser's or UTC's. */
|
|
95
|
+
timeZone?: string;
|
|
96
|
+
/** Clock (tests). */
|
|
97
|
+
now?: Date;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The declared scalar default of a field, resolved: `defaultValue` (host
|
|
101
|
+
* camelCase) or `default` (raw manifest, the kernel serves action/document-form
|
|
102
|
+
* fields without renaming it). A date field may declare `$today` / `today` and
|
|
103
|
+
* gets the org's calendar day. An unknown `$token` never lands in a date field
|
|
104
|
+
* (undefined): a literal "$today" posted as invoice_date is a 422, an empty
|
|
105
|
+
* field is just a required field to fill.
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolveFieldDefault(field: ActionFieldDef, ctx?: FieldDefaultContext): unknown;
|
|
108
|
+
export declare function buildFieldDefaults(fields: ActionFieldDef[], record: any, model?: string, ctx?: FieldDefaultContext): Record<string, any>;
|
|
109
|
+
export declare function seedOptionFromRecord(field: ActionFieldDef, value: any, record?: Record<string, any>): import('./use-options-resolver').ResolvedOption | undefined;
|
|
93
110
|
/** Renders one action field (same widgets as the action modals). Shared with DocumentFormDialog. */
|
|
94
111
|
export declare function renderField(field: ActionFieldDef, value: any, onChange: (value: any) => void, formValues?: Record<string, any>, record?: Record<string, any>, fieldErrors?: Record<string, string>): import("react").JSX.Element;
|
|
95
112
|
//# sourceMappingURL=action-modal-dispatcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"AAmDA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AA8B/C,OAAO,KAAK,EAAE,cAAc,EAAmC,MAAM,SAAS,CAAA;AAE9E,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAKxB,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
//
|
|
16
16
|
// The host injects its axios-like client via <ApiProvider>; we no longer
|
|
17
17
|
// depend on a bundler alias to `@/lib/api`.
|
|
18
|
-
import {
|
|
18
|
+
import { emitRecordMutationSettled } from './record-mutation-events';
|
|
19
19
|
import { Suspense, useState, useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
20
20
|
import { useTranslation } from 'react-i18next';
|
|
21
21
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, Input, Textarea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, } from '@asteby/metacore-ui/primitives';
|
|
@@ -49,6 +49,8 @@ import { requestFederatedAction } from './federated-action-loader';
|
|
|
49
49
|
import { useInstalledAddons } from './installed-addons-context';
|
|
50
50
|
import { federatedModalComponent, resolveFederatedModal } from './primitives/contributions';
|
|
51
51
|
import { installedPredicate, useContributionsVersion } from './primitives/use-contributions';
|
|
52
|
+
import { isTodayToken, todayInZone } from './calendar-date';
|
|
53
|
+
import { useTimeZone } from './org-runtime-context';
|
|
52
54
|
export function isPrefillSpec(v) {
|
|
53
55
|
return (typeof v === 'object' &&
|
|
54
56
|
v !== null &&
|
|
@@ -286,9 +288,11 @@ export function scalarDefaultFromRecord(field, record, model) {
|
|
|
286
288
|
export function ActionModalDispatcher({ open, onOpenChange, action, model, record, endpoint, onSuccess: onSuccessProp, }) {
|
|
287
289
|
// Every successful action may have created/updated/deleted a record of
|
|
288
290
|
// `model` (federated component, wizard, generic form or confirmation), so
|
|
289
|
-
// announce it: mounted lists of the model reload on their own (#1020)
|
|
291
|
+
// announce it: mounted lists of the model reload on their own (#1020) —
|
|
292
|
+
// and again once the server's event subscribers had time to write what the
|
|
293
|
+
// action triggers (estado tras timbrar, REP tras un pago).
|
|
290
294
|
const onSuccess = useCallback(() => {
|
|
291
|
-
|
|
295
|
+
emitRecordMutationSettled(model, 'update');
|
|
292
296
|
onSuccessProp?.();
|
|
293
297
|
}, [model, onSuccessProp]);
|
|
294
298
|
// Reactive read: a federated remote usually registers AFTER the first
|
|
@@ -686,6 +690,7 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
686
690
|
const reasonPrompt = useReasonPrompt();
|
|
687
691
|
const supervisor = useSupervisor();
|
|
688
692
|
const branchGate = useBranchCreateGate();
|
|
693
|
+
const orgTimeZone = useTimeZone();
|
|
689
694
|
const [formData, setFormData] = useState({});
|
|
690
695
|
const [executing, setExecuting] = useState(false);
|
|
691
696
|
// Per-field validation errors (localized), shown inline under each input.
|
|
@@ -724,11 +729,11 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
724
729
|
}, [open, model, recordId, api]);
|
|
725
730
|
useEffect(() => {
|
|
726
731
|
if (open && action.fields) {
|
|
727
|
-
setFormData(buildFieldDefaults(action.fields, record, model));
|
|
732
|
+
setFormData(buildFieldDefaults(action.fields, record, model, { timeZone: orgTimeZone }));
|
|
728
733
|
setFieldErrors({});
|
|
729
734
|
setFormError(undefined);
|
|
730
735
|
}
|
|
731
|
-
}, [open, action.fields, record, model]);
|
|
736
|
+
}, [open, action.fields, record, model, orgTimeZone]);
|
|
732
737
|
// Lines the list row does not carry: load them from the declared relation
|
|
733
738
|
// (see prefillRelationRequests) and seed the line-items grid.
|
|
734
739
|
useEffect(() => {
|
|
@@ -934,10 +939,32 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
934
939
|
return (_jsxs(FieldCell, { fullWidth: fullWidth, children: [_jsx(FieldLabel, { htmlFor: field.key, required: field.required, children: tl(field.label) }), renderField(field, formData[field.key], (v) => updateField(field.key, v), formData, record, fieldErrors), fieldErrors[field.key] && (_jsx("p", { className: "text-destructive text-xs mt-1", children: fieldErrors[field.key] }))] }, field.key));
|
|
935
940
|
}), embedRelations && relations.length > 0 && (_jsx(FieldCell, { fullWidth: true, children: _jsx(DynamicRelations, { record: record, relations: relations, lineSubtable: true, embedOnly: true }) }))] })] }), _jsxs(DialogFooter, { className: "shrink-0", children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: executing, children: t('common.cancel') }), _jsxs(Button, { onClick: execute, disabled: executing, style: action.color ? { backgroundColor: action.color, color: 'white' } : undefined, children: [executing ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(DynamicIcon, { name: action.icon, className: "mr-2 h-4 w-4" }), tl(action.label)] })] }), reasonPrompt.dialog] }) }));
|
|
936
941
|
}
|
|
942
|
+
const DATE_FIELD_TYPES = new Set(['date', 'datetime', 'timestamp', 'timestamptz']);
|
|
943
|
+
/**
|
|
944
|
+
* The declared scalar default of a field, resolved: `defaultValue` (host
|
|
945
|
+
* camelCase) or `default` (raw manifest, the kernel serves action/document-form
|
|
946
|
+
* fields without renaming it). A date field may declare `$today` / `today` and
|
|
947
|
+
* gets the org's calendar day. An unknown `$token` never lands in a date field
|
|
948
|
+
* (undefined): a literal "$today" posted as invoice_date is a 422, an empty
|
|
949
|
+
* field is just a required field to fill.
|
|
950
|
+
*/
|
|
951
|
+
export function resolveFieldDefault(field, ctx = {}) {
|
|
952
|
+
const f = field;
|
|
953
|
+
const raw = f.defaultValue ?? f.default;
|
|
954
|
+
if (raw === undefined || raw === null)
|
|
955
|
+
return undefined;
|
|
956
|
+
if (DATE_FIELD_TYPES.has(String(field.type ?? '').toLowerCase()) && typeof raw === 'string') {
|
|
957
|
+
if (isTodayToken(raw))
|
|
958
|
+
return todayInZone(ctx.timeZone, ctx.now);
|
|
959
|
+
if (raw.trim().startsWith('$'))
|
|
960
|
+
return undefined;
|
|
961
|
+
}
|
|
962
|
+
return raw;
|
|
963
|
+
}
|
|
937
964
|
// buildFieldDefaults seeds formData for a set of action fields, honoring the
|
|
938
965
|
// same line-items prefill spec + boolean/empty rules GenericActionModal uses, so
|
|
939
966
|
// wizard steps and single-page forms initialize identically.
|
|
940
|
-
export function buildFieldDefaults(fields, record, model) {
|
|
967
|
+
export function buildFieldDefaults(fields, record, model, ctx = {}) {
|
|
941
968
|
const defaults = {};
|
|
942
969
|
for (const field of fields) {
|
|
943
970
|
if (isLineItemsField(field)) {
|
|
@@ -954,7 +981,7 @@ export function buildFieldDefaults(fields, record, model) {
|
|
|
954
981
|
defaults[field.key] = fromRecord;
|
|
955
982
|
continue;
|
|
956
983
|
}
|
|
957
|
-
defaults[field.key] = field
|
|
984
|
+
defaults[field.key] = resolveFieldDefault(field, ctx) ?? (field.type === 'boolean' ? false : '');
|
|
958
985
|
}
|
|
959
986
|
return defaults;
|
|
960
987
|
}
|
|
@@ -980,6 +1007,7 @@ function WizardActionModal({ open, onOpenChange, action, model, record, endpoint
|
|
|
980
1007
|
const api = useApi();
|
|
981
1008
|
const reasonPrompt = useReasonPrompt();
|
|
982
1009
|
const supervisor = useSupervisor();
|
|
1010
|
+
const orgTimeZone = useTimeZone();
|
|
983
1011
|
const steps = action.steps ?? [];
|
|
984
1012
|
const [stepIndex, setStepIndex] = useState(0);
|
|
985
1013
|
const [formData, setFormData] = useState({});
|
|
@@ -992,10 +1020,10 @@ function WizardActionModal({ open, onOpenChange, action, model, record, endpoint
|
|
|
992
1020
|
if (!open)
|
|
993
1021
|
return;
|
|
994
1022
|
const allFields = steps.flatMap((s) => s.fields ?? []);
|
|
995
|
-
setFormData(buildFieldDefaults(allFields, record, model));
|
|
1023
|
+
setFormData(buildFieldDefaults(allFields, record, model, { timeZone: orgTimeZone }));
|
|
996
1024
|
setStepIndex(0);
|
|
997
1025
|
setFormError(undefined);
|
|
998
|
-
}, [open, action.steps, record, model]);
|
|
1026
|
+
}, [open, action.steps, record, model, orgTimeZone]);
|
|
999
1027
|
const updateField = (key, value) => setFormData((prev) => ({ ...prev, [key]: value }));
|
|
1000
1028
|
const step = steps[stepIndex];
|
|
1001
1029
|
const isLast = stepIndex === steps.length - 1;
|
|
@@ -1081,8 +1109,35 @@ function WizardActionModal({ open, onOpenChange, action, model, record, endpoint
|
|
|
1081
1109
|
return (_jsxs(FieldCell, { fullWidth: fullWidth, children: [_jsx(FieldLabel, { htmlFor: field.key, required: field.required, children: tl(field.label) }), renderField(field, formData[field.key], (v) => updateField(field.key, v), formData, record)] }, field.key));
|
|
1082
1110
|
}) })] }), _jsxs(DialogFooter, { className: "shrink-0", children: [stepIndex > 0 ? (_jsx(Button, { variant: "outline", onClick: goBack, disabled: executing, children: t('common.back', { defaultValue: 'Atrás' }) })) : (_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: executing, children: t('common.cancel') })), isLast ? (_jsxs(Button, { onClick: submit, disabled: executing, style: action.color ? { backgroundColor: action.color, color: 'white' } : undefined, children: [executing ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(DynamicIcon, { name: action.icon, className: "mr-2 h-4 w-4" }), tl(action.label)] })) : (_jsx(Button, { onClick: goNext, disabled: executing, style: action.color ? { backgroundColor: action.color, color: 'white' } : undefined, children: t('common.next', { defaultValue: 'Siguiente' }) }))] }), reasonPrompt.dialog] }) }));
|
|
1083
1111
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1112
|
+
/** Columnas con las que se nombra una fila cuando el selector no declara `options.label`. */
|
|
1113
|
+
const RECORD_LABEL_COLUMNS = ['number', 'folio', 'name', 'title', 'code', 'label'];
|
|
1114
|
+
/**
|
|
1115
|
+
* La etiqueta de la PROPIA fila cuando el campo se sembró con su id
|
|
1116
|
+
* («Factura a abonar» al registrar un pago desde la factura): la columna que el
|
|
1117
|
+
* selector usa de etiqueta (`options.label`) o la primera de nombre que tenga.
|
|
1118
|
+
* Sin esto el selector mostraba el UUID crudo (retest Pitsline r5).
|
|
1119
|
+
*/
|
|
1120
|
+
function selfLabel(field, record) {
|
|
1121
|
+
const opts = field.options;
|
|
1122
|
+
const declared = opts && !Array.isArray(opts) && typeof opts === 'object' ? opts.label : undefined;
|
|
1123
|
+
const cols = typeof declared === 'string' && declared ? [declared, ...RECORD_LABEL_COLUMNS] : RECORD_LABEL_COLUMNS;
|
|
1124
|
+
for (const c of cols) {
|
|
1125
|
+
const v = unwrapRecordScalar(record[c]);
|
|
1126
|
+
if ((typeof v === 'string' && v.trim()) || typeof v === 'number')
|
|
1127
|
+
return String(v);
|
|
1128
|
+
}
|
|
1129
|
+
return '';
|
|
1130
|
+
}
|
|
1131
|
+
export function seedOptionFromRecord(field, value, record) {
|
|
1132
|
+
if (!record)
|
|
1133
|
+
return undefined;
|
|
1134
|
+
const selfId = unwrapRecordScalar(record.id);
|
|
1135
|
+
if (value != null && value !== '' && selfId != null && String(selfId) === String(value)) {
|
|
1136
|
+
const label = selfLabel(field, record);
|
|
1137
|
+
if (label)
|
|
1138
|
+
return { id: String(value), value: String(value), label, name: label };
|
|
1139
|
+
}
|
|
1140
|
+
if (!field.key.endsWith('_id'))
|
|
1086
1141
|
return undefined;
|
|
1087
1142
|
const siblingKey = field.key.replace(/_id$/, '');
|
|
1088
1143
|
const sib = record[siblingKey];
|
|
@@ -11,7 +11,9 @@ export declare function isEditorLayout(type: Pick<DocumentFormType, 'layout' | '
|
|
|
11
11
|
export declare function editorLinesConfig(type: DocumentFormType, forms: Pick<DocumentFormsManifest, 'lines_field'>): EditorLinesConfig | undefined;
|
|
12
12
|
/**
|
|
13
13
|
* Divulgación progresiva: ¿el campo va plegado en «Opciones fiscales»?
|
|
14
|
-
* - campos de extensión (`fiscal_data.*`) que aportan otros addons
|
|
14
|
+
* - campos de extensión (`fiscal_data.*`) que aportan otros addons (los que
|
|
15
|
+
* el propio tipo declara obligatorios, p. ej. método y forma de pago de la
|
|
16
|
+
* factura, se marcan esenciales en splitEditorFields);
|
|
15
17
|
* - `section: "fiscal" | "advanced"`;
|
|
16
18
|
* - catálogos opcionales que ya traen un default (método, serie, relación…):
|
|
17
19
|
* el usuario no necesita tocarlos para guardar.
|
|
@@ -83,7 +85,10 @@ export declare function creditStatus(party: Record<string, any> | null, cols: {
|
|
|
83
85
|
export declare function linesFromSource(rows: unknown, map?: Record<string, string>, opts?: {
|
|
84
86
|
kind?: DocumentFormLines['kind'];
|
|
85
87
|
discountMode?: DocumentFormLines['discount_mode'];
|
|
88
|
+
defaultTaxRate?: number;
|
|
86
89
|
}): LineItem[];
|
|
90
|
+
/** El editor ocupa toda la pantalla: documentos comerciales con renglones (factura, cotización, pedido, OC). */
|
|
91
|
+
export declare function isFullscreenEditor(type: DocumentFormType, forms: Pick<DocumentFormsManifest, 'lines_field'>): boolean;
|
|
87
92
|
/** Cuántos renglones del origen se cargaron y cuántos se omitieron por estar ya cubiertos. */
|
|
88
93
|
export declare function sourceLoadSummary(rows: unknown): {
|
|
89
94
|
loaded: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-editor-model.d.ts","sourceRoot":"","sources":["../../src/business/document-editor-model.ts"],"names":[],"mappings":"AAMA,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGrE,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACzF,OAAO,KAAK,EACR,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,qBAAqB,EACrB,gBAAgB,EACnB,MAAM,UAAU,CAAA;AAEjB,eAAO,MAAM,gBAAgB,iBAAiB,CAAA;AAK9C,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAErE,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAI7G;AAED,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,CAAC,GAAG,iBAAiB,GAAG,SAAS,CAK1I;AAeD
|
|
1
|
+
{"version":3,"file":"document-editor-model.d.ts","sourceRoot":"","sources":["../../src/business/document-editor-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,7 +37,9 @@ function declaredDefault(f) {
|
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Divulgación progresiva: ¿el campo va plegado en «Opciones fiscales»?
|
|
40
|
-
* - campos de extensión (`fiscal_data.*`) que aportan otros addons
|
|
40
|
+
* - campos de extensión (`fiscal_data.*`) que aportan otros addons (los que
|
|
41
|
+
* el propio tipo declara obligatorios, p. ej. método y forma de pago de la
|
|
42
|
+
* factura, se marcan esenciales en splitEditorFields);
|
|
41
43
|
* - `section: "fiscal" | "advanced"`;
|
|
42
44
|
* - catálogos opcionales que ya traen un default (método, serie, relación…):
|
|
43
45
|
* el usuario no necesita tocarlos para guardar.
|
|
@@ -55,7 +57,10 @@ export function isAdvancedField(f, essentialKeys = new Set()) {
|
|
|
55
57
|
}
|
|
56
58
|
/** Reparte los campos del tipo (más los de extensión) en grupos de pantalla. */
|
|
57
59
|
export function splitEditorFields(fields, opts = {}) {
|
|
58
|
-
|
|
60
|
+
// Lo obligatorio que el tipo declara siempre está a la vista, aunque sea de
|
|
61
|
+
// extensión (`fiscal_data.metodo_pago`): es lo que el usuario debe decidir.
|
|
62
|
+
// Las extensiones que llegan del metadata (no declaradas) siguen plegadas.
|
|
63
|
+
const essentialKeys = new Set([...(opts.essentialKeys ?? []), ...fields.filter((f) => f.required).map((f) => f.key)]);
|
|
59
64
|
const out = { essential: [], advanced: [], notes: [] };
|
|
60
65
|
const seen = new Set();
|
|
61
66
|
for (const f of [...fields, ...(opts.extensionFields ?? [])]) {
|
|
@@ -168,13 +173,18 @@ export function linesFromSource(rows, map = {}, opts = {}) {
|
|
|
168
173
|
if (!hasValue(rate)) {
|
|
169
174
|
const sub = toAmount(get('subtotal'));
|
|
170
175
|
const tax = toAmount(get('tax_amount'));
|
|
171
|
-
|
|
176
|
+
// Un origen sin dato de impuesto (p. ej. una OT de taller: solo
|
|
177
|
+
// cantidad y precio) toma la tasa de la org, como un renglón nuevo.
|
|
178
|
+
rate = hasValue(get('tax_amount'))
|
|
179
|
+
? sub > 0 ? Math.round((tax / sub) * 10000) / 10000 : 0
|
|
180
|
+
: opts.defaultTaxRate ?? 0;
|
|
172
181
|
}
|
|
173
182
|
const r2 = toAmount(rate);
|
|
183
|
+
const product = get('product_id');
|
|
174
184
|
const line = makeLine({
|
|
175
|
-
product_id:
|
|
185
|
+
product_id: refValue(product),
|
|
176
186
|
sku: get('sku') ?? undefined,
|
|
177
|
-
description: String(get('description') ?? get('product_name') ?? ''),
|
|
187
|
+
description: String(get('description') ?? get('product_name') ?? refLabel(product) ?? refLabel(r.product) ?? ''),
|
|
178
188
|
quantity: qty,
|
|
179
189
|
unit_price: toAmount(get('unit_price')),
|
|
180
190
|
discount: toAmount(get('discount') ?? 0),
|
|
@@ -195,10 +205,38 @@ export function linesFromSource(rows, map = {}, opts = {}) {
|
|
|
195
205
|
return line;
|
|
196
206
|
});
|
|
197
207
|
}
|
|
208
|
+
/** Id de una celda de relación (`{value,label}`, `{id,name}` o el id plano). */
|
|
209
|
+
function refValue(v) {
|
|
210
|
+
if (v == null || v === '')
|
|
211
|
+
return undefined;
|
|
212
|
+
if (typeof v === 'object') {
|
|
213
|
+
const o = v;
|
|
214
|
+
const id = o.value ?? o.id;
|
|
215
|
+
return id == null || id === '' ? undefined : String(id);
|
|
216
|
+
}
|
|
217
|
+
return String(v);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Nombre de una celda de relación ya resuelta por el host (`{label}`/`{name}`).
|
|
221
|
+
* Un renglón de origen sin descripción propia (InvoiceItem solo lleva el
|
|
222
|
+
* producto) se nombra con su producto en vez de entrar en blanco.
|
|
223
|
+
*/
|
|
224
|
+
function refLabel(v) {
|
|
225
|
+
if (!v || typeof v !== 'object')
|
|
226
|
+
return undefined;
|
|
227
|
+
const o = v;
|
|
228
|
+
const l = o.label ?? o.name;
|
|
229
|
+
return typeof l === 'string' && l.trim() ? l : undefined;
|
|
230
|
+
}
|
|
198
231
|
function isFullyConsumed(raw) {
|
|
199
232
|
const r = (raw ?? {});
|
|
200
233
|
return hasValue(r.remaining_quantity) && toAmount(r.remaining_quantity) <= 0;
|
|
201
234
|
}
|
|
235
|
+
/** El editor ocupa toda la pantalla: documentos comerciales con renglones (factura, cotización, pedido, OC). */
|
|
236
|
+
export function isFullscreenEditor(type, forms) {
|
|
237
|
+
const kind = editorLinesConfig(type, forms)?.kind ?? 'sale';
|
|
238
|
+
return !!type.lines && (kind === 'sale' || kind === 'purchase');
|
|
239
|
+
}
|
|
202
240
|
/** Cuántos renglones del origen se cargaron y cuántos se omitieron por estar ya cubiertos. */
|
|
203
241
|
export function sourceLoadSummary(rows) {
|
|
204
242
|
if (!Array.isArray(rows))
|
|
@@ -218,8 +256,11 @@ export function localIssues(lines, opts = {}) {
|
|
|
218
256
|
issues.push({ field: 'lines', severity: 'error', message: 'Agrega al menos un renglón.' });
|
|
219
257
|
}
|
|
220
258
|
items.forEach((l, i) => {
|
|
221
|
-
|
|
222
|
-
|
|
259
|
+
// Un renglón con producto se describe con él (el servidor lo nombra
|
|
260
|
+
// desde el catálogo): solo un renglón libre exige texto, como en el
|
|
261
|
+
// editor de renglones (validateLineItems).
|
|
262
|
+
if (!l.description.trim() && !l.product_id)
|
|
263
|
+
issues.push({ field: `lines.${i}`, severity: 'error', message: `Renglón ${i + 1}: elige un producto o escribe la descripción.` });
|
|
223
264
|
if (toAmount(l.unit_price) <= 0)
|
|
224
265
|
issues.push({ field: `lines.${i}`, severity: 'warning', message: `Renglón ${i + 1}: precio en cero.` });
|
|
225
266
|
if (l.max_quantity != null && toAmount(l.quantity) > toAmount(l.max_quantity)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { CreditStatus, EditorIssue } from './document-editor-model';
|
|
3
3
|
import type { Formatter } from './format';
|
|
4
|
+
import { type LineItem } from './line-items';
|
|
4
5
|
/** Bloque con título discreto y espaciado generoso (sin bordes pesados). */
|
|
5
6
|
export declare function EditorSection({ title, hint, children, slot }: {
|
|
6
7
|
title?: ReactNode;
|
|
@@ -61,4 +62,21 @@ export interface PreviewContent {
|
|
|
61
62
|
export declare function PreviewPanel({ preview }: {
|
|
62
63
|
preview: PreviewContent | null;
|
|
63
64
|
}): import("react").JSX.Element | null;
|
|
65
|
+
export interface DraftPreviewProps {
|
|
66
|
+
title: string;
|
|
67
|
+
party?: string;
|
|
68
|
+
/** Datos del encabezado ya con su etiqueta y valor legibles. */
|
|
69
|
+
header: Array<{
|
|
70
|
+
label: string;
|
|
71
|
+
value: string;
|
|
72
|
+
}>;
|
|
73
|
+
lines: LineItem[];
|
|
74
|
+
totals: TotalsRow[];
|
|
75
|
+
fmt: Formatter;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Vista previa local del documento (sin servidor ni borrador): cómo queda con
|
|
79
|
+
* lo capturado. Para tipos sin acción `preview`; la del servidor (PDF/XML) gana.
|
|
80
|
+
*/
|
|
81
|
+
export declare function DraftPreview({ title, party, header, lines, totals, fmt }: DraftPreviewProps): import("react").JSX.Element;
|
|
64
82
|
//# sourceMappingURL=document-editor-panels.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-editor-panels.d.ts","sourceRoot":"","sources":["../../src/business/document-editor-panels.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGvD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"document-editor-panels.d.ts","sourceRoot":"","sources":["../../src/business/document-editor-panels.tsx"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
import { useId, useState } from 'react';
|
|
7
7
|
import { Badge } from '@asteby/metacore-ui/primitives';
|
|
8
8
|
import { AlertTriangle, ChevronDown, XCircle } from 'lucide-react';
|
|
9
|
+
import { computeLine } from './line-items';
|
|
9
10
|
/** Bloque con título discreto y espaciado generoso (sin bordes pesados). */
|
|
10
11
|
export function EditorSection({ title, hint, children, slot }) {
|
|
11
12
|
return (_jsxs("section", { className: "space-y-3", "data-slot": slot, children: [(title || hint) && (_jsxs("header", { className: "flex items-baseline justify-between gap-3", children: [title && _jsx("h3", { className: "text-sm font-semibold", children: title }), hint && _jsx("span", { className: "text-xs text-muted-foreground", children: hint })] })), children] }));
|
|
@@ -56,3 +57,11 @@ export function PreviewPanel({ preview }) {
|
|
|
56
57
|
return _jsx("pre", { className: "max-h-96 overflow-auto rounded bg-muted p-3 text-[11px] leading-tight", "data-slot": "preview-xml", children: preview.xml });
|
|
57
58
|
return null;
|
|
58
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Vista previa local del documento (sin servidor ni borrador): cómo queda con
|
|
62
|
+
* lo capturado. Para tipos sin acción `preview`; la del servidor (PDF/XML) gana.
|
|
63
|
+
*/
|
|
64
|
+
export function DraftPreview({ title, party, header, lines, totals, fmt }) {
|
|
65
|
+
const items = lines.filter((l) => l.kind === 'item');
|
|
66
|
+
return (_jsxs("article", { className: "space-y-3 rounded border bg-background p-4 text-xs", "data-slot": "preview-draft", children: [_jsxs("header", { className: "flex flex-wrap items-baseline justify-between gap-2 border-b pb-2", children: [_jsx("span", { className: "text-sm font-semibold", children: title }), party && _jsx("span", { className: "font-medium", children: party })] }), header.length > 0 && (_jsx("dl", { className: "grid grid-cols-2 gap-x-4 gap-y-1", children: header.map((h) => (_jsxs("div", { className: "min-w-0", children: [_jsx("dt", { className: "text-muted-foreground", children: h.label }), _jsx("dd", { className: "truncate", children: h.value })] }, h.label))) })), _jsx("table", { className: "w-full tabular-nums", children: _jsx("tbody", { children: items.map((l) => (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "py-1 pr-2", children: l.description }), _jsx("td", { className: "py-1 pr-2 text-right", children: l.quantity }), _jsx("td", { className: "py-1 text-right", children: fmt.money(computeLine(l).net) })] }, l.key))) }) }), _jsx(TotalsPanel, { rows: totals })] }));
|
|
67
|
+
}
|
|
@@ -22,6 +22,12 @@ export interface DocumentEditorProps {
|
|
|
22
22
|
currency?: string;
|
|
23
23
|
/** Hoy (días de atraso del reparto; inyectable en tests). */
|
|
24
24
|
today?: Date;
|
|
25
|
+
/**
|
|
26
|
+
* Pantalla completa (DocumentFormDialog la pide para documentos con
|
|
27
|
+
* renglones): a dos columnas en pantallas anchas, con totales, revisión y
|
|
28
|
+
* vista previa a la derecha; en una sola columna en móvil.
|
|
29
|
+
*/
|
|
30
|
+
fullscreen?: boolean;
|
|
25
31
|
}
|
|
26
|
-
export declare function DocumentEditor({ model, endpoint, forms, type, record, initialSource, onSaved, onCancel, searchProducts, productModel, defaultTaxRate, currency, today, }: DocumentEditorProps): import("react").JSX.Element;
|
|
32
|
+
export declare function DocumentEditor({ model, endpoint, forms, type, record, initialSource, onSaved, onCancel, searchProducts, productModel, defaultTaxRate, currency, today, fullscreen, }: DocumentEditorProps): import("react").JSX.Element;
|
|
27
33
|
//# sourceMappingURL=document-editor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-editor.d.ts","sourceRoot":"","sources":["../../src/business/document-editor.tsx"],"names":[],"mappings":"AA4BA,OAAO,EAAqB,KAAK,sBAAsB,EAAwB,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"document-editor.d.ts","sourceRoot":"","sources":["../../src/business/document-editor.tsx"],"names":[],"mappings":"AA4BA,OAAO,EAAqB,KAAK,sBAAsB,EAAwB,MAAM,qBAAqB,CAAA;AA4C1G,OAAO,KAAK,EAAsC,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAE3G,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,qBAAqB,CAAA;IAC5B,IAAI,EAAE,gBAAgB,CAAA;IACtB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,oEAAoE;IACpE,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,cAAc,CAAC,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IACjD,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACvB;AAID,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,MAAM,EACN,aAAa,EACb,OAAO,EACP,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,KAAK,EACL,UAAkB,GACrB,EAAE,mBAAmB,+BAysBrB"}
|