gufi-cli 0.1.50 → 0.1.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/commands/docs.js +1 -5
  2. package/dist/index.js +1 -0
  3. package/dist/lib/docs-resolver.d.ts +8 -0
  4. package/dist/lib/docs-resolver.js +27 -0
  5. package/dist/mcp.d.ts +3 -1
  6. package/dist/mcp.js +232 -34
  7. package/docs/dev-guide/1-01-architecture.md +358 -0
  8. package/docs/dev-guide/1-02-multi-tenant.md +415 -0
  9. package/docs/dev-guide/1-03-column-types.md +594 -0
  10. package/docs/dev-guide/1-04-json-config.md +442 -0
  11. package/docs/dev-guide/1-05-authentication.md +427 -0
  12. package/docs/dev-guide/2-01-api-reference.md +564 -0
  13. package/docs/dev-guide/2-02-automations.md +508 -0
  14. package/docs/dev-guide/2-03-gufi-cli.md +568 -0
  15. package/docs/dev-guide/2-04-realtime.md +401 -0
  16. package/docs/dev-guide/2-05-permissions.md +497 -0
  17. package/docs/dev-guide/2-06-integrations-overview.md +104 -0
  18. package/docs/dev-guide/2-07-stripe.md +173 -0
  19. package/docs/dev-guide/2-08-nayax.md +297 -0
  20. package/docs/dev-guide/2-09-ourvend.md +226 -0
  21. package/docs/dev-guide/2-10-tns.md +177 -0
  22. package/docs/dev-guide/2-11-custom-http.md +268 -0
  23. package/docs/dev-guide/3-01-custom-views.md +555 -0
  24. package/docs/dev-guide/3-02-webhooks-api.md +446 -0
  25. package/docs/mcp/00-overview.md +329 -0
  26. package/docs/mcp/01-architecture.md +220 -0
  27. package/docs/mcp/02-modules.md +285 -0
  28. package/docs/mcp/03-fields.md +357 -0
  29. package/docs/mcp/04-views.md +613 -0
  30. package/docs/mcp/05-automations.md +461 -0
  31. package/docs/mcp/06-api.md +480 -0
  32. package/docs/mcp/07-packages.md +246 -0
  33. package/docs/mcp/08-common-errors.md +284 -0
  34. package/docs/mcp/09-examples.md +453 -0
  35. package/docs/mcp/README.md +71 -0
  36. package/docs/mcp/tool-descriptions.json +49 -0
  37. package/package.json +3 -2
