# Google Drive Accounting Doc Router For Israeli Freelancers (Apps Script)

As a freelancer or business owner dealing with both Israeli and international clients, managing accounting documents can quickly become a complex task. That's why I developed a specialized Google Apps Script application that automates the organization of invoices, receipts, and expenses in Google Drive.

Github Project Link

The Challenge

Managing accounting documents across different tax jurisdictions presents unique challenges:

Separating Israeli and international documents for VAT purposes

Maintaining a consistent folder structure across years and months

Efficiently handling multiple document uploads

Ensuring proper naming conventions for easy retrieval

The Solution

I created a Google Apps Script application that provides an intuitive web interface for routing accounting documents to their appropriate folders in Google Drive. The application automatically:

Creates and maintains a structured folder hierarchy by year and month

Separates Israeli and non-Israeli documents

Handles batch uploads of up to 10 documents

Allows custom file renaming for better organization

Technical Implementation

The application is built using Google Apps Script, which provides seamless integration with Google Drive. It consists of two main components:

Frontend (index.html) Clean, user-friendly interface

File upload functionality

Document type selection

Israeli/non-Israeli toggle

File renaming capabilities Backend (Code.gs) Folder structure management

File routing logic

Google Drive API integration

Error handling and validation

Key Features

1. Intelligent Folder Structure

The application maintains a deterministic folder structure:

📁 Root ├── 📁 2024 │ ├── 📁 01_Jan │ ├── 📁 02_Feb │ └── 📁 03_Mar └── 📁 2025 ├── 📁 01_Jan ├── 📁 02_Feb └── 📁 03_Mar

2. Batch Upload Processing

Users can upload up to 10 documents simultaneously, streamlining the process of organizing multiple files.

3. Smart Document Routing

The application intelligently routes documents based on:

Document type (invoices, receipts, expenses)

Israeli/non-Israeli classification

Current date (for automatic month/year folder selection)

4. Custom File Naming

Users can rename files before routing to make them more descriptive and easier to find later.

Deployment Process

Deploying the application is straightforward:

Create a new Google Apps Script project Add the Code.gs and index.html files Configure deployment settings as a web app Set appropriate access permissions Deploy and obtain the public URL

Benefits

This application offers several key advantages:

Time Savings: Automates the tedious process of manually organizing documents Consistency: Ensures a uniform folder structure across all document types Compliance: Separates Israeli and international documents for easier tax reporting Scalability: Handles growing document volumes with ease User-Friendly: Intuitive interface requires minimal training

Open Source and Available for All

The project is licensed under CC-BY-4.0, allowing anyone to:

Use the application for their own document management needs

Modify and adapt the code for specific requirements

Share and distribute the solution with others

Screenshots (V1)

This tool demonstrates how Google Apps Script can be leveraged to create practical solutions for real-world business challenges. By automating document management, it frees up valuable time that can be better spent on core business activities.

The source code is available on GitHub, and I welcome contributions from the community to enhance its functionality further.

Code (V1)

Create a new project in Google Apps Script.

Save the first of these code blocks as Code.js (note: capitalisation) and the second as index.html (note: lower case ‘i’). The images are served from Cloudinary so you don't need to worry about hosting them locally.

Make sure to choose the appropriate permission for your deployment and select type as web app.

Code.js

