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.
- package/dist/commands/docs.js +1 -5
- package/dist/index.js +1 -0
- package/dist/lib/docs-resolver.d.ts +8 -0
- package/dist/lib/docs-resolver.js +27 -0
- package/dist/mcp.d.ts +3 -1
- package/dist/mcp.js +232 -34
- package/docs/dev-guide/1-01-architecture.md +358 -0
- package/docs/dev-guide/1-02-multi-tenant.md +415 -0
- package/docs/dev-guide/1-03-column-types.md +594 -0
- package/docs/dev-guide/1-04-json-config.md +442 -0
- package/docs/dev-guide/1-05-authentication.md +427 -0
- package/docs/dev-guide/2-01-api-reference.md +564 -0
- package/docs/dev-guide/2-02-automations.md +508 -0
- package/docs/dev-guide/2-03-gufi-cli.md +568 -0
- package/docs/dev-guide/2-04-realtime.md +401 -0
- package/docs/dev-guide/2-05-permissions.md +497 -0
- package/docs/dev-guide/2-06-integrations-overview.md +104 -0
- package/docs/dev-guide/2-07-stripe.md +173 -0
- package/docs/dev-guide/2-08-nayax.md +297 -0
- package/docs/dev-guide/2-09-ourvend.md +226 -0
- package/docs/dev-guide/2-10-tns.md +177 -0
- package/docs/dev-guide/2-11-custom-http.md +268 -0
- package/docs/dev-guide/3-01-custom-views.md +555 -0
- package/docs/dev-guide/3-02-webhooks-api.md +446 -0
- package/docs/mcp/00-overview.md +329 -0
- package/docs/mcp/01-architecture.md +220 -0
- package/docs/mcp/02-modules.md +285 -0
- package/docs/mcp/03-fields.md +357 -0
- package/docs/mcp/04-views.md +613 -0
- package/docs/mcp/05-automations.md +461 -0
- package/docs/mcp/06-api.md +480 -0
- package/docs/mcp/07-packages.md +246 -0
- package/docs/mcp/08-common-errors.md +284 -0
- package/docs/mcp/09-examples.md +453 -0
- package/docs/mcp/README.md +71 -0
- package/docs/mcp/tool-descriptions.json +49 -0
- 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) |
|