@powerhousedao/contributor-billing 0.0.98 → 0.1.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.
Files changed (114) hide show
  1. package/dist/document-models/billing-statement/src/reducers/general.d.ts.map +1 -1
  2. package/dist/document-models/billing-statement/src/reducers/general.js +8 -26
  3. package/dist/document-models/billing-statement/src/reducers/line-items.d.ts.map +1 -1
  4. package/dist/document-models/billing-statement/src/reducers/line-items.js +19 -29
  5. package/dist/document-models/billing-statement/src/reducers/tags.d.ts.map +1 -1
  6. package/dist/document-models/billing-statement/src/reducers/tags.js +20 -25
  7. package/dist/document-models/integrations/src/reducers/integrations.d.ts.map +1 -1
  8. package/dist/document-models/integrations/src/reducers/integrations.js +29 -44
  9. package/dist/document-models/invoice/src/reducers/general.d.ts.map +1 -1
  10. package/dist/document-models/invoice/src/reducers/general.js +31 -56
  11. package/dist/document-models/invoice/src/reducers/items.d.ts.map +1 -1
  12. package/dist/document-models/invoice/src/reducers/items.js +63 -88
  13. package/dist/document-models/invoice/src/reducers/parties.d.ts.map +1 -1
  14. package/dist/document-models/invoice/src/reducers/parties.js +199 -229
  15. package/dist/editors/billing-statement/components/lineItemsTable.d.ts +3 -2
  16. package/dist/editors/billing-statement/components/lineItemsTable.d.ts.map +1 -1
  17. package/dist/editors/billing-statement/components/lineItemsTable.js +12 -8
  18. package/dist/editors/billing-statement/components/objectSetTable.d.ts +3 -2
  19. package/dist/editors/billing-statement/components/objectSetTable.d.ts.map +1 -1
  20. package/dist/editors/billing-statement/editor.d.ts +3 -1
  21. package/dist/editors/billing-statement/editor.d.ts.map +1 -1
  22. package/dist/editors/billing-statement/editor.js +2 -2
  23. package/dist/editors/billing-statement/lineItemTags/lineItemTags.d.ts +2 -2
  24. package/dist/editors/billing-statement/lineItemTags/lineItemTags.d.ts.map +1 -1
  25. package/dist/editors/contributor-billing/components/DriveExplorer.d.ts +8 -1
  26. package/dist/editors/contributor-billing/components/DriveExplorer.d.ts.map +1 -1
  27. package/dist/editors/contributor-billing/components/DriveExplorer.js +18 -64
  28. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts +2 -8
  29. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts.map +1 -1
  30. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.js +21 -7
  31. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts +8 -10
  32. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts.map +1 -1
  33. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.js +27 -24
  34. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.d.ts +6 -8
  35. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.d.ts.map +1 -1
  36. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.js +8 -17
  37. package/dist/editors/contributor-billing/editor.d.ts +1 -10
  38. package/dist/editors/contributor-billing/editor.d.ts.map +1 -1
  39. package/dist/editors/contributor-billing/editor.js +2 -23
  40. package/dist/editors/contributor-billing/hooks/useTransformedNodes.d.ts +2 -1
  41. package/dist/editors/contributor-billing/hooks/useTransformedNodes.d.ts.map +1 -1
  42. package/dist/editors/contributor-billing/index.js +1 -1
  43. package/dist/editors/hooks/useBillingStatementDocument.d.ts +4 -0
  44. package/dist/editors/hooks/useBillingStatementDocument.d.ts.map +1 -0
  45. package/dist/editors/hooks/useBillingStatementDocument.js +8 -0
  46. package/dist/editors/hooks/useIntegrationsDocument.d.ts +4 -0
  47. package/dist/editors/hooks/useIntegrationsDocument.d.ts.map +1 -0
  48. package/dist/editors/hooks/useIntegrationsDocument.js +8 -0
  49. package/dist/editors/hooks/useInvoiceDocument.d.ts +4 -0
  50. package/dist/editors/hooks/useInvoiceDocument.d.ts.map +1 -0
  51. package/dist/editors/hooks/useInvoiceDocument.js +8 -0
  52. package/dist/editors/integrations/editor.d.ts +4 -1
  53. package/dist/editors/integrations/editor.d.ts.map +1 -1
  54. package/dist/editors/integrations/editor.js +7 -12
  55. package/dist/editors/invoice/components/lineItemCard.d.ts +21 -0
  56. package/dist/editors/invoice/components/lineItemCard.d.ts.map +1 -0
  57. package/dist/editors/invoice/components/lineItemCard.js +23 -0
  58. package/dist/editors/invoice/components/lineItemMobileModal.d.ts +33 -0
  59. package/dist/editors/invoice/components/lineItemMobileModal.d.ts.map +1 -0
  60. package/dist/editors/invoice/components/lineItemMobileModal.js +73 -0
  61. package/dist/editors/invoice/components/lineItemsEmptyState.d.ts +6 -0
  62. package/dist/editors/invoice/components/lineItemsEmptyState.d.ts.map +1 -0
  63. package/dist/editors/invoice/components/lineItemsEmptyState.js +5 -0
  64. package/dist/editors/invoice/editor.d.ts +4 -1
  65. package/dist/editors/invoice/editor.d.ts.map +1 -1
  66. package/dist/editors/invoice/editor.js +41 -22
  67. package/dist/editors/invoice/ingestPDF.js +1 -1
  68. package/dist/editors/invoice/invoiceToGnosis.d.ts +3 -2
  69. package/dist/editors/invoice/invoiceToGnosis.d.ts.map +1 -1
  70. package/dist/editors/invoice/invoiceToGnosis.js +11 -11
  71. package/dist/editors/invoice/legalEntity/bankSection.d.ts +1 -0
  72. package/dist/editors/invoice/legalEntity/bankSection.d.ts.map +1 -1
  73. package/dist/editors/invoice/legalEntity/bankSection.js +36 -12
  74. package/dist/editors/invoice/legalEntity/legalEntity.d.ts +2 -1
  75. package/dist/editors/invoice/legalEntity/legalEntity.d.ts.map +1 -1
  76. package/dist/editors/invoice/legalEntity/legalEntity.js +2 -2
  77. package/dist/editors/invoice/lineItemTags/lineItemTags.d.ts.map +1 -1
  78. package/dist/editors/invoice/lineItemTags/lineItemTags.js +25 -3
  79. package/dist/editors/invoice/lineItemTags/tagCard.d.ts +15 -0
  80. package/dist/editors/invoice/lineItemTags/tagCard.d.ts.map +1 -0
  81. package/dist/editors/invoice/lineItemTags/tagCard.js +13 -0
  82. package/dist/editors/invoice/lineItemTags/tagMobileModal.d.ts +18 -0
  83. package/dist/editors/invoice/lineItemTags/tagMobileModal.d.ts.map +1 -0
  84. package/dist/editors/invoice/lineItemTags/tagMobileModal.js +71 -0
  85. package/dist/editors/invoice/lineItems.d.ts.map +1 -1
  86. package/dist/editors/invoice/lineItems.js +76 -38
  87. package/dist/editors/invoice/requestFinance.js +2 -2
  88. package/dist/editors/invoice/uploadPdfChunked.js +1 -1
  89. package/dist/editors/invoice/validation/validationHandler.d.ts +1 -1
  90. package/dist/editors/invoice/validation/validationHandler.d.ts.map +1 -1
  91. package/dist/editors/invoice/validation/validationHandler.js +25 -9
  92. package/dist/editors/invoice/validation/validationManager.d.ts.map +1 -1
  93. package/dist/editors/invoice/validation/validationManager.js +3 -2
  94. package/dist/editors/invoice/validation/validationRules.d.ts +2 -0
  95. package/dist/editors/invoice/validation/validationRules.d.ts.map +1 -1
  96. package/dist/editors/invoice/validation/validationRules.js +37 -7
  97. package/dist/scripts/contributor-billing/createExpenseReportCsv.d.ts +5 -0
  98. package/dist/scripts/contributor-billing/createExpenseReportCsv.d.ts.map +1 -0
  99. package/dist/scripts/contributor-billing/createExpenseReportCsv.js +122 -0
  100. package/dist/style.css +180 -12
  101. package/dist/tailwind.config.js +1 -0
  102. package/package.json +13 -12
  103. package/dist/reducers/general.d.ts +0 -8
  104. package/dist/reducers/general.d.ts.map +0 -1
  105. package/dist/reducers/general.js +0 -73
  106. package/dist/reducers/items.d.ts +0 -8
  107. package/dist/reducers/items.d.ts.map +0 -1
  108. package/dist/reducers/items.js +0 -195
  109. package/dist/reducers/parties.d.ts +0 -8
  110. package/dist/reducers/parties.d.ts.map +0 -1
  111. package/dist/reducers/parties.js +0 -266
  112. package/dist/reducers/transitions.d.ts +0 -8
  113. package/dist/reducers/transitions.d.ts.map +0 -1
  114. package/dist/reducers/transitions.js +0 -162