const MONTHS = [ '01_Jan' , '02_Feb' , '03_Mar' , '04_Apr' , '05_May' , '06_Jun' , '07_Jul' , '08_Aug' , '09_Sep' , '10_Oct' , '11_Nov' , '12_Dec' ]; function doGet ( ) { return HtmlService.createHtmlOutputFromFile( 'index' ) .setTitle( 'Accounting File Router' ) .setFaviconUrl( 'https://www.google.com/images/drive/drive-48.png' ); } function createFolderStructure ( folderTypes ) { try { const config = JSON .parse(PropertiesService.getUserProperties().getProperty( 'accountingRouterConfig' ) || '{}' ); const now = new Date (); const year = now.getFullYear().toString(); const month = MONTHS[now.getMonth()]; let created = 0 ; folderTypes.forEach( folderType => { const folderId = config[folderType]; if (!folderId) { throw new Error ( `Folder ID not configured for ${folderType} ` ); } const rootFolder = DriveApp.getFolderById(folderId); const yearFolder = findOrCreateFolder(rootFolder, year); const monthFolder = findOrCreateFolder(yearFolder, month); findOrCreateFolder(monthFolder, 'Israeli' ); created++; }); return { success : true , message : `Successfully created folder structure for ${created} folder type(s)` }; } catch (error) { return { success : false , message : `Error creating folder structure: ${error.toString()} ` }; } } function processFiles ( files, folderType, monthOverride ) { try { const config = JSON .parse(PropertiesService.getUserProperties().getProperty( 'accountingRouterConfig' ) || '{}' ); const folderId = config[folderType]; if (!folderId) { throw new Error ( 'Folder ID not configured for ' + folderType); } let year, month; if (monthOverride) { const [targetYear, targetMonth] = monthOverride.split( '-' ); year = targetYear; month = MONTHS[ parseInt (targetMonth) - 1 ]; } else { const now = new Date (); year = now.getFullYear().toString(); month = MONTHS[now.getMonth()]; } const rootFolder = DriveApp.getFolderById(folderId); let yearFolder = findOrCreateFolder(rootFolder, year); let monthFolder = findOrCreateFolder(yearFolder, month); const results = []; files.forEach( file => { let targetFolder = monthFolder; if (file.isIsraeli) { targetFolder = findOrCreateFolder(monthFolder, 'Israeli' ); } const blob = Utilities.newBlob( Utilities.base64Decode(file.bytes), file.mimeType, file.fileName ); const newFile = targetFolder.createFile(blob); results.push({ name : file.fileName, success : true , url : newFile.getUrl() }); }); return { success : true , message : `Successfully processed ${files.length} file(s)` , results : results }; } catch (error) { return { success : false , message : `Error processing files: ${error.toString()} ` , results : [] }; } } function getConfiguration ( ) { try { const config = PropertiesService.getUserProperties().getProperty( 'accountingRouterConfig' ); return config ? JSON .parse(config) : null ; } catch (error) { console .error( 'Failed to get configuration:' , error); return null ; } } function saveConfiguration ( config ) { try { PropertiesService.getUserProperties().setProperty( 'accountingRouterConfig' , JSON .stringify(config)); return { success : true }; } catch (error) { throw new Error ( 'Failed to save configuration: ' + error.toString()); } } function findOrCreateFolder ( parentFolder, folderName ) { const folders = parentFolder.getFoldersByName(folderName); if (folders.hasNext()) { return folders.next(); } return parentFolder.createFolder(folderName); }

Index.html (but save with lower case ‘i’)!

