Automatically extracts invoice data and fills payment registration form
Size
6.7 KB
Version
1.0.1
Created
Jan 14, 2026
Updated
21 days ago
1// ==UserScript==
2// @name Moneybird Auto Payment Registration
3// @description Automatically extracts invoice data and fills payment registration form
4// @version 1.0.1
5// @match https://*.moneybird.com/*
6// @icon https://assets-app-cdn.moneybird.com/assets/favicon-b1fb00eb89b4530acc973e4c2cd93f58f7fc4095a79f9bb2a7d88f692b994273.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 // Debounce function to prevent multiple rapid executions
12 function debounce(func, wait) {
13 let timeout;
14 return function executedFunction(...args) {
15 const later = () => {
16 clearTimeout(timeout);
17 func(...args);
18 };
19 clearTimeout(timeout);
20 timeout = setTimeout(later, wait);
21 };
22 }
23
24 // Extract invoice data from document page
25 function extractInvoiceData() {
26 console.log('Extracting invoice data...');
27
28 const dateElement = document.querySelector('p.document-date');
29 const contactElement = document.querySelector('p.document-contact a');
30
31 if (!dateElement || !contactElement) {
32 console.log('Invoice data elements not found on this page');
33 return null;
34 }
35
36 const invoiceDate = dateElement.textContent.trim();
37 const contactName = contactElement.textContent.trim();
38
39 console.log('Extracted Invoice Date:', invoiceDate);
40 console.log('Extracted Contact Name:', contactName);
41
42 return { invoiceDate, contactName };
43 }
44
45 // Store invoice data and click register payment button
46 async function handleDocumentPage() {
47 console.log('On document page, checking for invoice data...');
48
49 const invoiceData = extractInvoiceData();
50 if (!invoiceData) {
51 return;
52 }
53
54 // Store the data for use on the next page
55 await GM.setValue('invoiceDate', invoiceData.invoiceDate);
56 await GM.setValue('contactName', invoiceData.contactName);
57 console.log('Invoice data stored successfully');
58
59 // Find and click the register payment button
60 const registerButton = document.querySelector('a.btn.btn--secondary[title="Registreer betaling"]');
61 if (registerButton) {
62 console.log('Register payment button found, clicking...');
63 registerButton.click();
64 } else {
65 console.log('Register payment button not found');
66 }
67 }
68
69 // Fill in the payment form on the payment registration page
70 async function fillPaymentForm() {
71 console.log('On payment registration page, filling form...');
72
73 // Retrieve stored invoice data
74 const invoiceDate = await GM.getValue('invoiceDate');
75 const contactName = await GM.getValue('contactName');
76
77 if (!invoiceDate || !contactName) {
78 console.log('No stored invoice data found');
79 return;
80 }
81
82 console.log('Retrieved Invoice Date:', invoiceDate);
83 console.log('Retrieved Contact Name:', contactName);
84
85 // Fill ledger account field
86 const ledgerAccountSelect = document.querySelector('select[name="payment[ledger_account_id]"]');
87 if (ledgerAccountSelect) {
88 const targetValue = `Tussenrekening ${contactName}`;
89 console.log('Looking for ledger account option:', targetValue);
90
91 // Find the option with matching text
92 const options = Array.from(ledgerAccountSelect.options);
93 const matchingOption = options.find(option => option.textContent.trim() === targetValue);
94
95 if (matchingOption) {
96 ledgerAccountSelect.value = matchingOption.value;
97 console.log('Ledger account set to:', targetValue);
98
99 // Trigger change event
100 ledgerAccountSelect.dispatchEvent(new Event('change', { bubbles: true }));
101 } else {
102 console.error('Could not find ledger account option:', targetValue);
103 console.log('Available options:', options.map(o => o.textContent.trim()));
104 }
105 } else {
106 console.log('Ledger account select field not found');
107 }
108
109 // Fill payment date field
110 const dateInput = document.querySelector('div.date-picker.date-picker--long input.date-picker__input');
111 if (dateInput) {
112 dateInput.value = invoiceDate;
113 console.log('Payment date set to:', invoiceDate);
114
115 // Trigger input and change events
116 dateInput.dispatchEvent(new Event('input', { bubbles: true }));
117 dateInput.dispatchEvent(new Event('change', { bubbles: true }));
118 } else {
119 console.log('Payment date input field not found');
120 }
121
122 console.log('Payment form filled successfully');
123
124 // Clear stored data after use
125 await GM.deleteValue('invoiceDate');
126 await GM.deleteValue('contactName');
127 }
128
129 // Determine which page we're on and take appropriate action
130 async function handlePage() {
131 const currentUrl = window.location.href;
132
133 // Check if we're on a document detail page (not a payment page)
134 if (currentUrl.includes('/documents/') && !currentUrl.includes('/payments/')) {
135 await handleDocumentPage();
136 }
137 // Check if we're on a payment registration page
138 else if (currentUrl.includes('/payments/new') || currentUrl.includes('/payments/balance_settlement')) {
139 // Wait a bit for the page to fully load
140 setTimeout(async () => {
141 await fillPaymentForm();
142 }, 1000);
143 }
144 }
145
146 // Initialize the extension
147 async function init() {
148 console.log('Moneybird Auto Payment Registration extension loaded');
149
150 // Handle the current page
151 await handlePage();
152
153 // Watch for URL changes (for single-page app navigation)
154 let lastUrl = window.location.href;
155 const urlObserver = new MutationObserver(debounce(async () => {
156 const currentUrl = window.location.href;
157 if (currentUrl !== lastUrl) {
158 console.log('URL changed from', lastUrl, 'to', currentUrl);
159 lastUrl = currentUrl;
160 await handlePage();
161 }
162 }, 500));
163
164 urlObserver.observe(document.body, {
165 childList: true,
166 subtree: true
167 });
168 }
169
170 // Wait for the page to be ready
171 if (document.readyState === 'loading') {
172 document.addEventListener('DOMContentLoaded', init);
173 } else {
174 init();
175 }
176})();