@@ -6,6 +6,9 @@ import { Tag } from "lucide-react";
6
6
  import { NumberForm } from "./components/numberForm.js";
7
7
  import { InputField } from "./components/inputField.js";
8
8
  import { LineItemTagsTable } from "./lineItemTags/lineItemTags.js";
9
+ import { LineItemCard } from "./components/lineItemCard.js";
10
+ import { LineItemMobileModal } from "./components/lineItemMobileModal.js";
11
+ import { LineItemsEmptyState } from "./components/lineItemsEmptyState.js";
9
12
  // Helper function to get precision based on currency
10
13
  function getCurrencyPrecision(currency) {
11
14
  return currency === "USDS" || currency === "DAI" ? 6 : 2;
@@ -286,6 +289,8 @@ export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, o
286
289
  const [editingId, setEditingId] = useState(null);
287
290
  const [isAddingNew, setIsAddingNew] = useState(false);
288
291
  const [showTagTable, setShowTagTable] = useState(false);
292
+ const [mobileEditItem, setMobileEditItem] = useState(null);
293
+ const [showMobileModal, setShowMobileModal] = useState(false);
289
294
  const containerRef = useRef(null);
290
295
  const tableContainerRef = useRef(null);
291
296
  const tableRef = useRef(null);
@@ -340,59 +345,92 @@ export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, o
340
345
  item: item.description,
341
346
  period: "", // Default value
342
347
  expenseAccount: "", // Default value
343
- total: `$${formatNumber(item.totalPriceTaxIncl)}`,
348
+ total: `${currency} ${formatNumber(item.totalPriceTaxIncl)}`,
344
349
  lineItemTag: item.lineItemTag,
345
350
  }));
346
351
  if (showTagTable) {
347
352
  return (_jsx(LineItemTagsTable, { lineItems: tagAssignmentRows, onClose: () => setShowTagTable(false), dispatch: dispatch, paymentAccounts: paymentAccounts }));
348
353
  }
