@fayz-ai/plugin-financial 0.26.3 → 0.27.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.
@@ -7,10 +7,10 @@ import React13__default, { useState, useRef, useEffect, useMemo } from 'react';
7
7
  import { useTranslation, createPendingCommand, useUnitMode, useDataChanged, useActiveUnitId, appHref, resolveCurrency, getSupabaseClientOptional } from '@fayz-ai/core';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
9
  import { Landmark, CreditCard, Banknote, TriangleAlert, Loader2, Check, X, Paperclip, Image, FileText, ChevronRight, UploadCloud, ArrowLeft, ChevronLeft, ExternalLink, Ban, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, Package, PackagePlus, Receipt, Gift, Briefcase, CalendarDays, DollarSign, LayoutList, Layers, Ticket, FileCheck, ArrowRightLeft, QrCode, Undo2, Building2, MoreVertical, Calendar, User, Wallet, Unlink, Trash2, ChevronDown, Search, Settings } from 'lucide-react';
10
- import { INLINE_LINK_TONE, SearchSelect, Button, useInModalTaskHost, ModalTask, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, toast, Skeleton, SubpageHeader, DatePicker, Checkbox, Textarea, ChoiceSelect, useSaveBar, tourAnchor, Input, SegmentedControl, CurrencyInput, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Badge, Hint, Popover, PopoverTrigger, PopoverContent } from '@fayz-ai/ui';
10
+ import { INLINE_LINK_TONE, SearchSelect, Button, useInModalTaskHost, ModalTask, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, toast, Skeleton, SubpageHeader, DatePicker, Checkbox, Textarea, ChoiceSelect, useSaveBar, tourAnchor, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Input, SegmentedControl, CurrencyInput, Badge, Hint, Popover, PopoverTrigger, PopoverContent } from '@fayz-ai/ui';
11
11
  import { createPortal } from 'react-dom';
12
12
  import { createIdempotencyKey } from '@fayz-ai/financial/data/idempotency';
13
- import { useCurrency, useUnitStore, OrderDetailSkeleton, WidgetSlot, PageActions, OrderDetailBlock, TagChip, OrderDetailView, PersonLink, CurrencyScope, useUnitFilter, usePermissionOptional, adminNavigateTo, createArchetypeLookup, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, TagsField, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, RecordMetaFooter, invalidateLimit, useHouseCurrency } from '@fayz-ai/admin';
13
+ import { useCurrency, useUnitStore, OrderDetailSkeleton, WidgetSlot, PageActions, OrderDetailBlock, TagChip, OrderDetailView, PersonLink, CurrencyScope, useUnitFilter, usePermissionOptional, adminNavigateTo, createArchetypeLookup, listPickablePriceTables, withPriceTable, useLimitGuard, useLookupCreate, OrderFormSkeleton, OrderFormBlock, initialPriceTableId, TagsField, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, RecordMetaFooter, quotePriceTable, invalidateLimit, useHouseCurrency } from '@fayz-ai/admin';
14
14
  import { T } from '@fayz-ai/financial/data/tables';
15
15
  import { groupPaymentsByBill, reversiblePayments } from '@fayz-ai/financial/domain/movement';
16
16
  import { describePayment, paymentHolderId } from '@fayz-ai/financial/domain/payment-method';
@@ -2512,9 +2512,19 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
2512
2512
  itemsLabel: t("financial.invoice.items"),
2513
2513
  totals: totalRows,
2514
2514
  totalsLabel: t("financial.invoice.payment"),
2515
- mainSections: (
2516
- // O cronograma no MESMO bloco que os itens e o pagamento — antes o título
2517
- // ficava fora do cartão, e a seção lia como se fosse de outra tela.
2515
+ mainSections: /* @__PURE__ */ jsxs(Fragment, { children: [
2516
+ /* @__PURE__ */ jsx(
2517
+ WidgetSlot,
2518
+ {
2519
+ zone: FINANCIAL_INVOICE_ZONES.notices,
2520
+ invoiceId: invoice.id,
2521
+ direction,
2522
+ cancelled,
2523
+ onChanged: () => {
2524
+ void loadData();
2525
+ }
2526
+ }
2527
+ ),
2518
2528
  /* @__PURE__ */ jsx(
2519
2529
  OrderDetailBlock,
2520
2530
  {
@@ -2660,7 +2670,7 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel, em
2660
2670
  ] })
2661
2671
  }
2662
2672
  )
2663
- ),
2673
+ ] }),
2664
2674
  extraSections: sideBlocks
