@asteby/metacore-runtime-react 45.2.0 → 45.4.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 +24 -0
- package/dist/action-modal-dispatcher.d.ts +29 -0
- package/dist/action-modal-dispatcher.d.ts.map +1 -1
- package/dist/action-modal-dispatcher.js +115 -6
- package/dist/activity-diff.d.ts +2 -0
- package/dist/activity-diff.d.ts.map +1 -1
- package/dist/activity-diff.js +2 -2
- package/dist/business/catalog.d.ts.map +1 -1
- package/dist/business/catalog.js +8 -0
- package/dist/business/index.d.ts +2 -0
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business/index.js +2 -0
- package/dist/business/refund-destination.js +1 -1
- package/dist/business/refund.d.ts +1 -1
- package/dist/business/refund.d.ts.map +1 -1
- package/dist/business/refund.js +1 -0
- package/dist/business/return-wizard.d.ts +23 -0
- package/dist/business/return-wizard.d.ts.map +1 -0
- package/dist/business/return-wizard.js +59 -0
- package/dist/business/return.d.ts +90 -0
- package/dist/business/return.d.ts.map +1 -0
- package/dist/business/return.js +112 -0
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +3 -1
- package/dist/dynamic-kanban.d.ts +62 -4
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +158 -27
- package/dist/dynamic-relation.d.ts +4 -0
- package/dist/dynamic-relation.d.ts.map +1 -1
- package/dist/dynamic-relation.js +10 -1
- package/dist/dynamic-row-actions.d.ts.map +1 -1
- package/dist/dynamic-row-actions.js +14 -2
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +28 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/reason-prompt.d.ts +59 -0
- package/dist/reason-prompt.d.ts.map +1 -0
- package/dist/reason-prompt.js +111 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-addon-settings.d.ts +17 -3
- package/dist/use-addon-settings.d.ts.map +1 -1
- package/dist/use-addon-settings.js +20 -8
- package/package.json +1 -1
- package/src/__tests__/dynamic-kanban.test.tsx +141 -0
- package/src/__tests__/prefill-from-record.test.ts +69 -0
- package/src/__tests__/reason-prompt.test.tsx +121 -0
- package/src/__tests__/use-addon-settings.test.ts +16 -1
- package/src/action-modal-dispatcher.tsx +145 -3
- package/src/activity-diff.tsx +10 -1
- package/src/business/business.test.ts +46 -0
- package/src/business/catalog.ts +8 -0
- package/src/business/index.ts +2 -0
- package/src/business/refund-destination.tsx +2 -2
- package/src/business/refund.ts +2 -1
- package/src/business/return-wizard.tsx +223 -0
- package/src/business/return.ts +181 -0
- package/src/dynamic-columns.tsx +5 -1
- package/src/dynamic-kanban.tsx +197 -19
- package/src/dynamic-relation.tsx +14 -1
- package/src/dynamic-row-actions.tsx +13 -1
- package/src/dynamic-table.tsx +22 -2
- package/src/index.ts +14 -0
- package/src/reason-prompt.tsx +247 -0
- package/src/types.ts +6 -0
- package/src/use-addon-settings.ts +26 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 45.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6c9c85f: DynamicKanban: tableros por referencia y tarjeta configurable desde el manifest.
|
|
8
|
+
|
|
9
|
+
- `group_by` sobre una columna referencia (técnico, rampa, responsable...) sin `stages` ni `options` ahora arma un carril por registro referenciado (mismo endpoint de opciones que los selects del formulario), sin transiciones: arrastrar una tarjeta a otro carril la reasigna. Antes el tablero salía sin carriles.
|
|
10
|
+
- Nueva prop `groupBy` (el `?group_by=` de la entrada de navegación): un modelo puede ofrecer varios tableros (por etapa, por técnico, por rampa) aunque el host proyecte un solo `group_by` por modelo. Agrupar por la columna de etapa (`stage_field`, nuevo campo opcional del metadata) conserva la máquina de etapas; por otra columna la descarta y deshabilita las personalizaciones por-org de etapa (carriles propios, overrides, orden, automatizaciones).
|
|
11
|
+
- La tarjeta se diseña con el `display_config` de las columnas: `card_title: true` elige el título y `card: true` cada campo, en orden de columna; con eso la tarjeta muestra todos los campos marcados (sin el tope de 3). Sin marcas, el comportamiento anterior no cambia.
|
|
12
|
+
- `display_config.overdue: true` (con `overdue_unless: { campo: [valores] }` opcional) pinta en rojo una fecha vencida en la tarjeta, p. ej. la hora prometida de una orden que aún no se entrega.
|
|
13
|
+
- Nuevos helpers puros exportados: `isCardOverdue`, `refLaneSource`, `withRefLanes`, `withGroupBy`; `selectCardColumns` devuelve además `explicit`.
|
|
14
|
+
|
|
15
|
+
## 45.3.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- ce3dd3b: Action modal: `PrefillSpec.fromField` — a line-items grid can be seeded from the document picked in a sibling ref field. A `create`-placed action (no record) such as «Recibir OC» now fills its rows as soon as the user selects the purchase order: `$prefillFromRecord` names a one_to_many relation of the referenced model, `map`/`remaining`/`lock` work as in row actions, and clearing the selection empties the grid. Exports `prefillFromFieldRequests` and `prefillFromFieldRelationRequest`.
|
|
20
|
+
- 6ffdaa9: Motivo obligatorio al eliminar/cancelar (PER-4). Un modelo que declara `reason_required` en su manifest responde 422 `errors.reason` a un DELETE o acción sin motivo; `useReasonPrompt().run({ request })` prueba la petición, y si el servidor lo exige pregunta una vez (`ReasonPromptDialog`) y reintenta con `reason`. Ya cableado en la eliminación de fila, la eliminación masiva (un motivo para el lote) y los modales de acción (confirmación y formulario). `ActivityEvent.reason` se pinta en `ActivityDiff` («Motivo: …»). `useAddonSettings`/`useUpdateAddonSettings` aceptan `branchId` para leer/escribir los ajustes por sucursal (`settings[].scope: branch`, POS-1); `addonSettingsKey`/`addonSettingsUrl` lo respetan.
|
|
21
|
+
- cfafe4e: `ReturnWizard` («Devolver mercancía», benchmark DEV-2): un solo asistente para POS, Pedido, Factura, RMA y OT. Renglones con tope vendido − devuelto, condición vendible/defectuoso → stock/merma/garantía, paso «Recibir» dentro del asistente cuando no hay almacén (PIT-021), reembolso con `RefundDestination` (nuevo destino `transfer`), estados de RMA con motivo obligatorio al rechazar/cancelar (`canTransitionRma`) y NC CFDI con relación 01/03 automática (`creditNoteRelation`). Helpers puros: `computeReturnTotals`, `validateReturnChoices`, `serializeReturn`, `returnSteps`.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 0eb49fb: DynamicRelation: una sub-tabla cuya carga falla (403/500) ya no se muestra como «No hay registros relacionados»; muestra «Sin permiso…» o un aviso de error (strings opcionales `forbiddenState` / `errorState`). La columna `tags` con objetos usa su `label`/`name`/`key` en lugar de «[object Object]» (PIT-045, PIT-049).
|
|
26
|
+
|
|
3
27
|
## 45.2.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
|
@@ -16,6 +16,16 @@ export interface PrefillSpec {
|
|
|
16
16
|
* flow (which carries no prefill spec); only the prefilled action locks them.
|
|
17
17
|
*/
|
|
18
18
|
lock?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Key of a SIBLING action field (a dynamic_select with a `ref`) whose selected
|
|
21
|
+
* value is the id of the document the rows are seeded from. Without it the
|
|
22
|
+
* rows come from the record the action was opened on (row actions); with it
|
|
23
|
+
* a `create`-placed action — which has no record — seeds its grid as soon as
|
|
24
|
+
* the user picks the source document (e.g. a goods receipt picking its
|
|
25
|
+
* purchase order). `$prefillFromRecord` then names a one_to_many relation of
|
|
26
|
+
* the REFERENCED model. Clearing the selection empties the grid.
|
|
27
|
+
*/
|
|
28
|
+
fromField?: string;
|
|
19
29
|
}
|
|
20
30
|
export declare function isPrefillSpec(v: unknown): v is PrefillSpec;
|
|
21
31
|
export declare function applyPrefillLock(field: ActionFieldDef): ActionFieldDef;
|
|
@@ -33,6 +43,25 @@ export declare function prefillRelationRequests(fields: ActionFieldDef[] | undef
|
|
|
33
43
|
foreign_key?: string;
|
|
34
44
|
scope?: Record<string, unknown>;
|
|
35
45
|
}>): PrefillRelationRequest[];
|
|
46
|
+
export interface PrefillFromFieldRequest {
|
|
47
|
+
fieldKey: string;
|
|
48
|
+
spec: PrefillSpec;
|
|
49
|
+
/** Model key the sibling field references (its `ref`). */
|
|
50
|
+
refModel: string;
|
|
51
|
+
/** Selected document id; '' when nothing is selected (grid must clear). */
|
|
52
|
+
refId: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function prefillFromFieldRequests(fields: ActionFieldDef[] | undefined, formData: Record<string, any>): PrefillFromFieldRequest[];
|
|
55
|
+
export declare function prefillFromFieldRelationRequest(req: PrefillFromFieldRequest, relations: Array<{
|
|
56
|
+
name?: string;
|
|
57
|
+
kind?: string;
|
|
58
|
+
through?: string;
|
|
59
|
+
foreign_key?: string;
|
|
60
|
+
scope?: Record<string, unknown>;
|
|
61
|
+
}>): {
|
|
62
|
+
endpoint: string;
|
|
63
|
+
params: Record<string, string | number>;
|
|
64
|
+
} | null;
|
|
36
65
|
export declare function unwrapRecordScalar(value: unknown): unknown;
|
|
37
66
|
export declare function readRecordPath(record: any, path: string): unknown;
|
|
38
67
|
/** Scalar seed for one action field from the row being acted on. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"action-modal-dispatcher.d.ts","sourceRoot":"","sources":["../src/action-modal-dispatcher.tsx"],"names":[],"mappings":"AA6EA,OAAO,KAAK,EAAE,cAAc,EAAmC,MAAM,SAAS,CAAA;AAE9E,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAGxB,MAAM,sBAAsB,CAAA;AAG7B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAA;AAiBhD,MAAM,WAAW,WAAW;IACxB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,SAAS,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,WAAW,CAM1D;AAsBD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAStE;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAkB3F;AAYD,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;CAC1C;AAED,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,cAAc,EAAE,GAAG,SAAS,EACpC,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC5H,sBAAsB,EAAE,CAyB1B;AASD,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,cAAc,EAAE,GAAG,SAAS,EACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,uBAAuB,EAAE,CAe3B;AAKD,wBAAgB,+BAA+B,CAC3C,GAAG,EAAE,uBAAuB,EAC5B,SAAS,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC5H;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CActE;AASD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAY1D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASjE;AAUD,oEAAoE;AACpE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAgBnF;AAED,wBAAgB,qBAAqB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,GACZ,EAAE,gBAAgB,sCAmFlB;AAED,yEAAyE;AACzE,eAAO,MAAM,iCAAiC,QAAS,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// ActionModalDispatcher — renders the right modal for a custom action:
|
|
3
3
|
// 1) Custom component from the SDK registry → use it
|
|
4
4
|
// 2) action.modal set but no registered component → loading state while the
|
|
@@ -15,7 +15,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
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 { useState, useEffect, useMemo, useSyncExternalStore } from 'react';
|
|
18
|
+
import { useState, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
19
19
|
import { useTranslation } from 'react-i18next';
|
|
20
20
|
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';
|
|
21
21
|
import { Loader2 } from 'lucide-react';
|
|
@@ -27,6 +27,7 @@ import { validateValues, bagHasErrors } from './validator';
|
|
|
27
27
|
import { clearFieldErrorTree, formatFieldErrorsDescription, labelsForValidationFields, labelForValidationPath, lineItemErrorsFor, } from './field-validation-ui';
|
|
28
28
|
import { validationCatalog } from './validation-catalog';
|
|
29
29
|
import { useApi } from './api-context';
|
|
30
|
+
import { useReasonPrompt } from './reason-prompt';
|
|
30
31
|
import { buildRelationFilterParams } from './dynamic-relation-helpers';
|
|
31
32
|
import { DynamicIcon } from './dynamic-icon';
|
|
32
33
|
import { DynamicLineItems } from './dynamic-line-items';
|
|
@@ -130,6 +131,45 @@ export function prefillRelationRequests(fields, record, relations) {
|
|
|
130
131
|
}
|
|
131
132
|
return out;
|
|
132
133
|
}
|
|
134
|
+
export function prefillFromFieldRequests(fields, formData) {
|
|
135
|
+
const out = [];
|
|
136
|
+
if (!fields)
|
|
137
|
+
return out;
|
|
138
|
+
for (const field of fields) {
|
|
139
|
+
if (!isLineItemsField(field))
|
|
140
|
+
continue;
|
|
141
|
+
const spec = lineItemsDefault(field);
|
|
142
|
+
if (!isPrefillSpec(spec) || !spec.fromField)
|
|
143
|
+
continue;
|
|
144
|
+
const source = fields.find((f) => f.key === spec.fromField);
|
|
145
|
+
const refModel = source ? getFieldRef(source) : undefined;
|
|
146
|
+
if (!refModel)
|
|
147
|
+
continue;
|
|
148
|
+
const raw = unwrapRecordScalar(formData?.[spec.fromField]);
|
|
149
|
+
const refId = raw === undefined || raw === null ? '' : String(raw);
|
|
150
|
+
out.push({ fieldKey: field.key, spec, refModel, refId });
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
// prefillFromFieldRelationRequest resolves the child-rows request for a
|
|
155
|
+
// selected document, from the referenced model's declared relations. Null when
|
|
156
|
+
// the relation is missing or not a plain one_to_many.
|
|
157
|
+
export function prefillFromFieldRelationRequest(req, relations) {
|
|
158
|
+
if (!req.refId)
|
|
159
|
+
return null;
|
|
160
|
+
const rel = (relations || []).find((r) => r?.name === req.spec.$prefillFromRecord && (r.kind ?? 'one_to_many') === 'one_to_many');
|
|
161
|
+
if (!rel?.through || !rel.foreign_key)
|
|
162
|
+
return null;
|
|
163
|
+
const scope = {};
|
|
164
|
+
for (const [k, v] of Object.entries(rel.scope ?? {})) {
|
|
165
|
+
if (v !== undefined && v !== null)
|
|
166
|
+
scope[k] = String(v);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
endpoint: `/data/${rel.through}`,
|
|
170
|
+
params: { ...buildRelationFilterParams(rel.foreign_key, req.refId, scope), per_page: 200 },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
133
173
|
// ---- scalar prefill from the acted-on record --------------------------------
|
|
134
174
|
//
|
|
135
175
|
// Row actions (stamp / refactura / cancel-with-reason) should open with the
|
|
@@ -481,6 +521,9 @@ function ConfirmActionDialog({ open, onOpenChange, action, model, record, endpoi
|
|
|
481
521
|
const { t, i18n } = useTranslation();
|
|
482
522
|
const api = useApi();
|
|
483
523
|
const [executing, setExecuting] = useState(false);
|
|
524
|
+
// Actions listed in the model's `reason_required.actions` (cancel, void…)
|
|
525
|
+
// answer 422 errors.reason without a motive; the prompt asks and retries.
|
|
526
|
+
const reasonPrompt = useReasonPrompt();
|
|
484
527
|
// `action.label` is an addon-contributed i18n key; its locale bundle loads
|
|
485
528
|
// asynchronously, so translate at render (defaultValue keeps an already
|
|
486
529
|
// localized label unchanged).
|
|
@@ -489,7 +532,14 @@ function ConfirmActionDialog({ open, onOpenChange, action, model, record, endpoi
|
|
|
489
532
|
setExecuting(true);
|
|
490
533
|
try {
|
|
491
534
|
const url = buildActionUrl(endpoint, model, record.id, action.key);
|
|
492
|
-
const res = await
|
|
535
|
+
const res = await reasonPrompt.run({
|
|
536
|
+
title: label,
|
|
537
|
+
description: t('actions.reason_desc', { defaultValue: 'Esta acción pide el motivo. Queda en la bitácora del documento.' }),
|
|
538
|
+
request: (reason) => api.post(url, reason ? { reason } : {}),
|
|
539
|
+
});
|
|
540
|
+
// Operator cancelled the reason prompt: nothing ran.
|
|
541
|
+
if (!res)
|
|
542
|
+
return;
|
|
493
543
|
if (res.data.success) {
|
|
494
544
|
toastServerSuccess(res.data, { t });
|
|
495
545
|
onOpenChange(false);
|
|
@@ -506,7 +556,7 @@ function ConfirmActionDialog({ open, onOpenChange, action, model, record, endpoi
|
|
|
506
556
|
setExecuting(false);
|
|
507
557
|
}
|
|
508
558
|
};
|
|
509
|
-
return (_jsx(AlertDialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsxs(AlertDialogTitle, { className: "flex items-center gap-2", children: [_jsx(DynamicIcon, { name: action.icon, className: "h-5 w-5" }), label] }), _jsx(AlertDialogDescription, { children: action.confirmMessage || `${label}?` })] }), record ? _jsx(RecordPreview, { model: model, record: record }) : null, _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: executing, children: t('common.cancel') }), _jsxs(AlertDialogAction, { onClick: (e) => { e.preventDefault(); execute(); }, disabled: executing, style: action.color ? { backgroundColor: action.color } : 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" }), label] })] })] }) }));
|
|
559
|
+
return (_jsxs(_Fragment, { children: [reasonPrompt.dialog, _jsx(AlertDialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsxs(AlertDialogTitle, { className: "flex items-center gap-2", children: [_jsx(DynamicIcon, { name: action.icon, className: "h-5 w-5" }), label] }), _jsx(AlertDialogDescription, { children: action.confirmMessage || `${label}?` })] }), record ? _jsx(RecordPreview, { model: model, record: record }) : null, _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: executing, children: t('common.cancel') }), _jsxs(AlertDialogAction, { onClick: (e) => { e.preventDefault(); execute(); }, disabled: executing, style: action.color ? { backgroundColor: action.color } : 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" }), label] })] })] }) })] }));
|
|
510
560
|
}
|
|
511
561
|
function GenericActionModal({ open, onOpenChange, action, model, record, endpoint, onSuccess }) {
|
|
512
562
|
const { t, i18n } = useTranslation();
|
|
@@ -515,6 +565,7 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
515
565
|
// defaultValue keeps an already-localized string unchanged.
|
|
516
566
|
const tl = (s) => t(s, { defaultValue: s });
|
|
517
567
|
const api = useApi();
|
|
568
|
+
const reasonPrompt = useReasonPrompt();
|
|
518
569
|
const branchGate = useBranchCreateGate();
|
|
519
570
|
const [formData, setFormData] = useState({});
|
|
520
571
|
const [executing, setExecuting] = useState(false);
|
|
@@ -580,6 +631,58 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
580
631
|
cancelled = true;
|
|
581
632
|
};
|
|
582
633
|
}, [open, action.fields, record, relations, api]);
|
|
634
|
+
// Lines seeded from a document picked in a sibling field (PrefillSpec.fromField).
|
|
635
|
+
// Re-runs only when the picked id changes; each pick loads the referenced
|
|
636
|
+
// model's relations (cached per model for the life of the modal) and then
|
|
637
|
+
// the child rows.
|
|
638
|
+
const fromFieldReqs = useMemo(() => prefillFromFieldRequests(action.fields, formData), [action.fields, formData]);
|
|
639
|
+
const fromFieldSig = fromFieldReqs.map((r) => `${r.fieldKey}=${r.refModel}:${r.refId}`).join('|');
|
|
640
|
+
const refRelationsCache = useRef(new Map());
|
|
641
|
+
useEffect(() => {
|
|
642
|
+
if (!open || fromFieldReqs.length === 0)
|
|
643
|
+
return;
|
|
644
|
+
let cancelled = false;
|
|
645
|
+
for (const req of fromFieldReqs) {
|
|
646
|
+
if (!req.refId) {
|
|
647
|
+
setFormData((prev) => Array.isArray(prev[req.fieldKey]) && prev[req.fieldKey].length === 0
|
|
648
|
+
? prev
|
|
649
|
+
: { ...prev, [req.fieldKey]: [] });
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
let relsPromise = refRelationsCache.current.get(req.refModel);
|
|
653
|
+
if (!relsPromise) {
|
|
654
|
+
relsPromise = api
|
|
655
|
+
.get(`/metadata/table/${req.refModel}`)
|
|
656
|
+
.then((res) => {
|
|
657
|
+
const rels = res?.data?.relations ?? res?.data?.data?.relations ?? [];
|
|
658
|
+
return Array.isArray(rels) ? rels : [];
|
|
659
|
+
})
|
|
660
|
+
.catch(() => []);
|
|
661
|
+
refRelationsCache.current.set(req.refModel, relsPromise);
|
|
662
|
+
}
|
|
663
|
+
relsPromise
|
|
664
|
+
.then((rels) => {
|
|
665
|
+
const call = prefillFromFieldRelationRequest(req, rels);
|
|
666
|
+
if (!call)
|
|
667
|
+
return;
|
|
668
|
+
return api.get(call.endpoint, { params: call.params }).then((res) => {
|
|
669
|
+
if (cancelled)
|
|
670
|
+
return;
|
|
671
|
+
const rows = res?.data?.data ?? res?.data ?? [];
|
|
672
|
+
if (!Array.isArray(rows))
|
|
673
|
+
return;
|
|
674
|
+
const built = buildPrefillRows(req.spec, { [req.spec.$prefillFromRecord]: rows });
|
|
675
|
+
setFormData((prev) => ({ ...prev, [req.fieldKey]: built }));
|
|
676
|
+
});
|
|
677
|
+
})
|
|
678
|
+
.catch(() => { });
|
|
679
|
+
}
|
|
680
|
+
return () => {
|
|
681
|
+
cancelled = true;
|
|
682
|
+
};
|
|
683
|
+
// fromFieldSig captures every dependency of fromFieldReqs that matters.
|
|
684
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
685
|
+
}, [open, fromFieldSig, api]);
|
|
583
686
|
const updateField = (key, value) => {
|
|
584
687
|
setFormData((prev) => ({ ...prev, [key]: value }));
|
|
585
688
|
setFieldErrors((prev) => clearFieldErrorTree(prev, key));
|
|
@@ -643,7 +746,13 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
643
746
|
payload = { ...payload, branch_id: branchId };
|
|
644
747
|
}
|
|
645
748
|
const url = buildActionUrl(endpoint, model, record?.id, action.key);
|
|
646
|
-
const res = await
|
|
749
|
+
const res = await reasonPrompt.run({
|
|
750
|
+
title: tl(action.label),
|
|
751
|
+
description: t('actions.reason_desc', { defaultValue: 'Esta acción pide el motivo. Queda en la bitácora del documento.' }),
|
|
752
|
+
request: (reason) => api.post(url, reason ? { ...payload, reason } : payload),
|
|
753
|
+
});
|
|
754
|
+
if (!res)
|
|
755
|
+
return;
|
|
647
756
|
if (res.data.success) {
|
|
648
757
|
toastServerSuccess(res.data, { t });
|
|
649
758
|
onOpenChange(false);
|
|
@@ -680,7 +789,7 @@ function GenericActionModal({ open, onOpenChange, action, model, record, endpoin
|
|
|
680
789
|
resolveWidget(field) === 'textarea' ||
|
|
681
790
|
resolveWidget(field) === 'richtext';
|
|
682
791
|
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));
|
|
683
|
-
}), 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)] })] })] }) }));
|
|
792
|
+
}), 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] }) }));
|
|
684
793
|
}
|
|
685
794
|
// buildFieldDefaults seeds formData for a set of action fields, honoring the
|
|
686
795
|
// same line-items prefill spec + boolean/empty rules GenericActionModal uses, so
|
package/dist/activity-diff.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface ActivityEvent {
|
|
|
43
43
|
to: unknown;
|
|
44
44
|
}> | null;
|
|
45
45
|
summary?: string | null;
|
|
46
|
+
/** Motive the operator stated for a delete / reason_required action (PER-4). */
|
|
47
|
+
reason?: string | null;
|
|
46
48
|
occurred_at: string;
|
|
47
49
|
}
|
|
48
50
|
export interface ActivityDiffProps {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-diff.d.ts","sourceRoot":"","sources":["../src/activity-diff.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAO/C;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,iBAAiB;IAC9B,oCAAoC;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AA8GD;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"activity-diff.d.ts","sourceRoot":"","sources":["../src/activity-diff.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAO/C;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,iBAAiB;IAC9B,oCAAoC;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AA8GD;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAmLpD,CAAA"}
|
package/dist/activity-diff.js
CHANGED
|
@@ -142,10 +142,10 @@ export const ActivityDiff = ({ event, columns, timeZone, currency, locale = 'es'
|
|
|
142
142
|
const isCreated = variant === 'created';
|
|
143
143
|
const isDeleted = variant === 'deleted';
|
|
144
144
|
const variantBadge = VARIANT_BADGE[variant] ?? VARIANT_BADGE.other;
|
|
145
|
-
if (allKeys.length === 0 && !event.summary) {
|
|
145
|
+
if (allKeys.length === 0 && !event.summary && !event.reason) {
|
|
146
146
|
return (_jsx("div", { className: cn('text-sm text-muted-foreground italic py-1', className), children: "Sin campos registrados." }));
|
|
147
147
|
}
|
|
148
|
-
return (_jsxs("div", { className: cn('space-y-2', className), children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx(Badge, { variant: "outline", className: cn('text-xs font-medium px-2 py-0.5', variantBadge.className), children: variantBadge.label || event.action }), changed.size > 0 && variant === 'updated' && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [changed.size, " campo", changed.size !== 1 ? 's' : '', " modificado", changed.size !== 1 ? 's' : ''] })), allKeys.length > 0 && variant === 'updated' && (_jsx("button", { type: "button", onClick: () => setShowOnlyChanged((v) => !v), className: "ml-auto text-xs text-primary hover:underline", children: showOnlyChanged ? `Ver todos (${allKeys.length})` : 'Solo cambios' }))] }), event.summary && (_jsx("p", { className: "text-sm text-muted-foreground italic", children: event.summary })), displayedKeys.length > 0 && (_jsxs("div", { className: "rounded-lg border border-border/60 overflow-hidden text-sm", children: [isCreated || isDeleted ? (_jsxs("div", { className: "grid grid-cols-[1fr_2fr] border-b border-border/40 bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground gap-x-2", children: [_jsx("span", { children: "Campo" }), _jsx("span", { children: isDeleted ? 'Valor anterior' : 'Valor' })] })) : (_jsxs("div", { className: "grid grid-cols-[1fr_1fr_1fr] border-b border-border/40 bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground gap-x-2", children: [_jsx("span", { children: "Campo" }), _jsx("span", { children: "Antes" }), _jsx("span", { children: "Despu\u00E9s" })] })), displayedKeys.map((key, idx) => {
|
|
148
|
+
return (_jsxs("div", { className: cn('space-y-2', className), children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx(Badge, { variant: "outline", className: cn('text-xs font-medium px-2 py-0.5', variantBadge.className), children: variantBadge.label || event.action }), changed.size > 0 && variant === 'updated' && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [changed.size, " campo", changed.size !== 1 ? 's' : '', " modificado", changed.size !== 1 ? 's' : ''] })), allKeys.length > 0 && variant === 'updated' && (_jsx("button", { type: "button", onClick: () => setShowOnlyChanged((v) => !v), className: "ml-auto text-xs text-primary hover:underline", children: showOnlyChanged ? `Ver todos (${allKeys.length})` : 'Solo cambios' }))] }), event.reason && (_jsxs("p", { className: "text-sm", "data-testid": "activity-reason", children: [_jsx("span", { className: "font-medium", children: "Motivo:" }), " ", event.reason] })), event.summary && (_jsx("p", { className: "text-sm text-muted-foreground italic", children: event.summary })), displayedKeys.length > 0 && (_jsxs("div", { className: "rounded-lg border border-border/60 overflow-hidden text-sm", children: [isCreated || isDeleted ? (_jsxs("div", { className: "grid grid-cols-[1fr_2fr] border-b border-border/40 bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground gap-x-2", children: [_jsx("span", { children: "Campo" }), _jsx("span", { children: isDeleted ? 'Valor anterior' : 'Valor' })] })) : (_jsxs("div", { className: "grid grid-cols-[1fr_1fr_1fr] border-b border-border/40 bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground gap-x-2", children: [_jsx("span", { children: "Campo" }), _jsx("span", { children: "Antes" }), _jsx("span", { children: "Despu\u00E9s" })] })), displayedKeys.map((key, idx) => {
|
|
149
149
|
const col = resolveColumn(key, columns);
|
|
150
150
|
const label = resolveLabel(key, columns);
|
|
151
151
|
const isChanged = changed.has(key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/business/catalog.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IAClC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,kDAAkD;IAClD,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,uDAAuD;IACvD,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,eAAO,MAAM,mBAAmB,EAAE,SAAS,qBAAqB,
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/business/catalog.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IAClC,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,kDAAkD;IAClD,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,uDAAuD;IACvD,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,eAAO,MAAM,mBAAmB,EAAE,SAAS,qBAAqB,EAiDtD,CAAA"}
|
package/dist/business/catalog.js
CHANGED
|
@@ -39,4 +39,12 @@ export const BUSINESS_COMPONENTS = [
|
|
|
39
39
|
usedBy: ['ReturnWizard', 'Cancelación de apartado', 'Anticipos'],
|
|
40
40
|
helpers: ['validateRefund', 'singleDestination', 'DEFAULT_REFUND_DESTINATIONS'],
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
name: 'ReturnWizard',
|
|
44
|
+
value: 'lines: ReturnableLine[]',
|
|
45
|
+
events: ['onSubmit', 'onCancel'],
|
|
46
|
+
permissions: [],
|
|
47
|
+
usedBy: ['POS', 'Pedido', 'Factura', 'RMA', 'OT'],
|
|
48
|
+
helpers: ['computeReturnTotals', 'validateReturnChoices', 'creditNoteRelation', 'returnSteps', 'serializeReturn'],
|
|
49
|
+
},
|
|
42
50
|
];
|
package/dist/business/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/business/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/business/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA"}
|
package/dist/business/index.js
CHANGED
|
@@ -27,5 +27,5 @@ export function RefundDestination({ total, value, onChange, options = DEFAULT_RE
|
|
|
27
27
|
const off = o.available === false;
|
|
28
28
|
const on = chosen(o.kind);
|
|
29
29
|
return (_jsx(Button, { type: "button", variant: on ? 'default' : 'outline', role: allowSplit ? 'checkbox' : 'radio', "aria-checked": on, disabled: disabled || off, title: off ? o.disabledReason : undefined, className: "h-auto justify-start whitespace-normal py-2 text-left", onClick: () => toggle(o), children: _jsxs("span", { children: [_jsx("span", { className: "block", children: o.label }), off && o.disabledReason && _jsx("span", { className: "block text-xs opacity-70", children: o.disabledReason }), !off && o.maxAmount != null && _jsxs("span", { className: "block text-xs opacity-70", children: [t('refund.max', { defaultValue: 'Máx.' }), " ", fmt.money(o.maxAmount)] })] }) }, o.kind));
|
|
30
|
-
}) }), value.map((a, i) => (_jsxs("div", { className: "grid grid-cols-[1fr_8rem_10rem] items-start gap-2", children: [_jsx("span", { className: "pt-2 text-sm", children: options.find((o) => o.kind === a.kind)?.label ?? a.kind }), _jsxs("div", { children: [_jsx(Input, { inputMode: "decimal", className: "text-right", value: String(a.amount), disabled: disabled || !allowSplit, "aria-invalid": !!errors[`${i}.amount`], onChange: (e) => emit(value.map((x, j) => (j === i ? { ...x, amount: Number(e.target.value.replace(',', '.')) || 0 } : x))) }), errors[`${i}.amount`] && _jsx("p", { className: "mt-0.5 text-xs text-destructive", children: errors[`${i}.amount`] })] }), (a.kind === 'card' || a.kind === 'credit_note') && (_jsx(Input, { placeholder: a.kind === 'card' ? t('refund.cardRef', { defaultValue: 'Referencia' }) : t('refund.ncRef', { defaultValue: 'Folio / UUID relacionado' }), value: a.reference ?? '', disabled: disabled, onChange: (e) => emit(value.map((x, j) => (j === i ? { ...x, reference: e.target.value } : x))) })), errors[`${i}.kind`] && _jsx("p", { className: "col-span-3 text-xs text-destructive", children: errors[`${i}.kind`] })] }, a.kind))), validation.form && value.length > 0 && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: validation.form })] }));
|
|
30
|
+
}) }), value.map((a, i) => (_jsxs("div", { className: "grid grid-cols-[1fr_8rem_10rem] items-start gap-2", children: [_jsx("span", { className: "pt-2 text-sm", children: options.find((o) => o.kind === a.kind)?.label ?? a.kind }), _jsxs("div", { children: [_jsx(Input, { inputMode: "decimal", className: "text-right", value: String(a.amount), disabled: disabled || !allowSplit, "aria-invalid": !!errors[`${i}.amount`], onChange: (e) => emit(value.map((x, j) => (j === i ? { ...x, amount: Number(e.target.value.replace(',', '.')) || 0 } : x))) }), errors[`${i}.amount`] && _jsx("p", { className: "mt-0.5 text-xs text-destructive", children: errors[`${i}.amount`] })] }), (a.kind === 'card' || a.kind === 'transfer' || a.kind === 'credit_note') && (_jsx(Input, { placeholder: a.kind === 'card' || a.kind === 'transfer' ? t('refund.cardRef', { defaultValue: 'Referencia' }) : t('refund.ncRef', { defaultValue: 'Folio / UUID relacionado' }), value: a.reference ?? '', disabled: disabled, onChange: (e) => emit(value.map((x, j) => (j === i ? { ...x, reference: e.target.value } : x))) })), errors[`${i}.kind`] && _jsx("p", { className: "col-span-3 text-xs text-destructive", children: errors[`${i}.kind`] })] }, a.kind))), validation.form && value.length > 0 && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: validation.form })] }));
|
|
31
31
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RefundDestinationKind = 'cash' | 'card' | 'wallet' | 'store_credit' | 'credit_note';
|
|
1
|
+
export type RefundDestinationKind = 'cash' | 'card' | 'transfer' | 'wallet' | 'store_credit' | 'credit_note';
|
|
2
2
|
export interface RefundDestinationOption {
|
|
3
3
|
kind: RefundDestinationKind;
|
|
4
4
|
label: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refund.d.ts","sourceRoot":"","sources":["../../src/business/refund.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"refund.d.ts","sourceRoot":"","sources":["../../src/business/refund.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,CAAA;AAE5G,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,qBAAqB,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,qBAAqB,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,0EAA0E;AAC1E,eAAO,MAAM,2BAA2B,EAAE,uBAAuB,EAOhE,CAAA;AAED,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;CACjB;AAED,wBAAgB,cAAc,CAC1B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,gBAAgB,EAAE,EAC/B,OAAO,GAAE,uBAAuB,EAAgC,GACjE,gBAAgB,CAqBlB;AAED,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAEhG"}
|
package/dist/business/refund.js
CHANGED
|
@@ -5,6 +5,7 @@ import { roundMoney, toAmount } from './format';
|
|
|
5
5
|
export const DEFAULT_REFUND_DESTINATIONS = [
|
|
6
6
|
{ kind: 'cash', label: 'Efectivo' },
|
|
7
7
|
{ kind: 'card', label: 'Tarjeta' },
|
|
8
|
+
{ kind: 'transfer', label: 'Transferencia' },
|
|
8
9
|
{ kind: 'wallet', label: 'Monedero' },
|
|
9
10
|
{ kind: 'store_credit', label: 'Saldo a favor' },
|
|
10
11
|
{ kind: 'credit_note', label: 'Nota de crédito (CFDI)' },
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type RefundDestinationOption } from './refund';
|
|
2
|
+
import { type ReturnSubmitPayload, type ReturnableLine } from './return';
|
|
3
|
+
export interface ReturnWizardProps {
|
|
4
|
+
lines: ReturnableLine[];
|
|
5
|
+
/** ¿Hay un addon de almacén que confirma la recepción? Sin él, «Recibir» es un paso. */
|
|
6
|
+
warehouseConnected?: boolean;
|
|
7
|
+
/** La venta está facturada: habilita la NC CFDI con relación 01/03 automática. */
|
|
8
|
+
invoiced?: boolean;
|
|
9
|
+
/** Destinos de reembolso; `credit_note` se deshabilita solo si no está facturada. */
|
|
10
|
+
refundOptions?: RefundDestinationOption[];
|
|
11
|
+
reasons?: {
|
|
12
|
+
value: string;
|
|
13
|
+
label: string;
|
|
14
|
+
}[];
|
|
15
|
+
currency?: string;
|
|
16
|
+
submitting?: boolean;
|
|
17
|
+
/** Errores del servidor (`"<key>.<campo>"`). */
|
|
18
|
+
serverErrors?: Record<string, string>;
|
|
19
|
+
onSubmit: (payload: ReturnSubmitPayload) => void | Promise<void>;
|
|
20
|
+
onCancel?: () => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function ReturnWizard({ lines, warehouseConnected, invoiced, refundOptions, reasons, currency, submitting, serverErrors, onSubmit, onCancel, }: ReturnWizardProps): import("react").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=return-wizard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-wizard.d.ts","sourceRoot":"","sources":["../../src/business/return-wizard.tsx"],"names":[],"mappings":"AAQA,OAAO,EAGH,KAAK,uBAAuB,EAC/B,MAAM,UAAU,CAAA;AACjB,OAAO,EAcH,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACtB,MAAM,UAAU,CAAA;AAEjB,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,wFAAwF;IACxF,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,qFAAqF;IACrF,aAAa,CAAC,EAAE,uBAAuB,EAAE,CAAA;IACzC,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,QAAQ,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAYD,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,kBAA0B,EAC1B,QAAgB,EAChB,aAA2C,EAC3C,OAAyB,EACzB,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,QAAQ,GACX,EAAE,iBAAiB,+BAyJnB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// ReturnWizard («Devolver mercancía») — un solo componente para POS, Pedido,
|
|
3
|
+
// Factura, RMA y OT (benchmark §7, DEV-2). No conoce endpoints: recibe los
|
|
4
|
+
// renglones devolvibles y entrega un payload por `onSubmit`.
|
|
5
|
+
import { useMemo, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Button, Input } from '@asteby/metacore-ui';
|
|
8
|
+
import { useFormatter } from './format';
|
|
9
|
+
import { RefundDestination } from './refund-destination';
|
|
10
|
+
import { DEFAULT_REFUND_DESTINATIONS, } from './refund';
|
|
11
|
+
import { RETURN_CONDITION_DESTINATIONS, computeReturnTotals, creditNoteRelation, defaultStockDestination, returnSteps, returnableQty, serializeReturn, validateReturnChoices, validateReturnRefund, } from './return';
|
|
12
|
+
const DEFAULT_REASONS = [
|
|
13
|
+
{ value: 'not_wanted', label: 'No lo quiso' },
|
|
14
|
+
{ value: 'defective', label: 'Defectuoso' },
|
|
15
|
+
{ value: 'wrong_item', label: 'Producto equivocado' },
|
|
16
|
+
{ value: 'warranty', label: 'Garantía' },
|
|
17
|
+
];
|
|
18
|
+
const STOCK_LABEL = { stock: 'Regresa a stock', scrap: 'Merma', warranty: 'Garantía con proveedor' };
|
|
19
|
+
const STEP_LABEL = { lines: 'Renglones', condition: 'Condición', receive: 'Recibir', refund: 'Reembolso' };
|
|
20
|
+
export function ReturnWizard({ lines, warehouseConnected = false, invoiced = false, refundOptions = DEFAULT_REFUND_DESTINATIONS, reasons = DEFAULT_REASONS, currency, submitting, serverErrors, onSubmit, onCancel, }) {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const fmt = useFormatter({ currency });
|
|
23
|
+
const steps = useMemo(() => returnSteps({ warehouseConnected }), [warehouseConnected]);
|
|
24
|
+
const [stepIdx, setStepIdx] = useState(0);
|
|
25
|
+
const [choices, setChoices] = useState({});
|
|
26
|
+
const [reason, setReason] = useState('');
|
|
27
|
+
const [notes, setNotes] = useState('');
|
|
28
|
+
const [received, setReceived] = useState(false);
|
|
29
|
+
const [refund, setRefund] = useState([]);
|
|
30
|
+
const step = steps[stepIdx];
|
|
31
|
+
const totals = useMemo(() => computeReturnTotals(lines, choices), [lines, choices]);
|
|
32
|
+
const validation = useMemo(() => validateReturnChoices(lines, choices, reason), [lines, choices, reason]);
|
|
33
|
+
const relation = useMemo(() => creditNoteRelation(lines, choices), [lines, choices]);
|
|
34
|
+
const options = useMemo(() => refundOptions.map((o) => o.kind === 'credit_note' && !invoiced
|
|
35
|
+
? { ...o, available: false, disabledReason: 'La venta no está facturada' }
|
|
36
|
+
: o.kind === 'credit_note'
|
|
37
|
+
? { ...o, label: `${o.label} · relación ${relation}` }
|
|
38
|
+
: o), [refundOptions, invoiced, relation]);
|
|
39
|
+
const refundValidation = useMemo(() => validateReturnRefund(totals.total, refund, options), [totals.total, refund, options]);
|
|
40
|
+
const errors = { ...validation.errors, ...serverErrors };
|
|
41
|
+
const setChoice = (key, patch) => setChoices((prev) => {
|
|
42
|
+
const cur = prev[key] ?? { quantity: 0, condition: 'sellable', destination: 'stock' };
|
|
43
|
+
const next = { ...cur, ...patch };
|
|
44
|
+
if (patch.condition && !patch.destination)
|
|
45
|
+
next.destination = defaultStockDestination(patch.condition);
|
|
46
|
+
return { ...prev, [key]: next };
|
|
47
|
+
});
|
|
48
|
+
const canNext = step === 'lines' ? validation.valid
|
|
49
|
+
: step === 'condition' ? validation.valid
|
|
50
|
+
: step === 'receive' ? received
|
|
51
|
+
: refundValidation.valid;
|
|
52
|
+
const submit = () => onSubmit(serializeReturn({ lines, choices, reason, notes, received: warehouseConnected ? false : received, refund }));
|
|
53
|
+
return (_jsxs("div", { "data-slot": "return-wizard", className: "space-y-4", children: [_jsxs("ol", { className: "flex flex-wrap gap-2 text-xs", children: [steps.map((s, i) => (_jsxs("li", { "aria-current": i === stepIdx ? 'step' : undefined, className: i === stepIdx ? 'font-semibold text-foreground' : 'text-muted-foreground', children: [i + 1, ". ", t(`returns.step.${s}`, { defaultValue: STEP_LABEL[s] })] }, s))), warehouseConnected && (_jsx("li", { className: "text-muted-foreground", children: t('returns.step.warehouse', { defaultValue: 'Almacén confirma la recepción' }) }))] }), (step === 'lines' || step === 'condition') && (_jsxs("div", { className: "space-y-2", children: [lines.map((l) => {
|
|
54
|
+
const c = choices[l.key];
|
|
55
|
+
const max = returnableQty(l);
|
|
56
|
+
return (_jsxs("div", { className: "grid gap-2 rounded-md border p-2 sm:grid-cols-[1fr_6rem_auto]", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: l.description }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [t('returns.sold', { defaultValue: 'Vendido' }), ": ", l.sold, " \u00B7 ", t('returns.returnable', { defaultValue: 'Por devolver' }), ": ", max, " \u00B7 ", fmt.money(l.unit_price)] })] }), step === 'lines' ? (_jsxs("div", { children: [_jsx(Input, { inputMode: "decimal", className: "text-right", "aria-label": l.description, disabled: max === 0, value: c ? String(c.quantity) : '', placeholder: "0", onChange: (e) => setChoice(l.key, { quantity: Number(e.target.value.replace(',', '.')) || 0 }) }), errors[`${l.key}.quantity`] && _jsx("p", { className: "mt-0.5 text-xs text-destructive", children: errors[`${l.key}.quantity`] })] })) : ((c?.quantity ?? 0) > 0 && (_jsxs("div", { className: "sm:col-span-2 flex flex-wrap items-center gap-2", children: [['sellable', 'defective'].map((cond) => (_jsx(Button, { type: "button", size: "sm", variant: c.condition === cond ? 'default' : 'outline', onClick: () => setChoice(l.key, { condition: cond }), children: cond === 'sellable' ? t('returns.sellable', { defaultValue: 'Vendible' }) : t('returns.defective', { defaultValue: 'Defectuoso' }) }, cond))), RETURN_CONDITION_DESTINATIONS[c.condition].length > 1 &&
|
|
57
|
+
RETURN_CONDITION_DESTINATIONS[c.condition].map((d) => (_jsx(Button, { type: "button", size: "sm", variant: c.destination === d ? 'secondary' : 'ghost', onClick: () => setChoice(l.key, { destination: d }), children: STOCK_LABEL[d] }, d))), RETURN_CONDITION_DESTINATIONS[c.condition].length === 1 && (_jsx("span", { className: "text-xs text-muted-foreground", children: STOCK_LABEL[c.destination] })), errors[`${l.key}.destination`] && _jsx("p", { className: "w-full text-xs text-destructive", children: errors[`${l.key}.destination`] })] })))] }, l.key));
|
|
58
|
+
}), step === 'lines' && (_jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [_jsxs("select", { className: "h-9 rounded-md border bg-background px-2 text-sm", value: reason, onChange: (e) => setReason(e.target.value), "aria-label": t('returns.reason', { defaultValue: 'Motivo' }), children: [_jsxs("option", { value: "", children: [t('returns.reason', { defaultValue: 'Motivo' }), "\u2026"] }), reasons.map((r) => (_jsx("option", { value: r.value, children: r.label }, r.value)))] }), _jsx(Input, { value: notes, onChange: (e) => setNotes(e.target.value), placeholder: t('returns.notes', { defaultValue: 'Notas (opcional)' }) })] })), validation.form && stepIdx === 0 && Object.keys(choices).length > 0 && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: validation.form })] })), step === 'receive' && (_jsxs("label", { className: "flex items-start gap-2 rounded-md border p-3 text-sm", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: received, onChange: (e) => setReceived(e.target.checked) }), _jsx("span", { children: t('returns.receiveHere', { defaultValue: 'Confirmo que la mercancía ya está en tienda. No hay almacén que la reciba, así que la recepción se registra aquí.' }) })] })), step === 'refund' && (_jsx(RefundDestination, { total: totals.total, value: refund, onChange: setRefund, options: options, currency: currency, allowSplit: true, serverErrors: serverErrors })), _jsxs("div", { className: "flex items-center justify-between gap-2 border-t pt-3", children: [_jsxs("span", { className: "text-sm", children: [t('returns.total', { defaultValue: 'Total a devolver' }), ": ", _jsx("strong", { children: fmt.money(totals.total) })] }), _jsxs("div", { className: "flex gap-2", children: [onCancel && stepIdx === 0 && _jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: t('common.cancel', { defaultValue: 'Cancelar' }) }), stepIdx > 0 && _jsx(Button, { type: "button", variant: "outline", onClick: () => setStepIdx(stepIdx - 1), children: t('common.back', { defaultValue: 'Atrás' }) }), stepIdx < steps.length - 1 ? (_jsx(Button, { type: "button", disabled: !canNext, onClick: () => setStepIdx(stepIdx + 1), children: t('common.next', { defaultValue: 'Siguiente' }) })) : (_jsxs(Button, { type: "button", disabled: !canNext || submitting, onClick: submit, children: [t('returns.confirm', { defaultValue: 'Confirmar devolución' }), " ", fmt.money(totals.total)] }))] })] })] }));
|
|
59
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type RefundAllocation, type RefundDestinationOption } from './refund';
|
|
2
|
+
/** Estado físico de lo que regresa. */
|
|
3
|
+
export type ReturnCondition = 'sellable' | 'defective';
|
|
4
|
+
/** A dónde va la mercancía: stock vendible, merma o garantía (reclamo al proveedor). */
|
|
5
|
+
export type ReturnStockDestination = 'stock' | 'scrap' | 'warranty';
|
|
6
|
+
export declare const RETURN_CONDITION_DESTINATIONS: Record<ReturnCondition, ReturnStockDestination[]>;
|
|
7
|
+
/** Destino por defecto de una condición (vendible → stock, defectuoso → merma). */
|
|
8
|
+
export declare function defaultStockDestination(condition: ReturnCondition): ReturnStockDestination;
|
|
9
|
+
/** Renglón devolvible: lo vendido menos lo ya devuelto. */
|
|
10
|
+
export interface ReturnableLine {
|
|
11
|
+
key: string;
|
|
12
|
+
product_id?: string;
|
|
13
|
+
description: string;
|
|
14
|
+
sold: number;
|
|
15
|
+
alreadyReturned?: number;
|
|
16
|
+
/** Precio unitario neto de descuento tal como se vendió (congelado). */
|
|
17
|
+
unit_price: number;
|
|
18
|
+
/** Fracción (0.16). */
|
|
19
|
+
tax_rate?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ReturnLineChoice {
|
|
22
|
+
quantity: number;
|
|
23
|
+
condition: ReturnCondition;
|
|
24
|
+
destination: ReturnStockDestination;
|
|
25
|
+
}
|
|
26
|
+
export type ReturnChoices = Record<string, ReturnLineChoice>;
|
|
27
|
+
export declare function returnableQty(l: ReturnableLine): number;
|
|
28
|
+
export interface ReturnTotals {
|
|
29
|
+
base: number;
|
|
30
|
+
tax: number;
|
|
31
|
+
total: number;
|
|
32
|
+
goodsLines: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function computeReturnTotals(lines: ReturnableLine[], choices: ReturnChoices): ReturnTotals;
|
|
35
|
+
/**
|
|
36
|
+
* Relación SAT (c_TipoRelacion) de la NC: 03 «devolución de mercancía» si regresan
|
|
37
|
+
* renglones con producto; 01 «nota de crédito de los documentos relacionados» si es
|
|
38
|
+
* solo ajuste de importe. Misma regla que aplica el backend al proyectar la NC.
|
|
39
|
+
*/
|
|
40
|
+
export declare function creditNoteRelation(lines: ReturnableLine[], choices: ReturnChoices): '01' | '03';
|
|
41
|
+
export interface ReturnValidation {
|
|
42
|
+
errors: Record<string, string>;
|
|
43
|
+
form?: string;
|
|
44
|
+
valid: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare function validateReturnChoices(lines: ReturnableLine[], choices: ReturnChoices, reason?: string, requireReason?: boolean): ReturnValidation;
|
|
47
|
+
/** Pasos visibles. Sin almacén, «Recibir» es un paso del mismo asistente (PIT-021). */
|
|
48
|
+
export type ReturnStep = 'lines' | 'condition' | 'receive' | 'refund';
|
|
49
|
+
export declare function returnSteps(opts: {
|
|
50
|
+
warehouseConnected: boolean;
|
|
51
|
+
}): ReturnStep[];
|
|
52
|
+
export interface ReturnSubmitPayload {
|
|
53
|
+
reason: string;
|
|
54
|
+
notes?: string;
|
|
55
|
+
/** true cuando el asistente confirmó la recepción (sin almacén). */
|
|
56
|
+
received: boolean;
|
|
57
|
+
lines: {
|
|
58
|
+
key: string;
|
|
59
|
+
product_id?: string;
|
|
60
|
+
quantity: number;
|
|
61
|
+
condition: ReturnCondition;
|
|
62
|
+
destination: ReturnStockDestination;
|
|
63
|
+
}[];
|
|
64
|
+
refund: RefundAllocation[];
|
|
65
|
+
/** Presente solo si el reembolso incluye una NC CFDI. */
|
|
66
|
+
credit_note?: {
|
|
67
|
+
relation: '01' | '03';
|
|
68
|
+
};
|
|
69
|
+
totals: ReturnTotals;
|
|
70
|
+
}
|
|
71
|
+
/** Única puerta de salida hacia el backend: solo renglones con cantidad > 0. */
|
|
72
|
+
export declare function serializeReturn(args: {
|
|
73
|
+
lines: ReturnableLine[];
|
|
74
|
+
choices: ReturnChoices;
|
|
75
|
+
reason: string;
|
|
76
|
+
notes?: string;
|
|
77
|
+
received: boolean;
|
|
78
|
+
refund: RefundAllocation[];
|
|
79
|
+
}): ReturnSubmitPayload;
|
|
80
|
+
/** Valida el paso de reembolso contra el total de la devolución. */
|
|
81
|
+
export declare function validateReturnRefund(total: number, refund: RefundAllocation[], options?: RefundDestinationOption[]): import("./refund").RefundValidation;
|
|
82
|
+
export type RmaState = 'draft' | 'authorized' | 'received' | 'settled' | 'closed' | 'rejected' | 'cancelled';
|
|
83
|
+
export declare const RMA_TRANSITIONS: Record<RmaState, RmaState[]>;
|
|
84
|
+
/** Estados a los que se llega con motivo obligatorio. */
|
|
85
|
+
export declare const RMA_REASON_REQUIRED: RmaState[];
|
|
86
|
+
export declare function canTransitionRma(from: RmaState, to: RmaState, reason?: string): {
|
|
87
|
+
ok: boolean;
|
|
88
|
+
error?: string;
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=return.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return.d.ts","sourceRoot":"","sources":["../../src/business/return.ts"],"names":[],"mappings":"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"}
|