349
- return (_jsx("div", { ref: containerRef, className: "relative w-full", children: _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between", children: [_jsx("div", { className: "flex items-center gap-4", children: _jsx("h4", { className: "text-xl font-semibold text-gray-900", children: "Line Items" }) }), _jsx(RWAButton, { className: "mb-2", disabled: isAddingNew, onClick: handleAddClick, children: "Add Line Item" })] }), _jsx("div", { ref: tableContainerRef, className: "overflow-x-auto rounded-lg border border-gray-200", children: _jsxs("table", { ref: tableRef, className: "w-full table-fixed border-collapse bg-white", children: [_jsxs("colgroup", { children: [_jsx("col", { style: { width: "30%" } }), _jsx("col", { style: { width: "10%" } }), _jsx("col", { style: { width: "12%" } }), _jsx("col", { style: { width: "8%" } }), _jsx("col", {}), _jsx("col", {}), _jsx("col", {})] }), _jsx("thead", { children: _jsxs("tr", { className: "bg-gray-50", children: [_jsx("th", { className: "border-b border-gray-200 p-3 text-left", children: "Description" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Quantity" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Unit Price (excl. tax)" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Tax %" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Total (excl. tax)" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Total (incl. tax)" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-center", children: _jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsx("span", { className: "text-sm", children: "Actions" }), _jsx(Tag, { onClick: () => setShowTagTable(true), style: {
350
- cursor: "pointer",
351
- width: 28,
352
- height: 28,
353
- color: "white",
354
- fill: "#475264",
355
- } })] }) })] }) }), _jsxs("tbody", { children: [lineItems.map((item) => editingId === item.id ? (_jsx(EditableLineItem, { currency: currency, item: item, onCancel: () => setEditingId(null), onSave: (updatedItem) => {
356
- try {
357
- onUpdateItem(updatedItem);
358
- setEditingId(null);
359
- }
360
- catch (error) {
361
- console.error(error);
362
- if (error?.message?.includes("Invalid action input:")) {
363
- try {
364
- const zodError = JSON.parse(error.message.split("Invalid action input: ")[1]);
365
- if (Array.isArray(zodError) &&
366
- zodError.length > 0) {
367
- const firstError = zodError[0];
368
- const errorJSX = (_jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Failed to update line item" }), _jsxs("p", { children: [firstError.message, ": "] }), zodError.map((err, index) => (_jsx("ul", { children: _jsxs("li", { className: "text-red-500 font-semibold", children: ["- ", err.path.join(".")] }) }, index)))] }));
369
- toast(errorJSX, {
354
+ // Calculate totals for mobile footer
355
+ const totalPriceTaxExcl = lineItems.reduce((sum, item) => sum + item.totalPriceTaxExcl, 0);
356
+ const totalPriceTaxIncl = lineItems.reduce((sum, item) => sum + item.totalPriceTaxIncl, 0);
357
+ return (_jsxs("div", { ref: containerRef, className: "relative w-full", children: [showMobileModal && (_jsx(LineItemMobileModal, { item: mobileEditItem || {}, currency: currency, isNew: !mobileEditItem?.id || mobileEditItem.id === "", onSave: (item) => {
358
+ try {
359
+ // If editing an item with empty ID, delete it first, then add new one
360
+ if (mobileEditItem?.id === "") {
361
+ onDeleteItem({ id: "" });
362
+ onAddItem(item);
363
+ }
364
+ else if (mobileEditItem?.id) {
365
+ onUpdateItem(item);
366
+ }
367
+ else {
368
+ onAddItem(item);
369
+ }
370
+ setShowMobileModal(false);
371
+ setMobileEditItem(null);
372
+ }
373
+ catch (error) {
374
+ toast(error.message || "Failed to save line item", {
375
+ type: "error",
376
+ });
377
+ }
378
+ }, onCancel: () => {
379
+ setShowMobileModal(false);
380
+ setMobileEditItem(null);
381
+ } }, mobileEditItem?.id || "new")), _jsxs("div", { className: "mt-4", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between", children: [_jsx("h4", { className: "text-xl font-semibold text-gray-900", children: "Line Items" }), _jsxs("div", { className: "hidden md:flex items-center gap-3", children: [_jsxs("button", { onClick: () => setShowTagTable(true), className: "flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors text-sm font-medium text-gray-700", title: "Manage Tags for All Line Items", children: [_jsx(Tag, { className: "w-4 h-4" }), _jsx("span", { className: "hidden md:inline", children: "Manage Tags" })] }), _jsx(RWAButton, { className: "hidden md:block", disabled: isAddingNew, onClick: handleAddClick, children: "Add Line Item" })] })] }), lineItems.length === 0 && !isAddingNew && (_jsx("div", { className: "md:hidden", children: _jsx(LineItemsEmptyState, { onAddItem: () => {
382
+ setMobileEditItem({});
383
+ setShowMobileModal(true);
384
+ } }) })), lineItems.length === 0 && !isAddingNew && (_jsx("div", { className: "hidden md:block", children: _jsx(LineItemsEmptyState, { onAddItem: handleAddClick }) })), lineItems.length > 0 && (_jsxs("div", { className: "md:hidden space-y-3", children: [_jsxs("div", { className: "flex gap-2 mb-4", children: [_jsxs("button", { onClick: () => setShowTagTable(true), className: "flex items-center justify-center gap-2 px-4 py-3 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors font-medium text-gray-700", title: "Manage Tags for All Line Items", children: [_jsx(Tag, { className: "w-4 h-4" }), _jsx("span", { children: "Tags" })] }), _jsx("button", { onClick: () => {
385
+ setMobileEditItem({});
386
+ setShowMobileModal(true);
387
+ }, className: "flex-1 py-3 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors font-medium", children: "Add Line Item" })] }), lineItems.map((item) => (_jsx(LineItemCard, { item: item, currency: currency, onEdit: () => {
388
+ setMobileEditItem(item);
389
+ setShowMobileModal(true);
390
+ }, onDelete: () => {
391
+ const input = { id: item.id };
392
+ onDeleteItem(input);
393
+ } }, item.id)))] })), (lineItems.length > 0 || isAddingNew) && (_jsx("div", { ref: tableContainerRef, className: "hidden md:block overflow-x-auto rounded-lg border border-gray-200", children: _jsxs("table", { ref: tableRef, className: "w-full table-fixed border-collapse bg-white", children: [_jsxs("colgroup", { children: [_jsx("col", { style: { width: "30%" } }), _jsx("col", { style: { width: "10%" } }), _jsx("col", { style: { width: "12%" } }), _jsx("col", { style: { width: "8%" } }), _jsx("col", {}), _jsx("col", {}), _jsx("col", {})] }), _jsx("thead", { children: _jsxs("tr", { className: "bg-gray-50", children: [_jsx("th", { className: "border-b border-gray-200 p-3 text-left", children: "Description" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Quantity" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Unit Price (excl. tax)" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Tax %" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Total (excl. tax)" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-right", children: "Total (incl. tax)" }), _jsx("th", { className: "border-b border-gray-200 p-3 text-center", children: "Actions" })] }) }), _jsxs("tbody", { children: [lineItems.map((item) => editingId === item.id ? (_jsx(EditableLineItem, { currency: currency, item: item, onCancel: () => setEditingId(null), onSave: (updatedItem) => {
394
+ try {
395
+ onUpdateItem(updatedItem);
396
+ setEditingId(null);
397
+ }
398
+ catch (error) {
399
+ console.error(error);
400
+ if (error?.message?.includes("Invalid action input:")) {
401
+ try {
402
+ const zodError = JSON.parse(error.message.split("Invalid action input: ")[1]);
403
+ if (Array.isArray(zodError) &&
404
+ zodError.length > 0) {
405
+ const firstError = zodError[0];
406
+ const errorJSX = (_jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Failed to update line item" }), _jsxs("p", { children: [firstError.message, ": "] }), zodError.map((err, index) => (_jsx("ul", { children: _jsxs("li", { className: "text-red-500 font-semibold", children: ["- ", err.path.join(".")] }) }, index)))] }));
407
+ toast(errorJSX, {
408
+ type: "error",
409
+ });
410
+ return;
411
+ }
412
+ }
413
+ catch (parseError) {
414
+ console.error("Failed to parse Zod error:", parseError);
415
+ toast("Invalid input data", {
370
416
  type: "error",
371
417
  });
372
418
  return;
373
419
  }
374
420
  }
375
- catch (parseError) {
376
- console.error("Failed to parse Zod error:", parseError);
377
- toast("Invalid input data", {
421
+ else if (error?.message) {
422
+ toast(error.message, {
378
423
  type: "error",
379
424
  });
380
425
  return;
381
426
  }
382
- }
383
- else if (error?.message) {
384
- toast(error.message, {
427
+ toast("Failed to update line item", {
385
428
  type: "error",
386
429
  });
387
- return;
388
430
  }
389
- toast("Failed to update line item", {
390
- type: "error",
391
- });
392
- }
393
- }, onEditingItemChange: onEditingItemChange }, item.id)) : (_jsxs("tr", { className: "hover:bg-gray-50 table-row", children: [_jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: item.description }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: item.quantity % 1 === 0
394
- ? item.quantity.toString()
395
- : item.quantity.toFixed(2) }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: formatNumber(item.unitPriceTaxExcl) }), _jsxs("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: [typeof item.taxPercent === "number"
396
- ? Math.round(item.taxPercent)
397
- : 0, "%"] }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxExcl) }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxIncl) }), _jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: _jsxs("div", { className: "flex justify-center space-x-2", children: [_jsx("button", { className: "rounded bg-blue-500 px-3 py-1 text-white hover:bg-blue-200", onClick: () => setEditingId(item.id), children: "Edit" }), _jsx("button", { className: "rounded bg-red-600 px-3 py-1 text-white hover:bg-red-700", onClick: () => onDeleteItem({ id: item.id }), children: "Delete" })] }) })] }, item.id))), isAddingNew ? (_jsx(EditableLineItem, { currency: currency, item: {}, onCancel: handleCancelNewItem, onSave: handleSaveNewItem, onEditingItemChange: onEditingItemChange })) : null] })] }) })] }) }));
431
+ }, onEditingItemChange: onEditingItemChange }, item.id)) : (_jsxs("tr", { className: "hover:bg-gray-50 table-row", children: [_jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: item.description }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: item.quantity % 1 === 0
432
+ ? item.quantity.toString()
433
+ : item.quantity.toFixed(2) }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: formatNumber(item.unitPriceTaxExcl) }), _jsxs("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: [typeof item.taxPercent === "number"
434
+ ? Math.round(item.taxPercent)
435
+ : 0, "%"] }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxExcl) }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxIncl) }), _jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: _jsxs("div", { className: "flex justify-center space-x-2", children: [_jsx("button", { className: "rounded bg-blue-500 px-3 py-1 text-white hover:bg-blue-200", onClick: () => setEditingId(item.id), children: "Edit" }), _jsx("button", { className: "rounded bg-red-600 px-3 py-1 text-white hover:bg-red-700", onClick: () => onDeleteItem({ id: item.id }), children: "Delete" })] }) })] }, item.id))), isAddingNew ? (_jsx(EditableLineItem, { currency: currency, item: {}, onCancel: handleCancelNewItem, onSave: handleSaveNewItem, onEditingItemChange: onEditingItemChange })) : null] })] }) }))] }), lineItems.length > 0 && (_jsxs("div", { className: "md:hidden mt-4 bg-white border border-gray-200 rounded-lg p-4 space-y-2", children: [_jsxs("div", { className: "flex justify-between text-sm", children: [_jsx("span", { className: "text-gray-600", children: "Subtotal (excl. tax):" }), _jsxs("span", { className: "font-medium text-gray-900", children: [currency, " ", formatNumber(totalPriceTaxExcl)] })] }), _jsxs("div", { className: "flex justify-between text-sm", children: [_jsx("span", { className: "text-gray-600", children: "Total tax:" }), _jsxs("span", { className: "font-medium text-gray-900", children: [currency, " ", formatNumber(totalPriceTaxIncl - totalPriceTaxExcl)] })] }), _jsxs("div", { className: "flex justify-between text-base pt-2 border-t border-gray-200", children: [_jsx("span", { className: "font-semibold text-gray-900", children: "Total (incl. tax):" }), _jsxs("span", { className: "font-bold text-gray-900", children: [currency, " ", formatNumber(totalPriceTaxIncl)] })] })] }))] }));
398
436
  }