2665
2675
  }
2666
2676
  ),
@@ -3014,6 +3024,7 @@ var formId = 1;
3014
3024
  function fid() {
3015
3025
  return `f${formId++}`;
3016
3026
  }
3027
+ var NO_PRICE_TABLE = "__none__";
3017
3028
  function sourceMeta(inst) {
3018
3029
  return {
3019
3030
  payMethodTypeId: inst.payMethodTypeId || null,
@@ -3118,7 +3129,30 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3118
3129
  }
3119
3130
  };
3120
3131
  }, [giftCardsEnabled, provider, t]);
3121
- const entityLookups = useMemo(() => giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups, [config.entityLookups, giftCardLookup]);
3132
+ const [priceTables, setPriceTables] = useState([]);
3133
+ const [priceTableId, setPriceTableId] = useState();
3134
+ const [savedPriceTableId, setSavedPriceTableId] = useState();
3135
+ useEffect(() => {
3136
+ if (direction !== "credit") return;
3137
+ let alive = true;
3138
+ listPickablePriceTables().then((tables) => {
3139
+ if (alive) setPriceTables(tables);
3140
+ }).catch(() => {
3141
+ });
3142
+ return () => {
3143
+ alive = false;
3144
+ };
3145
+ }, [direction]);
3146
+ const pickedTable = priceTables.find((pt) => pt.id === priceTableId);
3147
+ const entityLookups = useMemo(() => {
3148
+ const base = giftCardLookup ? { ...config.entityLookups, gift_card: giftCardLookup } : config.entityLookups ?? {};
3149
+ if (!pickedTable) return base;
3150
+ return {
3151
+ ...base,
3152
+ ...base.product ? { product: withPriceTable(base.product, pickedTable.id, { kinds: ["good"] }) } : {},
3153
+ ...base.service ? { service: withPriceTable(base.service, pickedTable.id, { kinds: ["service"] }) } : {}
3154
+ };
3155
+ }, [config.entityLookups, giftCardLookup, pickedTable]);
3122
3156
  const translatedItemTypes = React13__default.useMemo(
3123
3157
  () => itemTypesRaw.map((tp) => {
3124
3158
  const traduzido = t(`financial.itemType.${tp.value}`);
@@ -3180,6 +3214,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3180
3214
  );
3181
3215
  const [savedItemsKey, setSavedItemsKey] = useState("");
3182
3216
  const [loaded, setLoaded] = useState(!editId);
3217
+ const [reloadTick, setReloadTick] = useState(0);
3183
3218
  const [loading, setLoading] = useState(!!editId);
3184
3219
  const [contactName, setContactName] = useState("");
3185
3220
  const [contactId, setContactId] = useState();
@@ -3274,13 +3309,15 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3274
3309
  }, [menuOpen]);
3275
3310
  const [recordMeta, setRecordMeta] = useState(null);
3276
3311
  useEffect(() => {
3277
- if (!editId || loaded) return;
3312
+ if (!editId || loaded && reloadTick === 0) return;
3278
3313
  setLoading(true);
3279
3314
  (async () => {
3280
3315
  const invoice = await provider.getInvoiceById(editId);
3281
3316
  if (!invoice) return;
3282
3317
  setContactName(invoice.contactName ?? "");
3283
3318
  setContactId(invoice.contactId);
3319
+ setPriceTableId(invoice.priceTableId ?? void 0);
3320
+ setSavedPriceTableId(invoice.priceTableId ?? void 0);
3284
3321
  if (invoice.contactId) {
3285
3322
  try {
3286
3323
  const contact = await contactLookup.getById(invoice.contactId);
@@ -3359,7 +3396,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3359
3396
  setLoaded(true);
3360
3397
  setLoading(false);
3361
3398
  })();
3362
- }, [editId]);
3399
+ }, [editId, reloadTick]);
3363
3400
  const totalAmount = items.reduce((sum, it) => {
3364
3401
  return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
3365
3402
  }, 0);
