Moneybird Auto Payment Registration

Automatically extracts invoice data and fills payment registration forms on Moneybird

Size

10.6 KB

Version

1.1.2

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 forms on Moneybird
4// @version		1.1.2
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    // Store extracted data in session storage for cross-page access
25    async function storeInvoiceData(invoiceDate, contactName) {
26        await GM.setValue('moneybird_invoice_date', invoiceDate);
27        await GM.setValue('moneybird_contact_name', contactName);
28        console.log('Stored invoice data:', { invoiceDate, contactName });
29    }
30
31    async function getStoredInvoiceData() {
32        const invoiceDate = await GM.getValue('moneybird_invoice_date', null);
33        const contactName = await GM.getValue('moneybird_contact_name', null);
34        return { invoiceDate, contactName };
35    }
36
37    // Extract invoice data from document page
38    function extractInvoiceData() {
39        const dateElement = document.querySelector('p.document-date');
40        const contactElement = document.querySelector('p.document-contact a');
41
42        if (dateElement && contactElement) {
43            const invoiceDate = dateElement.textContent.trim();
44            const contactName = contactElement.textContent.trim();
45            
46            console.log('Extracted Invoice Date:', invoiceDate);
47            console.log('Extracted Contact Name:', contactName);
48            
49            return { invoiceDate, contactName };
50        }
51        return null;
52    }
53
54    // Add "Auto Register Payment" button to document page
55    function addAutoRegisterButton() {
56        // Check if button already exists
57        if (document.querySelector('#auto-register-payment-btn')) {
58            return;
59        }
60
61        // Try to find the direct register button first
62        let registerButton = document.querySelector('a[title="Registreer betaling"].btn.btn--secondary');
63        
64        // If not found, look for the Betaling dropdown button
65        if (!registerButton) {
66            registerButton = document.querySelector('a[title="Betaling"].btn.btn--secondary');
67        }
68        
69        if (!registerButton) {
70            return;
71        }
72
73        // Create auto-register button
74        const autoButton = document.createElement('a');
75        autoButton.id = 'auto-register-payment-btn';
76        autoButton.className = 'btn btn--primary';
77        autoButton.style.marginLeft = '10px';
78        autoButton.href = '#';
79        autoButton.innerHTML = '<span class="btn__text">Auto Registreer Betaling</span>';
80        
81        autoButton.addEventListener('click', async (e) => {
82            e.preventDefault();
83            
84            // Extract and store data
85            const data = extractInvoiceData();
86            if (data) {
87                await storeInvoiceData(data.invoiceDate, data.contactName);
88                
89                // Click the register payment button
90                console.log('Clicking Register Payment button...');
91                
92                // If it's a dropdown button, we need to open it first and then click the register option
93                if (registerButton.title === 'Betaling') {
94                    console.log('Opening Betaling dropdown...');
95                    registerButton.click();
96                    
97                    // Wait for dropdown to open
98                    await new Promise(resolve => setTimeout(resolve, 300));
99                    
100                    // Find and click "Registreer betaling" in the dropdown
101                    const registerOption = document.querySelector('a[href*="/payments/new"]');
102                    if (registerOption) {
103                        console.log('Clicking Registreer betaling option...');
104                        registerOption.click();
105                    } else {
106                        console.error('Could not find Registreer betaling option in dropdown');
107                    }
108                } else {
109                    registerButton.click();
110                }
111            } else {
112                console.error('Failed to extract invoice data');
113                alert('Could not extract invoice data. Please check the page structure.');
114            }
115        });
116
117        // Insert button next to the original register button
118        registerButton.parentNode.insertBefore(autoButton, registerButton.nextSibling);
119        console.log('Auto Register Payment button added');
120    }
121
122    // Handle payment method selection page
123    async function handlePaymentMethodPage() {
124        console.log('On payment method selection page');
125        
126        // Wait for the page to fully load
127        await new Promise(resolve => setTimeout(resolve, 1000));
128        
129        const balanceLink = document.querySelector('div.link-list__item[title="Verrekenen met balanscategorie"] a');
130        
131        if (balanceLink) {
132            console.log('Found "Verrekenen met balanscategorie" link, clicking...');
133            balanceLink.click();
134        } else {
135            console.log('Balance category link not found yet, will retry...');
136        }
137    }
138
139    // Fill in the payment form
140    async function fillPaymentForm() {
141        console.log('On payment form page');
142        
143        // Wait for form to load
144        await new Promise(resolve => setTimeout(resolve, 1500));
145        
146        const { invoiceDate, contactName } = await getStoredInvoiceData();
147        
148        if (!invoiceDate || !contactName) {
149            console.log('No stored invoice data found');
150            return;
151        }
152
153        console.log('Filling form with:', { invoiceDate, contactName });
154
155        // Fill ledger account (Tussenrekening) - Handle custom dropdown
156        const ledgerAccountDropdown = document.querySelector('#payment_ledger_account_id_inner');
157        if (ledgerAccountDropdown) {
158            const targetValue = `Tussenrekening ${contactName}`;
159            console.log('Looking for ledger account:', targetValue);
160            
161            // Click to open dropdown
162            ledgerAccountDropdown.click();
163            console.log('Clicked dropdown to open');
164            
165            // Wait for dropdown to open
166            await new Promise(resolve => setTimeout(resolve, 500));
167            
168            // Find the option in the dropdown list
169            const dropdownList = document.querySelector('.select__list--dropdown');
170            if (dropdownList) {
171                const dropdownOptions = dropdownList.querySelectorAll('.select__item');
172                console.log('Found dropdown options:', dropdownOptions.length);
173                
174                let foundOption = null;
175                dropdownOptions.forEach(option => {
176                    const optionText = option.textContent.trim();
177                    console.log('Checking option:', optionText);
178                    if (optionText === targetValue) {
179                        foundOption = option;
180                    }
181                });
182                
183                if (foundOption) {
184                    console.log('Found matching option, clicking...');
185                    foundOption.click();
186                    await new Promise(resolve => setTimeout(resolve, 300));
187                    console.log('Set ledger account to:', targetValue);
188                } else {
189                    console.error('Could not find ledger account option:', targetValue);
190                    // Log all available options
191                    const allOptions = Array.from(dropdownOptions).map(o => o.textContent.trim());
192                    console.log('Available options:', allOptions);
193                }
194            } else {
195                console.error('Dropdown list not found');
196            }
197        } else {
198            console.log('Ledger account dropdown not found');
199        }
200
201        // Fill payment date
202        const dateInput = document.querySelector('input[name="payment[payment_date]"]');
203        if (dateInput) {
204            console.log('Found date input, current value:', dateInput.value);
205            
206            // Clear existing value
207            dateInput.value = '';
208            dateInput.focus();
209            
210            // Set new value
211            dateInput.value = invoiceDate;
212            console.log('Set payment date to:', invoiceDate);
213            
214            // Trigger events to ensure the change is registered
215            dateInput.dispatchEvent(new Event('input', { bubbles: true }));
216            dateInput.dispatchEvent(new Event('change', { bubbles: true }));
217            dateInput.dispatchEvent(new Event('blur', { bubbles: true }));
218            
219            // Verify the value was set
220            console.log('Date input value after setting:', dateInput.value);
221        } else {
222            console.log('Date input not found');
223        }
224
225        console.log('Form filling completed');
226    }
227
228    // Detect which page we're on and take appropriate action
229    async function detectAndAct() {
230        const url = window.location.href;
231        
232        // Document detail page (where we extract data)
233        if (url.match(/\/documents\/\d+$/) && !url.includes('/payments')) {
234            console.log('On document detail page');
235            addAutoRegisterButton();
236        }
237        // Payment method selection page
238        else if (url.includes('/payments/new')) {
239            console.log('On payment method selection page');
240            await handlePaymentMethodPage();
241        }
242        // Payment form page (balance settlement)
243        else if (url.includes('/payments/balance_settlement')) {
244            console.log('On payment form page');
245            await fillPaymentForm();
246        }
247    }
248
249    // Initialize the script
250    async function init() {
251        console.log('Moneybird Auto Payment Registration initialized');
252        
253        // Run initial detection
254        await detectAndAct();
255        
256        // Watch for DOM changes (for dynamic content loading)
257        const debouncedDetect = debounce(detectAndAct, 500);
258        const observer = new MutationObserver(debouncedDetect);
259        
260        observer.observe(document.body, {
261            childList: true,
262            subtree: true
263        });
264    }
265
266    // Wait for page to be ready
267    if (document.readyState === 'loading') {
268        document.addEventListener('DOMContentLoaded', init);
269    } else {
270        init();
271    }
272})();
Moneybird Auto Payment Registration | Robomonkey