@@ -3,8 +3,8 @@ import { useState } from "react";
3
3
  import { actions } from "../../document-models/invoice/index.js";
4
4
  import { generateId } from "document-model";
5
5
  let GRAPHQL_URL = "http://localhost:4001/graphql/invoice";
6
- if (!window.document.baseURI.includes('localhost')) {
7
- GRAPHQL_URL = 'https://jetstream.powerhouse.io/api/graphql/invoice';
6
+ if (!window.document.baseURI.includes("localhost")) {
7
+ GRAPHQL_URL = "https://switchboard-dev.powerhouse.xyz/graphql/invoice";
8
8
  }
9
9
  const RequestFinance = ({ docState, dispatch, }) => {
10
10
  const [isLoading, setIsLoading] = useState(false);
@@ -7,7 +7,7 @@
7
7
  */
8
8
  let GRAPHQL_URL = 'http://localhost:4001/graphql/invoice';
9
9
  if (!window.document.baseURI.includes('localhost')) {
10
- GRAPHQL_URL = 'https://jetstream.powerhouse.io/api/graphql/invoice';
10
+ GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
11
11
  }
12
12
  export async function uploadPdfChunked(pdfData, endpoint = GRAPHQL_URL, chunkSize = 500 * 1024, // 500KB chunks
13
13
  onProgress) {
@@ -1,4 +1,4 @@
1
1
  import { ValidationResult } from "./validationManager.js";
2
- declare const validateStatusBeforeContinue: (newStatus: string, state: any, setInvoiceValidation: (validation: ValidationResult) => void, setWalletValidation: (validation: ValidationResult) => void, setCurrencyValidation: (validation: ValidationResult) => void, setMainCountryValidation: (validation: ValidationResult) => void, setBankCountryValidation: (validation: ValidationResult) => void, setIbanValidation: (validation: ValidationResult) => void, setBicValidation: (validation: ValidationResult) => void, setBankNameValidation: (validation: ValidationResult) => void, setStreetAddressValidation: (validation: ValidationResult) => void, setCityValidation: (validation: ValidationResult) => void, setPostalCodeValidation: (validation: ValidationResult) => void, setPayerEmailValidation: (validation: ValidationResult) => void, setLineItemValidation: (validation: ValidationResult) => void, setRoutingNumberValidation: (validation: ValidationResult) => void, isFiatCurrency: (currency: string) => boolean) => boolean | undefined;
2
+ declare const validateStatusBeforeContinue: (newStatus: string, state: any, setInvoiceValidation: (validation: ValidationResult) => void, setWalletValidation: (validation: ValidationResult) => void, setCurrencyValidation: (validation: ValidationResult) => void, setMainCountryValidation: (validation: ValidationResult) => void, setBankCountryValidation: (validation: ValidationResult) => void, setIbanValidation: (validation: ValidationResult) => void, setBicValidation: (validation: ValidationResult) => void, setAccountNumberValidation: (validation: ValidationResult) => void, setBankNameValidation: (validation: ValidationResult) => void, setStreetAddressValidation: (validation: ValidationResult) => void, setCityValidation: (validation: ValidationResult) => void, setPostalCodeValidation: (validation: ValidationResult) => void, setPayerEmailValidation: (validation: ValidationResult) => void, setLineItemValidation: (validation: ValidationResult) => void, setRoutingNumberValidation: (validation: ValidationResult) => void, isFiatCurrency: (currency: string) => boolean) => boolean | undefined;
3
3
  export default validateStatusBeforeContinue;
4
4
  //# sourceMappingURL=validationHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validationHandler.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/validation/validationHandler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAoC,MAAM,wBAAwB,CAAC;AAG5F,QAAA,MAAM,4BAA4B,GAC9B,WAAW,MAAM,EACjB,OAAO,GAAG,EACV,sBAAsB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC5D,qBAAqB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC3D,uBAAuB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC7D,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAChE,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAChE,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EACzD,kBAAkB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EACxD,uBAAuB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC7D,4BAA4B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAClE,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EACzD,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC/D,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC/D,uBAAuB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC7D,4BAA4B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAClE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,wBAmMhD,CAAA;AAED,eAAe,4BAA4B,CAAC"}
1
+ {"version":3,"file":"validationHandler.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/validation/validationHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAoC,MAAM,wBAAwB,CAAC;AAI5F,QAAA,MAAM,4BAA4B,GAC9B,WAAW,MAAM,EACjB,OAAO,GAAG,EACV,sBAAsB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC5D,qBAAqB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC3D,uBAAuB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC7D,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAChE,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAChE,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EACzD,kBAAkB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EACxD,4BAA4B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAClE,uBAAuB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC7D,4BAA4B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAClE,mBAAmB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EACzD,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC/D,yBAAyB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC/D,uBAAuB,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAC7D,4BAA4B,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,EAClE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,wBAqNhD,CAAA;AAED,eAAe,4BAA4B,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { validateField } from "./validationManager.js";
2
2
  import { toast } from "@powerhousedao/design-system";
3
- const validateStatusBeforeContinue = (newStatus, state, setInvoiceValidation, setWalletValidation, setCurrencyValidation, setMainCountryValidation, setBankCountryValidation, setIbanValidation, setBicValidation, setBankNameValidation, setStreetAddressValidation, setCityValidation, setPostalCodeValidation, setPayerEmailValidation, setLineItemValidation, setRoutingNumberValidation, isFiatCurrency) => {
3
+ import { isValidIBAN } from "./validationRules.js";
4
+ const validateStatusBeforeContinue = (newStatus, state, setInvoiceValidation, setWalletValidation, setCurrencyValidation, setMainCountryValidation, setBankCountryValidation, setIbanValidation, setBicValidation, setAccountNumberValidation, setBankNameValidation, setStreetAddressValidation, setCityValidation, setPostalCodeValidation, setPayerEmailValidation, setLineItemValidation, setRoutingNumberValidation, isFiatCurrency) => {
4
5
  if (newStatus === "PAYMENTSCHEDULED" || newStatus === "ISSUED") {
5
6
  const context = {
6
7
  currency: state.currency,
@@ -43,11 +44,25 @@ const validateStatusBeforeContinue = (newStatus, state, setInvoiceValidation, se
43
44
  if (bankCountryValidation && !bankCountryValidation.isValid) {
44
45
  validationErrors.push(bankCountryValidation);
45
46
  }
46
- // Validate EUR&GBP IBAN account number
47
- const ibanValidation = validateField("accountNum", state.issuer.paymentRouting?.bank?.accountNum, context);
48
- setIbanValidation(ibanValidation);
49
- if (ibanValidation && !ibanValidation.isValid) {
50
- validationErrors.push(ibanValidation);
47
+ // Validate account number or IBAN depending on currency to avoid duplicate validation of the same field
48
+ const IBAN_CURRENCIES = ["EUR", "GBP", "DKK"];
49
+ if (IBAN_CURRENCIES.includes(state.currency)) {
50
+ // Only IBAN applies
51
+ const ibanValidation = validateField("accountNum", state.issuer.paymentRouting?.bank?.accountNum, context);
52
+ setIbanValidation(ibanValidation);
53
+ setAccountNumberValidation(null);
54
+ if (ibanValidation && !ibanValidation.isValid) {
55
+ validationErrors.push(ibanValidation);
56
+ }
57
+ }
58
+ else {
59
+ // Generic account number applies
60
+ const accountNumberValidation = validateField("accountNum", state.issuer.paymentRouting?.bank?.accountNum, context);
61
+ setAccountNumberValidation(accountNumberValidation);
62
+ setIbanValidation(null);
63
+ if (accountNumberValidation && !accountNumberValidation.isValid) {
64
+ validationErrors.push(accountNumberValidation);
65
+ }
51
66
  }
52
67
  // Validate BIC/SWIFT number
53
68
  const bicValidation = validateField("bicNumber", state.issuer.paymentRouting?.bank?.BIC || state.issuer.paymentRouting?.bank?.SWIFT, context);
@@ -57,9 +72,10 @@ const validateStatusBeforeContinue = (newStatus, state, setInvoiceValidation, se
57
72
  }
58
73
  // Validate routing number
59
74
  const routingNumberValidation = validateField("routingNumber", state.issuer.paymentRouting?.bank?.ABA, context);
60
- setRoutingNumberValidation(routingNumberValidation);
61
- if (routingNumberValidation && !routingNumberValidation.isValid) {
62
- validationErrors.push(routingNumberValidation);
75
+ const usdIbanPayment = isValidIBAN(state.issuer.paymentRouting?.bank?.accountNum ?? "") && state.currency === "USD";
76
+ setRoutingNumberValidation(usdIbanPayment ? null : routingNumberValidation);
77
+ if (usdIbanPayment ? null : routingNumberValidation && !routingNumberValidation.isValid) {
78
+ validationErrors.push(usdIbanPayment ? { isValid: true, message: '', severity: 'none' } : routingNumberValidation);
63
79
  }
64
80
  // Validate bank name
65
81
  const bankNameValidation = validateField("bankName", state.issuer.paymentRouting?.bank?.name, context);
@@ -1 +1 @@
1
- {"version":3,"file":"validationManager.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/validation/validationManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAInE,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE9D,MAAM,MAAM,gBAAgB,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,kBAAkB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,gBAAgB,CAAC;IAC1D,SAAS,EAAE;QACP,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;QAClC,iBAAiB,EAAE;YACf,IAAI,EAAE,MAAM,EAAE,CAAC;YACf,EAAE,EAAE,MAAM,EAAE,CAAC;SAChB,CAAC;KACL,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACxB,CAAC;AAgCF,wBAAgB,aAAa,CACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,iBAAiB,GAC3B,gBAAgB,GAAG,IAAI,CAqBzB;AAGD,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,OAAO,EAAE,iBAAiB,GAC3B,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAalC;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAE5D;AAGD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAKxD"}
1
+ {"version":3,"file":"validationManager.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/validation/validationManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAoBnE,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE9D,MAAM,MAAM,gBAAgB,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,kBAAkB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,gBAAgB,CAAC;IAC1D,SAAS,EAAE;QACP,UAAU,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;QAClC,iBAAiB,EAAE;YACf,IAAI,EAAE,MAAM,EAAE,CAAC;YACf,EAAE,EAAE,MAAM,EAAE,CAAC;SAChB,CAAC;KACL,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACxB,CAAC;AAiCF,wBAAgB,aAAa,CACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,iBAAiB,GAC3B,gBAAgB,GAAG,IAAI,CAqBzB;AAGD,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,OAAO,EAAE,iBAAiB,GAC3B,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAalC;AAGD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAE5D;AAGD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAKxD"}
@@ -1,4 +1,4 @@
1
- import { accountNumberRule, bicNumberRule, bankNameRule, currencyRule, ethereumAddressRule, invoiceNumberRule, issuerPostalCodeRule, issuerStreetAddressRule, issuerCityRule, payerEmailRule, lineItemRule, mainCountryRule, bankCountryRule, routingNumberRule } from "./validationRules.js";
1
+ import { accountIbanRule, bicNumberRule, bankNameRule, currencyRule, ethereumAddressRule, invoiceNumberRule, issuerPostalCodeRule, issuerStreetAddressRule, issuerCityRule, payerEmailRule, lineItemRule, mainCountryRule, bankCountryRule, routingNumberRule, accountNumberRule } from "./validationRules.js";
2
2
  // Validation rules registry
3
3
  const validationRules = [];
4
4
  // Register rules
@@ -7,7 +7,7 @@ validationRules.push(ethereumAddressRule);
7
7
  validationRules.push(currencyRule);
8
8
  validationRules.push(mainCountryRule);
9
9
  validationRules.push(bankCountryRule);
10
- validationRules.push(accountNumberRule);
10
+ validationRules.push(accountIbanRule);
11
11
  validationRules.push(bicNumberRule);
12
12
  validationRules.push(bankNameRule);
13
13
  validationRules.push(issuerStreetAddressRule);
@@ -16,6 +16,7 @@ validationRules.push(issuerPostalCodeRule);
16
16
  validationRules.push(payerEmailRule);
17
17
  validationRules.push(lineItemRule);
18
18
  validationRules.push(routingNumberRule);
19
+ validationRules.push(accountNumberRule);
19
20
  // Helper to check if a rule applies to the current context
20
21
  function ruleAppliesToContext(rule, context) {
21
22
  const { currencies, statusTransitions } = rule.appliesTo;
@@ -1,9 +1,11 @@
1
1
  import { ValidationRule } from "./validationManager.js";
2
+ export declare function isValidIBAN(iban: string): boolean;
2
3
  export declare const invoiceNumberRule: ValidationRule;
3
4
  export declare const ethereumAddressRule: ValidationRule;
4
5
  export declare const currencyRule: ValidationRule;
5
6
  export declare const mainCountryRule: ValidationRule;
6
7
  export declare const bankCountryRule: ValidationRule;
8
+ export declare const accountIbanRule: ValidationRule;
7
9
  export declare const accountNumberRule: ValidationRule;
8
10
  export declare const bicNumberRule: ValidationRule;
9
11
  export declare const bankNameRule: ValidationRule;
@@ -1 +1 @@
1
- {"version":3,"file":"validationRules.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/validation/validationRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AA8BxD,eAAO,MAAM,iBAAiB,EAAE,cAuB/B,CAAC;AAGF,eAAO,MAAM,mBAAmB,EAAE,cA8BjC,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAuB1B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,cAuB7B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,cAuB7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,cA8B/B,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,cAkC3B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAuB1B,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,cAuBrC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,cAuB5B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,cAuBlC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,cA8B5B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAuB1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,cAqC/B,CAAC"}
1
+ {"version":3,"file":"validationRules.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/validation/validationRules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAQxD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYjD;AAQD,eAAO,MAAM,iBAAiB,EAAE,cAuB/B,CAAC;AAGF,eAAO,MAAM,mBAAmB,EAAE,cA8BjC,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAuB1B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,cAuB7B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,cAuB7B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,cA8B7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,cA+B/B,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,cAkC3B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAuB1B,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,cAuBrC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,cAuB5B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,cAuBlC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,cA8B5B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAuB1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,cAqC/B,CAAC"}
@@ -3,17 +3,15 @@ function isValidEthereumAddress(address) {
3
3
  const ethereumAddressRegex = /^0x[a-fA-F0-9]{40}$/;
4
4
  return ethereumAddressRegex.test(address);
5
5
  }
6
- function isValidIBAN(iban) {
7
- const ibanRegex = /^([A-Z]{2}[0-9]{2})(?=(?:[A-Z0-9]){9,30}$)((?:[A-Z0-9]{3,5}){2,7})([A-Z0-9]{1,3})?$/;
8
- const hasNumbers = /\d/.test(iban);
6
+ export function isValidIBAN(iban) {
7
+ const ibanRegex = /^([A-Z]{2}[-]?[0-9]{2})(?=(?:[ -]?[A-Z0-9]){9,30}$)((?:[ -]?[A-Z0-9]{3,5}){2,7})([-]?[A-Z0-9]{1,3})?$/;
9
8
  // Extract country code from IBAN (first 2 letters)
10
9
  const countryCode = iban.substring(0, 2).toUpperCase();
11
10
  // If IBAN starts with a valid country code (2 letters), validate full IBAN format
12
11
  if (/^[A-Z]{2}$/.test(countryCode)) {
13
12
  return ibanRegex.test(iban);
14
13
  }
15
- // Otherwise just check if it's not empty and has numbers
16
- return iban.trim() !== '' && hasNumbers;
14
+ return false;
17
15
  }
18
16
  function isValidEmail(email) {
19
17
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -148,7 +146,7 @@ export const bankCountryRule = {
148
146
  }
149
147
  }
150
148
  };
151
- export const accountNumberRule = {
149
+ export const accountIbanRule = {
152
150
  field: 'accountNum',
153
151
  validate: (value, document) => {
154
152
  if (!value || value.trim() === '') {
@@ -172,7 +170,39 @@ export const accountNumberRule = {
172
170
  };
173
171
  },
174
172
  appliesTo: {
175
- currencies: ['EUR', 'GBP'],
173
+ currencies: ['EUR', 'GBP', 'DKK'],
174
+ statusTransitions: {
175
+ from: ['DRAFT'],
176
+ to: ['ISSUED']
177
+ }
178
+ }
179
+ };
180
+ export const accountNumberRule = {
181
+ field: 'accountNum',
182
+ validate: (value) => {
183
+ if (!value || value.trim() === '') {
184
+ return {
185
+ isValid: false,
186
+ message: 'Account number is required',
187
+ severity: 'warning'
188
+ };
189
+ }
190
+ // Valid account numbers are 6-25 alphanumeric characters. If it DOES NOT match, it's invalid.
191
+ if (!/^[\da-zA-Z]{6,25}$/.test(value)) {
192
+ return {
193
+ isValid: false,
194
+ message: 'Invalid account number format - For account number, ensure it is 6-25 characters long',
195
+ severity: 'warning'
196
+ };
197
+ }
198
+ return {
199
+ isValid: true,
200
+ message: '',
201
+ severity: 'none'
202
+ };
203
+ },
204
+ appliesTo: {
205
+ currencies: ['USD', 'JPY', 'CNY', 'CHF'],
176
206
  statusTransitions: {
177
207
  from: ['DRAFT'],
178
208
  to: ['ISSUED']
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Creates an expense report CSV that categorizes line items by their tags
3
+ */
4
+ export declare function exportExpenseReportCSV(invoiceStates: any[], baseCurrency: string): Promise<void>;
5
+ //# sourceMappingURL=createExpenseReportCsv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createExpenseReportCsv.d.ts","sourceRoot":"","sources":["../../../scripts/contributor-billing/createExpenseReportCsv.ts"],"names":[],"mappings":"AA+CA;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgGtG"}
@@ -0,0 +1,122 @@
1
+ // Simple in-memory cache for exchange rates
2
+ const exchangeRateCache = {};
3
+ /**
4
+ * Fetches the exchange rate from `from` currency to `to` currency for a given date using Frankfurter API.
5
+ * Returns 1 for unsupported currencies or errors.
6
+ */
7
+ async function getExchangeRate(date, from, to) {
8
+ if (!date || !from || !to || from === to)
9
+ return 1;
10
+ // Convert crypto currencies to USD for API compatibility
11
+ let effectiveFrom = from;
12
+ if (from === 'DAI' || from === 'USDS') {
13
+ effectiveFrom = 'USD';
14
+ }
15
+ let effectiveTo = to;
16
+ if (to === 'DAI' || to === 'USDS') {
17
+ effectiveTo = 'USD';
18
+ }
19
+ const formattedDate = date.split('T')[0];
20
+ const cacheKey = `${formattedDate}|${effectiveFrom}|${effectiveTo}`;
21
+ if (exchangeRateCache[cacheKey] !== undefined) {
22
+ return exchangeRateCache[cacheKey];
23
+ }
24
+ try {
25
+ const url = `https://api.frankfurter.dev/v1/${formattedDate}?base=${effectiveFrom}&symbols=${effectiveTo}`;
26
+ console.log('Fetching from Frankfurter URL:', url);
27
+ const res = await fetch(url);
28
+ if (!res.ok) {
29
+ throw new Error(`Failed to fetch Frankfurter exchange rate: ${res.status} ${res.statusText}`);
30
+ }
31
+ const data = await res.json();
32
+ const rate = data?.rates?.[effectiveTo];
33
+ if (typeof rate !== 'number') {
34
+ throw new Error(`Frankfurter exchange rate for ${effectiveFrom} to ${effectiveTo} on ${formattedDate} not found in response`);
35
+ }
36
+ exchangeRateCache[cacheKey] = rate;
37
+ return rate;
38
+ }
39
+ catch (err) {
40
+ console.error('Frankfurter ForEx API error:', err);
41
+ return 1; // Fallback to 1:1 on error
42
+ }
43
+ }
44
+ /**
45
+ * Creates an expense report CSV that categorizes line items by their tags
46
+ */
47
+ export async function exportExpenseReportCSV(invoiceStates, baseCurrency) {
48
+ // Track invoices missing tags
49
+ const missingTagInvoices = [];
50
+ // Data structure to aggregate expenses by tag label
51
+ const expensesByTag = {};
52
+ // Process each selected invoice
53
+ for (const invoiceState of invoiceStates) {
54
+ const state = invoiceState.state.global;
55
+ const invoiceId = invoiceState.header.id;
56
+ const invoiceName = state.name || invoiceId;
57
+ const items = state.lineItems || [];
58
+ const dateIssued = state.dateIssued;
59
+ const currency = state.currency || 'USD';
60
+ // Check if any line item is missing tags
61
+ const hasMissingTags = items.some((item) => {
62
+ return !item.lineItemTag || item.lineItemTag.length === 0;
63
+ });
64
+ if (hasMissingTags) {
65
+ missingTagInvoices.push(invoiceName);
66
+ continue; // Skip this invoice
67
+ }
68
+ // Get exchange rate for this invoice
69
+ let effectiveCurrency = currency;
70
+ if (currency === 'DAI' || currency === 'USDS') {
71
+ effectiveCurrency = 'USD';
72
+ }
73
+ const exchangeRate = await getExchangeRate(dateIssued, effectiveCurrency, baseCurrency);
74
+ // Aggregate line items by tag
75
+ items.forEach((item) => {
76
+ const lineItemTags = item.lineItemTag || [];
77
+ const lineItemTotal = item.totalPriceTaxIncl || 0;
78
+ const convertedAmount = lineItemTotal * exchangeRate;
79
+ lineItemTags.forEach((tag) => {
80
+ const dimension = tag.dimension || '';
81
+ const label = tag.label || tag.value || 'Unknown';
82
+ // Skip accounting-period dimension
83
+ if (dimension === 'accounting-period') {
84
+ return;
85
+ }
86
+ // Aggregate by tag label in base currency
87
+ if (!expensesByTag[label]) {
88
+ expensesByTag[label] = 0;
89
+ }
90
+ expensesByTag[label] += convertedAmount;
91
+ });
92
+ });
93
+ }
94
+ // If any invoices are missing tags, throw an error
95
+ if (missingTagInvoices.length > 0) {
96
+ throw {
97
+ message: `The following invoices have line items missing tags: ${[...new Set(missingTagInvoices)].join(', ')}`,
98
+ missingTagInvoices: missingTagInvoices
99
+ };
100
+ }
101
+ // Create CSV headers
102
+ const headers = ['Tag Label', 'Currency', 'Total Amount'];
103
+ // Convert aggregated data to rows
104
+ const expenseRows = Object.entries(expensesByTag)
105
+ .sort(([labelA], [labelB]) => labelA.localeCompare(labelB))
106
+ .map(([label, total]) => [label, baseCurrency, total.toFixed(2)]);
107
+ // Combine headers and data rows
108
+ const allRows = [headers, ...expenseRows];
109
+ // Convert to CSV format
110
+ const csvLines = allRows.map((row) => row.map((value) => `"${value}"`).join(','));
111
+ const csvData = csvLines.join('\n');
112
+ // Download CSV file (browser)
113
+ const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' });
114
+ const link = document.createElement('a');
115
+ const url = URL.createObjectURL(blob);
116
+ link.setAttribute('href', url);
117
+ link.setAttribute('download', `expense-report-by-tag-${new Date().toISOString().split('T')[0]}.csv`);
118
+ link.style.visibility = 'hidden';
119
+ document.body.appendChild(link);
120
+ link.click();
121
+ document.body.removeChild(link);
122
+ }