@@ -3457,6 +3494,27 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3457
3494
  setItems((current) => current.flatMap((it) => it._id === item._id ? novas : [it]));
3458
3495
  setExpandedItemId(null);
3459
3496
  }
3497
+ async function changePriceTable(next) {
3498
+ setPriceTableId(next);
3499
+ const table = priceTables.find((pt) => pt.id === next);
3500
+ if (!table || !canEditItems) return;
3501
+ const priced = items.filter((it) => it.referenceId && (it.itemKind === "product" || it.itemKind === "service"));
3502
+ if (priced.length === 0) return;
3503
+ try {
3504
+ const quotes = await quotePriceTable(table.id, priced.map((it) => ({ productId: it.referenceId, quantity: it.quantity })));
3505
+ const prices = /* @__PURE__ */ new Map();
3506
+ priced.forEach((it, i) => {
3507
+ const q = quotes[i];
3508
+ if (q) prices.set(it._id, q.price);
3509
+ });
3510
+ setItems((current) => current.map((it) => prices.has(it._id) ? { ...it, unitPrice: prices.get(it._id) } : it));
3511
+ if (prices.size > 0) toast.success(t("financial.invoiceForm.priceTableRepriced", { count: prices.size, table: table.name }));
3512
+ const missing = priced.length - prices.size;
3513
+ if (missing > 0) toast.warning(t("financial.invoiceForm.priceTableMissing", { count: missing, table: table.name }));
3514
+ } catch (err) {
3515
+ toast.error(err instanceof Error ? err.message : String(err));
3516
+ }
3517
+ }
3460
3518
  function removeItem(id) {
3461
3519
  setItems((current) => current.filter((it) => it._id !== id));
3462
3520
  }
@@ -3614,7 +3672,8 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3614
3672
  // 110 abriu a CONTRAPARTE, com o mesmo limite: quem digita seis
3615
3673
  // contas seguidas escolhe o fornecedor da linha de cima na quarta, e
3616
3674
  // até aqui a correção era cancelar a conta e lançar outra.
3617
- ...contactId !== savedContactId ? { contactId, contactName } : {}
3675
+ ...contactId !== savedContactId ? { contactId, contactName } : {},
3676
+ ...priceTableId !== savedPriceTableId ? { priceTableId: priceTableId ?? null } : {}
3618
3677
  });
3619
3678
  const shapeChanged = itemsDirty || installments.length !== savedMovements.length || installments.some((inst, i) => Math.round(inst.amount * 100) !== Math.round((savedMovements[i]?.amount ?? 0) * 100));
3620
3679
  if (canReschedule && provider.rescheduleInvoice && shapeChanged) {
@@ -3649,6 +3708,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3649
3708
  contactName: contactName || void 0,
3650
3709
  unitId: unitId || void 0,
3651
3710
  currency: documentCurrency,
3711
+ ...priceTableId ? { priceTableId } : {},
3652
3712
  tags,
3653
3713
  fiscalNumber: fiscalNumber || void 0,
3654
3714
  observations: observations || void 0,
@@ -3722,6 +3782,7 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3722
3782
  const fields = {
3723
3783
  contactName,
3724
3784
  contactId,
3785
+ priceTableId,
3725
3786
  unitId,
3726
3787
  invoiceDate,
3727
3788
  fiscalNumber,
@@ -3966,6 +4027,11 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
3966
4027
  setContactId(id || void 0);
3967
4028
  setContactName(opt?.label ?? "");
3968
4029
  setPendingContact(null);
4030
+ if (id && priceTables.length > 0) {
4031
+ const result = opt?.data ?? {};
4032
+ const next = initialPriceTableId(priceTables, result.data?.price_table_id);
4033
+ if (next !== priceTableId) void changePriceTable(next);
4034
+ }
3969
4035
  const request = ++accountingPolicyRequest.current;
3970
4036
  if (!id) {
3971
4037
  setCounterpartyAccounting({ allowedAccountIds: [], limitAccounts: false, addItemsAutomatically: false });
@@ -4016,6 +4082,26 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4016
4082
  ] }),
4017
4083
  contactCreate.modal
