Skip to content

Commit e140636

Browse files
committed
Update
1 parent a3b9734 commit e140636

2 files changed

Lines changed: 93 additions & 61 deletions

File tree

technology/google/apps-script/woocommerce-orders-retrieve-to-google-sheets.js

Lines changed: 90 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// WooCommerce - WooCommerce Orders Retrieve to Google Sheets
2-
// Last update: 2025-08-31
2+
// Last update: 2025-09-01
33

44

55
// https://script.google.com > New project >
@@ -8,20 +8,20 @@
88
// -- Choose which function to run: WooCommerceOrdersRetrieve
99
// -- Choose which deployment should run: Head
1010
// -- Select event source: Time-driven
11-
// -- Select type of time based trigger: Minutes timer
12-
// -- Select minute interval: Every 30 minutes
11+
// -- Select type of time based trigger: Hour timer
12+
// -- Select minute interval: Every 12 hours
1313

1414

1515
// Google Sheets Query to aggregate orders from line/item level to order level
16-
// =QUERY('Orders Line Level'!A:AY, "SELECT A, MAX(B), MAX(C), MAX(D), MAX(E), MAX(F), MAX(G), MAX(H), MAX(I), MAX(J), MAX(K), MAX(L), MAX(M), MAX(N), MAX(O), MAX(P), MAX(Q), MAX(R), MAX(S), MAX(T), MAX(U), MAX(V), MAX(W), MAX(X), MAX(Y), MAX(Z), MAX(AA), MAX(AB), MAX(AC), MAX(AD), MAX(AE), MAX(AF), MAX(AG), MAX(AH), MAX(AI), MAX(AJ), MAX(AK), MAX(AL), MAX(AM), MAX(AN), MAX(AO), MAX(AP), MAX(AQ), MAX(AR), MAX(AS), MAX(AT), MAX(AU), MAX(AV) WHERE A IS NOT NULL GROUP BY A LABEL A 'order_id', MAX(B) 'created_via', MAX(C) 'status', MAX(D) 'currency', MAX(E) 'prices_include_tax', MAX(F) 'customer_id', MAX(G) 'customer_user_agent', MAX(H) 'customer_note', MAX(I) 'payment_method', MAX(J) 'payment_method_title', MAX(K) 'language_code', MAX(L) 'billing_first_name', MAX(M) 'billing_last_name', MAX(N) 'billing_company', MAX(O) 'billing_address_1', MAX(P) 'billing_address_2', MAX(Q) 'billing_city', MAX(R) 'billing_state', MAX(S) 'billing_postcode', MAX(T) 'billing_country', MAX(U) 'billing_email', MAX(V) 'billing_phone', MAX(W) 'shipping_first_name', MAX(X) 'shipping_last_name', MAX(Y) 'shipping_company', MAX(Z) 'shipping_address_1', MAX(AA) 'shipping_address_2', MAX(AB) 'shipping_city', MAX(AC) 'shipping_state', MAX(AD) 'shipping_postcode', MAX(AE) 'shipping_country', MAX(AF) 'shipping_methods', MAX(AG) 'date_created', MAX(AH) 'date_created_gmt', MAX(AI) 'date_modified', MAX(AJ) 'date_modified_gmt', MAX(AK) 'date_paid', MAX(AL) 'date_paid_gmt', MAX(AM) 'date_completed', MAX(AN) 'date_completed_gmt', MAX(AO) 'discount_tax', MAX(AP) 'discount_total', MAX(AQ) 'shipping_tax', MAX(AR) 'shipping_total', MAX(AS) 'cart_tax', MAX(AT) 'cart', MAX(AU) 'transaction_fee', MAX(AV) 'total_tax' ", 1)
16+
// =QUERY('sales_orders_articles'!A:BD, "SELECT MAX(A), MAX(B), MAX(C), MAX(D), MAX(E), MAX(F), MAX(G), MAX(H), MAX(I), MAX(J), MAX(K), MAX(L), MAX(M), MAX(N), MAX(O), MAX(P), MAX(Q), MAX(R), MAX(S), MAX(T), MAX(U), MAX(V), MAX(W), MAX(X), MAX(Y), MAX(Z), MAX(AA), MAX(AB), MAX(AC), MAX(AD), MAX(AE), MAX(AF), MAX(AG), MAX(AH), MAX(AI), MAX(AJ), MAX(AK), MAX(AL), MAX(AM), MAX(AN), MAX(AO), MAX(AP), MAX(AQ), SUM(AR), SUM(AS), SUM(AT), SUM(AU), SUM(AV), SUM(AW), SUM(AX), SUM(AY), SUM(AZ), SUM(BA), SUM(BB), SUM(BC), SUM(BD) WHERE A IS NOT NULL GROUP BY A, B LABEL MAX(A) 'source_system', MAX(B) 'order_id', MAX(C) 'created_via', MAX(D) 'status', MAX(E) 'date_created', MAX(F) 'date_created_gmt', MAX(G) 'date_modified', MAX(H) 'date_modified_gmt', MAX(I) 'date_paid', MAX(J) 'date_paid_gmt', MAX(K) 'date_completed', MAX(L) 'date_completed_gmt', MAX(M) 'currency', MAX(N) 'prices_include_tax', MAX(O) 'order_attribution_origin', MAX(P) 'order_attribution_device_type', MAX(Q) 'customer_id', MAX(R) 'customer_user_agent', MAX(S) 'customer_note', MAX(T) 'payment_method', MAX(U) 'payment_method_title', MAX(V) 'language_code', MAX(W) 'billing_first_name', MAX(X) 'billing_last_name', MAX(Y) 'billing_company', MAX(Z) 'billing_address_1', MAX(AA) 'billing_address_2', MAX(AB) 'billing_city', MAX(AC) 'billing_state', MAX(AD) 'billing_postcode', MAX(AE) 'billing_country', MAX(AF) 'billing_email', MAX(AG) 'billing_phone', MAX(AH) 'shipping_first_name', MAX(AI) 'shipping_last_name', MAX(AJ) 'shipping_company', MAX(AK) 'shipping_address_1', MAX(AL) 'shipping_address_2', MAX(AM) 'shipping_city', MAX(AN) 'shipping_state', MAX(AO) 'shipping_postcode', MAX(AP) 'shipping_country', MAX(AQ) 'shipping_methods', SUM(AR) 'discount_amount_net', SUM(AS) 'discount_amount_tax', SUM(AT) 'discount_amount_gross', SUM(AU) 'shipping_amount_net', SUM(AV) 'shipping_amount_tax', SUM(AW) 'shipping_amount_gross', SUM(AX) 'items_amount_net', SUM(AY) 'items_amount_tax', SUM(AZ) 'items_amount_gross', SUM(BA) 'transaction_fee', SUM(BB) 'refund_amount_net', SUM(BC) 'refund_amount_tax', SUM(BD) 'refund_amount_gross'", 1)
1717

