@simonfestl/husky-cli 1.12.0 → 1.14.0
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/biz/emove.d.ts +12 -0
- package/dist/commands/biz/emove.js +249 -0
- package/dist/commands/biz/skuterzone.d.ts +12 -0
- package/dist/commands/biz/skuterzone.js +248 -0
- package/dist/commands/biz/wattiz.d.ts +12 -0
- package/dist/commands/biz/wattiz.js +252 -0
- package/dist/commands/biz.js +7 -1
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.js +12 -1
- package/dist/index.js +1 -0
- package/dist/lib/biz/emove-playwright.d.ts +22 -0
- package/dist/lib/biz/emove-playwright.js +289 -0
- package/dist/lib/biz/emove-types.d.ts +60 -0
- package/dist/lib/biz/emove-types.js +7 -0
- package/dist/lib/biz/skuterzone-playwright.d.ts +21 -0
- package/dist/lib/biz/skuterzone-playwright.js +271 -0
- package/dist/lib/biz/skuterzone-types.d.ts +60 -0
- package/dist/lib/biz/skuterzone-types.js +7 -0
- package/dist/lib/biz/wattiz-playwright.d.ts +22 -0
- package/dist/lib/biz/wattiz-playwright.js +530 -0
- package/dist/lib/biz/wattiz-types.d.ts +61 -0
- package/dist/lib/biz/wattiz-types.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Husky Biz Wattiz Command
|
|
3
|
+
*
|
|
4
|
+
* Manages wattiz.fr (B2B e-mobility parts supplier) via web scraping
|
|
5
|
+
* PrestaShop platform integration
|
|
6
|
+
* - Parts search and catalog browsing
|
|
7
|
+
* - Order history and details
|
|
8
|
+
* - Invoice download
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import { WattizPlaywrightClient } from "../../lib/biz/wattiz-playwright.js";
|
|
12
|
+
import { getConfig, saveConfig } from "../config.js";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
export const wattizCommand = new Command("wattiz")
|
|
16
|
+
.description("Manage wattiz.fr (e-mobility parts supplier - PrestaShop)");
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// husky biz wattiz login
|
|
19
|
+
// ============================================================================
|
|
20
|
+
wattizCommand
|
|
21
|
+
.command("login")
|
|
22
|
+
.description("Setup Wattiz credentials")
|
|
23
|
+
.requiredOption("-u, --username <username>", "Login username or email")
|
|
24
|
+
.requiredOption("-p, --password <password>", "Login password")
|
|
25
|
+
.option("--base-url <url>", "Base URL", "https://www.wattiz.fr")
|
|
26
|
+
.option("--language <lang>", "Language (gb, fr, de, es)", "gb")
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
try {
|
|
29
|
+
// Save to config
|
|
30
|
+
const config = getConfig();
|
|
31
|
+
config.wattizUsername = options.username;
|
|
32
|
+
config.wattizPassword = options.password;
|
|
33
|
+
if (options.baseUrl) {
|
|
34
|
+
config.wattizBaseUrl = options.baseUrl;
|
|
35
|
+
}
|
|
36
|
+
if (options.language) {
|
|
37
|
+
config.wattizLanguage = options.language;
|
|
38
|
+
}
|
|
39
|
+
saveConfig(config);
|
|
40
|
+
console.log("Testing authentication...");
|
|
41
|
+
// Test login
|
|
42
|
+
const client = WattizPlaywrightClient.fromConfig();
|
|
43
|
+
try {
|
|
44
|
+
const result = await client.login();
|
|
45
|
+
if (result.success) {
|
|
46
|
+
console.log("✓ Successfully authenticated with wattiz.fr");
|
|
47
|
+
console.log("\nYou can now use:");
|
|
48
|
+
console.log(" husky biz wattiz orders list");
|
|
49
|
+
console.log(" husky biz wattiz orders get <id>");
|
|
50
|
+
console.log(" husky biz wattiz invoice <order-id>");
|
|
51
|
+
console.log(" husky biz wattiz products <query>");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.error("✗ Login failed:", result.error);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await client.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error("✗ Login failed:", error.message);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// husky biz wattiz orders
|
|
69
|
+
// ============================================================================
|
|
70
|
+
const ordersSubcommand = new Command("orders")
|
|
71
|
+
.description("View supplier orders");
|
|
72
|
+
ordersSubcommand
|
|
73
|
+
.command("list")
|
|
74
|
+
.description("List all orders from Wattiz")
|
|
75
|
+
.option("--json", "Output as JSON")
|
|
76
|
+
.action(async (options) => {
|
|
77
|
+
const client = WattizPlaywrightClient.fromConfig();
|
|
78
|
+
try {
|
|
79
|
+
const orders = await client.listOrders();
|
|
80
|
+
if (options.json) {
|
|
81
|
+
console.log(JSON.stringify(orders, null, 2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (orders.length === 0) {
|
|
85
|
+
console.log("\n No orders found.\n");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log(`\n 📦 Wattiz Orders (${orders.length} found)\n`);
|
|
89
|
+
// Header
|
|
90
|
+
console.log(` ${"Order #".padEnd(15)} │ ` +
|
|
91
|
+
`${"Date".padEnd(20)} │ ` +
|
|
92
|
+
`${"Status".padEnd(15)} │ ` +
|
|
93
|
+
`${"Total".padEnd(12)}`);
|
|
94
|
+
console.log(" " + "─".repeat(75));
|
|
95
|
+
// Orders
|
|
96
|
+
for (const order of orders) {
|
|
97
|
+
console.log(` ${order.orderNumber.padEnd(15)} │ ` +
|
|
98
|
+
`${order.date.padEnd(20)} │ ` +
|
|
99
|
+
`${order.status.padEnd(15)} │ ` +
|
|
100
|
+
`${order.total}`);
|
|
101
|
+
}
|
|
102
|
+
console.log("");
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("Error:", error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
await client.close();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
ordersSubcommand
|
|
113
|
+
.command("get <id>")
|
|
114
|
+
.description("Get order details")
|
|
115
|
+
.option("--json", "Output as JSON")
|
|
116
|
+
.action(async (id, options) => {
|
|
117
|
+
const client = WattizPlaywrightClient.fromConfig();
|
|
118
|
+
try {
|
|
119
|
+
const order = await client.getOrder(id);
|
|
120
|
+
if (options.json) {
|
|
121
|
+
console.log(JSON.stringify(order, null, 2));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(`\n Order ${order.orderNumber}`);
|
|
125
|
+
console.log(" " + "─".repeat(60));
|
|
126
|
+
console.log(` Status: ${order.status}`);
|
|
127
|
+
console.log(` Date: ${order.date}`);
|
|
128
|
+
console.log(` Total: ${order.total}`);
|
|
129
|
+
if (order.trackingNumber) {
|
|
130
|
+
console.log(` Tracking: ${order.trackingNumber}`);
|
|
131
|
+
}
|
|
132
|
+
if (order.paymentMethod) {
|
|
133
|
+
console.log(` Payment: ${order.paymentMethod}`);
|
|
134
|
+
}
|
|
135
|
+
if (order.customer && order.customer.name) {
|
|
136
|
+
console.log(`\n Shipping To:`);
|
|
137
|
+
console.log(` ${order.customer.name}`);
|
|
138
|
+
if (order.customer.company) {
|
|
139
|
+
console.log(` ${order.customer.company}`);
|
|
140
|
+
}
|
|
141
|
+
if (order.customer.address) {
|
|
142
|
+
console.log(` ${order.customer.address}`);
|
|
143
|
+
}
|
|
144
|
+
if (order.customer.postcode && order.customer.city) {
|
|
145
|
+
console.log(` ${order.customer.postcode} ${order.customer.city}`);
|
|
146
|
+
}
|
|
147
|
+
if (order.customer.email) {
|
|
148
|
+
console.log(` ${order.customer.email}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (order.items.length > 0) {
|
|
152
|
+
console.log(`\n Items:`);
|
|
153
|
+
for (const item of order.items) {
|
|
154
|
+
const sku = item.sku ? `[${item.sku}]`.padEnd(18) : "".padEnd(18);
|
|
155
|
+
const name = item.name.slice(0, 35).padEnd(35);
|
|
156
|
+
console.log(` ${item.quantity}x ${sku} ${name} (${item.total})`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (order.subtotal || order.shipping || order.tax) {
|
|
160
|
+
console.log(`\n Totals:`);
|
|
161
|
+
if (order.subtotal)
|
|
162
|
+
console.log(` Subtotal: ${order.subtotal}`);
|
|
163
|
+
if (order.shipping)
|
|
164
|
+
console.log(` Shipping: ${order.shipping}`);
|
|
165
|
+
if (order.tax)
|
|
166
|
+
console.log(` Tax: ${order.tax}`);
|
|
167
|
+
console.log(` Total: ${order.total}`);
|
|
168
|
+
}
|
|
169
|
+
if (order.invoiceUrl) {
|
|
170
|
+
console.log(`\n 📄 Invoice available`);
|
|
171
|
+
console.log(` Download with: husky biz wattiz invoice ${order.id}`);
|
|
172
|
+
}
|
|
173
|
+
console.log("");
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error("Error:", error.message);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await client.close();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
wattizCommand.addCommand(ordersSubcommand);
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// husky biz wattiz invoice
|
|
186
|
+
// ============================================================================
|
|
187
|
+
wattizCommand
|
|
188
|
+
.command("invoice <order-id>")
|
|
189
|
+
.description("Download invoice PDF for order")
|
|
190
|
+
.option("-o, --output <path>", "Save path (default: ~/Downloads/wattiz-invoice-{id}.pdf)")
|
|
191
|
+
.action(async (orderId, options) => {
|
|
192
|
+
const client = WattizPlaywrightClient.fromConfig();
|
|
193
|
+
try {
|
|
194
|
+
const savePath = options.output ||
|
|
195
|
+
path.join(homedir(), 'Downloads', `wattiz-invoice-${orderId}.pdf`);
|
|
196
|
+
console.log(`Downloading invoice for order #${orderId}...`);
|
|
197
|
+
const success = await client.downloadInvoice(orderId, savePath);
|
|
198
|
+
if (success) {
|
|
199
|
+
console.log(`✓ Invoice saved to: ${savePath}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.error("✗ Invoice not available for this order");
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.error("Error:", error.message);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
await client.close();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// husky biz wattiz products
|
|
216
|
+
// ============================================================================
|
|
217
|
+
wattizCommand
|
|
218
|
+
.command("products <query>")
|
|
219
|
+
.description("Search for parts in catalog")
|
|
220
|
+
.option("--json", "Output as JSON")
|
|
221
|
+
.action(async (query, options) => {
|
|
222
|
+
const client = WattizPlaywrightClient.fromConfig();
|
|
223
|
+
try {
|
|
224
|
+
const products = await client.searchProducts(query);
|
|
225
|
+
if (options.json) {
|
|
226
|
+
console.log(JSON.stringify(products, null, 2));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (products.length === 0) {
|
|
230
|
+
console.log(`\n No products found for: "${query}"\n`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
console.log(`\n 🔍 Search results for: "${query}" (${products.length} found)\n`);
|
|
234
|
+
for (const product of products) {
|
|
235
|
+
const sku = product.sku ? `[${product.sku}]` : '';
|
|
236
|
+
const price = product.price || 'Price hidden (login required)';
|
|
237
|
+
const stock = product.stockStatus || '';
|
|
238
|
+
console.log(` ${product.name}`);
|
|
239
|
+
console.log(` ${sku} ${price}${stock ? ' - ' + stock : ''}`);
|
|
240
|
+
console.log(` ${product.url}`);
|
|
241
|
+
console.log("");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
console.error("Error:", error.message);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
await client.close();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
export default wattizCommand;
|
package/dist/commands/biz.js
CHANGED
|
@@ -12,6 +12,9 @@ import { customersCommand } from "./biz/customers.js";
|
|
|
12
12
|
import { seatableCommand } from "./biz/seatable.js";
|
|
13
13
|
import { qdrantCommand } from "./biz/qdrant.js";
|
|
14
14
|
import { gotessCommand } from "./biz/gotess.js";
|
|
15
|
+
import { skuterzoneCommand } from "./biz/skuterzone.js";
|
|
16
|
+
import { emoveCommand } from "./biz/emove.js";
|
|
17
|
+
import { wattizCommand } from "./biz/wattiz.js";
|
|
15
18
|
import { guards } from "../lib/permissions.js";
|
|
16
19
|
export const bizCommand = new Command("biz")
|
|
17
20
|
.description("Business operations for autonomous agents")
|
|
@@ -22,5 +25,8 @@ export const bizCommand = new Command("biz")
|
|
|
22
25
|
.addCommand(customersCommand)
|
|
23
26
|
.addCommand(seatableCommand)
|
|
24
27
|
.addCommand(qdrantCommand)
|
|
25
|
-
.addCommand(gotessCommand)
|
|
28
|
+
.addCommand(gotessCommand)
|
|
29
|
+
.addCommand(skuterzoneCommand)
|
|
30
|
+
.addCommand(emoveCommand)
|
|
31
|
+
.addCommand(wattizCommand);
|
|
26
32
|
export default bizCommand;
|
|
@@ -35,6 +35,13 @@ interface Config {
|
|
|
35
35
|
skuterzoneUsername?: string;
|
|
36
36
|
skuterzonePassword?: string;
|
|
37
37
|
skuterzoneBaseUrl?: string;
|
|
38
|
+
emoveUsername?: string;
|
|
39
|
+
emovePassword?: string;
|
|
40
|
+
emoveBaseUrl?: string;
|
|
41
|
+
wattizUsername?: string;
|
|
42
|
+
wattizPassword?: string;
|
|
43
|
+
wattizBaseUrl?: string;
|
|
44
|
+
wattizLanguage?: string;
|
|
38
45
|
}
|
|
39
46
|
export declare function getConfig(): Config;
|
|
40
47
|
export declare function saveConfig(config: Config): void;
|
package/dist/commands/config.js
CHANGED
|
@@ -197,6 +197,15 @@ configCommand
|
|
|
197
197
|
"skuterzone-username": "skuterzoneUsername",
|
|
198
198
|
"skuterzone-password": "skuterzonePassword",
|
|
199
199
|
"skuterzone-base-url": "skuterzoneBaseUrl",
|
|
200
|
+
// Emove Distribution
|
|
201
|
+
"emove-username": "emoveUsername",
|
|
202
|
+
"emove-password": "emovePassword",
|
|
203
|
+
"emove-base-url": "emoveBaseUrl",
|
|
204
|
+
// Wattiz
|
|
205
|
+
"wattiz-username": "wattizUsername",
|
|
206
|
+
"wattiz-password": "wattizPassword",
|
|
207
|
+
"wattiz-base-url": "wattizBaseUrl",
|
|
208
|
+
"wattiz-language": "wattizLanguage",
|
|
200
209
|
};
|
|
201
210
|
const configKey = keyMappings[key];
|
|
202
211
|
if (!configKey) {
|
|
@@ -212,6 +221,8 @@ configCommand
|
|
|
212
221
|
console.log(" Gemini: gemini-api-key");
|
|
213
222
|
console.log(" NocoDB: nocodb-api-token, nocodb-base-url, nocodb-workspace-id");
|
|
214
223
|
console.log(" Skuterzone: skuterzone-username, skuterzone-password, skuterzone-base-url");
|
|
224
|
+
console.log(" Emove: emove-username, emove-password, emove-base-url");
|
|
225
|
+
console.log(" Wattiz: wattiz-username, wattiz-password, wattiz-base-url, wattiz-language");
|
|
215
226
|
console.log(" Brain: agent-type");
|
|
216
227
|
console.error("\n💡 For configuration help: husky explain config");
|
|
217
228
|
process.exit(1);
|
|
@@ -233,7 +244,7 @@ configCommand
|
|
|
233
244
|
config[configKey] = value;
|
|
234
245
|
saveConfig(config);
|
|
235
246
|
// Mask sensitive values in output
|
|
236
|
-
const sensitiveKeys = ["api-key", "billbee-api-key", "billbee-password", "zendesk-api-token", "seatable-api-token", "gotess-token", "gemini-api-key", "nocodb-api-token", "skuterzone-username", "skuterzone-password"];
|
|
247
|
+
const sensitiveKeys = ["api-key", "billbee-api-key", "billbee-password", "zendesk-api-token", "seatable-api-token", "gotess-token", "gemini-api-key", "nocodb-api-token", "skuterzone-username", "skuterzone-password", "emove-username", "emove-password", "wattiz-username", "wattiz-password"];
|
|
237
248
|
const displayValue = sensitiveKeys.includes(key) ? "***" : value;
|
|
238
249
|
console.log(`✓ Set ${key} = ${displayValue}`);
|
|
239
250
|
});
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emove Distribution Client with Playwright
|
|
3
|
+
*
|
|
4
|
+
* Uses real browser automation for emovedistribution.com
|
|
5
|
+
* WooCommerce platform with Spanish locale
|
|
6
|
+
*/
|
|
7
|
+
import { EmoveConfig, EmoveOrder, EmoveOrderDetails, EmoveProduct, EmoveLoginResult } from './emove-types.js';
|
|
8
|
+
export declare class EmovePlaywrightClient {
|
|
9
|
+
private config;
|
|
10
|
+
private browser?;
|
|
11
|
+
private page?;
|
|
12
|
+
constructor(config: EmoveConfig);
|
|
13
|
+
static fromConfig(): EmovePlaywrightClient;
|
|
14
|
+
private ensureBrowser;
|
|
15
|
+
login(): Promise<EmoveLoginResult>;
|
|
16
|
+
listOrders(): Promise<EmoveOrder[]>;
|
|
17
|
+
getOrder(orderId: string): Promise<EmoveOrderDetails>;
|
|
18
|
+
downloadInvoice(orderId: string, savePath: string): Promise<boolean>;
|
|
19
|
+
searchProducts(query: string): Promise<EmoveProduct[]>;
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export default EmovePlaywrightClient;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emove Distribution Client with Playwright
|
|
3
|
+
*
|
|
4
|
+
* Uses real browser automation for emovedistribution.com
|
|
5
|
+
* WooCommerce platform with Spanish locale
|
|
6
|
+
*/
|
|
7
|
+
import { chromium } from 'playwright';
|
|
8
|
+
import { getConfig } from '../../commands/config.js';
|
|
9
|
+
export class EmovePlaywrightClient {
|
|
10
|
+
config;
|
|
11
|
+
browser;
|
|
12
|
+
page;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
static fromConfig() {
|
|
17
|
+
const config = getConfig();
|
|
18
|
+
const env = process.env.HUSKY_ENV || 'PROD';
|
|
19
|
+
const emoveConfig = {
|
|
20
|
+
username: process.env[`${env}_EMOVE_USERNAME`] ||
|
|
21
|
+
process.env.EMOVE_USERNAME ||
|
|
22
|
+
config.emoveUsername || '',
|
|
23
|
+
password: process.env[`${env}_EMOVE_PASSWORD`] ||
|
|
24
|
+
process.env.EMOVE_PASSWORD ||
|
|
25
|
+
config.emovePassword || '',
|
|
26
|
+
baseUrl: process.env[`${env}_EMOVE_BASE_URL`] ||
|
|
27
|
+
process.env.EMOVE_BASE_URL ||
|
|
28
|
+
config.emoveBaseUrl ||
|
|
29
|
+
'https://emovedistribution.com',
|
|
30
|
+
};
|
|
31
|
+
if (!emoveConfig.username || !emoveConfig.password) {
|
|
32
|
+
throw new Error('Missing Emove credentials. Configure with:\n' +
|
|
33
|
+
' husky config set emove-username <username>\n' +
|
|
34
|
+
' husky config set emove-password <password>');
|
|
35
|
+
}
|
|
36
|
+
return new EmovePlaywrightClient(emoveConfig);
|
|
37
|
+
}
|
|
38
|
+
async ensureBrowser() {
|
|
39
|
+
if (!this.browser) {
|
|
40
|
+
this.browser = await chromium.launch({ headless: true });
|
|
41
|
+
this.page = await this.browser.newPage();
|
|
42
|
+
}
|
|
43
|
+
return this.page;
|
|
44
|
+
}
|
|
45
|
+
async login() {
|
|
46
|
+
try {
|
|
47
|
+
const page = await this.ensureBrowser();
|
|
48
|
+
// Navigate to login page (Spanish WooCommerce uses /mi-cuenta/)
|
|
49
|
+
await page.goto(`${this.config.baseUrl}/mi-cuenta/`);
|
|
50
|
+
await page.waitForLoadState('networkidle');
|
|
51
|
+
// Wait for the WooCommerce login form
|
|
52
|
+
await page.waitForSelector('input#username', { state: 'visible', timeout: 10000 });
|
|
53
|
+
// Fill in login form
|
|
54
|
+
await page.fill('input#username', this.config.username);
|
|
55
|
+
await page.fill('input#password', this.config.password);
|
|
56
|
+
// Submit form
|
|
57
|
+
await page.click('button[name="login"]');
|
|
58
|
+
// Wait for navigation
|
|
59
|
+
await page.waitForLoadState('networkidle');
|
|
60
|
+
// Check if logged in
|
|
61
|
+
const isLoggedIn = await page.locator('body.logged-in').count() > 0;
|
|
62
|
+
if (!isLoggedIn) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
cookies: '',
|
|
66
|
+
error: 'Login failed - check credentials'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Extract cookies
|
|
70
|
+
const cookies = await page.context().cookies();
|
|
71
|
+
const cookieString = cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
cookies: cookieString
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
cookies: '',
|
|
81
|
+
error: `Login error: ${error.message}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async listOrders() {
|
|
86
|
+
const page = await this.ensureBrowser();
|
|
87
|
+
// Navigate to orders page (Spanish: pedidos instead of orders)
|
|
88
|
+
await page.goto(`${this.config.baseUrl}/mi-cuenta/pedidos/`);
|
|
89
|
+
await page.waitForLoadState('networkidle');
|
|
90
|
+
// Check if we need to login
|
|
91
|
+
const needsLogin = await page.locator('form.woocommerce-form-login').count() > 0;
|
|
92
|
+
if (needsLogin) {
|
|
93
|
+
await this.login();
|
|
94
|
+
await page.goto(`${this.config.baseUrl}/mi-cuenta/pedidos/`);
|
|
95
|
+
await page.waitForLoadState('networkidle');
|
|
96
|
+
}
|
|
97
|
+
const orders = [];
|
|
98
|
+
// Find all order rows
|
|
99
|
+
const orderRows = page.locator('table.woocommerce-orders-table tr.woocommerce-orders-table__row');
|
|
100
|
+
const count = await orderRows.count();
|
|
101
|
+
for (let i = 0; i < count; i++) {
|
|
102
|
+
const row = orderRows.nth(i);
|
|
103
|
+
const orderNumberEl = row.locator('.woocommerce-orders-table__cell-order-number a');
|
|
104
|
+
const orderNumber = await orderNumberEl.textContent() || '';
|
|
105
|
+
const orderLink = await orderNumberEl.getAttribute('href') || '';
|
|
106
|
+
const orderId = orderLink.match(/ver-pedido\/(\d+)|view-order\/(\d+)/)?.[1] ||
|
|
107
|
+
orderLink.match(/ver-pedido\/(\d+)|view-order\/(\d+)/)?.[2] || '';
|
|
108
|
+
const date = await row.locator('.woocommerce-orders-table__cell-order-date').textContent() || '';
|
|
109
|
+
const status = await row.locator('.woocommerce-orders-table__cell-order-status').textContent() || '';
|
|
110
|
+
const total = await row.locator('.woocommerce-orders-table__cell-order-total').textContent() || '';
|
|
111
|
+
if (orderId) {
|
|
112
|
+
orders.push({
|
|
113
|
+
id: orderId,
|
|
114
|
+
orderNumber: orderNumber.trim(),
|
|
115
|
+
date: date.trim(),
|
|
116
|
+
status: status.trim(),
|
|
117
|
+
total: total.trim(),
|
|
118
|
+
itemCount: 0,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return orders;
|
|
123
|
+
}
|
|
124
|
+
async getOrder(orderId) {
|
|
125
|
+
const page = await this.ensureBrowser();
|
|
126
|
+
// Try Spanish and English URLs
|
|
127
|
+
const urls = [
|
|
128
|
+
`${this.config.baseUrl}/mi-cuenta/ver-pedido/${orderId}/`,
|
|
129
|
+
`${this.config.baseUrl}/mi-cuenta/view-order/${orderId}/`
|
|
130
|
+
];
|
|
131
|
+
let loaded = false;
|
|
132
|
+
for (const url of urls) {
|
|
133
|
+
try {
|
|
134
|
+
await page.goto(url);
|
|
135
|
+
await page.waitForLoadState('networkidle');
|
|
136
|
+
loaded = true;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (!loaded) {
|
|
144
|
+
throw new Error(`Could not load order ${orderId}`);
|
|
145
|
+
}
|
|
146
|
+
// Check if we need to login
|
|
147
|
+
const needsLogin = await page.locator('form.woocommerce-form-login').count() > 0;
|
|
148
|
+
if (needsLogin) {
|
|
149
|
+
await this.login();
|
|
150
|
+
await page.goto(urls[0]);
|
|
151
|
+
await page.waitForLoadState('networkidle');
|
|
152
|
+
}
|
|
153
|
+
// Extract order details
|
|
154
|
+
const orderNumber = await page.locator('mark.order-number').textContent().catch(() => orderId) || orderId;
|
|
155
|
+
const orderDate = await page.locator('mark.order-date').textContent().catch(() => '') || '';
|
|
156
|
+
const orderStatus = await page.locator('mark.order-status').textContent().catch(() => '') || '';
|
|
157
|
+
// Extract customer info
|
|
158
|
+
let customerName = '';
|
|
159
|
+
let customerAddress = '';
|
|
160
|
+
let customerEmail = '';
|
|
161
|
+
let customerPhone = '';
|
|
162
|
+
try {
|
|
163
|
+
const addressBlock = page.locator('.woocommerce-customer-details address').first();
|
|
164
|
+
const addressText = await addressBlock.textContent() || '';
|
|
165
|
+
const lines = addressText.split('\n').map(l => l.trim()).filter(l => l);
|
|
166
|
+
if (lines.length > 0)
|
|
167
|
+
customerName = lines[0];
|
|
168
|
+
if (lines.length > 1)
|
|
169
|
+
customerAddress = lines.slice(1).filter(l => !l.includes('@') && !l.startsWith('+')).join(', ');
|
|
170
|
+
customerEmail = await page.locator('.woocommerce-customer-details--email').first().textContent().catch(() => '') || '';
|
|
171
|
+
customerPhone = await page.locator('.woocommerce-customer-details--phone').first().textContent().catch(() => '') || '';
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
// Customer details not available
|
|
175
|
+
}
|
|
176
|
+
// Extract line items
|
|
177
|
+
const items = [];
|
|
178
|
+
const itemRows = page.locator('table.woocommerce-table--order-details tbody tr.woocommerce-table__line-item');
|
|
179
|
+
const itemCount = await itemRows.count();
|
|
180
|
+
for (let i = 0; i < itemCount; i++) {
|
|
181
|
+
const itemRow = itemRows.nth(i);
|
|
182
|
+
const name = await itemRow.locator('.woocommerce-table__product-name').textContent() || '';
|
|
183
|
+
const qty = await itemRow.locator('.product-quantity').textContent() || '1';
|
|
184
|
+
const total = await itemRow.locator('.woocommerce-table__product-total').textContent() || '';
|
|
185
|
+
items.push({
|
|
186
|
+
sku: '',
|
|
187
|
+
name: name.trim(),
|
|
188
|
+
quantity: parseInt(qty.replace(/\D/g, ''), 10) || 1,
|
|
189
|
+
price: '',
|
|
190
|
+
total: total.trim(),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// Look for invoice link
|
|
194
|
+
const invoiceLink = await page.locator('a.print-invoice').first().getAttribute('href').catch(() => '');
|
|
195
|
+
// Extract total
|
|
196
|
+
const totalText = await page.locator('table.woocommerce-table--order-details tfoot tr:last-child .woocommerce-Price-amount').textContent().catch(() => '');
|
|
197
|
+
return {
|
|
198
|
+
id: orderId,
|
|
199
|
+
orderNumber: orderNumber?.trim() || orderId,
|
|
200
|
+
date: orderDate?.trim() || '',
|
|
201
|
+
status: orderStatus?.trim() || '',
|
|
202
|
+
total: totalText?.trim() || '',
|
|
203
|
+
itemCount: items.length,
|
|
204
|
+
invoiceUrl: invoiceLink || undefined,
|
|
205
|
+
customer: {
|
|
206
|
+
name: customerName.trim(),
|
|
207
|
+
address: customerAddress.trim(),
|
|
208
|
+
city: '',
|
|
209
|
+
postcode: '',
|
|
210
|
+
email: customerEmail.trim(),
|
|
211
|
+
phone: customerPhone.trim(),
|
|
212
|
+
},
|
|
213
|
+
items,
|
|
214
|
+
subtotal: '',
|
|
215
|
+
shipping: '',
|
|
216
|
+
tax: '',
|
|
217
|
+
paymentMethod: '',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async downloadInvoice(orderId, savePath) {
|
|
221
|
+
try {
|
|
222
|
+
const order = await this.getOrder(orderId);
|
|
223
|
+
if (!order.invoiceUrl) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const page = await this.ensureBrowser();
|
|
227
|
+
// Construct full URL
|
|
228
|
+
const invoiceUrl = order.invoiceUrl.startsWith('http')
|
|
229
|
+
? order.invoiceUrl
|
|
230
|
+
: `${this.config.baseUrl}${order.invoiceUrl}`;
|
|
231
|
+
// Set up download listener
|
|
232
|
+
const downloadPromise = page.waitForEvent('download', { timeout: 30000 });
|
|
233
|
+
// Navigate to invoice URL
|
|
234
|
+
await page.goto(invoiceUrl, { waitUntil: 'commit' }).catch(() => { });
|
|
235
|
+
// Wait for download
|
|
236
|
+
const download = await downloadPromise;
|
|
237
|
+
// Save file
|
|
238
|
+
await download.saveAs(savePath);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error('Invoice download error:', error);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async searchProducts(query) {
|
|
247
|
+
const page = await this.ensureBrowser();
|
|
248
|
+
const searchUrl = `${this.config.baseUrl}/?s=${encodeURIComponent(query)}&post_type=product`;
|
|
249
|
+
await page.goto(searchUrl);
|
|
250
|
+
await page.waitForLoadState('networkidle');
|
|
251
|
+
// Check if we need to login
|
|
252
|
+
const needsLogin = await page.locator('form.woocommerce-form-login').count() > 0;
|
|
253
|
+
if (needsLogin) {
|
|
254
|
+
await this.login();
|
|
255
|
+
await page.goto(searchUrl);
|
|
256
|
+
await page.waitForLoadState('networkidle');
|
|
257
|
+
}
|
|
258
|
+
const products = [];
|
|
259
|
+
// Find all product items
|
|
260
|
+
const productItems = page.locator('li.product, .product-item');
|
|
261
|
+
const count = await productItems.count();
|
|
262
|
+
for (let i = 0; i < count; i++) {
|
|
263
|
+
const item = productItems.nth(i);
|
|
264
|
+
const link = item.locator('a').first();
|
|
265
|
+
const url = await link.getAttribute('href') || '';
|
|
266
|
+
const name = await item.locator('h2, h3, .woocommerce-loop-product__title').first().textContent() || '';
|
|
267
|
+
const price = await item.locator('.price').first().textContent().catch(() => undefined);
|
|
268
|
+
const img = await item.locator('img').first().getAttribute('src');
|
|
269
|
+
if (name && url) {
|
|
270
|
+
products.push({
|
|
271
|
+
id: '',
|
|
272
|
+
name: name.trim(),
|
|
273
|
+
url,
|
|
274
|
+
price: price?.trim(),
|
|
275
|
+
imageUrl: img || undefined,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return products;
|
|
280
|
+
}
|
|
281
|
+
async close() {
|
|
282
|
+
if (this.browser) {
|
|
283
|
+
await this.browser.close();
|
|
284
|
+
this.browser = undefined;
|
|
285
|
+
this.page = undefined;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export default EmovePlaywrightClient;
|