4018
4084
  ] }) });
4085
+ const priceTableSection = direction === "credit" && priceTables.length > 0 ? /* @__PURE__ */ jsxs(OrderFormBlock, { title: t("financial.invoiceForm.priceTable"), children: [
4086
+ /* @__PURE__ */ jsxs(
4087
+ Select,
4088
+ {
4089
+ value: priceTableId ?? NO_PRICE_TABLE,
4090
+ onValueChange: (v) => {
4091
+ void changePriceTable(v === NO_PRICE_TABLE ? void 0 : v);
4092
+ },
4093
+ disabled: !canEditItems,
4094
+ children: [
4095
+ /* @__PURE__ */ jsx(SelectTrigger, { "aria-label": t("financial.invoiceForm.priceTable"), "data-testid": "invoice-price-table", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("financial.invoiceForm.priceTableNone") }) }),
4096
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
4097
+ /* @__PURE__ */ jsx(SelectItem, { value: NO_PRICE_TABLE, children: t("financial.invoiceForm.priceTableNone") }),
4098
+ priceTables.map((pt) => /* @__PURE__ */ jsx(SelectItem, { value: pt.id, children: pt.name }, pt.id))
4099
+ ] })
4100
+ ]
4101
+ }
4102
+ ),
4103
+ pickedTable && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-muted-foreground", children: t("financial.invoiceForm.priceTableHint") })
4104
+ ] }) : null;
4019
4105
  const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
4020
4106
  /* @__PURE__ */ jsx(OrderFormBlock, { title: t("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
4021
4107
  showUnitField && /* @__PURE__ */ jsx(UnitField, { choices: unitChoices, value: unitId, onChange: setUnitId }),
@@ -4104,10 +4190,23 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4104
4190
  parentLabel: embedded ? void 0 : direction === "debit" ? t("financial.nav.payables") : t("financial.nav.receivables"),
4105
4191
  onBack: embedded ? void 0 : () => onSaved?.(),
4106
4192
  actions,
4107
- banner: isEdit && hasPayments ? /* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning-soft px-4 py-2.5 text-xs text-warning-soft-foreground", children: t("financial.invoiceForm.issuedLocked") }) : void 0,
4193
+ banner: isEdit ? /* @__PURE__ */ jsxs(Fragment, { children: [
4194
+ hasPayments && /* @__PURE__ */ jsx("p", { className: "rounded-card border border-warning/40 bg-warning-soft px-4 py-2.5 text-xs text-warning-soft-foreground", children: t("financial.invoiceForm.issuedLocked") }),
4195
+ /* @__PURE__ */ jsx(
4196
+ WidgetSlot,
4197
+ {
4198
+ zone: FINANCIAL_INVOICE_ZONES.notices,
4199
+ invoiceId: editId,
4200
+ direction,
4201
+ cancelled,
4202
+ onChanged: () => setReloadTick((n) => n + 1)
4203
+ }
4204
+ )
4205
+ ] }) : void 0,
4108
4206
  sideSections,
4109
4207
  children: [
4110
4208
  contactSection,
4209
+ priceTableSection,
4111
4210
  /* @__PURE__ */ jsxs(
4112
4211
  OrderFormItemsCard,
4113
4212
  {
@@ -4336,5 +4435,5 @@ function InvoiceFormView({ direction, editId, onSaved, focusReceipt = false, fro
4336
4435
  }
4337
4436
 
4338
4437
  export { ACCOUNTS_ROUTE, AccountIcon, BankLogo, BankPicker, CancelInvoicesDialog, FinancialCounterpartySearchSelect, InvoiceAttachmentsModal, InvoiceDetailView, InvoiceFormView, PaymentModal, UnitField, createFinancialCounterpartyLookup, findBrBank, marcarItemParaAbrir, renderAccountAvatar, renderCardChip, useStockedItemTypes, useWritableUnits };
4339
- //# sourceMappingURL=chunk-K37Q7SKX.js.map
4340
- //# sourceMappingURL=chunk-K37Q7SKX.js.map
4438
+ //# sourceMappingURL=chunk-IQHWMVUM.js.map
4439
+ //# sourceMappingURL=chunk-IQHWMVUM.js.map