@@ -0,0 +1,173 @@
1
+ ---
2
+ id: stripe
3
+ title: "Stripe Integration"
4
+ description: "Payment processing with Stripe"
5
+ icon: Zap
6
+ category: dev
7
+ part: 2
8
+ group: integrations
9
+ ---
10
+
11
+ # Stripe Integration
12
+
13
+ Payment processing with Stripe
14
+
15
+ ## Overview
16
+
17
+ Stripe is a payment processing platform. Gufi's integration lets you create Checkout Sessions that redirect customers to Stripe's hosted payment page.
18
+
19
+ ## Setup
20
+
21
+ 1. Create account at [stripe.com](https://stripe.com)
22
+ 2. Go to Dashboard → Developers → API keys
23
+ 3. Copy your **Secret Key** (starts with `sk_test_` or `sk_live_`)
24
+ 4. In Gufi: Settings → Environment Variables → Add `STRIPE_SECRET_KEY`
25
+
26
+ :::warning
27
+ Use test keys (`sk_test_`) during development. Never commit secret keys to code.
28
+ :::
29
+
30
+ ## Available Methods
31
+
32
+ | Method | Description |
33
+ |--------|-------------|
34
+ | `createCheckoutSession` | Create a Stripe Checkout Session for payment |
35
+
36
+ ## Method: createCheckoutSession
37
+
38
+ Creates a Stripe Checkout Session URL that customers can use to pay.
39
+
40
+ ## Parameters
41
+
42
+ | Parameter | Type | Required | Description |
43
+ |-----------|------|----------|-------------|
44
+ | secretKey | string | Yes | Your Stripe secret key from `env` |
45
+ | lineItems | array | Yes | Products: `[{ name, amount, quantity }]` |
46
+ | customerEmail | string | No | Pre-fill customer email |
47
+ | successUrl | string | No | Redirect after successful payment |
48
+ | cancelUrl | string | No | Redirect if customer cancels |
49
+ | metadata | object | No | Custom key-value data to attach |
50
+ | shippingCountries | array | No | Allowed shipping countries (ISO codes) |
51
+ | locale | string | No | Checkout language (default: es) |
52
+
53
+ ## Returns
54
+
55
+ ```javascript
56
+ {
57
+ url: "https://checkout.stripe.com/...", // Redirect customer here
58
+ sessionId: "cs_test_..." // Stripe session ID
59
+ }
60
+ ```
61
+
62
+ ## Basic Example
63
+
64
+ ```javascript
65
+ async function create_payment(gufi) {
66
+ const { row, env } = gufi.context;
67
+
68
+ const { url, sessionId } = await gufi.integrations.stripe.createCheckoutSession({
69
+ secretKey: env.STRIPE_SECRET_KEY,
70
+ lineItems: [
71
+ { name: 'Product Name', amount: 1999, quantity: 1 } // €19.99
72
+ ],
73
+ successUrl: 'https://mysite.com/success',
74
+ cancelUrl: 'https://mysite.com/cart'
75
+ });
76
+
77
+ return { checkout_url: url };
78
+ }
79
+ ```
80
+
81
+ ## E-commerce Order Example
82
+
83
+ Trigger: `on_create` for orders entity
84
+
85
+ ```javascript
86
+ async function process_new_order(gufi) {
87
+ const { row, env } = gufi.context;
88
+
89
+ // Build line items from order
90
+ const lineItems = [
91
+ {
92
+ name: row.product_name,
93
+ amount: Math.round(row.subtotal * 100), // Convert to cents
94
+ quantity: row.quantity
95
+ }
96
+ ];
97
+
98
+ // Add shipping if present
99
+ if (row.shipping_cost > 0) {
100
+ lineItems.push({
101
+ name: 'Shipping',
102
+ amount: Math.round(row.shipping_cost * 100),
103
+ quantity: 1
104
+ });
105
+ }
106
+
107
+ const { url, sessionId } = await gufi.integrations.stripe.createCheckoutSession({
108
+ secretKey: env.STRIPE_SECRET_KEY,
109
+ lineItems,
110
+ customerEmail: row.customer_email,
111
+ successUrl: `https://mystore.com/order/${row.id}/success`,
112
+ cancelUrl: `https://mystore.com/order/${row.id}`,
113
+ metadata: {
114
+ order_id: String(row.id),
115
+ customer_id: String(row.customer_id)
116
+ }
117
+ });
118
+
119
+ // Save checkout URL to order
120
+ await gufi.query(
121
+ `UPDATE orders SET checkout_url = $1, stripe_session = $2, status = 'pending_payment' WHERE id = $3`,
122
+ [url, sessionId, row.id]
123
+ );
124
+
125
+ gufi.logger.info('Checkout created', { orderId: row.id, sessionId });
126
+
127
+ return { checkout_url: url };
128
+ }
129
+ ```
130
+
131
+ ## On-Click Payment Button
132
+
133
+ Trigger: `on_click` - User clicks "Generate Payment Link"
134
+
135
+ ```javascript
136
+ async function generate_payment_link(gufi) {
137
+ const { row, env, input } = gufi.context;
138
+
139
+ const { url } = await gufi.integrations.stripe.createCheckoutSession({
140
+ secretKey: env.STRIPE_SECRET_KEY,
141
+ lineItems: [{
142
+ name: input.description || `Invoice #${row.id}`,
143
+ amount: Math.round(row.amount * 100),
144
+ quantity: 1
145
+ }],
146
+ customerEmail: row.email,
147
+ successUrl: 'https://mysite.com/payment-success',
148
+ metadata: { invoice_id: String(row.id) }
149
+ });
150
+
151
+ // Could also save to record or send by email
152
+ return {
153
+ message: 'Payment link generated',
154
+ payment_url: url
155
+ };
156
+ }
157
+ ```
158
+
159
+ ## Troubleshooting
160
+
161
+ | Error | Cause | Solution |
162
+ |-------|-------|----------|
163
+ | Invalid API Key | Wrong key format | Check `STRIPE_SECRET_KEY` starts with `sk_test_` or `sk_live_` |
164
+ | Amount must be at least 50 | Amount too small | Amounts are in cents. €19.99 = 1999 |
165
+ | No such customer | Invalid email | `customerEmail` is optional - remove it or use valid email |
166
+ | Invalid currency | Missing currency | Default is EUR. For other currencies, check Stripe docs |
167
+
168
+ ## Tips
169
+
170
+ - **Test mode**: Use `sk_test_` keys during development
171
+ - **Amounts in cents**: Always multiply by 100 (€19.99 → 1999)
172
+ - **Metadata**: Store your internal IDs for webhook reconciliation
173
+ - **Success URL**: Include `{CHECKOUT_SESSION_ID}` to get session ID back
@@ -0,0 +1,297 @@
1
+ ---
2
+ id: nayax
3
+ title: "Nayax Integration"
4
+ description: "Vending machine telemetry"
5
+ icon: Zap
6
+ category: dev
7
+ part: 2
8
+ group: integrations
9
+ ---
10
+
11
+ # Nayax Integration
12
+
13
+ Vending machine telemetry and sales sync
14
+
15
+ ## Overview
16
+
17
+ Nayax provides telemetry for vending machines. This integration syncs:
18
+
19
+ - Machines and their status
20
+ - Products and pricing
21
+ - Sales transactions
22
+ - Alerts and notifications
23
+ - Machine inventory (planograms)
24
+ - Pick lists (low stock items)
25
+
26
+ ## Setup
27
+
28
+ 1. Get your API token from Nayax Management Suite
29
+ 2. In Gufi: Settings → Environment Variables → Add `NAYAX_TOKEN`
30
+ 3. Create required tables in your module:
31
+ - `machines` - Machine inventory
32
+ - `products` - Product catalog
33
+ - `sales` - Sales transactions
34
+ - `alerts` - Machine alerts
35
+ - `machine_products` - Planograms (what's in each machine)
36
+
37
+ ## Available Methods
38
+
39
+ | Method | Description | Direction |
40
+ |--------|-------------|-----------|
41
+ | `sync_machines` | Sync all machines from Nayax | GET ← Nayax |
42
+ | `sync_products` | Sync product catalog | GET ← Nayax |
43
+ | `sync_machine_products` | Sync planograms (products in machines) | GET ← Nayax |
44
+ | `sync_last_sales` | Sync recent sales transactions | GET ← Nayax |
45
+ | `sync_alerts` | Sync machine alerts | GET ← Nayax |
46
+ | `sync_single_machine_capacity` | Update capacity for one machine | GET ← Nayax |
47
+ | `update_pick_list` | Send pick list to Nayax (quantities to restock) | PUT → Nayax |
48
+
49
+ ## Sync Order
50
+
51
+ :::warning
52
+ Always sync in this order to maintain foreign key relationships:
53
+ 1. `sync_machines` (first - no dependencies)
54
+ 2. `sync_products` (second - no dependencies)
55
+ 3. `sync_machine_products` (needs machines + products)
56
+ 4. `sync_last_sales` (needs machines + products)
57
+ 5. `sync_alerts` (needs machines + products)
58
+ :::
59
+
60
+ ## Method: sync_machines
61
+
62
+ Syncs all machines from your Nayax account.
63
+
64
+ ```javascript
65
+ const result = await gufi.integrations.nayax.sync_machines({
66
+ token: env.NAYAX_TOKEN,
67
+ tableName: 'nayax.machines'
68
+ });
69
+ // Returns: { synced: 45, created: 3, updated: 42 }
70
+ ```
71
+
72
+ ## Method: sync_products
73
+
74
+ Syncs your product catalog.
75
+
76
+ ```javascript
77
+ const result = await gufi.integrations.nayax.sync_products({
78
+ token: env.NAYAX_TOKEN,
79
+ tableName: 'nayax.products',
80
+ operatorActorId: 12345 // Optional: your Nayax operator ID
81
+ });
82
+ ```
83
+
84
+ ## Method: sync_machine_products
85
+
86
+ Syncs planograms - which products are in which machines.
87
+
88
+ ```javascript
89
+ const result = await gufi.integrations.nayax.sync_machine_products({
90
+ token: env.NAYAX_TOKEN,
91
+ tableName: 'nayax.machine_products',
92
+ machinesTable: 'nayax.machines',
93
+ productsTable: 'nayax.products'
94
+ });
95
+ ```
96
+
97
+ ## Method: sync_last_sales
98
+
99
+ Syncs recent sales transactions.
100
+
101
+ ```javascript
102
+ const result = await gufi.integrations.nayax.sync_last_sales({
103
+ token: env.NAYAX_TOKEN,
104
+ tableName: 'nayax.sales',
105
+ machinesTable: 'nayax.machines',
106
+ productsTable: 'nayax.products'
107
+ });
108
+ // Returns: { synced: 150, created: 150 }
109
+ ```
110
+
111
+ ## Method: sync_alerts
112
+
113
+ Syncs machine alerts and notifications.
114
+
115
+ ```javascript
116
+ const result = await gufi.integrations.nayax.sync_alerts({
117
+ token: env.NAYAX_TOKEN,
118
+ tableName: 'nayax.alerts',
119
+ machinesTable: 'nayax.machines',
120
+ productsTable: 'nayax.products'
121
+ });
122
+ ```
123
+
124
+ ## Method: sync_single_machine_capacity
125
+
126
+ Updates inventory for a specific machine. Use after replenishment.
127
+
128
+ ```javascript
129
+ const result = await gufi.integrations.nayax.sync_single_machine_capacity({
130
+ machineId: 123, // Gufi machine ID
131
+ token: env.NAYAX_TOKEN,
132
+ machineProductsTable: 'nayax.machine_products',
133
+ machinesTable: 'nayax.machines',
134
+ productsTable: 'nayax.products'
135
+ });
136
+ // Returns: { capacity: 85, synced: true }
137
+ ```
138
+
139
+ ## Method: update_pick_list
140
+
141
+ Sends pick list to Nayax - uploads quantities to restock for a machine.
142
+
143
+ **Flow:**
144
+ 1. `sync_machine_products` - Get inventory from Nayax (current stock + capacity)
145
+ 2. Gufi calculates - `pick_qty = par - on_hand_stock`
146
+ 3. `update_pick_list` - Send calculated quantities to Nayax
147
+
148
+ ```javascript
149
+ const result = await gufi.integrations.nayax.update_pick_list({
150
+ token: env.NAYAX_TOKEN,
151
+ machineId: 123, // Gufi machine ID (resolved to nayax_machine_id internally)
152
+ machinesTable: 'nayax.machines',
153
+ machineProductsTable: 'nayax.machine_products',
154
+ products: [
155
+ { product_id: 45, pick_qty: 10 },
156
+ { product_id: 67, pick_qty: 5 },
157
+ ],
158
+ });
159
+ // Returns: { success: true, machineId: 123, nayaxMachineId: 98765, productsUpdated: 2 }
160
+ ```
161
+
162
+ ### Generate Pick List Automation Example
163
+
164
+ ```javascript
165
+ async function generate_pick_list(gufi) {
166
+ const { row, env } = gufi.context;
167
+
168
+ // Get products with low stock for this machine
169
+ const products = await gufi.query(`
170
+ SELECT product_id, par, on_hand_stock, (par - on_hand_stock) as pick_qty
171
+ FROM nayax.machine_products
172
+ WHERE machine_id = $1 AND on_hand_stock < par
173
+ `, [row.machine_id]);
174
+
175
+ if (products.length === 0) {
176
+ gufi.logger.info('No products need restocking');
177
+ return { skipped: true };
178
+ }
179
+
180
+ // Send to Nayax
181
+ const result = await gufi.integrations.nayax.update_pick_list({
182
+ token: env.NAYAX_TOKEN,
183
+ machineId: row.machine_id,
184
+ machinesTable: 'nayax.machines',
185
+ machineProductsTable: 'nayax.machine_products',
186
+ products: products.map(p => ({
187
+ product_id: p.product_id,
188
+ pick_qty: p.pick_qty
189
+ })),
190
+ });
191
+
192
+ gufi.logger.info('Pick list sent to Nayax', result);
193
+ return result;
194
+ }
195
+ ```
196
+
197
+ ## Complete Daily Sync Example
198
+
199
+ Trigger: `scheduled` with cron `0 6 * * *` (6 AM daily)
200
+
201
+ ```javascript
202
+ async function nayax_daily_sync(gufi) {
203
+ const { env } = gufi.context;
204
+
205
+ gufi.logger.info('Starting Nayax daily sync');
206
+
207
+ // 1. Sync machines first
208
+ const machines = await gufi.integrations.nayax.sync_machines({
209
+ token: env.NAYAX_TOKEN,
210
+ tableName: 'nayax.machines'
211
+ });
212
+ gufi.logger.info('Machines synced', machines);
213
+
214
+ // 2. Sync products
215
+ const products = await gufi.integrations.nayax.sync_products({
216
+ token: env.NAYAX_TOKEN,
217
+ tableName: 'nayax.products'
218
+ });
219
+ gufi.logger.info('Products synced', products);
220
+
221
+ // 3. Sync planograms
222
+ const planograms = await gufi.integrations.nayax.sync_machine_products({
223
+ token: env.NAYAX_TOKEN,
224
+ tableName: 'nayax.machine_products',
225
+ machinesTable: 'nayax.machines',
226
+ productsTable: 'nayax.products'
227
+ });
228
+ gufi.logger.info('Planograms synced', planograms);
229
+
230
+ // 4. Sync sales
231
+ const sales = await gufi.integrations.nayax.sync_last_sales({
232
+ token: env.NAYAX_TOKEN,
233
+ tableName: 'nayax.sales',
234
+ machinesTable: 'nayax.machines',
235
+ productsTable: 'nayax.products'
236
+ });
237
+ gufi.logger.info('Sales synced', sales);
238
+
239
+ // 5. Sync alerts
240
+ const alerts = await gufi.integrations.nayax.sync_alerts({
241
+ token: env.NAYAX_TOKEN,
242
+ tableName: 'nayax.alerts',
243
+ machinesTable: 'nayax.machines',
244
+ productsTable: 'nayax.products'
245
+ });
246
+ gufi.logger.info('Alerts synced', alerts);
247
+
248
+ return {
249
+ machines: machines.synced,
250
+ products: products.synced,
251
+ sales: sales.synced,
252
+ alerts: alerts.synced
253
+ };
254
+ }
255
+ ```
256
+
257
+ ## Sync After Replenishment
258
+
259
+ Trigger: `on_update` for replenishments entity (when status = 'completed')
260
+
261
+ ```javascript
262
+ async function update_capacity_after_replenishment(gufi) {
263
+ const { row, env, changes } = gufi.context;
264
+
265
+ // Only run when status changes to completed
266
+ if (changes.status !== 'completed') {
267
+ return { skipped: true };
268
+ }
269
+
270
+ const result = await gufi.integrations.nayax.sync_single_machine_capacity({
271
+ machineId: row.machine_id,
272
+ token: env.NAYAX_TOKEN,
273
+ machineProductsTable: 'nayax.machine_products',
274
+ machinesTable: 'nayax.machines',
275
+ productsTable: 'nayax.products'
276
+ });
277
+
278
+ gufi.logger.info('Machine capacity updated', {
279
+ machineId: row.machine_id,
280
+ capacity: result.capacity
281
+ });
282
+
283
+ return { capacity: result.capacity };
284
+ }
285
+ ```
286
+
287
+ ## Troubleshooting
288
+
289
+ | Error | Cause | Solution |
290
+ |-------|-------|----------|
291
+ | 401 Unauthorized | Token expired | Get new token from Nayax Management Suite |
292
+ | Machine not found | Missing FK | Run `sync_machines` first |
293
+ | Product not found | Missing FK | Run `sync_products` first |
294
+ | FK constraint violation | Wrong sync order | Follow sync order: machines → products → rest |
295
+ | Timeout | Large sync | Consider syncing in smaller batches |
296
+ | MachineProductId not found | Product not in machine | Check `machine_products` has the product for that machine |
297
+ | update_pick_list: No products to update | Empty products array | Ensure products array is not empty |
@@ -0,0 +1,226 @@
1
+ ---
2
+ id: ourvend
3
+ title: "OurVend Integration"
4
+ description: "RedPanda vending system"
5
+ icon: Zap
6
+ category: dev
7
+ part: 2
8
+ group: integrations
9
+ ---
10
+
11
+ # OurVend Integration
12
+
13
+ RedPanda vending machine system
14
+
15
+ ## Overview
16
+
17
+ OurVend (RedPanda) is a vending machine management platform. This integration syncs:
18
+
19
+ - Machine groups (locations/categories)
20
+ - Machines
21
+ - Products
22
+ - Sales transactions
23
+
24
+ ## Setup
25
+
26
+ 1. Get API credentials from RedPanda
27
+ 2. In Gufi: Settings → Environment Variables, add:
28
+ - `OURVEND_USER` - Your API username
29
+ - `OURVEND_PASSWORD` - Your API password
30
+ 3. Create tables: `groups`, `machines`, `products`, `sales`
31
+
32
+ ## Available Methods
33
+
34
+ | Method | Description |
35
+ |--------|-------------|
36
+ | `sync_groups` | Sync machine groups/locations |
37
+ | `sync_machines` | Sync machines |
38
+ | `sync_products` | Sync product catalog |
39
+ | `sync_sales` | Sync sales with date range |
40
+
41
+ ## Sync Order
42
+
43
+ :::warning
44
+ Always sync in this order:
45
+ 1. `sync_groups` (first - no dependencies)
46
+ 2. `sync_machines` (needs groups)
47
+ 3. `sync_products` (no dependencies)
48
+ 4. `sync_sales` (needs machines, products, groups)
49
+ :::
50
+
51
+ ## Method: sync_groups
52
+
53
+ Syncs machine groups (locations, categories).
54
+
55
+ ```javascript
56
+ const result = await gufi.integrations.ourvend.sync_groups({
57
+ user: env.OURVEND_USER,
58
+ password: env.OURVEND_PASSWORD,
59
+ tableName: 'ourvend.groups'
60
+ });
61
+ // Returns: { synced: 12, created: 2, updated: 10 }
62
+ ```
63
+
64
+ ## Method: sync_machines
65
+
66
+ Syncs all machines. Requires groups to be synced first.
67
+
68
+ ```javascript
69
+ const result = await gufi.integrations.ourvend.sync_machines({
70
+ user: env.OURVEND_USER,
71
+ password: env.OURVEND_PASSWORD,
72
+ tableName: 'ourvend.machines',
73
+ groupsTable: 'ourvend.groups'
74
+ });
75
+ ```
76
+
77
+ ## Method: sync_products
78
+
79
+ Syncs the product catalog.
80
+
81
+ ```javascript
82
+ const result = await gufi.integrations.ourvend.sync_products({
83
+ user: env.OURVEND_USER,
84
+ password: env.OURVEND_PASSWORD,
85
+ tableName: 'ourvend.products'
86
+ });
87
+ ```
88
+
89
+ ## Method: sync_sales
90
+
91
+ Syncs sales transactions. Supports date range filtering.
92
+
93
+ | Parameter | Required | Description |
94
+ |-----------|----------|-------------|
95
+ | user | Yes | API username |
96
+ | password | Yes | API password |
97
+ | tableName | Yes | Target sales table |
98
+ | machinesTable | Yes | Machines table for FK |
99
+ | productsTable | Yes | Products table for FK |
100
+ | groupsTable | Yes | Groups table for FK |
101
+ | dateFrom | No | Start date (ISO format) |
102
+ | dateTo | No | End date (ISO format) |
103
+
104
+ ```javascript
105
+ const result = await gufi.integrations.ourvend.sync_sales({
106
+ user: env.OURVEND_USER,
107
+ password: env.OURVEND_PASSWORD,
108
+ tableName: 'ourvend.sales',
109
+ machinesTable: 'ourvend.machines',
110
+ productsTable: 'ourvend.products',
111
+ groupsTable: 'ourvend.groups',
112
+ dateFrom: '2024-01-01',
113
+ dateTo: '2024-01-31'
114
+ });
115
+ // Returns: { synced: 1250, created: 1250 }
116
+ ```
117
+
118
+ ## Complete Daily Sync Example
119
+
120
+ Trigger: `scheduled` with cron `0 5 * * *` (5 AM daily)
121
+
122
+ ```javascript
123
+ async function ourvend_daily_sync(gufi) {
124
+ const { env } = gufi.context;
125
+
126
+ // 1. Sync groups first
127
+ const groups = await gufi.integrations.ourvend.sync_groups({
128
+ user: env.OURVEND_USER,
129
+ password: env.OURVEND_PASSWORD,
130
+ tableName: 'ourvend.groups'
131
+ });
132
+ gufi.logger.info('Groups synced', groups);
133
+
134
+ // 2. Sync machines (needs groups)
135
+ const machines = await gufi.integrations.ourvend.sync_machines({
136
+ user: env.OURVEND_USER,
137
+ password: env.OURVEND_PASSWORD,
138
+ tableName: 'ourvend.machines',
139
+ groupsTable: 'ourvend.groups'
140
+ });
141
+ gufi.logger.info('Machines synced', machines);
142
+
143
+ // 3. Sync products
144
+ const products = await gufi.integrations.ourvend.sync_products({
145
+ user: env.OURVEND_USER,
146
+ password: env.OURVEND_PASSWORD,
147
+ tableName: 'ourvend.products'
148
+ });
149
+ gufi.logger.info('Products synced', products);
150
+
151
+ // 4. Sync yesterday's sales
152
+ const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
153
+ const today = new Date().toISOString().split('T')[0];
154
+
155
+ const sales = await gufi.integrations.ourvend.sync_sales({
156
+ user: env.OURVEND_USER,
157
+ password: env.OURVEND_PASSWORD,
158
+ tableName: 'ourvend.sales',
159
+ machinesTable: 'ourvend.machines',
160
+ productsTable: 'ourvend.products',
161
+ groupsTable: 'ourvend.groups',
162
+ dateFrom: yesterday,
163
+ dateTo: today
164
+ });
165
+ gufi.logger.info('Sales synced', sales);
166
+
167
+ return {
168
+ groups: groups.synced,
169
+ machines: machines.synced,
170
+ products: products.synced,
171
+ sales: sales.synced
172
+ };
173
+ }
174
+ ```
175
+
176
+ ## Monthly Sales Sync
177
+
178
+ Trigger: `on_click` - Manual sync of historical data
179
+
180
+ ```javascript
181
+ async function sync_monthly_sales(gufi) {
182
+ const { env, input } = gufi.context;
183
+
184
+ // Get dates from input or default to previous month
185
+ const now = new Date();
186
+ const firstDay = input.dateFrom || new Date(now.getFullYear(), now.getMonth() - 1, 1).toISOString().split('T')[0];
187
+ const lastDay = input.dateTo || new Date(now.getFullYear(), now.getMonth(), 0).toISOString().split('T')[0];
188
+
189
+ const sales = await gufi.integrations.ourvend.sync_sales({
190
+ user: env.OURVEND_USER,
191
+ password: env.OURVEND_PASSWORD,
192
+ tableName: 'ourvend.sales',
193
+ machinesTable: 'ourvend.machines',
194
+ productsTable: 'ourvend.products',
195
+ groupsTable: 'ourvend.groups',
196
+ dateFrom: firstDay,
197
+ dateTo: lastDay
198
+ });
199
+
200
+ return {
201
+ message: `Synced ${sales.synced} sales from ${firstDay} to ${lastDay}`
202
+ };
203
+ }
204
+ ```
205
+
206
+ ## Integration with TNS
207
+
208
+ OurVend sales contain product codes that can be matched with TNS catalog:
209
+
210
+ ```javascript
211
+ // After OurVend sync, sync TNS products from sales
212
+ await gufi.integrations.tns.sync_products_from_sales({
213
+ productsTable: 'tns.products',
214
+ categoriesTable: 'tns.categories',
215
+ salesTable: 'ourvend.sales'
216
+ });
217
+ ```
218
+
219
+ ## Troubleshooting
220
+
221
+ | Error | Cause | Solution |
222
+ |-------|-------|----------|
223
+ | Authentication failed | Wrong credentials | Verify `OURVEND_USER` and `OURVEND_PASSWORD` |
224
+ | Group not found | Missing FK | Run `sync_groups` before `sync_machines` |
225
+ | Product not found | Missing FK | Run `sync_products` before `sync_sales` |
226
+ | Timeout | Large date range | Sync in smaller date ranges (e.g., weekly) |