< html > < head > < base target = "_top" > < meta charset = "UTF-8" > < title > Google Drive Accounting File Router </ title > < style > @keyframes slideUp { from { transform : translateY ( 100% ); opacity : 0 ; } to { transform : translateY ( 0 ); opacity : 1 ; } } @keyframes slideDown { from { transform : translateY ( 0 ); opacity : 1 ; } to { transform : translateY ( 100% ); opacity : 0 ; } } .israeli-animation { animation : slideUp 0.5s ease forwards; } .israeli-animation-exit { animation : slideDown 0.5s ease forwards; } .tabs { display : flex; margin-bottom : 20px ; border-bottom : 2px solid #ddd ; } .tab { padding : 10px 20px ; cursor : pointer; border : none; background : none; font-weight : bold; color : #666 ; } .tab .active { color : #4285f4 ; border-bottom : 2px solid #4285f4 ; margin-bottom : - 2px ; } .tab-content { display : none; } .tab-content .active { display : block; } .config-group { margin-bottom : 15px ; } .config-group input { width : 100% ; padding : 8px ; border : 1px solid #ddd ; border-radius : 4px ; } .help-text { display : block; margin-top : 4px ; color : #666 ; font-size : 0.9em ; } body { font-family : Arial, sans-serif; max-width : 800px ; margin : 20px auto; padding : 20px ; background-color : #f5f5f5 ; } .container { background-color : white; padding : 20px ; border-radius : 8px ; box-shadow : 0 2px 4px rgba ( 0 , 0 , 0 , 0.1 ); } .form-group { margin-bottom : 20px ; } label { display : block; margin-bottom : 8px ; font-weight : bold; } select { width : 100% ; padding : 8px ; border : 1px solid #ddd ; border-radius : 4px ; margin-bottom : 16px ; } .file-list { border : 2px dashed #ddd ; border-radius : 4px ; padding : 10px ; margin-bottom : 16px ; min-height : 150px ; max-height : 300px ; overflow-y : auto; transition : all 0.3s ease; background-color : #fff ; position : relative; } .file-list .drag-over { border-color : #4285f4 ; background-color : #f8f9fa ; } .drop-message { position : absolute; top : 50% ; left : 50% ; transform : translate (- 50% , - 50% ); color : #666 ; font-size : 1.1em ; text-align : center; pointer-events : none; } .file-item { display : flex; justify-content : space-between; align-items : center; padding : 8px ; border-bottom : 1px solid #eee ; } .file-item :last-child { border-bottom : none; } .button-group { display : flex; gap : 10px ; margin-top : 20px ; } button { padding : 10px 20px ; border : none; border-radius : 4px ; cursor : pointer; font-weight : bold; } .upload-btn { background-color : #4285f4 ; color : white; } .clear-btn { background-color : #dc3545 ; color : white; } .status { margin-top : 20px ; padding : 10px ; border-radius : 4px ; display : none; } .success { background-color : #d4edda ; color : #155724 ; border : 1px solid #c3e6cb ; } .error { background-color : #f8d7da ; color : #721c24 ; border : 1px solid #f5c6cb ; } .loading { display : none; text-align : center; margin : 20px 0 ; } .checkbox-label { display : inline-flex; align-items : center; margin : 0 ; gap : 5px ; font-weight : normal; } .file-rename { width : 100% ; padding : 4px ; margin-bottom : 4px ; border : 1px solid #ddd ; border-radius : 4px ; } .file-original { color : #666 ; font-size : 0.8em ; margin-bottom : 4px ; } </ style > </ head > < body > < div class = "container" > < h1 > Google Drive Accounting File Router </ h1 > < h2 style = "color: #666; margin: 0 0 15px 0;" > Routes accounting documents in Google Drive </ h2 > < div id = "currentMonth" style = "color: #666; margin-bottom: 15px; display: flex; align-items: center; gap: 10px;" > < span > </ span > < img src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAAxUlEQVQoU2P8//8/AyUAIwUGwA2orKxk/P//P8PmzZvRvQszYAEQnwBiZSB+D8SbgPgMEP8F4t1AvBiIQWwUgGKAGxCvBeI/QCwAxIeB+BUQGwAxyIA9QCwOxMgGIGvGMOA/EDsC8WMg/gTEpkD8GIjPArEFEIMM2QTESkCMzQCQpBUQPwBiEH8BEL8BYgMgBhmyE4jFgBjDAJBkHhDfAmKQQisg/gDEukAMMmQDECsCMU4DQAoSgfg6EIMM+QvEq4AYJM6AAgAp8kAx/Db8YAAAAABJRU5ErkJggg==" alt = "Israeli Flag" style = "width: 24px; height: 24px;" > </ div > < div class = "tabs" > < button class = "tab active" onclick = "showTab('upload')" > Upload Files </ button > < button class = "tab" onclick = "showTab('config')" > Configuration </ button > < button class = "tab" onclick = "showTab('howto')" > How To Use </ button > < button class = "tab" onclick = "showTab('create')" > Create Folders </ button > </ div > < div id = "uploadTab" class = "tab-content active" > < div class = "form-group" > < label for = "folderType" > Select Document Type: </ label > < select id = "folderType" > < option value = "" > Please select... </ option > < option value = "EXPENSE" > Expense </ option > < option value = "INVOICES" > Invoice </ option > < option value = "RECEIPTS" > Receipt </ option > </ select > < label for = "monthOverride" > Target Month: </ label > < select id = "monthOverride" > < option value = "" > Current Month </ option > </ select > </ div > < div class = "form-group" > < label > Select Files (up to 10): </ label > < input type = "file" id = "files" multiple accept = "application/pdf,image/*,.doc,.docx,.xls,.xlsx" style = "display: none;" > < button onclick = "document.getElementById('files').click()" class = "upload-btn" > Choose Files </ button > </ div > < div class = "file-list" id = "fileList" ondrop = "handleDrop(event)" ondragover = "handleDragOver(event)" ondragleave = "handleDragLeave(event)" > < div class = "drop-message" > Drag and drop files here < br > < small > or use the Choose Files button </ small > </ div > < div id = "fileListContent" > </ div > </ div > < div class = "button-group" > < button onclick = "processFiles()" class = "upload-btn" > Upload Files </ button > < button onclick = "clearAll()" class = "clear-btn" > Clear All </ button > </ div > < div class = "loading" id = "loading" > Processing files... Please wait... </ div > < div id = "statusImage" style = "text-align: center; margin: 20px 0;" > < img src = "https://res.cloudinary.com/domtm8wiy/image/upload/v1738782120/accounting-router/otidlvt8pofbzcpknbc6.png" alt = "Status" style = "max-width: 200px;" > </ div > < div class = "status" id = "status" > </ div > </ div > < div id = "configTab" class = "tab-content" > < h2 > Folder Configuration </ h2 > < p > Configure the Google Drive folders for each document type. You can paste the full folder URL or just the folder ID. </ p > < div class = "config-group" > < label for = "expenseFolder" > Expense Folder: </ label > < input type = "text" id = "expenseFolder" placeholder = "Paste folder URL or ID" onchange = "handleFolderInput(this)" > < small class = "help-text" > Example: https://drive.google.com/drive/folders/YOUR_FOLDER_ID </ small > </ div > < div class = "config-group" > < label for = "invoicesFolder" > Invoices Folder: </ label > < input type = "text" id = "invoicesFolder" placeholder = "Paste folder URL or ID" onchange = "handleFolderInput(this)" > < small class = "help-text" > Example: https://drive.google.com/drive/folders/YOUR_FOLDER_ID </ small > </ div > < div class = "config-group" > < label for = "receiptsFolder" > Receipts Folder: </ label > < input type = "text" id = "receiptsFolder" placeholder = "Paste folder URL or ID" onchange = "handleFolderInput(this)" > < small class = "help-text" > Example: https://drive.google.com/drive/folders/YOUR_FOLDER_ID </ small > </ div > < button onclick = "saveConfig()" class = "upload-btn" > Save Configuration </ button > < div class = "status" id = "configStatus" > </ div > </ div > < div id = "howtoTab" class = "tab-content" > < h2 > How To Use </ h2 > < div style = "line-height: 1.6" > < p style = "margin-bottom: 20px;" > This app was developed by Daniel Rosehill (danielrosehill.com) using Claude Sonnet 3.5 & Cline. </ p > < h3 > Setup </ h3 > < ol > < li > Go to the Configuration tab and set up your folder IDs for each document type. </ li > < li > Use the Create Folders tab to create the initial folder structure if needed. </ li > </ ol > < h3 > Uploading Files </ h3 > < ol > < li > Select the document type (Expense, Invoice, or Receipt). </ li > < li > Choose files using the button or drag and drop them into the designated area. </ li > < li > Optionally rename files before uploading. </ li > < li > For Israeli documents, check the "Israeli" checkbox next to the file. </ li > < li > Click "Upload Files" to process the documents. </ li > </ ol > < h3 > Folder Structure </ h3 > < p > Files are automatically organized in the following structure: </ p > < pre style = "background: #f5f5f5; padding: 10px; border-radius: 4px;" > Root Folder (Expense/Invoices/Receipts) └── Year (e.g., 2024) └── Month (e.g., 01_Jan) ├── Regular files └── Israeli (folder for Israeli documents) </ pre > </ div > </ div > < div id = "createTab" class = "tab-content" > < h2 > Create Folder Structure </ h2 > < p > Use this section to create the initial folder structure for your documents. This will create the year and month folders according to the current date. </ p > < div class = "form-group" > < label > Select folder types to create: </ label > < div style = "margin: 10px 0;" > < label class = "checkbox-label" > < input type = "checkbox" id = "createExpense" checked > Expense Folders </ label > </ div > < div style = "margin: 10px 0;" > < label class = "checkbox-label" > < input type = "checkbox" id = "createInvoices" checked > Invoice Folders </ label > </ div > < div style = "margin: 10px 0;" > < label class = "checkbox-label" > < input type = "checkbox" id = "createReceipts" checked > Receipt Folders </ label > </ div > </ div > < button onclick = "createFolderStructure()" class = "upload-btn" > Create Folders </ button > </ div > </ div > < script > const months = [ 'January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ]; function updateStatusImage ( state ) { const statusImage = document .getElementById( 'statusImage' ).querySelector( 'img' ); switch (state) { case 'ready' : statusImage.src = 'https://res.cloudinary.com/domtm8wiy/image/upload/v1738782120/accounting-router/otidlvt8pofbzcpknbc6.png' ; break ; case 'uploading' : statusImage.src = 'https://res.cloudinary.com/domtm8wiy/image/upload/v1738782120/accounting-router/lvameumllsc7lny3pddj.png' ; break ; case 'saved' : statusImage.src = 'https://res.cloudinary.com/domtm8wiy/image/upload/v1738782120/accounting-router/izle2zu9xgfkmkskmv2r.png' ; break ; case 'israeli' : const img = statusImage; img.src = 'https://res.cloudinary.com/domtm8wiy/image/upload/v1738782120/accounting-router/yiw6yhd5r6oc4icka3ad.png' ; img.classList.add( 'israeli-animation' ); setTimeout ( () => { img.classList.add( 'israeli-animation-exit' ); setTimeout ( () => { img.classList.remove( 'israeli-animation' , 'israeli-animation-exit' ); updateStatusImage( 'ready' ); }, 3000 ); }, 500 ); break ; } } function populateMonthsDropdown ( ) { const monthSelect = document .getElementById( 'monthOverride' ); const now = new Date (); const currentYear = now.getFullYear(); for ( let i = 0 ; i < 12 ; i++) { const date = new Date (currentYear, now.getMonth() - i, 1 ); const monthNum = String (date.getMonth() + 1 ).padStart( 2 , '0' ); const monthName = months[date.getMonth()]; const year = date.getFullYear(); const value = ` ${year} - ${monthNum} ` ; const text = ` ${monthNum} _ ${monthName} ${year} ` ; const option = new Option(text, value); monthSelect.add(option); } } window .onload = function ( ) { populateMonthsDropdown(); const now = new Date (); document .getElementById( 'currentMonth' ).querySelector( 'span' ).textContent = `Current Accounting Month: ${ String (now.getMonth() + 1 ).padStart( 2 , '0' )} _ ${months[now.getMonth()]} ${now.getFullYear()} ` ; const localConfig = JSON .parse( localStorage .getItem( 'accountingRouterConfig' ) || '{}' ); document .getElementById( 'expenseFolder' ).value = localConfig.EXPENSE || '' ; document .getElementById( 'invoicesFolder' ).value = localConfig.INVOICES || '' ; document .getElementById( 'receiptsFolder' ).value = localConfig.RECEIPTS || '' ; google.script.run .withSuccessHandler( config => { if (config) { document .getElementById( 'expenseFolder' ).value = config.EXPENSE || '' ; document .getElementById( 'invoicesFolder' ).value = config.INVOICES || '' ; document .getElementById( 'receiptsFolder' ).value = config.RECEIPTS || '' ; localStorage .setItem( 'accountingRouterConfig' , JSON .stringify(config)); } }) .getConfiguration(); updateStatusImage( 'ready' ); }; function extractFolderId ( input ) { const patterns = [ /\/folders\/([a-zA-Z0-9_-]+)(?:\/|\?|$)/ , /id=([a-zA-Z0-9_-]+)(?:&|$)/ , /^([a-zA-Z0-9_-]+)$/ ]; for ( const pattern of patterns) { const match = input.match(pattern); if (match) return match[ 1 ]; } return input; } function handleFolderInput ( input ) { const value = input.value.trim(); if (value) { const folderId = extractFolderId(value); input.value = folderId; } } function showTab ( tabName ) { document .querySelectorAll( '.tab-content' ).forEach( tab => { tab.classList.remove( 'active' ); }); document .querySelectorAll( '.tab' ).forEach( tab => { tab.classList.remove( 'active' ); }); document .getElementById(tabName + 'Tab' ).classList.add( 'active' ); document .querySelector( `.tab[onclick="showTab(' ${tabName} ')"]` ).classList.add( 'active' ); } function saveConfig ( ) { const config = { EXPENSE : document .getElementById( 'expenseFolder' ).value, INVOICES : document .getElementById( 'invoicesFolder' ).value, RECEIPTS : document .getElementById( 'receiptsFolder' ).value }; localStorage .setItem( 'accountingRouterConfig' , JSON .stringify(config)); const statusDiv = document .getElementById( 'configStatus' ); statusDiv.className = 'status success' ; statusDiv.style.display = 'block' ; statusDiv.innerHTML = 'Saving configuration...' ; google.script.run .withSuccessHandler( () => { statusDiv.innerHTML = 'Configuration saved successfully! ✓' ; setTimeout ( () => { statusDiv.style.display = 'none' ; }, 3000 ); }) .withFailureHandler( error => { statusDiv.className = 'status error' ; statusDiv.innerHTML = 'Error saving configuration: ' + error.message; }) .saveConfiguration(config); } function createFolderStructure ( ) { const config = JSON .parse( localStorage .getItem( 'accountingRouterConfig' ) || '{}' ); const folderTypes = []; if ( document .getElementById( 'createExpense' ).checked) folderTypes.push( 'EXPENSE' ); if ( document .getElementById( 'createInvoices' ).checked) folderTypes.push( 'INVOICES' ); if ( document .getElementById( 'createReceipts' ).checked) folderTypes.push( 'RECEIPTS' ); if (folderTypes.length === 0 ) { showStatus( 'Please select at least one folder type.' , true ); return ; } document .getElementById( 'loading' ).style.display = 'block' ; google.script.run .withSuccessHandler( response => { document .getElementById( 'loading' ).style.display = 'none' ; showStatus(response.message, !response.success); }) .withFailureHandler( error => { document .getElementById( 'loading' ).style.display = 'none' ; showStatus( 'Error creating folders: ' + error.message, true ); }) .createFolderStructure(folderTypes); } let selectedFiles = []; document .getElementById( 'files' ).addEventListener( 'change' , function ( e ) { const files = Array .from(e.target.files).map( file => ({ file, isIsraeli : false , newName : file.name })); if (files.length > 10 ) { alert( 'Please select up to 10 files only.' ); return ; } selectedFiles = files; updateFileList(); }); function toggleIsraeli ( index ) { selectedFiles[index].isIsraeli = !selectedFiles[index].isIsraeli; if (selectedFiles[index].isIsraeli) { updateStatusImage( 'israeli' ); setTimeout ( () => updateStatusImage( 'ready' ), 1000 ); } updateFileList(); } function updateFileName ( index, newName ) { selectedFiles[index].newName = newName; } function handleDragOver ( event ) { event.preventDefault(); event.stopPropagation(); document .getElementById( 'fileList' ).classList.add( 'drag-over' ); } function handleDragLeave ( event ) { event.preventDefault(); event.stopPropagation(); document .getElementById( 'fileList' ).classList.remove( 'drag-over' ); } function handleDrop ( event ) { event.preventDefault(); event.stopPropagation(); const fileList = document .getElementById( 'fileList' ); fileList.classList.remove( 'drag-over' ); const droppedFiles = Array .from(event.dataTransfer.files).map( file => ({ file, isIsraeli : false , newName : file.name })); if (selectedFiles.length + droppedFiles.length > 10 ) { alert( 'Total number of files cannot exceed 10.' ); return ; } selectedFiles = [...selectedFiles, ...droppedFiles]; updateFileList(); } function updateFileList ( ) { const fileListContent = document .getElementById( 'fileListContent' ); if (selectedFiles.length === 0 ) { fileListContent.innerHTML = '' ; return ; } fileListContent.innerHTML = selectedFiles.map( ( fileObj, index ) => ` <div class="file-item"> <div style="flex: 1;"> <input type="text" class="file-rename" value=" ${fileObj.newName} " onchange="updateFileName( ${index} , this.value)" placeholder="Enter new filename"> <div class="file-original">Original: ${fileObj.file.name} </div> </div> <div style="display: flex; align-items: center; gap: 10px; margin-left: 10px;"> <label class="checkbox-label"> <input type="checkbox" onchange="toggleIsraeli( ${index} )" ${fileObj.isIsraeli ? 'checked' : '' } > Israeli </label> <button onclick="removeFile( ${index} )" style="color: red; background: none; border: none;">×</button> </div> </div> ` ).join( '' ); } function removeFile ( index ) { selectedFiles.splice(index, 1 ); updateFileList(); } function clearAll ( ) { selectedFiles = []; document .getElementById( 'folderType' ).value = '' ; document .getElementById( 'files' ).value = '' ; document .getElementById( 'status' ).style.display = 'none' ; updateStatusImage( 'ready' ); updateFileList(); } function showStatus ( message, isError = false ) { const status = document .getElementById( 'status' ); status.className = 'status ' + (isError ? 'error' : 'success' ); status.style.display = 'block' ; status.innerHTML = message; if (!isError) { setTimeout ( () => { status.style.display = 'none' ; }, 5000 ); } } function processFiles ( ) { const folderType = document .getElementById( 'folderType' ).value; if (!folderType) { showStatus( 'Please select a document type.' , true ); return ; } if (selectedFiles.length === 0 ) { showStatus( 'Please select at least one file.' , true ); return ; } document .getElementById( 'loading' ).style.display = 'block' ; showStatus( 'Preparing to upload files...' , false ); updateStatusImage( 'uploading' ); const promises = selectedFiles.map( fileObj => { return new Promise ( ( resolve, reject ) => { const reader = new FileReader(); reader.onload = ( e ) => { resolve({ fileName : fileObj.newName, mimeType : fileObj.file.type, bytes : e.target.result.split( ',' )[ 1 ], isIsraeli : fileObj.isIsraeli }); }; reader.onerror = ( e ) => reject(e); reader.readAsDataURL(fileObj.file); }); }); Promise .all(promises) .then( fileObjects => { const monthOverride = document .getElementById( 'monthOverride' ).value; showStatus( 'Uploading files to Google Drive...' , false ); google.script.run .withSuccessHandler( response => { document .getElementById( 'loading' ).style.display = 'none' ; if (response.success) { let message = response.message + '<br><br>Files processed:' ; response.results.forEach( result => { message += `<br>• ${result.name} - <a href=" ${result.url} " target="_blank">View file</a>` ; }); showStatus(message); updateStatusImage( 'saved' ); setTimeout ( () => { clearAll(); updateStatusImage( 'ready' ); }, 3000 ); } else { showStatus(response.message, true ); updateStatusImage( 'ready' ); } }) .withFailureHandler( error => { document .getElementById( 'loading' ).style.display = 'none' ; showStatus( 'Error processing files: ' + error.message, true ); updateStatusImage( 'ready' ); }) .processFiles(fileObjects, folderType, monthOverride); }) .catch( error => { document .getElementById( 'loading' ).style.display = 'none' ; showStatus( 'Error preparing files: ' + error.message, true ); updateStatusImage( 'ready' ); }); } </ script > </ body > </ html >

This project was created by Daniel Rosehill (public at danielrosehill dot com) and is licensed under CC-BY-4.0.

**Source:** [https://heyitworks.tech/google-drive-accounting-doc-router-for-israeli-freelancers-apps-script](https://heyitworks.tech/google-drive-accounting-doc-router-for-israeli-freelancers-apps-script)