-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCode.gs
More file actions
126 lines (110 loc) · 5.04 KB
/
Copy pathCode.gs
File metadata and controls
126 lines (110 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
* Invovate for Google Sheets — generate PDF invoices from spreadsheet rows
* via the Invovate API (https://invovate.com/api).
*
* Each selected data row becomes one invoice; the resulting 7-day PDF link is
* written into a "PDF Link" column. Expected header columns (row 1, any order):
* From Name | To Name | Description | Quantity | Unit Price | Tax % |
* Currency | Language | PDF Link
*
* JSON totals work without a key; PDF links, UBL, and QR require a free key. Set a free
* key from https://invovate.com/auth via the Invovate menu for direct output.
*/
var INVOVATE_URL = 'https://invovate.com/api/generate-invoice';
var INVOVATE_KEY_PROP = 'INVOVATE_API_KEY';
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Invovate')
.addItem('Generate invoices for selected rows', 'invovateGenerateSelected')
.addSeparator()
.addItem('Insert example header row', 'invovateInsertHeader')
.addItem('Set API key…', 'invovateSetApiKey')
.addToUi();
}
function invovateSetApiKey() {
var ui = SpreadsheetApp.getUi();
var res = ui.prompt(
'Invovate API key',
'Paste your free key (starts with inv_) from https://invovate.com/auth. ' +
'Leave blank to clear. Required for PDF links/UBL/QR; JSON totals work without a key.',
ui.ButtonSet.OK_CANCEL
);
if (res.getSelectedButton() !== ui.Button.OK) return;
var key = (res.getResponseText() || '').trim();
var props = PropertiesService.getUserProperties();
if (key) props.setProperty(INVOVATE_KEY_PROP, key);
else props.deleteProperty(INVOVATE_KEY_PROP);
ui.alert(key ? 'API key saved.' : 'API key cleared.');
}
function invovateInsertHeader() {
var sheet = SpreadsheetApp.getActiveSheet();
var headers = ['From Name', 'To Name', 'Description', 'Quantity', 'Unit Price', 'Tax %', 'Currency', 'Language', 'PDF Link'];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]).setFontWeight('bold');
}
function invovateGenerateSelected() {
var ui = SpreadsheetApp.getUi();
var sheet = SpreadsheetApp.getActiveSheet();
var lastCol = sheet.getLastColumn();
if (lastCol < 1) { ui.alert('Add a header row first (Invovate → Insert example header row).'); return; }
var header = sheet.getRange(1, 1, 1, lastCol).getValues()[0].map(function (h) {
return String(h).trim().toLowerCase();
});
function col(name) { return header.indexOf(name.toLowerCase()); }
var cFrom = col('From Name'), cTo = col('To Name'), cDesc = col('Description'),
cQty = col('Quantity'), cPrice = col('Unit Price'), cTax = col('Tax %'),
cCur = col('Currency'), cLang = col('Language'), cLink = col('PDF Link');
if (cFrom < 0 || cTo < 0 || cDesc < 0 || cPrice < 0) {
ui.alert('Missing required columns. Need at least: From Name, To Name, Description, Unit Price.');
return;
}
if (cLink < 0) { cLink = lastCol; sheet.getRange(1, cLink + 1).setValue('PDF Link').setFontWeight('bold'); }
var sel = sheet.getActiveRange();
var startRow = Math.max(2, sel.getRow());
var numRows = (sel.getRow() < 2) ? sheet.getLastRow() - 1 : sel.getNumRows();
if (numRows < 1) { ui.alert('Select one or more data rows (below the header).'); return; }
var key = PropertiesService.getUserProperties().getProperty(INVOVATE_KEY_PROP) || '';
var values = sheet.getRange(startRow, 1, numRows, lastCol).getValues();
var ok = 0, fail = 0;
for (var i = 0; i < values.length; i++) {
var r = values[i];
var fromName = r[cFrom], toName = r[cTo], desc = r[cDesc];
if (!fromName || !toName || !desc) continue; // skip blanks
var item = { description: String(desc), quantity: Number(r[cQty]) || 1, unit_price: Number(r[cPrice]) || 0 };
if (cTax >= 0 && r[cTax] !== '' && r[cTax] != null) item.tax_rate = Number(r[cTax]) || 0;
var body = {
from: { name: String(fromName) },
to: { name: String(toName) },
items: [item],
currency: (cCur >= 0 && r[cCur]) ? String(r[cCur]) : 'USD',
language: (cLang >= 0 && r[cLang]) ? String(r[cLang]) : 'en',
output: 'json',
features: { hosted_link: true }
};
var headers = {};
if (key) headers['Authorization'] = 'Bearer ' + key;
try {
var resp = UrlFetchApp.fetch(INVOVATE_URL, {
method: 'post',
contentType: 'application/json',
headers: headers,
payload: JSON.stringify(body),
muteHttpExceptions: true
});
var code = resp.getResponseCode();
var json = JSON.parse(resp.getContentText());
if (code >= 200 && code < 300 && json && json.invoice && json.invoice.hosted_url) {
sheet.getRange(startRow + i, cLink + 1).setValue(json.invoice.hosted_url);
ok++;
} else {
var msg = (json && json.error && json.error.message) || ('HTTP ' + code);
sheet.getRange(startRow + i, cLink + 1).setValue('Error: ' + msg);
fail++;
}
} catch (e) {
sheet.getRange(startRow + i, cLink + 1).setValue('Error: ' + e.message);
fail++;
}
Utilities.sleep(150); // be gentle on rate limits
}
ui.alert('Invovate: ' + ok + ' invoice(s) generated' + (fail ? ', ' + fail + ' failed.' : '.'));
}