1818

1919
function WooCommerceOrdersRetrieve() {
2020
const options = {
2121
site_url: "https://website.com",
2222
consumer_key: "ck_xxxxxxxx",
2323
consumer_secret: "cs_xxxxxxxx",
24-
output_sheet_name: "Orders Line Level",
24+
output_sheet_name: "sales_orders_articles",
2525
test_mode: false,
2626
last_fetch_date_reset: false,
2727
};
@@ -40,7 +40,7 @@ function WooCommerceOrdersRetrieve() {
4040
// Get last fetch date to fetch only new orders
4141
let lastFetchDate = PropertiesService.getScriptProperties().getProperty("SETTINGS_LAST_SYNC");
4242
if (lastFetchDate) lastFetchDate = lastFetchDate.slice(0, lastFetchDate.lastIndexOf('.')) + 'Z';
43-
Logger.log("The last fetch date is: " + lastFetchDate);
43+
Logger.log("The last imported modified date is: " + lastFetchDate);
4444

4545
// Reusable API fetch function
4646
const fetchWooCommerceAPI = (endpoint, params = {}) => {
@@ -93,7 +93,7 @@ function WooCommerceOrdersRetrieve() {
9393

9494
// Fetch paginated orders
9595
while (true) {
96-
const params = { per_page: perPage, page: page, orderby: "date", order: "asc", orderby: "modified" };
96+
const params = { per_page: perPage, page: page, orderby: "date", order: "asc" };
9797
if (lastFetchDate) {
9898
params.modified_after = lastFetchDate;
9999
}
@@ -120,16 +120,26 @@ function WooCommerceOrdersRetrieve() {
120120
// If sheet is empty, add headers
121121
if (sheet.getLastRow() === 0) {
122122
const headers = [
123-
// Order core fields
123+
"source_system",
124124
"order_id",
125125
// "parent_id",
126126
// "number",
127127
// "order_key",
128128
"created_via",
129129
// "version",
130130
"status",
131+
"date_created",
132+
"date_created_gmt",
133+
"date_modified",
134+
"date_modified_gmt",
135+
"date_paid",
136+
"date_paid_gmt",
137+
"date_completed",
138+
"date_completed_gmt",
131139
"currency",
132140
"prices_include_tax",
141+
"order_attribution_origin",
142+
"order_attribution_device_type",
133143
"customer_id",
134144
// "customer_ip_address",
135145
"customer_user_agent",
@@ -166,38 +176,31 @@ function WooCommerceOrdersRetrieve() {
166176
"shipping_postcode",
167177
"shipping_country",
168178
"shipping_methods",
169-
"date_created",
170-
"date_created_gmt",
171-
"date_modified",
172-
"date_modified_gmt",
173-
"date_paid",
174-
"date_paid_gmt",
175-
"date_completed",
176-
"date_completed_gmt",
177-
"discount_tax",
178-
"discount_total",
179-
"shipping_tax",
180-
"shipping_total",
181-
"cart_tax",
182-
"cart",
179+
"discount_amount_net",
180+
"discount_amount_tax",
181+
"discount_amount_gross",
182+
"shipping_amount_net",
183+
"shipping_amount_tax",
184+
"shipping_amount_gross",
185+
"items_amount_net",
186+
"items_amount_tax",
187+
"items_amount_gross",
183188
"transaction_fee",
184-
"total_tax",
185-
"total",
186-
"refund_tax",
187-
"refund",
189+
"refund_amount_net",
190+
"refund_amount_tax",
191+
"refund_amount_gross",
188192
"item_id",
189193
"item_name",
190194
// "product_id",
191195
"sku",
192196
"variation_id",
193197
"variation_name",
194-
"price",
195-
"quantity",
196198
"tax_class",
197-
"subtotal_tax",
198-
"subtotal",
199-
"line_total_tax",
200-
"line_total",
199+
"quantity",
200+
"price_gross",
201+
"line_item_amount_net",
202+
"line_item_amount_tax",
203+
"line_item_amount_gross",
201204
// "taxes",
202205
// "line_meta_data",
203206
];
@@ -211,12 +214,13 @@ function WooCommerceOrdersRetrieve() {
211214
values.forEach((row, i) => {
212215
if (row[0]) {
213216
if (!existing[row[0]]) existing[row[0]] = [];
214-
existing[row[0]].push(i + 2); // store row numbers
217+
existing[row[0]].push(i + 2); // Store row numbers
215218
}
216219
});
217220
}
218221

219222
const fieldMap = [
223+
order => "WooCommerce",
220224
order => order.id,
221225
// order => order.parent_id,
222226
// order => order.number,
@@ -230,8 +234,24 @@ function WooCommerceOrdersRetrieve() {
230234
}
231235
return status;
232236
},
237+
order => order.date_created,
238+
order => order.date_created_gmt,
239+
order => order.date_modified,
240+
order => order.date_modified_gmt,
241+
order => order.date_paid,
242+
order => order.date_paid_gmt,
243+
order => order.date_completed,
244+
order => order.date_completed_gmt,
233245
order => order.currency,
234246
order => order.prices_include_tax,
247+
order => {
248+
let source = (order.meta_data?.find(m => m.key === "_wc_order_attribution_source_type") || {}).value || "";
249+
if (source === "typein") {
250+
return "Direct";
251+
}
252+
return source.charAt(0).toUpperCase() + source.slice(1);
253+
},
254+
order => (order.meta_data?.find(m => m.key === "_wc_order_attribution_device_type") || {}).value || "",
235255
order => order.customer_id === 0 ? "" : order.customer_id,
236256
// order => order.customer_ip_address,
237257
order => order.customer_user_agent,
@@ -270,29 +290,23 @@ function WooCommerceOrdersRetrieve() {
270290
order => order.shipping_lines && order.shipping_lines.length > 0
271291
? order.shipping_lines.map(sl => woocommerceShippingMethods[sl.method_id] || sl.method_title).join(", ")
272292
: "",
273-
order => order.date_created,
274-
order => order.date_created_gmt,
275-
order => order.date_modified,
276-
order => order.date_modified_gmt,
277-
order => order.date_paid,
278-
order => order.date_paid_gmt,
279-
order => order.date_completed,
280-
order => order.date_completed_gmt,
281-
order => order.discount_tax,
282293
order => order.discount_total,
283-
order => order.shipping_tax,
294+
order => order.discount_tax,
295+
order => parseFloat(order.discount_total || 0) + parseFloat(order.discount_tax || 0),
284296
order => order.shipping_total,
285-
order => order.cart_tax,
286-
order => parseFloat(order.total || 0) - parseFloat(order.shipping_total || 0),
297+
order => order.shipping_tax,
298+
order => parseFloat(order.shipping_total || 0) + parseFloat(order.shipping_tax || 0),
299+
order => order.line_items.reduce((sum, item) => sum + parseFloat(item.total || 0), 0),
300+
order => order.line_items.reduce((sum, item) => sum + parseFloat(item.total_tax || 0), 0),
301+
order => order.line_items.reduce((sum, item) => sum + parseFloat(item.total || 0) + parseFloat(item.total_tax || 0), 0),
287302
order => {
288303
const paypalFee = order.meta_data ? order.meta_data.find(m => m.key === 'PayPal Transaction Fee') : null;
289304
const stripeFee = order.meta_data ? order.meta_data.find(m => m.key === '_stripe_fee') : null;
290305
return paypalFee ? paypalFee.value : (stripeFee ? stripeFee.value : null);
291306
},
292-
order => order.total_tax,
293-
order => order.total,
294-
order => order.refunds && order.refunds.length > 0 ? order.refunds.reduce((sum, r) => sum + Math.abs(parseFloat(r.total_tax || 0)), 0) : 0,
295307
order => order.refunds && order.refunds.length > 0 ? order.refunds.reduce((sum, r) => sum + Math.abs(parseFloat(r.total || 0)), 0) : 0,
308+
order => order.refunds && order.refunds.length > 0 ? order.refunds.reduce((sum, r) => sum + Math.abs(parseFloat(r.total_tax || 0)), 0) : 0,
309+
order => order.refunds && order.refunds.length > 0 ? order.refunds.reduce((sum, r) => sum + Math.abs(parseFloat(r.total || 0) + parseFloat(r.total_tax || 0)), 0) : 0,
296310
(order, item) => item.id,
297311
(order, item) => item.name,
298312
// (order, item) => item.product_id,
@@ -301,34 +315,52 @@ function WooCommerceOrdersRetrieve() {
301315
(order, item) => item.meta_data && item.meta_data.length > 0
302316
? item.meta_data.filter(m => m.key && m.key.indexOf("pa_") === 0).map(m => m.display_key + ": " + m.display_value).join(", ")
303317
: "",
304-
(order, item) => parseFloat(item.total || 0) / parseFloat(item.quantity || 1),
305-
(order, item) => item.quantity,
306318
(order, item) => woocommerceTaxRates[item.tax_class] || (item.tax_class === "" ? woocommerceTaxRates["standard"] : ""),
307-
(order, item) => item.subtotal_tax,
308-
(order, item) => item.subtotal,
309-
(order, item) => item.total_tax,
319+
(order, item) => item.quantity,
320+
(order, item) => parseFloat(item.price || 0).toFixed(2),
310321
(order, item) => item.total,
322+
(order, item) => item.total_tax,
323+
(order, item) => parseFloat(item.total || 0) + parseFloat(item.total_tax || 0),
311324
// (order, item) => JSON.stringify(item.taxes || []),
312325
// (order, item) => JSON.stringify(item.meta_data || [])
313326
];
314327

315-
// Map and write orders
328+
// Collect all rows to delete (for orders that already exist on the sheet), and also prepare the new rows to append
316329
const rows = [];
330+
const rowsToDeleteAll = [];
331+
317332
orders.forEach(order => {
318333
if (!order.line_items || order.line_items.length === 0) return;
319-
// Remove old rows for this order_id
334+
335+
// If the order already exists in the sheet, mark its rows for deletion
320336
if (existing[order.id]) {
321-
const rowsToDelete = existing[order.id];
322-
// Delete from bottom to top (to not mess indices)
323-
rowsToDelete.sort((a, b) => b - a).forEach(r => sheet.deleteRow(r));
337+
rowsToDeleteAll.push(...existing[order.id]);
324338
}
325339

340+
// Build new rows for this order's line items
326341
order.line_items.forEach(item => {
327342
const row = fieldMap.map(fieldFn => fieldFn(order, item));
328343
rows.push(row);
329344
});
330345
});
331346

347+
// Remove duplicates, sort descending and delete
348+
if (rowsToDeleteAll.length > 0) {
349+
const uniqueRows = Array.from(new Set(rowsToDeleteAll));
350+
uniqueRows.sort((a, b) => b - a); // Descending
351+
352+
// Delete each row if it's still within the current sheet bounds
353+
for (let i = 0; i < uniqueRows.length; i++) {
354+
const r = uniqueRows[i];
355+
// Safety guard to avoid out-of-bounds
356+
if (r >= 1 && r <= sheet.getLastRow()) {
357+
sheet.deleteRow(r);
358+
} else {
359+
Logger.log("Skipped deleting out-of-bounds row: " + r);
360+
}
361+
}
362+
}
363+
332364
// Batch append rows for efficiency
333365
if (rows.length > 0) {
334366
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);

technology/server/server-setup.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Debian and Virtualmin Server Setup
22

33
> [!NOTE]
4-
> Last update: 2025-08-26
4+
> Last update: 2025-09-01
55
66
```.sh
77
# Settings
@@ -604,9 +604,9 @@ server {
604604
set $php_socket_path unix:/run/php/${php_socket_id}.sock;
605605
server_name website.com www.website.com mail.website.com webmail.website.com admin.website.com;
606606
listen 100.00.000.01;
607-
listen 100.00.000.01:443 ssl;
607+
listen 100.00.000.01:443 ssl http2;
608608
listen [1000:0000:0000:0000:0000:0000:0000:0000];
609-
listen [1000:0000:0000:0000:0000:0000:0000:0000]:443 ssl;
609+
listen [1000:0000:0000:0000:0000:0000:0000:0000]:443 ssl http2;
610610
ssl_certificate /etc/ssl/virtualmin/100000000000000/ssl.cert;
611611
ssl_certificate_key /etc/ssl/virtualmin/100000000000000/ssl.key;
612612
set $content_security_policy "default-src 'self'; connect-src 'self' https://api.wordpress.org https://google.com https://pagead2.googlesyndication.com https://*.google-analytics.com https://analytics.google.com https://www.googletagmanager.com https://googleads.g.doubleclick.net https://www.googleadservices.com https://*.googleapis.com https://www.paypal.com https://www.sandbox.paypal.com https://*.stripe.com https://*.mercadopago.com https://*.mercadolibre.com https://api.mercadolibre.com; font-src 'self' data: https://fonts.gstatic.com; worker-src 'self' blob:; frame-src 'self' https://www.google.com https://www.googletagmanager.com https://td.doubleclick.net https://recaptcha.google.com https://www.youtube-nocookie.com https://www.paypal.com https://*.stripe.com https://www.mercadolibre.com https://api-static.mercadopago.com; img-src 'self' data: https://ps.w.org https://s.w.org https://t.paypal.com https://www.paypalobjects.com https://www.google.com https://www.google.de https://www.google-analytics.com https://www.googletagmanager.com https://googleads.g.doubleclick.net https://pagead2.googlesyndication.com https://*.stripe.com https://*.mercadopago.com https://*.mercadolibre.com https://http2.mlstatic.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.cloudflare.com https://static.cloudflareinsights.com https://www.google.com https://www.googletagmanager.com https://www.google-analytics.com https://www.gstatic.com https://googleads.g.doubleclick.net https://www.youtube.com https://www.youtube-nocookie.com https://www.paypal.com https://www.paypalobjects.com https://*.mercadopago.com https://http2.mlstatic.com https://www.googleadservices.com https://pagead2.googlesyndication.com https://*.stripe.com https://*.googleapis.com; style-src 'self' 'unsafe-inline' https://*.googleapis.com https://www.gstatic.com https://http2.mlstatic.com;";

0 commit comments

Comments
 (0)