@powerhousedao/contributor-billing 0.0.65 → 0.0.66
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/dist/document-models/invoice/src/reducers/items.js +51 -0
- package/dist/editors/invoice/editor.d.ts.map +1 -1
- package/dist/editors/invoice/editor.js +28 -7
- package/dist/editors/invoice/ingestPDF.js +1 -1
- package/dist/editors/invoice/invoiceToGnosis.d.ts.map +1 -1
- package/dist/editors/invoice/invoiceToGnosis.js +42 -12
- package/dist/editors/invoice/lineItems.d.ts +7 -1
- package/dist/editors/invoice/lineItems.d.ts.map +1 -1
- package/dist/editors/invoice/lineItems.js +191 -56
- package/dist/editors/invoice/requestFinance.d.ts.map +1 -1
- package/dist/editors/invoice/requestFinance.js +39 -14
- package/dist/editors/invoice/uploadPdfChunked.js +1 -1
- package/dist/scripts/invoice/gnosisTransactionBuilder.js +1 -1
- package/dist/style.css +6 -0
- package/package.json +1 -1
|
@@ -35,6 +35,7 @@ export const reducer = {
|
|
|
35
35
|
...sanitizedInput,
|
|
36
36
|
};
|
|
37
37
|
validatePrices(nextItem);
|
|
38
|
+
applyInvariants(state, nextItem);
|
|
38
39
|
Object.assign(stateItem, nextItem);
|
|
39
40
|
updateTotals(state);
|
|
40
41
|
}
|
|
@@ -142,3 +143,53 @@ function validatePrices(item) {
|
|
|
142
143
|
throw new Error("Tax inclusive/exclusive totals failed comparison.");
|
|
143
144
|
}
|
|
144
145
|
}
|
|
146
|
+
const applyInvariants = (state, nextItem) => {
|
|
147
|
+
const EPSILON = 0.00001; // Small value for floating point comparisons
|
|
148
|
+
// Helper function to compare floating point numbers
|
|
149
|
+
const isClose = (a, b) => Math.abs(a - b) < EPSILON;
|
|
150
|
+
// Helper function to check if a value has changed significantly
|
|
151
|
+
const hasChanged = (oldValue, newValue) => !isClose(oldValue, newValue);
|
|
152
|
+
// Find the current state of this line item
|
|
153
|
+
const currentItem = state.lineItems.find(item => item.id === nextItem.id);
|
|
154
|
+
if (!currentItem) {
|
|
155
|
+
// New item, no comparison needed
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const taxRate = nextItem.taxPercent / 100;
|
|
159
|
+
// Check if totalPriceTaxExcl was changed and update unitPriceTaxExcl accordingly
|
|
160
|
+
const expectedTotalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
|
|
161
|
+
if (hasChanged(expectedTotalPriceTaxExcl, nextItem.totalPriceTaxExcl)) {
|
|
162
|
+
// Total was changed, update unit price
|
|
163
|
+
nextItem.unitPriceTaxExcl = nextItem.totalPriceTaxExcl / nextItem.quantity;
|
|
164
|
+
// Update tax-inclusive unit price to maintain tax relationship
|
|
165
|
+
nextItem.unitPriceTaxIncl = nextItem.unitPriceTaxExcl * (1 + taxRate);
|
|
166
|
+
// Update tax-inclusive total to maintain consistency
|
|
167
|
+
nextItem.totalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
|
|
168
|
+
}
|
|
169
|
+
// Check if totalPriceTaxIncl was changed and update unitPriceTaxIncl accordingly
|
|
170
|
+
const expectedTotalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
|
|
171
|
+
if (hasChanged(expectedTotalPriceTaxIncl, nextItem.totalPriceTaxIncl)) {
|
|
172
|
+
// Total was changed, update unit price
|
|
173
|
+
nextItem.unitPriceTaxIncl = nextItem.totalPriceTaxIncl / nextItem.quantity;
|
|
174
|
+
// Update tax-exclusive unit price to maintain tax relationship
|
|
175
|
+
nextItem.unitPriceTaxExcl = nextItem.unitPriceTaxIncl / (1 + taxRate);
|
|
176
|
+
// Update tax-exclusive total to maintain consistency
|
|
177
|
+
nextItem.totalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
|
|
178
|
+
}
|
|
179
|
+
// Check if unitPriceTaxExcl was changed and update totals accordingly
|
|
180
|
+
const expectedUnitPriceTaxIncl = nextItem.unitPriceTaxExcl * (1 + taxRate);
|
|
181
|
+
if (hasChanged(expectedUnitPriceTaxIncl, nextItem.unitPriceTaxIncl)) {
|
|
182
|
+
// Unit price was changed, update tax-inclusive unit price and totals
|
|
183
|
+
nextItem.unitPriceTaxIncl = nextItem.unitPriceTaxExcl * (1 + taxRate);
|
|
184
|
+
nextItem.totalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
|
|
185
|
+
nextItem.totalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
|
|
186
|
+
}
|
|
187
|
+
// Check if unitPriceTaxIncl was changed and update totals accordingly
|
|
188
|
+
const expectedUnitPriceTaxExcl = nextItem.unitPriceTaxIncl / (1 + taxRate);
|
|
189
|
+
if (hasChanged(expectedUnitPriceTaxExcl, nextItem.unitPriceTaxExcl)) {
|
|
190
|
+
// Unit price was changed, update tax-exclusive unit price and totals
|
|
191
|
+
nextItem.unitPriceTaxExcl = nextItem.unitPriceTaxIncl / (1 + taxRate);
|
|
192
|
+
nextItem.totalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
|
|
193
|
+
nextItem.totalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/invoice/editor.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,wCAAwC,CAAC;AA6ChD,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;AAElD,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/invoice/editor.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,wCAAwC,CAAC;AA6ChD,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;AAElD,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,EAAE,MAAM,2CAu2B3C"}
|
|
@@ -62,6 +62,7 @@ export default function Editor(props) {
|
|
|
62
62
|
const [txnRef, setTxnRef] = useState("");
|
|
63
63
|
const [paymentIssue, setPaymentIssue] = useState("");
|
|
64
64
|
const [paymentAmount, setPaymentAmount] = useState("");
|
|
65
|
+
const [editingItemValues, setEditingItemValues] = useState(null);
|
|
65
66
|
const prevStatus = useRef(state.status);
|
|
66
67
|
useEffect(() => {
|
|
67
68
|
setFiatMode(isFiatCurrency(state.currency));
|
|
@@ -84,15 +85,35 @@ export default function Editor(props) {
|
|
|
84
85
|
};
|
|
85
86
|
}, []);
|
|
86
87
|
const itemsTotalTaxExcl = useMemo(() => {
|
|
87
|
-
|
|
88
|
-
return
|
|
88
|
+
let total = state.lineItems.reduce((sum, lineItem) => {
|
|
89
|
+
return sum + lineItem.quantity * lineItem.unitPriceTaxExcl;
|
|
89
90
|
}, 0.0);
|
|
90
|
-
|
|
91
|
+
// If there's an item being edited, replace its contribution with the edited values
|
|
92
|
+
if (editingItemValues) {
|
|
93
|
+
const originalItem = state.lineItems.find(item => item.id === editingItemValues.id);
|
|
94
|
+
if (originalItem) {
|
|
95
|
+
// Subtract the original contribution and add the edited contribution
|
|
96
|
+
total = total - (originalItem.quantity * originalItem.unitPriceTaxExcl) +
|
|
97
|
+
(editingItemValues.quantity * editingItemValues.unitPriceTaxExcl);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return total;
|
|
101
|
+
}, [state.lineItems, editingItemValues]);
|
|
91
102
|
const itemsTotalTaxIncl = useMemo(() => {
|
|
92
|
-
|
|
93
|
-
return
|
|
103
|
+
let total = state.lineItems.reduce((sum, lineItem) => {
|
|
104
|
+
return sum + lineItem.quantity * lineItem.unitPriceTaxIncl;
|
|
94
105
|
}, 0.0);
|
|
95
|
-
|
|
106
|
+
// If there's an item being edited, replace its contribution with the edited values
|
|
107
|
+
if (editingItemValues) {
|
|
108
|
+
const originalItem = state.lineItems.find(item => item.id === editingItemValues.id);
|
|
109
|
+
if (originalItem) {
|
|
110
|
+
// Subtract the original contribution and add the edited contribution
|
|
111
|
+
total = total - (originalItem.quantity * originalItem.unitPriceTaxIncl) +
|
|
112
|
+
(editingItemValues.quantity * editingItemValues.unitPriceTaxIncl);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return total;
|
|
116
|
+
}, [state.lineItems, editingItemValues]);
|
|
96
117
|
const STATUS_OPTIONS = [
|
|
97
118
|
"DRAFT",
|
|
98
119
|
"ISSUED",
|
|
@@ -354,7 +375,7 @@ export default function Editor(props) {
|
|
|
354
375
|
})), onAddItem: (item) => dispatch(actions.addLineItem(item)), onDeleteItem: (input) => dispatch(actions.deleteLineItem(input)), onUpdateCurrency: (input) => {
|
|
355
376
|
setFiatMode(input.currency !== "USDS");
|
|
356
377
|
dispatch(actions.editInvoice(input));
|
|
357
|
-
}, onUpdateItem: (item) => dispatch(actions.editLineItem(item)), dispatch: dispatch, paymentAccounts: state.invoiceTags || [] }) }), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-2 gap-4", children: [_jsx("div", { className: "col-span-1", children: _jsx("div", { className: "", children: _jsx(Textarea, { label: "Notes", placeholder: "Add notes", autoExpand: false, rows: 4, multiline: true, value: notes, onBlur: (e) => {
|
|
378
|
+
}, onUpdateItem: (item) => dispatch(actions.editLineItem(item)), onEditingItemChange: setEditingItemValues, dispatch: dispatch, paymentAccounts: state.invoiceTags || [] }) }), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-2 gap-4", children: [_jsx("div", { className: "col-span-1", children: _jsx("div", { className: "", children: _jsx(Textarea, { label: "Notes", placeholder: "Add notes", autoExpand: false, rows: 4, multiline: true, value: notes, onBlur: (e) => {
|
|
358
379
|
const newValue = e.target.value;
|
|
359
380
|
if (newValue !== state.notes) {
|
|
360
381
|
dispatch(actions.editInvoice({ notes: newValue }));
|
|
@@ -6,7 +6,7 @@ import { uploadPdfChunked } from "./uploadPdfChunked.js";
|
|
|
6
6
|
import { getCountryCodeFromName } from "./utils/utils.js";
|
|
7
7
|
let GRAPHQL_URL = 'http://localhost:4001/graphql/invoice';
|
|
8
8
|
if (!window.document.baseURI.includes('localhost')) {
|
|
9
|
-
GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
|
|
9
|
+
GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
|
|
10
10
|
}
|
|
11
11
|
export async function loadPDFFile({ file, dispatch, }) {
|
|
12
12
|
if (!file)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoiceToGnosis.d.ts","sourceRoot":"","sources":["../../../editors/invoice/invoiceToGnosis.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"invoiceToGnosis.d.ts","sourceRoot":"","sources":["../../../editors/invoice/invoiceToGnosis.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,GAAG,CAAC;IACd,QAAQ,EAAE,GAAG,CAAC;CACf;AAED,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAmQnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -1,12 +1,12 @@
|
|
|
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
|
import { useState } from "react";
|
|
3
|
-
import { actions
|
|
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(
|
|
7
|
-
GRAPHQL_URL =
|
|
6
|
+
if (!window.document.baseURI.includes("localhost")) {
|
|
7
|
+
GRAPHQL_URL = "https://switchboard-dev.powerhouse.xyz/graphql/invoice";
|
|
8
8
|
}
|
|
9
|
-
const InvoiceToGnosis = ({ docState, dispatch }) => {
|
|
9
|
+
const InvoiceToGnosis = ({ docState, dispatch, }) => {
|
|
10
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
11
11
|
const [error, setError] = useState(null);
|
|
12
12
|
const [responseData, setResponseData] = useState(null);
|
|
@@ -14,6 +14,7 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
|
|
|
14
14
|
const [safeTxHash, setsafeTxHash] = useState(null);
|
|
15
15
|
const currency = docState.currency;
|
|
16
16
|
const chainName = docState.issuer.paymentRouting.wallet.chainName;
|
|
17
|
+
const invoiceStatus = docState.status;
|
|
17
18
|
const TOKEN_ADDRESSES = {
|
|
18
19
|
BASE: {
|
|
19
20
|
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -92,11 +93,19 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
|
|
|
92
93
|
if (data.success) {
|
|
93
94
|
const dataObj = typeof data.data === "string" ? JSON.parse(data.data) : data.data;
|
|
94
95
|
setsafeTxHash(dataObj.txHash);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
if (invoiceStatus === "ACCEPTED") {
|
|
97
|
+
dispatch(actions.schedulePayment({
|
|
98
|
+
id: generateId(),
|
|
99
|
+
processorRef: dataObj.txHash,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
dispatch(actions.addPayment({
|
|
104
|
+
id: generateId(),
|
|
105
|
+
processorRef: dataObj.txHash,
|
|
106
|
+
confirmed: false,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
100
109
|
if (dataObj.paymentDetails) {
|
|
101
110
|
// Format the payment details for better readability
|
|
102
111
|
const formattedDetails = {
|
|
@@ -109,12 +118,24 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
|
|
|
109
118
|
}
|
|
110
119
|
else {
|
|
111
120
|
setError(data.error);
|
|
121
|
+
dispatch(actions.addPayment({
|
|
122
|
+
id: generateId(),
|
|
123
|
+
processorRef: "",
|
|
124
|
+
confirmed: false,
|
|
125
|
+
issue: data.error.message,
|
|
126
|
+
}));
|
|
112
127
|
}
|
|
113
128
|
setIsLoading(false);
|
|
114
129
|
}
|
|
115
130
|
catch (error) {
|
|
116
|
-
console.error("Error during transfer:", error);
|
|
117
131
|
setIsLoading(false);
|
|
132
|
+
console.error("Error during transfer:", error);
|
|
133
|
+
dispatch(actions.addPayment({
|
|
134
|
+
id: generateId(),
|
|
135
|
+
processorRef: "",
|
|
136
|
+
confirmed: false,
|
|
137
|
+
issue: error.message,
|
|
138
|
+
}));
|
|
118
139
|
}
|
|
119
140
|
};
|
|
120
141
|
if (!currency || !chainName || currency === "" || chainName === "") {
|
|
@@ -131,6 +152,15 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
|
|
|
131
152
|
}
|
|
132
153
|
};
|
|
133
154
|
const urlChainName = parseChainName(chainName);
|
|
134
|
-
|
|
155
|
+
const linkText = "Gnosis Safe [>]";
|
|
156
|
+
return (_jsxs("div", { className: "space-y-4", children: [currency && chainName && currency !== "" && chainName !== "" && (_jsx("button", { className: "bg-blue-500 text-black px-4 py-2 rounded-md hover:bg-blue-600", onClick: handleInvoiceToGnosis, disabled: isLoading, children: isLoading
|
|
157
|
+
? "Processing..."
|
|
158
|
+
: invoiceStatus === "ACCEPTED"
|
|
159
|
+
? "Schedule Payment in Gnosis Safe"
|
|
160
|
+
: "Reschedule Payment in Gnosis Safe" })), error && (_jsx("div", { className: "text-red-500 bg-red-50 p-3 rounded-md", children: error })), safeTxHash && (_jsxs("div", { className: "bg-gray-50 mt-4 rounded-md space-y-2", children: [_jsx("a", { href: `https://app.safe.global/transactions/queue?safe=${urlChainName}:0x1FB6bEF04230d67aF0e3455B997a28AFcCe1F45e`, target: "_blank", rel: "noopener noreferrer", className: "text-blue-500 hover:text-blue-600 underline block", children: linkText }), _jsxs("p", { className: "font-medium", children: ["Safe Transaction Hash:", _jsx("span", { className: "font-mono text-sm ml-2 break-all", children: safeTxHash })] })] })), !safeTxHash &&
|
|
161
|
+
!error &&
|
|
162
|
+
invoiceStatus === "PAYMENTSCHEDULED" &&
|
|
163
|
+
docState.payments.length > 0 && (_jsx(_Fragment, { children: docState.payments[docState.payments.length - 1].issue !== "" ? (_jsx("div", { className: "mt-4", children: _jsxs("p", { className: "text-red-700 font-medium", children: ["Issue: ", docState.payments[docState.payments.length - 1].issue] }) })) : (_jsxs("div", { className: "mt-4", children: [_jsx("div", { className: "invoice-link text-blue-900 hover:text-blue-600", children: _jsx("a", { className: "view-invoice-button", href: `https://app.safe.global/transactions/queue?safe=${urlChainName}:0x1FB6bEF04230d67aF0e3455B997a28AFcCe1F45e`, target: "_blank", rel: "noopener noreferrer", children: linkText }) }), _jsxs("p", { className: "mt-4 font-medium", children: ["Safe Transaction Hash:", _jsx("span", { className: "font-mono text-sm ml-2 break-all", children: docState.payments[docState.payments.length - 1]
|
|
164
|
+
.processorRef })] })] })) })), invoiceStatusResponse && (_jsxs("div", { className: "bg-gray-50 p-4 rounded-md", children: [_jsx("p", { className: "font-medium", children: "Payment Details:" }), _jsx("p", { className: "text-gray-700", children: invoiceStatusResponse })] }))] }));
|
|
135
165
|
};
|
|
136
166
|
export default InvoiceToGnosis;
|
|
@@ -20,9 +20,15 @@ type LineItemsTableProps = {
|
|
|
20
20
|
readonly onUpdateItem: (item: LineItem) => void;
|
|
21
21
|
readonly onDeleteItem: (input: DeleteLineItemInput) => void;
|
|
22
22
|
readonly onUpdateCurrency: (input: EditInvoiceInput) => void;
|
|
23
|
+
readonly onEditingItemChange?: (values: {
|
|
24
|
+
id: string;
|
|
25
|
+
quantity: number;
|
|
26
|
+
unitPriceTaxExcl: number;
|
|
27
|
+
unitPriceTaxIncl: number;
|
|
28
|
+
} | null) => void;
|
|
23
29
|
readonly dispatch: Dispatch<any>;
|
|
24
30
|
readonly paymentAccounts: InvoiceTag[];
|
|
25
31
|
};
|
|
26
|
-
export declare function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, dispatch, paymentAccounts, }: LineItemsTableProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export declare function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, onEditingItemChange, dispatch, paymentAccounts, }: LineItemsTableProps): import("react/jsx-runtime").JSX.Element;
|
|
27
33
|
export {};
|
|
28
34
|
//# sourceMappingURL=lineItems.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lineItems.d.ts","sourceRoot":"","sources":["../../../editors/invoice/lineItems.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lineItems.d.ts","sourceRoot":"","sources":["../../../editors/invoice/lineItems.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAChB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,OAAO,CAAC;AAYf,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,KAAK,QAAQ,GAAG;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAC;AA+aF,KAAK,mBAAmB,GAAG;IACzB,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAC7B,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,GAAG,IAAI,KACL,IAAI,CAAC;IACV,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,UAAU,EAAE,CAAC;CACxC,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACR,eAAe,GAChB,EAAE,mBAAmB,2CA+LrB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { RWAButton } from "@powerhousedao/design-system";
|
|
3
|
-
import { forwardRef, useState, useMemo, useRef, } from "react";
|
|
3
|
+
import { forwardRef, useState, useMemo, useRef, useEffect, } from "react";
|
|
4
4
|
import { generateId } from "document-model";
|
|
5
5
|
import { Tag } from "lucide-react";
|
|
6
6
|
import { NumberForm } from "./components/numberForm.js";
|
|
@@ -11,33 +11,21 @@ function getCurrencyPrecision(currency) {
|
|
|
11
11
|
return currency === "USDS" || currency === "DAI" ? 6 : 2;
|
|
12
12
|
}
|
|
13
13
|
export function formatNumber(value) {
|
|
14
|
-
// Check if the value has decimal places
|
|
15
|
-
const hasDecimals = value % 1 !== 0;
|
|
16
|
-
// If no decimals or only trailing zeros after 2 decimal places, show 2 decimal places
|
|
17
|
-
if (!hasDecimals || value.toFixed(5).endsWith("000")) {
|
|
18
|
-
return value.toLocaleString("en-US", {
|
|
19
|
-
minimumFractionDigits: 2,
|
|
20
|
-
maximumFractionDigits: 2,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
// Otherwise, show actual decimal places up to 5
|
|
24
|
-
const stringValue = value.toString();
|
|
25
|
-
const decimalPart = stringValue.split(".")[1] || "";
|
|
26
|
-
// Determine how many decimal places to show (up to 5)
|
|
27
|
-
const decimalPlaces = Math.min(Math.max(2, decimalPart.length), 5);
|
|
28
14
|
return value.toLocaleString("en-US", {
|
|
29
|
-
minimumFractionDigits:
|
|
30
|
-
maximumFractionDigits:
|
|
15
|
+
minimumFractionDigits: 2,
|
|
16
|
+
maximumFractionDigits: 2,
|
|
31
17
|
});
|
|
32
18
|
}
|
|
33
19
|
const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
|
|
34
|
-
const { item, onSave, onCancel, currency } = props;
|
|
20
|
+
const { item, onSave, onCancel, currency, onEditingItemChange } = props;
|
|
35
21
|
const [editedItem, setEditedItem] = useState({
|
|
36
22
|
...item,
|
|
37
23
|
currency,
|
|
38
24
|
quantity: item.quantity ?? "",
|
|
39
25
|
taxPercent: item.taxPercent ?? "",
|
|
40
26
|
unitPriceTaxExcl: item.unitPriceTaxExcl ?? "",
|
|
27
|
+
totalPriceTaxExcl: item.totalPriceTaxExcl ?? "",
|
|
28
|
+
totalPriceTaxIncl: item.totalPriceTaxIncl ?? "",
|
|
41
29
|
});
|
|
42
30
|
const calculatedValues = useMemo(() => {
|
|
43
31
|
const quantity = typeof editedItem.quantity === "string"
|
|
@@ -55,16 +43,103 @@ const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
|
|
|
55
43
|
? 0
|
|
56
44
|
: Number(editedItem.taxPercent)
|
|
57
45
|
: (editedItem.taxPercent ?? 0);
|
|
58
|
-
const totalPriceTaxExcl =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
46
|
+
const totalPriceTaxExcl = typeof editedItem.totalPriceTaxExcl === "string"
|
|
47
|
+
? editedItem.totalPriceTaxExcl === ""
|
|
48
|
+
? 0
|
|
49
|
+
: Number(editedItem.totalPriceTaxExcl)
|
|
50
|
+
: (editedItem.totalPriceTaxExcl ?? 0);
|
|
51
|
+
const totalPriceTaxIncl = typeof editedItem.totalPriceTaxIncl === "string"
|
|
52
|
+
? editedItem.totalPriceTaxIncl === ""
|
|
53
|
+
? 0
|
|
54
|
+
: Number(editedItem.totalPriceTaxIncl)
|
|
55
|
+
: (editedItem.totalPriceTaxIncl ?? 0);
|
|
56
|
+
const taxRate = taxPercent / 100;
|
|
57
|
+
const EPSILON = 0.00001;
|
|
58
|
+
// Helper function to compare floating point numbers
|
|
59
|
+
const isClose = (a, b) => Math.abs(a - b) < EPSILON;
|
|
60
|
+
// Check if user explicitly edited any fields
|
|
61
|
+
const userEditedQuantity = editedItem.quantity !== undefined &&
|
|
62
|
+
editedItem.quantity !== item.quantity;
|
|
63
|
+
const userEditedUnitPriceTaxExcl = editedItem.unitPriceTaxExcl !== undefined &&
|
|
64
|
+
editedItem.unitPriceTaxExcl !== item.unitPriceTaxExcl;
|
|
65
|
+
const userEditedTotalPriceTaxExcl = editedItem.totalPriceTaxExcl !== undefined &&
|
|
66
|
+
editedItem.totalPriceTaxExcl !== item.totalPriceTaxExcl;
|
|
67
|
+
const userEditedTotalPriceTaxIncl = editedItem.totalPriceTaxIncl !== undefined &&
|
|
68
|
+
editedItem.totalPriceTaxIncl !== item.totalPriceTaxIncl;
|
|
69
|
+
let finalUnitPriceTaxExcl = unitPriceTaxExcl;
|
|
70
|
+
let finalUnitPriceTaxIncl = unitPriceTaxExcl * (1 + taxRate);
|
|
71
|
+
let finalTotalPriceTaxExcl = quantity * unitPriceTaxExcl;
|
|
72
|
+
let finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
|
|
73
|
+
// If user explicitly edited totalPriceTaxExcl, use that value and calculate unit price
|
|
74
|
+
if (userEditedTotalPriceTaxExcl && totalPriceTaxExcl !== 0) {
|
|
75
|
+
finalTotalPriceTaxExcl = totalPriceTaxExcl;
|
|
76
|
+
finalUnitPriceTaxExcl = totalPriceTaxExcl / quantity;
|
|
77
|
+
finalUnitPriceTaxIncl = finalUnitPriceTaxExcl * (1 + taxRate);
|
|
78
|
+
finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
|
|
79
|
+
}
|
|
80
|
+
// If user explicitly edited totalPriceTaxIncl, use that value and calculate unit price
|
|
81
|
+
else if (userEditedTotalPriceTaxIncl && totalPriceTaxIncl !== 0) {
|
|
82
|
+
finalTotalPriceTaxIncl = totalPriceTaxIncl;
|
|
83
|
+
finalUnitPriceTaxIncl = totalPriceTaxIncl / quantity;
|
|
84
|
+
finalUnitPriceTaxExcl = finalUnitPriceTaxIncl / (1 + taxRate);
|
|
85
|
+
finalTotalPriceTaxExcl = quantity * finalUnitPriceTaxExcl;
|
|
86
|
+
}
|
|
87
|
+
// If user explicitly edited unitPriceTaxExcl, use that value and calculate totals
|
|
88
|
+
else if (userEditedUnitPriceTaxExcl && unitPriceTaxExcl !== 0) {
|
|
89
|
+
finalUnitPriceTaxExcl = unitPriceTaxExcl;
|
|
90
|
+
finalUnitPriceTaxIncl = unitPriceTaxExcl * (1 + taxRate);
|
|
91
|
+
finalTotalPriceTaxExcl = quantity * finalUnitPriceTaxExcl;
|
|
92
|
+
finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
|
|
93
|
+
}
|
|
94
|
+
// If user explicitly edited quantity, recalculate totals
|
|
95
|
+
else if (userEditedQuantity) {
|
|
96
|
+
finalTotalPriceTaxExcl = quantity * finalUnitPriceTaxExcl;
|
|
97
|
+
finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
|
|
98
|
+
}
|
|
62
99
|
return {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
100
|
+
quantity: quantity,
|
|
101
|
+
taxPercent: taxPercent,
|
|
102
|
+
totalPriceTaxExcl: finalTotalPriceTaxExcl,
|
|
103
|
+
totalPriceTaxIncl: finalTotalPriceTaxIncl,
|
|
104
|
+
unitPriceTaxIncl: finalUnitPriceTaxIncl,
|
|
105
|
+
unitPriceTaxExcl: finalUnitPriceTaxExcl,
|
|
66
106
|
};
|
|
67
|
-
}, [
|
|
107
|
+
}, [
|
|
108
|
+
editedItem.quantity,
|
|
109
|
+
editedItem.unitPriceTaxExcl,
|
|
110
|
+
editedItem.taxPercent,
|
|
111
|
+
editedItem.totalPriceTaxExcl,
|
|
112
|
+
editedItem.totalPriceTaxIncl,
|
|
113
|
+
item.quantity,
|
|
114
|
+
item.unitPriceTaxExcl,
|
|
115
|
+
item.totalPriceTaxExcl,
|
|
116
|
+
item.totalPriceTaxIncl,
|
|
117
|
+
]);
|
|
118
|
+
// Update parent component with current editing values for live totals
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (onEditingItemChange && item.id) {
|
|
121
|
+
onEditingItemChange({
|
|
122
|
+
id: item.id,
|
|
123
|
+
quantity: calculatedValues.quantity,
|
|
124
|
+
unitPriceTaxExcl: calculatedValues.unitPriceTaxExcl,
|
|
125
|
+
unitPriceTaxIncl: calculatedValues.unitPriceTaxIncl,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}, [
|
|
129
|
+
calculatedValues.quantity,
|
|
130
|
+
calculatedValues.unitPriceTaxExcl,
|
|
131
|
+
calculatedValues.unitPriceTaxIncl,
|
|
132
|
+
onEditingItemChange,
|
|
133
|
+
item.id,
|
|
134
|
+
]);
|
|
135
|
+
// Clean up when component unmounts
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
return () => {
|
|
138
|
+
if (onEditingItemChange && item.id) {
|
|
139
|
+
onEditingItemChange(null);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}, [onEditingItemChange, item.id]);
|
|
68
143
|
function handleInputChange(field) {
|
|
69
144
|
return function handleChange(event) {
|
|
70
145
|
const value = event.target.value;
|
|
@@ -99,6 +174,24 @@ const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
|
|
|
99
174
|
setEditedItem((prev) => ({ ...prev, [field]: value }));
|
|
100
175
|
}
|
|
101
176
|
}
|
|
177
|
+
else if (field === "totalPriceTaxExcl") {
|
|
178
|
+
// For total price, allow up to dynamic decimal places based on currency
|
|
179
|
+
const maxDecimals = getCurrencyPrecision(currency);
|
|
180
|
+
// Allow negative numbers with optional minus sign at start
|
|
181
|
+
const regex = new RegExp(`^-?\\d*\\.?\\d{0,${maxDecimals}}$`);
|
|
182
|
+
if (regex.test(value)) {
|
|
183
|
+
setEditedItem((prev) => ({ ...prev, [field]: Number(value) }));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (field === "totalPriceTaxIncl") {
|
|
187
|
+
// For total price, allow up to dynamic decimal places based on currency
|
|
188
|
+
const maxDecimals = getCurrencyPrecision(currency);
|
|
189
|
+
// Allow negative numbers with optional minus sign at start
|
|
190
|
+
const regex = new RegExp(`^-?\\d*\\.?\\d{0,${maxDecimals}}$`);
|
|
191
|
+
if (regex.test(value)) {
|
|
192
|
+
setEditedItem((prev) => ({ ...prev, [field]: value }));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
102
195
|
else {
|
|
103
196
|
// For other decimal fields
|
|
104
197
|
if (/^-?\d*\.?\d*$/.test(value)) {
|
|
@@ -108,40 +201,82 @@ const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
|
|
|
108
201
|
};
|
|
109
202
|
}
|
|
110
203
|
function handleSave() {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
: (editedItem.quantity ?? 0);
|
|
116
|
-
const unitPriceTaxExcl = typeof editedItem.unitPriceTaxExcl === "string"
|
|
117
|
-
? editedItem.unitPriceTaxExcl === ""
|
|
118
|
-
? 0
|
|
119
|
-
: Number(editedItem.unitPriceTaxExcl)
|
|
120
|
-
: (editedItem.unitPriceTaxExcl ?? 0);
|
|
121
|
-
const taxPercent = typeof editedItem.taxPercent === "string"
|
|
122
|
-
? editedItem.taxPercent === ""
|
|
123
|
-
? 0
|
|
124
|
-
: Number(editedItem.taxPercent)
|
|
125
|
-
: (editedItem.taxPercent ?? 0);
|
|
126
|
-
const completeItem = {
|
|
204
|
+
// Helper function for floating point comparison
|
|
205
|
+
const isClose = (a, b) => Math.abs(a - b) < 0.00001;
|
|
206
|
+
// Create an object with only the fields that have changed
|
|
207
|
+
const updateInput = {
|
|
127
208
|
id: editedItem.id ?? generateId(),
|
|
128
|
-
currency
|
|
129
|
-
description: editedItem.description ?? "",
|
|
130
|
-
quantity: quantity,
|
|
131
|
-
taxPercent: taxPercent,
|
|
132
|
-
unitPriceTaxExcl: unitPriceTaxExcl,
|
|
133
|
-
unitPriceTaxIncl: calculatedValues.unitPriceTaxIncl,
|
|
134
|
-
totalPriceTaxExcl: calculatedValues.totalPriceTaxExcl,
|
|
135
|
-
totalPriceTaxIncl: calculatedValues.totalPriceTaxIncl,
|
|
136
|
-
lineItemTag: [],
|
|
209
|
+
currency,
|
|
137
210
|
};
|
|
138
|
-
|
|
211
|
+
// Check if any field was explicitly edited by the user
|
|
212
|
+
const hasEditedDescription = editedItem.description !== undefined &&
|
|
213
|
+
editedItem.description !== item.description;
|
|
214
|
+
const hasEditedQuantity = editedItem.quantity !== undefined &&
|
|
215
|
+
editedItem.quantity !== item.quantity;
|
|
216
|
+
const hasEditedTaxPercent = editedItem.taxPercent !== undefined &&
|
|
217
|
+
editedItem.taxPercent !== item.taxPercent;
|
|
218
|
+
const hasEditedUnitPriceTaxExcl = editedItem.unitPriceTaxExcl !== undefined &&
|
|
219
|
+
editedItem.unitPriceTaxExcl !== item.unitPriceTaxExcl;
|
|
220
|
+
const hasEditedTotalPriceTaxExcl = editedItem.totalPriceTaxExcl !== undefined &&
|
|
221
|
+
editedItem.totalPriceTaxExcl !== item.totalPriceTaxExcl;
|
|
222
|
+
const hasEditedTotalPriceTaxIncl = editedItem.totalPriceTaxIncl !== undefined &&
|
|
223
|
+
editedItem.totalPriceTaxIncl !== item.totalPriceTaxIncl;
|
|
224
|
+
// Always include fields that were explicitly edited
|
|
225
|
+
if (hasEditedDescription) {
|
|
226
|
+
updateInput.description = editedItem.description ?? "";
|
|
227
|
+
}
|
|
228
|
+
if (hasEditedQuantity) {
|
|
229
|
+
updateInput.quantity = calculatedValues.quantity;
|
|
230
|
+
}
|
|
231
|
+
if (hasEditedTaxPercent) {
|
|
232
|
+
updateInput.taxPercent = calculatedValues.taxPercent;
|
|
233
|
+
}
|
|
234
|
+
if (hasEditedUnitPriceTaxExcl) {
|
|
235
|
+
updateInput.unitPriceTaxExcl = calculatedValues.unitPriceTaxExcl;
|
|
236
|
+
}
|
|
237
|
+
if (hasEditedTotalPriceTaxExcl) {
|
|
238
|
+
updateInput.totalPriceTaxExcl = calculatedValues.totalPriceTaxExcl;
|
|
239
|
+
}
|
|
240
|
+
if (hasEditedTotalPriceTaxIncl) {
|
|
241
|
+
updateInput.totalPriceTaxIncl = calculatedValues.totalPriceTaxIncl;
|
|
242
|
+
}
|
|
243
|
+
// Also include fields that changed due to calculations (even if not explicitly edited)
|
|
244
|
+
if (!hasEditedQuantity &&
|
|
245
|
+
!isClose(calculatedValues.quantity, item.quantity ?? 0)) {
|
|
246
|
+
updateInput.quantity = calculatedValues.quantity;
|
|
247
|
+
}
|
|
248
|
+
if (!hasEditedTaxPercent &&
|
|
249
|
+
!isClose(calculatedValues.taxPercent, item.taxPercent ?? 0)) {
|
|
250
|
+
updateInput.taxPercent = calculatedValues.taxPercent;
|
|
251
|
+
}
|
|
252
|
+
if (!hasEditedUnitPriceTaxExcl &&
|
|
253
|
+
!isClose(calculatedValues.unitPriceTaxExcl, item.unitPriceTaxExcl ?? 0)) {
|
|
254
|
+
updateInput.unitPriceTaxExcl = calculatedValues.unitPriceTaxExcl;
|
|
255
|
+
}
|
|
256
|
+
if (!isClose(calculatedValues.unitPriceTaxIncl, item.unitPriceTaxIncl ?? 0)) {
|
|
257
|
+
updateInput.unitPriceTaxIncl = calculatedValues.unitPriceTaxIncl;
|
|
258
|
+
}
|
|
259
|
+
if (!hasEditedTotalPriceTaxExcl &&
|
|
260
|
+
!isClose(calculatedValues.totalPriceTaxExcl, item.totalPriceTaxExcl ?? 0)) {
|
|
261
|
+
updateInput.totalPriceTaxExcl = calculatedValues.totalPriceTaxExcl;
|
|
262
|
+
}
|
|
263
|
+
if (!hasEditedTotalPriceTaxIncl &&
|
|
264
|
+
!isClose(calculatedValues.totalPriceTaxIncl, item.totalPriceTaxIncl ?? 0)) {
|
|
265
|
+
updateInput.totalPriceTaxIncl = calculatedValues.totalPriceTaxIncl;
|
|
266
|
+
}
|
|
267
|
+
onSave(updateInput);
|
|
139
268
|
}
|
|
140
269
|
return (_jsxs("tr", { ref: ref, className: "hover:bg-gray-50 table-row", children: [_jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(InputField, { onBlur: () => { }, handleInputChange: (e) => {
|
|
141
270
|
setEditedItem((prev) => ({ ...prev, description: e.target.value }));
|
|
142
|
-
}, value: editedItem.description ?? "", placeholder: "Description", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number:
|
|
271
|
+
}, value: editedItem.description ?? "", placeholder: "Description", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: calculatedValues.quantity, precision: 0, handleInputChange: handleInputChange("quantity"), placeholder: "Quantity", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: calculatedValues.unitPriceTaxExcl % 1 === 0
|
|
272
|
+
? calculatedValues.unitPriceTaxExcl.toString()
|
|
273
|
+
: calculatedValues.unitPriceTaxExcl.toFixed(2), precision: 2, handleInputChange: handleInputChange("unitPriceTaxExcl"), pattern: "^-?\\d*\\.?\\d*$", placeholder: "Unit Price (excl. tax)", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: calculatedValues.taxPercent, precision: 0, pattern: "^(100|[1-9]?[0-9])$", handleInputChange: handleInputChange("taxPercent"), placeholder: "Tax %", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 text-right font-medium table-cell", children: _jsx(NumberForm, { number: calculatedValues.totalPriceTaxExcl % 1 === 0
|
|
274
|
+
? calculatedValues.totalPriceTaxExcl.toString()
|
|
275
|
+
: calculatedValues.totalPriceTaxExcl.toFixed(2), precision: 2, handleInputChange: handleInputChange("totalPriceTaxExcl"), pattern: "^-?\\d*\\.?\\d*$", placeholder: "Total (excl. tax)", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 text-right font-medium table-cell", children: _jsx(NumberForm, { number: calculatedValues.totalPriceTaxIncl % 1 === 0
|
|
276
|
+
? calculatedValues.totalPriceTaxIncl.toString()
|
|
277
|
+
: calculatedValues.totalPriceTaxIncl.toFixed(2), precision: 2, handleInputChange: handleInputChange("totalPriceTaxIncl"), pattern: "^-?\\d*\\.?\\d*$", placeholder: "Total (incl. tax)", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsxs("div", { className: "flex space-x-2", children: [_jsx("button", { className: "rounded bg-blue-500 px-3 py-1 text-white hover:bg-blue-700", onClick: handleSave, children: "Save" }), _jsx("button", { className: "rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600", onClick: onCancel, children: "Cancel" })] }) })] }));
|
|
143
278
|
});
|
|
144
|
-
export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, dispatch, paymentAccounts, }) {
|
|
279
|
+
export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, onEditingItemChange, dispatch, paymentAccounts, }) {
|
|
145
280
|
const [editingId, setEditingId] = useState(null);
|
|
146
281
|
const [isAddingNew, setIsAddingNew] = useState(false);
|
|
147
282
|
const [showTagTable, setShowTagTable] = useState(false);
|
|
@@ -180,7 +315,7 @@ export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, o
|
|
|
180
315
|
} })] }) })] }) }), _jsxs("tbody", { children: [lineItems.map((item) => editingId === item.id ? (_jsx(EditableLineItem, { currency: currency, item: item, onCancel: () => setEditingId(null), onSave: (updatedItem) => {
|
|
181
316
|
onUpdateItem(updatedItem);
|
|
182
317
|
setEditingId(null);
|
|
183
|
-
} }, 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 }), _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"
|
|
318
|
+
}, 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 }), _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"
|
|
184
319
|
? Math.round(item.taxPercent)
|
|
185
|
-
: 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 })) : null] })] }) })] }) }));
|
|
320
|
+
: 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] })] }) })] }) }));
|
|
186
321
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requestFinance.d.ts","sourceRoot":"","sources":["../../../editors/invoice/requestFinance.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"requestFinance.d.ts","sourceRoot":"","sources":["../../../editors/invoice/requestFinance.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,GAAG,CAAC;IACd,QAAQ,EAAE,GAAG,CAAC;CACf;AAED,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAgQjD,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -1,17 +1,18 @@
|
|
|
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
|
import { useState } from "react";
|
|
3
|
-
import { actions
|
|
3
|
+
import { actions } from "../../document-models/invoice/index.js";
|
|
4
4
|
import { generateId } from "document-model";
|
|
5
|
-
let GRAPHQL_URL =
|
|
6
|
-
if (!window.document.baseURI.includes(
|
|
7
|
-
GRAPHQL_URL =
|
|
5
|
+
let GRAPHQL_URL = "http://localhost:4001/graphql/invoice";
|
|
6
|
+
if (!window.document.baseURI.includes("localhost")) {
|
|
7
|
+
GRAPHQL_URL = "https://switchboard-dev.powerhouse.xyz/graphql/invoice";
|
|
8
8
|
}
|
|
9
|
-
const RequestFinance = ({ docState, dispatch }) => {
|
|
9
|
+
const RequestFinance = ({ docState, dispatch, }) => {
|
|
10
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
11
11
|
const [error, setError] = useState(null);
|
|
12
12
|
const [responseData, setResponseData] = useState(null);
|
|
13
13
|
const [invoiceLink, setInvoiceLink] = useState(null);
|
|
14
14
|
const [directPaymentStatus, setDirectPaymentStatus] = useState(null);
|
|
15
|
+
const invoiceStatus = docState.status;
|
|
15
16
|
// Function to call the createDirectPayment mutation
|
|
16
17
|
const createDirectPayment = async (paymentData) => {
|
|
17
18
|
try {
|
|
@@ -45,7 +46,8 @@ const RequestFinance = ({ docState, dispatch }) => {
|
|
|
45
46
|
return result.data.Invoice_createRequestFinancePayment.data;
|
|
46
47
|
}
|
|
47
48
|
else {
|
|
48
|
-
throw new Error(result.data?.Invoice_createRequestFinancePayment?.error ||
|
|
49
|
+
throw new Error(result.data?.Invoice_createRequestFinancePayment?.error ||
|
|
50
|
+
"Unknown error");
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
catch (err) {
|
|
@@ -87,7 +89,7 @@ const RequestFinance = ({ docState, dispatch }) => {
|
|
|
87
89
|
invoiceNumber: docState.invoiceNo,
|
|
88
90
|
buyerInfo: {
|
|
89
91
|
// email: docState.payer.contactInfo.email,
|
|
90
|
-
email:
|
|
92
|
+
email: "",
|
|
91
93
|
firstName: docState.payer.name,
|
|
92
94
|
// lastName: docState.payer.name.split(" ")[1] || "Liberty",
|
|
93
95
|
businessName: docState.payer.name,
|
|
@@ -102,7 +104,7 @@ const RequestFinance = ({ docState, dispatch }) => {
|
|
|
102
104
|
sellerInfo: {
|
|
103
105
|
email: docState.issuer.contactInfo.email,
|
|
104
106
|
firstName: docState.issuer.name,
|
|
105
|
-
lastName:
|
|
107
|
+
lastName: "",
|
|
106
108
|
address: {
|
|
107
109
|
country: docState.issuer.address.country,
|
|
108
110
|
city: docState.issuer.address.city,
|
|
@@ -137,10 +139,19 @@ const RequestFinance = ({ docState, dispatch }) => {
|
|
|
137
139
|
// Process the response
|
|
138
140
|
if (directPaymentResult?.response?.invoiceLinks?.pay) {
|
|
139
141
|
setInvoiceLink(directPaymentResult.response.invoiceLinks.pay);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
if (invoiceStatus === "ACCEPTED") {
|
|
143
|
+
dispatch(actions.schedulePayment({
|
|
144
|
+
id: generateId(),
|
|
145
|
+
processorRef: directPaymentResult.response.invoiceLinks.pay,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
dispatch(actions.addPayment({
|
|
150
|
+
id: generateId(),
|
|
151
|
+
processorRef: directPaymentResult.response.invoiceLinks.pay,
|
|
152
|
+
confirmed: false,
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
144
155
|
}
|
|
145
156
|
setResponseData(directPaymentResult);
|
|
146
157
|
setDirectPaymentStatus("Direct payment created successfully");
|
|
@@ -152,11 +163,25 @@ const RequestFinance = ({ docState, dispatch }) => {
|
|
|
152
163
|
errorMessage = err.message;
|
|
153
164
|
}
|
|
154
165
|
setError(errorMessage);
|
|
166
|
+
dispatch(actions.addPayment({
|
|
167
|
+
id: generateId(),
|
|
168
|
+
processorRef: "",
|
|
169
|
+
confirmed: false,
|
|
170
|
+
issue: errorMessage,
|
|
171
|
+
}));
|
|
155
172
|
}
|
|
156
173
|
finally {
|
|
157
174
|
setIsLoading(false);
|
|
158
175
|
}
|
|
159
176
|
};
|
|
160
|
-
|
|
177
|
+
const liktText = "Request Finance [>]";
|
|
178
|
+
return (_jsxs("div", { children: [_jsx("button", { className: "bg-blue-500 text-black px-4 py-2 rounded-md", onClick: handleRequestFinance, disabled: isLoading, children: isLoading
|
|
179
|
+
? "Processing..."
|
|
180
|
+
: invoiceStatus === "ACCEPTED"
|
|
181
|
+
? "Schedule Payment in Request Finance"
|
|
182
|
+
: "Reschedule Payment in Request Finance" }), invoiceLink && (_jsxs("div", { children: [_jsx("div", { className: "direct-payment-status", children: _jsx("p", { children: directPaymentStatus }) }), _jsx("div", { className: "invoice-link text-blue-900 hover:text-blue-600", children: _jsx("a", { href: invoiceLink, target: "_blank", rel: "noopener noreferrer", className: "view-invoice-button", children: liktText }) })] })), !invoiceLink &&
|
|
183
|
+
invoiceStatus === "PAYMENTSCHEDULED" &&
|
|
184
|
+
docState.payments.length > 0 && (_jsx(_Fragment, { children: docState.payments[docState.payments.length - 1].issue !== "" ? (_jsx("div", { className: "mt-4", children: _jsxs("p", { className: "text-red-700 font-medium", children: ["Issue: ", docState.payments[docState.payments.length - 1].issue] }) })) : (_jsx("div", { className: "mt-4", children: _jsx("div", { className: "invoice-link text-blue-900 hover:text-blue-600", children: _jsx("a", { className: "view-invoice-button", href: docState.payments[docState.payments.length - 1]
|
|
185
|
+
.processorRef, target: "_blank", rel: "noopener noreferrer", children: liktText }) }) })) }))] }));
|
|
161
186
|
};
|
|
162
187
|
export default RequestFinance;
|
|
@@ -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://switchboard.powerhouse.xyz/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) {
|
|
@@ -4,7 +4,7 @@ import { OperationType } from '@safe-global/types-kit';
|
|
|
4
4
|
import { ethers, AbiCoder } from 'ethers';
|
|
5
5
|
import dotenv from 'dotenv';
|
|
6
6
|
dotenv.config();
|
|
7
|
-
const safeAddress = process.env.
|
|
7
|
+
const safeAddress = process.env.DEV_STAGING_SAFE_ADDRESS;
|
|
8
8
|
if (!safeAddress) {
|
|
9
9
|
throw new Error('Missing SAFE_ADDRESS in .env');
|
|
10
10
|
}
|
package/dist/style.css
CHANGED
|
@@ -871,6 +871,9 @@
|
|
|
871
871
|
.text-blue-600 {
|
|
872
872
|
color: var(--color-blue-600);
|
|
873
873
|
}
|
|
874
|
+
.text-blue-900 {
|
|
875
|
+
color: var(--color-blue-900);
|
|
876
|
+
}
|
|
874
877
|
.text-gray-50 {
|
|
875
878
|
color: var(--color-gray-50);
|
|
876
879
|
}
|
|
@@ -904,6 +907,9 @@
|
|
|
904
907
|
.text-red-600 {
|
|
905
908
|
color: var(--color-red-600);
|
|
906
909
|
}
|
|
910
|
+
.text-red-700 {
|
|
911
|
+
color: var(--color-red-700);
|
|
912
|
+
}
|
|
907
913
|
.text-slate-800 {
|
|
908
914
|
color: var(--color-slate-800);
|
|
909
915
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/contributor-billing",
|
|
3
3
|
"description": "Document models that help contributors of open organisations get paid anonymously for their work on a monthly basis.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.66",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|