Esketit - Add Net Return to Statement

Proving that it's important

当前为 2025-06-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Esketit - Add Net Return to Statement
  3. // @namespace http://esketit.com/
  4. // @version 2025-06-09
  5. // @description Proving that it's important
  6. // @author rs232
  7. // @match https://*esketit.com/investor/account-statement
  8. // @icon https://www.google.com/s2/favicons?sz=32&domain_url=https%3A%2F%2Fwww.esketit.com
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Helper: parse a currency string (e.g. "€2 089,10") to a number.
  16. function parseCurrency(value) {
  17. return parseFloat(value.replace('€', '').replace(/\s/g, '').replace(',', '.')) || 0;
  18. }
  19.  
  20. // Calculate Net Return from the 5 rows in the Summary table.
  21. function calculateNetReturn() {
  22. const rows = [
  23. "Interest received",
  24. "Bonus received",
  25. "Referral bonus received",
  26. "Secondary market income",
  27. "Secondary market expense"
  28. ];
  29. let total = 0;
  30. rows.forEach(rowLabel => {
  31. const row = document.evaluate(
  32. `//tr[td[text()='${rowLabel}']]`,
  33. document,
  34. null,
  35. XPathResult.FIRST_ORDERED_NODE_TYPE,
  36. null
  37. ).singleNodeValue;
  38. if (row) {
  39. const valueCell = row.querySelector('td:nth-child(2)');
  40. if (valueCell) {
  41. const valueText = valueCell.textContent.replace('€', '').replace(/\s/g, '').replace(',', '.');
  42. total += parseCurrency(valueText);
  43. }
  44. }
  45. });
  46. return total;
  47. }
  48.  
  49. // Update the Net Return row below the Closing Balance row.
  50. function addNetReturnRow() {
  51. const closingBalanceRow = document.evaluate(
  52. "//tr[td[text()='Closing balance']]",
  53. document,
  54. null,
  55. XPathResult.FIRST_ORDERED_NODE_TYPE,
  56. null
  57. ).singleNodeValue;
  58. if (closingBalanceRow) {
  59. const netReturn = calculateNetReturn();
  60. const formattedNetReturn = `€${netReturn.toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.')}`;
  61. let netReturnRow = document.getElementById('net-return-row');
  62. if (!netReturnRow) {
  63. netReturnRow = document.createElement('tr');
  64. netReturnRow.id = 'net-return-row';
  65. netReturnRow.setAttribute('data-v-344f568a', '');
  66. closingBalanceRow.parentNode.insertBefore(netReturnRow, closingBalanceRow.nextSibling);
  67. }
  68. netReturnRow.innerHTML = `
  69. <td data-v-344f568a="" style="font-weight: bold; color: green;">Net return</td>
  70. <td data-v-344f568a="" style="text-align: right; font-weight: bold; color: green;">${formattedNetReturn}</td>
  71. `;
  72. // Always display the Net Return row with a pale green background.
  73. netReturnRow.style.backgroundColor = "#e6ffe6";
  74. // Colorize the Summary rows after updating the Net Return value.
  75. colorizeSummaryRows();
  76. } else {
  77. console.warn("Closing Balance row not found.");
  78. }
  79. }
  80.  
  81. // Colorizes each Summary row:
  82. // - For "Interest received", "Bonus received", "Referral bonus received", and "Secondary market income":
  83. // if their value > 0, set the label and value text to green and the row background to a pale green.
  84. // - For "Secondary market expense": if its value is negative, set the label and value text to red and the row background to a pale red.
  85. // - For "Sold on secondary market": set the label and value text to dark amber and the row background to a light amber.
  86. function colorizeSummaryRows() {
  87. const greenRows = [
  88. "Interest received",
  89. "Bonus received",
  90. "Referral bonus received",
  91. "Secondary market income"
  92. ];
  93. greenRows.forEach(label => {
  94. const row = document.evaluate(
  95. `//tr[td[text()='${label}']]`,
  96. document,
  97. null,
  98. XPathResult.FIRST_ORDERED_NODE_TYPE,
  99. null
  100. ).singleNodeValue;
  101. if (row) {
  102. const valueCell = row.querySelector('td:nth-child(2)');
  103. if (valueCell) {
  104. const value = parseCurrency(valueCell.textContent);
  105. if (value > 0) {
  106. row.querySelector('td:nth-child(1)').style.color = "green";
  107. row.querySelector('td:nth-child(2)').style.color = "green";
  108. row.style.backgroundColor = "#e6ffe6"; // pale green background
  109. } else {
  110. row.querySelector('td:nth-child(1)').style.color = "";
  111. row.querySelector('td:nth-child(2)').style.color = "";
  112. row.style.backgroundColor = "";
  113. }
  114. }
  115. }
  116. });
  117. // For "Secondary market expense"
  118. const redLabel = "Secondary market expense";
  119. const redRow = document.evaluate(
  120. `//tr[td[text()='${redLabel}']]`,
  121. document,
  122. null,
  123. XPathResult.FIRST_ORDERED_NODE_TYPE,
  124. null
  125. ).singleNodeValue;
  126. if (redRow) {
  127. const valueCell = redRow.querySelector('td:nth-child(2)');
  128. if (valueCell) {
  129. const value = parseCurrency(valueCell.textContent);
  130. if (value < 0) {
  131. redRow.querySelector('td:nth-child(1)').style.color = "red";
  132. redRow.querySelector('td:nth-child(2)').style.color = "red";
  133. redRow.style.backgroundColor = "#ffe6e6"; // pale red background
  134. } else {
  135. redRow.querySelector('td:nth-child(1)').style.color = "";
  136. redRow.querySelector('td:nth-child(2)').style.color = "";
  137. redRow.style.backgroundColor = "";
  138. }
  139. }
  140. }
  141. // For "Sold on secondary market"
  142. const amberLabel = "Sold on secondary market";
  143. const amberRow = document.evaluate(
  144. `//tr[td[text()='${amberLabel}']]`,
  145. document,
  146. null,
  147. XPathResult.FIRST_ORDERED_NODE_TYPE,
  148. null
  149. ).singleNodeValue;
  150. if (amberRow) {
  151. const valueCell = amberRow.querySelector('td:nth-child(2)');
  152. if (valueCell) {
  153. const value = parseCurrency(valueCell.textContent);
  154. if (value > 0) {
  155. amberRow.querySelector('td:nth-child(1)').style.color = "#FF8F00"; // dark amber text
  156. amberRow.querySelector('td:nth-child(2)').style.color = "#FF8F00";
  157. amberRow.style.backgroundColor = "#FFECB3"; // light amber background
  158. } else {
  159. amberRow.querySelector('td:nth-child(1)').style.color = "";
  160. amberRow.querySelector('td:nth-child(2)').style.color = "";
  161. amberRow.style.backgroundColor = "";
  162. }
  163. }
  164. }
  165. }
  166.  
  167. // Debounce helper to avoid multiple rapid recalculations.
  168. let recalcTimeout;
  169. function scheduleRecalculation() {
  170. if (recalcTimeout) clearTimeout(recalcTimeout);
  171. recalcTimeout = setTimeout(addNetReturnRow, 50);
  172. }
  173.  
  174. // Monitor the value cells for changes.
  175. function monitorValueChanges() {
  176. const rows = [
  177. "Interest received",
  178. "Bonus received",
  179. "Referral bonus received",
  180. "Secondary market income",
  181. "Secondary market expense"
  182. ];
  183. rows.forEach(rowLabel => {
  184. const row = document.evaluate(
  185. `//tr[td[text()='${rowLabel}']]`,
  186. document,
  187. null,
  188. XPathResult.FIRST_ORDERED_NODE_TYPE,
  189. null
  190. ).singleNodeValue;
  191. if (row) {
  192. const valueCell = row.querySelector('td:nth-child(2)');
  193. if (valueCell) {
  194. const observer = new MutationObserver(scheduleRecalculation);
  195. observer.observe(valueCell, { childList: true, characterData: true, subtree: true });
  196. }
  197. }
  198. });
  199. }
  200.  
  201. // On page load, add the Net Return row and start monitoring.
  202. window.addEventListener('load', () => {
  203. addNetReturnRow();
  204. monitorValueChanges();
  205. });
  206. })();