@fullqueso/mcp-bc-gastos 1.14.2 → 1.19.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/CHANGELOG.md +137 -0
- package/config/bank-gl-map.json +93 -0
- package/lib/bc-client.js +143 -0
- package/package.json +1 -1
- package/scripts/generate-pending-invoices-xlsx.py +378 -0
- package/scripts/generate-pending-sales-invoices-xlsx.py +372 -0
- package/scripts/report-pending-invoices.js +250 -0
- package/scripts/report-pending-sales-invoices.js +253 -0
- package/server.js +61 -0
- package/tools/auditoria/bank-ledger-entries.js +104 -0
- package/tools/auditoria/gl-account-entries.js +75 -0
- package/tools/auditoria/pm-receipts.js +182 -0
- package/tools/auditoria/reconcile-pos-sales.js +39 -16
- package/tools/cobranzas/customer-list.js +63 -0
- package/tools/inventario/index.js +4 -0
- package/tools/inventario/inventory-by-location.js +212 -0
- package/tools/inventario/inventory-change.js +386 -0
- package/tools/inventario/inventory-levels.js +214 -0
- package/tools/inventario/item-card.js +1 -50
- package/tools/inventario/item-cost-trend.js +185 -0
- package/tools/inventario/shared/cost-calculator.js +64 -0
- package/tools/ventas/index.js +3 -0
- package/tools/ventas/product-performance.js +182 -0
- package/tools/ventas/sales-analysis.js +211 -0
- package/tools/ventas/sales-store-comparison.js +192 -0
- package/utils/sales-aggregation.js +82 -0
- package/utils/sales-insights.js +167 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reporte dinámico: Posted Sales Invoices Pendientes
|
|
4
|
+
* 3 tabs Excel: Sin Draft, Con Draft, Pago Parcial
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/report-pending-sales-invoices.js --store FQ01 --from 2025-12 --to 2026-01
|
|
8
|
+
* node scripts/report-pending-sales-invoices.js --store FQ28 --from 2025-05 --to 2025-07
|
|
9
|
+
* node scripts/report-pending-sales-invoices.js --store all --from 2026-02 --to 2026-02
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { join, dirname } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { writeFileSync, unlinkSync, existsSync } from 'node:fs';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import { parseArgs } from 'node:util';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const PROJECT_ROOT = dirname(__dirname);
|
|
21
|
+
|
|
22
|
+
// ── Parse CLI args ──
|
|
23
|
+
const { values: args } = parseArgs({
|
|
24
|
+
options: {
|
|
25
|
+
store: { type: 'string', short: 's' },
|
|
26
|
+
from: { type: 'string', short: 'f' },
|
|
27
|
+
to: { type: 'string', short: 't' },
|
|
28
|
+
out: { type: 'string', short: 'o' },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!args.store || !args.from || !args.to) {
|
|
33
|
+
console.error('Usage: node scripts/report-pending-sales-invoices.js --store FQ01 --from 2025-12 --to 2026-01');
|
|
34
|
+
console.error(' --store FQ01 | FQ28 | FQ88 | all');
|
|
35
|
+
console.error(' --from YYYY-MM (mes inicio)');
|
|
36
|
+
console.error(' --to YYYY-MM (mes fin)');
|
|
37
|
+
console.error(' --out (opcional) ruta Excel de salida');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Validate month format and build date range ──
|
|
42
|
+
function parseMonth(ym) {
|
|
43
|
+
const m = ym.match(/^(\d{4})-(\d{2})$/);
|
|
44
|
+
if (!m) { console.error(`Formato inválido: "${ym}". Usar YYYY-MM`); process.exit(1); }
|
|
45
|
+
return { year: parseInt(m[1]), month: parseInt(m[2]), raw: ym };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function lastDayOfMonth(year, month) {
|
|
49
|
+
return new Date(year, month, 0).getDate();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const fromM = parseMonth(args.from);
|
|
53
|
+
const toM = parseMonth(args.to);
|
|
54
|
+
const DATE_START = `${fromM.raw}-01`;
|
|
55
|
+
const DATE_END = `${toM.raw}-${lastDayOfMonth(toM.year, toM.month)}`;
|
|
56
|
+
|
|
57
|
+
// ── Load env & modules ──
|
|
58
|
+
const envPath = join(PROJECT_ROOT, '.env');
|
|
59
|
+
if (existsSync(envPath)) {
|
|
60
|
+
const dotenv = await import('dotenv');
|
|
61
|
+
dotenv.config({ path: envPath });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { BCClient } = await import('../lib/bc-client.js');
|
|
65
|
+
const { resolveStores } = await import('../config/company-config.js');
|
|
66
|
+
|
|
67
|
+
const storeParam = args.store.toUpperCase();
|
|
68
|
+
const stores = resolveStores(storeParam === 'ALL' ? null : [storeParam]);
|
|
69
|
+
const bcClient = new BCClient();
|
|
70
|
+
|
|
71
|
+
// ── Process each store ──
|
|
72
|
+
async function processStore(storeInfo) {
|
|
73
|
+
const companyId = storeInfo.companyId;
|
|
74
|
+
const code = storeInfo.code;
|
|
75
|
+
|
|
76
|
+
// Customer ledger: open invoices in date range
|
|
77
|
+
const ledgerUrl = bcClient.buildBetaApiUrl(companyId, 'customerLedgerEntries', {
|
|
78
|
+
$filter: `open eq true and documentType eq 'Invoice' and postingDate ge ${DATE_START} and postingDate le ${DATE_END}`,
|
|
79
|
+
$select: 'entryNumber,documentNumber,customerNumber,postingDate,amount,amountLocalCurrency,currencyCode,description',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Sales multi-payment headers (non-posted)
|
|
83
|
+
const mpUrl = bcClient.buildMultiPaymentApiUrl(companyId, 'multiPaymentHeaders', {
|
|
84
|
+
$filter: "status ne 'Posted'",
|
|
85
|
+
$select: 'no,invoiceNo,customerNo,customerName,invoiceAmount,remainingAmount,totalPaymentAmount,status,postingDate,currencyCode',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const [openInvoices, mpHeaders, exchangeRates] = await Promise.all([
|
|
89
|
+
bcClient.apiCallAllPages(ledgerUrl),
|
|
90
|
+
bcClient.apiCallAllPages(mpUrl),
|
|
91
|
+
bcClient.getExchangeRates(code),
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
console.error(`[${code}] ${openInvoices.length} open sales invoices, ${mpHeaders.length} MP headers`);
|
|
95
|
+
|
|
96
|
+
// Build MP lookup by invoice number
|
|
97
|
+
const mpByInvoice = {};
|
|
98
|
+
const customerNames = {};
|
|
99
|
+
for (const mp of mpHeaders) {
|
|
100
|
+
if (!mpByInvoice[mp.invoiceNo]) mpByInvoice[mp.invoiceNo] = [];
|
|
101
|
+
mpByInvoice[mp.invoiceNo].push(mp);
|
|
102
|
+
if (mp.customerNo && mp.customerName) customerNames[mp.customerNo] = mp.customerName;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Fetch MP lines in bulk
|
|
106
|
+
const linesByMp = {};
|
|
107
|
+
if (mpHeaders.length > 0) {
|
|
108
|
+
const mpNos = new Set(mpHeaders.map((mp) => mp.no));
|
|
109
|
+
const linesParams = {
|
|
110
|
+
$select: 'documentNo,lineNo,paymentMethod,amountLCY,currencyCode,bankAccountNo,referenceNo,description',
|
|
111
|
+
};
|
|
112
|
+
if (mpHeaders.length <= 50) {
|
|
113
|
+
linesParams.$filter = [...mpNos].map((no) => `documentNo eq '${no}'`).join(' or ');
|
|
114
|
+
}
|
|
115
|
+
const allLinesUrl = bcClient.buildMultiPaymentApiUrl(companyId, 'multiPaymentLines', linesParams);
|
|
116
|
+
const allLines = await bcClient.apiCallAllPages(allLinesUrl);
|
|
117
|
+
for (const line of allLines) {
|
|
118
|
+
if (mpNos.has(line.documentNo)) {
|
|
119
|
+
if (!linesByMp[line.documentNo]) linesByMp[line.documentNo] = [];
|
|
120
|
+
linesByMp[line.documentNo].push(line);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Compute USD for each MP
|
|
126
|
+
const mpUsdInfo = {};
|
|
127
|
+
for (const mp of mpHeaders) {
|
|
128
|
+
mpUsdInfo[mp.no] = computeMpUsd(bcClient, mp, linesByMp[mp.no] || [], exchangeRates);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Classify invoices
|
|
132
|
+
const invoices = [];
|
|
133
|
+
for (const inv of openInvoices) {
|
|
134
|
+
const docNo = inv.documentNumber;
|
|
135
|
+
// Customer invoices are POSITIVE in amountLocalCurrency (unlike vendor which are negative)
|
|
136
|
+
const amountUsd = round2(Math.abs(inv.amountLocalCurrency || 0));
|
|
137
|
+
const mps = mpByInvoice[docNo] || [];
|
|
138
|
+
|
|
139
|
+
let tier, multiPayment = null;
|
|
140
|
+
|
|
141
|
+
if (mps.length === 0) {
|
|
142
|
+
tier = 'fully_pending';
|
|
143
|
+
} else {
|
|
144
|
+
let totalDraftedUsd = 0;
|
|
145
|
+
for (const mp of mps) totalDraftedUsd += mpUsdInfo[mp.no].usd;
|
|
146
|
+
totalDraftedUsd = round2(totalDraftedUsd);
|
|
147
|
+
const hasTransferred = mps.some((mp) => mp.status === 'Transferred');
|
|
148
|
+
tier = hasTransferred ? 'in_journal' : 'draft_in_progress';
|
|
149
|
+
multiPayment = {
|
|
150
|
+
mp_no: mps[0].no,
|
|
151
|
+
status: mps[0].status,
|
|
152
|
+
total_payment_amount: totalDraftedUsd,
|
|
153
|
+
net_pending: round2(Math.max(0, amountUsd - totalDraftedUsd)),
|
|
154
|
+
};
|
|
155
|
+
if (mps.length > 1) {
|
|
156
|
+
multiPayment.additional_mps = mps.slice(1).map((mp) => ({
|
|
157
|
+
mp_no: mp.no, status: mp.status, total_payment_amount: mpUsdInfo[mp.no].usd,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
invoices.push({
|
|
163
|
+
invoice_no: docNo,
|
|
164
|
+
customer_no: inv.customerNumber,
|
|
165
|
+
customer_name: customerNames[inv.customerNumber] || inv.description || inv.customerNumber,
|
|
166
|
+
posting_date: inv.postingDate,
|
|
167
|
+
invoice_amount: amountUsd,
|
|
168
|
+
currency_code: inv.currencyCode || 'USD',
|
|
169
|
+
tier,
|
|
170
|
+
multi_payment: multiPayment,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
invoices.sort((a, b) => a.posting_date.localeCompare(b.posting_date));
|
|
175
|
+
return { store: code, store_name: storeInfo.name, invoices };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Helpers ──
|
|
179
|
+
function round2(n) { return Math.round(n * 100) / 100; }
|
|
180
|
+
|
|
181
|
+
function computeMpUsd(bcClient, mp, lines, exchangeRates) {
|
|
182
|
+
const rateInfo = bcClient.getExchangeRateForDate(exchangeRates, mp.postingDate);
|
|
183
|
+
const rate = rateInfo ? rateInfo.rate : null;
|
|
184
|
+
if (lines.length === 0) {
|
|
185
|
+
if (rate) return { usd: round2((mp.totalPaymentAmount || 0) / rate), rate };
|
|
186
|
+
return { usd: round2(mp.totalPaymentAmount || 0), rate: null };
|
|
187
|
+
}
|
|
188
|
+
if (lines.some((l) => l.currencyCode === 'VES') && !rate) {
|
|
189
|
+
return { usd: round2(mp.totalPaymentAmount || 0), rate: null };
|
|
190
|
+
}
|
|
191
|
+
let usdTotal = 0;
|
|
192
|
+
for (const line of lines) {
|
|
193
|
+
const amount = line.amountLCY || 0;
|
|
194
|
+
usdTotal += line.currencyCode === 'VES' ? amount / rate : amount;
|
|
195
|
+
}
|
|
196
|
+
return { usd: round2(usdTotal), rate };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Main ──
|
|
200
|
+
console.error(`[sales-report] Stores: ${stores.map(s => s.code).join(', ')} | Period: ${DATE_START} → ${DATE_END}`);
|
|
201
|
+
|
|
202
|
+
const results = await Promise.all(stores.map(processStore));
|
|
203
|
+
|
|
204
|
+
const allInvoices = results.flatMap(r => r.invoices);
|
|
205
|
+
const storeLabel = results.length === 1 ? results[0].store : 'ALL';
|
|
206
|
+
const storeName = results.length === 1 ? results[0].store_name : 'Todas las tiendas';
|
|
207
|
+
|
|
208
|
+
const counts = { sin_draft: 0, con_draft: 0, pago_parcial: 0 };
|
|
209
|
+
for (const inv of allInvoices) {
|
|
210
|
+
if (inv.tier === 'fully_pending') counts.sin_draft++;
|
|
211
|
+
else if (inv.tier === 'draft_in_progress') counts.con_draft++;
|
|
212
|
+
else if (inv.tier === 'in_journal') counts.pago_parcial++;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.error(`[sales-report] Total: ${allInvoices.length} invoices | Sin Draft=${counts.sin_draft}, Con Draft=${counts.con_draft}, Pago Parcial=${counts.pago_parcial}`);
|
|
216
|
+
|
|
217
|
+
// Write JSON
|
|
218
|
+
const reportDate = new Date().toISOString().substring(0, 10);
|
|
219
|
+
const jsonData = {
|
|
220
|
+
report_type: 'sales',
|
|
221
|
+
store: storeLabel,
|
|
222
|
+
store_name: storeName,
|
|
223
|
+
report_date: reportDate,
|
|
224
|
+
date_range: { start: DATE_START, end: DATE_END },
|
|
225
|
+
invoices: allInvoices,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const ts = Date.now();
|
|
229
|
+
const jsonPath = `/tmp/pending_sales_${storeLabel}_${ts}.json`;
|
|
230
|
+
writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2), 'utf-8');
|
|
231
|
+
|
|
232
|
+
// Generate Excel
|
|
233
|
+
const periodSlug = `${args.from.replace('-', '')}-${args.to.replace('-', '')}`;
|
|
234
|
+
const defaultOut = join(process.env.HOME || '/tmp', 'Downloads', `${storeLabel}_Ventas_Pendientes_${periodSlug}_${reportDate.replace(/-/g, '')}.xlsx`);
|
|
235
|
+
const outputPath = args.out ? args.out.replace(/^~/, process.env.HOME || '/tmp') : defaultOut;
|
|
236
|
+
|
|
237
|
+
const scriptPath = join(PROJECT_ROOT, 'scripts', 'generate-pending-sales-invoices-xlsx.py');
|
|
238
|
+
const venvPython = join(process.env.HOME || '/tmp', 'mcp-venv', 'bin', 'python3');
|
|
239
|
+
const pythonCmd = existsSync(venvPython) ? venvPython : 'python3';
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const stdout = execSync(`"${pythonCmd}" "${scriptPath}" "${jsonPath}" "${outputPath}"`, {
|
|
243
|
+
timeout: 60000, stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8',
|
|
244
|
+
});
|
|
245
|
+
console.error(`[sales-report] ✓ Excel: ${outputPath}`);
|
|
246
|
+
console.error(stdout);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
console.error(`[sales-report] Error: ${e.message}`);
|
|
249
|
+
if (e.stderr) console.error(e.stderr);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
} finally {
|
|
252
|
+
try { unlinkSync(jsonPath); } catch (_) {}
|
|
253
|
+
}
|
package/server.js
CHANGED
|
@@ -43,6 +43,9 @@ import { findPotentialMatchesTool, handleFindPotentialMatches } from './tools/au
|
|
|
43
43
|
import { suggestJournalEntriesTool, handleSuggestJournalEntries } from './tools/auditoria/suggest-journal-entries.js';
|
|
44
44
|
import { reconcilePOSSalesTool, handleReconcilePOSSales } from './tools/auditoria/reconcile-pos-sales.js';
|
|
45
45
|
import { bankReconciliationReportTool, handleBankReconciliationReport } from './tools/auditoria/bank-reconciliation-report.js';
|
|
46
|
+
import { glAccountEntriesTool, handleGLAccountEntries } from './tools/auditoria/gl-account-entries.js';
|
|
47
|
+
import { bankLedgerEntriesTool, handleBankLedgerEntries } from './tools/auditoria/bank-ledger-entries.js';
|
|
48
|
+
import { pmReceiptsTool, handlePmReceipts } from './tools/auditoria/pm-receipts.js';
|
|
46
49
|
|
|
47
50
|
// Cobranzas tools (accounts receivable / payable)
|
|
48
51
|
import { customerBalancesTool, handleCustomerBalances } from './tools/cobranzas/customer-balances.js';
|
|
@@ -51,6 +54,7 @@ import { openReceivablesTool, handleOpenReceivables } from './tools/cobranzas/op
|
|
|
51
54
|
import { collectionStatusTool, handleCollectionStatus } from './tools/cobranzas/collection-status.js';
|
|
52
55
|
import { vendorLedgerTool, handleVendorLedger } from './tools/cobranzas/vendor-ledger.js';
|
|
53
56
|
import { openPayablesTool, handleOpenPayables } from './tools/cobranzas/open-payables.js';
|
|
57
|
+
import { customerListTool, handleCustomerList } from './tools/cobranzas/customer-list.js';
|
|
54
58
|
|
|
55
59
|
// Multi-Payment draft visibility tools
|
|
56
60
|
import {
|
|
@@ -71,8 +75,19 @@ import {
|
|
|
71
75
|
itemCardTool, handleItemCard,
|
|
72
76
|
itemLedgerEntriesTool, handleItemLedgerEntries,
|
|
73
77
|
itemValueEntriesTool, handleItemValueEntries,
|
|
78
|
+
itemCostTrendTool, handleItemCostTrend,
|
|
79
|
+
inventoryLevelsTool, handleInventoryLevels,
|
|
80
|
+
inventoryChangeTool, handleInventoryChange,
|
|
81
|
+
inventoryByLocationTool, handleInventoryByLocation,
|
|
74
82
|
} from './tools/inventario/index.js';
|
|
75
83
|
|
|
84
|
+
// Ventas tools (sales analysis)
|
|
85
|
+
import {
|
|
86
|
+
salesAnalysisTool, handleSalesAnalysis,
|
|
87
|
+
productPerformanceTool, handleProductPerformance,
|
|
88
|
+
salesStoreComparisonTool, handleSalesStoreComparison,
|
|
89
|
+
} from './tools/ventas/index.js';
|
|
90
|
+
|
|
76
91
|
// Reports tools
|
|
77
92
|
import { managerReportTool, handleGenerateManagerReport } from './tools/reports/manager-report.js';
|
|
78
93
|
import { cxpReportTool, handleGenerateCxpReport } from './tools/reports/cxp-report.js';
|
|
@@ -115,6 +130,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
115
130
|
suggestJournalEntriesTool,
|
|
116
131
|
reconcilePOSSalesTool,
|
|
117
132
|
bankReconciliationReportTool,
|
|
133
|
+
glAccountEntriesTool,
|
|
134
|
+
bankLedgerEntriesTool,
|
|
135
|
+
pmReceiptsTool,
|
|
118
136
|
// Cobranzas (accounts receivable / payable)
|
|
119
137
|
customerBalancesTool,
|
|
120
138
|
customerLedgerTool,
|
|
@@ -122,6 +140,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
122
140
|
collectionStatusTool,
|
|
123
141
|
vendorLedgerTool,
|
|
124
142
|
openPayablesTool,
|
|
143
|
+
customerListTool,
|
|
125
144
|
// Multi-Payment draft visibility
|
|
126
145
|
draftReceivablesTool,
|
|
127
146
|
draftPayablesTool,
|
|
@@ -134,6 +153,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
134
153
|
itemCardTool,
|
|
135
154
|
itemLedgerEntriesTool,
|
|
136
155
|
itemValueEntriesTool, // get_item_cost_analysis
|
|
156
|
+
itemCostTrendTool,
|
|
157
|
+
inventoryLevelsTool,
|
|
158
|
+
inventoryChangeTool,
|
|
159
|
+
inventoryByLocationTool,
|
|
160
|
+
// Ventas (sales analysis)
|
|
161
|
+
salesAnalysisTool,
|
|
162
|
+
productPerformanceTool,
|
|
163
|
+
salesStoreComparisonTool,
|
|
137
164
|
// Reports
|
|
138
165
|
managerReportTool,
|
|
139
166
|
cxpReportTool,
|
|
@@ -204,6 +231,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
204
231
|
case 'get_bank_reconciliation_report':
|
|
205
232
|
result = await handleBankReconciliationReport(bcClient, args);
|
|
206
233
|
break;
|
|
234
|
+
case 'get_gl_account_entries':
|
|
235
|
+
result = await handleGLAccountEntries(bcClient, args);
|
|
236
|
+
break;
|
|
237
|
+
case 'get_bank_ledger_entries':
|
|
238
|
+
result = await handleBankLedgerEntries(bcClient, args);
|
|
239
|
+
break;
|
|
240
|
+
case 'get_pm_receipts':
|
|
241
|
+
result = await handlePmReceipts(bcClient, args);
|
|
242
|
+
break;
|
|
207
243
|
// Cobranzas (accounts receivable / payable)
|
|
208
244
|
case 'get_customer_balances':
|
|
209
245
|
result = await handleCustomerBalances(bcClient, args);
|
|
@@ -223,6 +259,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
223
259
|
case 'get_open_payables':
|
|
224
260
|
result = await handleOpenPayables(bcClient, args);
|
|
225
261
|
break;
|
|
262
|
+
case 'get_customer_list':
|
|
263
|
+
result = await handleCustomerList(bcClient, args);
|
|
264
|
+
break;
|
|
226
265
|
// Multi-Payment draft visibility
|
|
227
266
|
case 'get_draft_receivables':
|
|
228
267
|
result = await handleDraftReceivables(bcClient, args);
|
|
@@ -253,6 +292,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
253
292
|
case 'get_item_cost_analysis':
|
|
254
293
|
result = await handleItemValueEntries(bcClient, args);
|
|
255
294
|
break;
|
|
295
|
+
case 'get_item_cost_trend':
|
|
296
|
+
result = await handleItemCostTrend(bcClient, args);
|
|
297
|
+
break;
|
|
298
|
+
case 'get_inventory_levels':
|
|
299
|
+
result = await handleInventoryLevels(bcClient, args);
|
|
300
|
+
break;
|
|
301
|
+
case 'get_inventory_change':
|
|
302
|
+
result = await handleInventoryChange(bcClient, args);
|
|
303
|
+
break;
|
|
304
|
+
case 'get_inventory_by_location':
|
|
305
|
+
result = await handleInventoryByLocation(bcClient, args);
|
|
306
|
+
break;
|
|
307
|
+
// Ventas (sales analysis)
|
|
308
|
+
case 'get_sales_analysis':
|
|
309
|
+
result = await handleSalesAnalysis(bcClient, args);
|
|
310
|
+
break;
|
|
311
|
+
case 'get_product_performance':
|
|
312
|
+
result = await handleProductPerformance(bcClient, args);
|
|
313
|
+
break;
|
|
314
|
+
case 'compare_sales_by_store':
|
|
315
|
+
result = await handleSalesStoreComparison(bcClient, args);
|
|
316
|
+
break;
|
|
256
317
|
// Reports
|
|
257
318
|
case 'generate_manager_report':
|
|
258
319
|
result = await handleGenerateManagerReport(bcClient, args);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { resolveStores } from '../../config/company-config.js';
|
|
2
|
+
|
|
3
|
+
export const bankLedgerEntriesTool = {
|
|
4
|
+
name: 'get_bank_ledger_entries',
|
|
5
|
+
description:
|
|
6
|
+
'Todos los movimientos del libro de banco (abiertos y cerrados) para una cuenta bancaria. A diferencia de get_unmatched_ledger_entries, retorna TODAS las entradas incluyendo las ya conciliadas. Útil para ver el historial completo de pagos y depósitos registrados en BC para una cuenta.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
store: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
enum: ['FQ01', 'FQ28', 'FQ88'],
|
|
13
|
+
description: 'Tienda a consultar.',
|
|
14
|
+
},
|
|
15
|
+
bank_account: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Número de cuenta bancaria (ej: "FQ01-MN0008").',
|
|
18
|
+
},
|
|
19
|
+
date_from: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Fecha inicio (YYYY-MM-DD).',
|
|
22
|
+
},
|
|
23
|
+
date_to: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Fecha fin (YYYY-MM-DD).',
|
|
26
|
+
},
|
|
27
|
+
open_only: {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
description: 'Si true, solo entradas abiertas (no conciliadas). Default: false (todas).',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ['store', 'bank_account', 'date_from', 'date_to'],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export async function handleBankLedgerEntries(bcClient, args) {
|
|
37
|
+
const { store, bank_account, date_from, date_to } = args;
|
|
38
|
+
const openOnly = args.open_only || false;
|
|
39
|
+
|
|
40
|
+
if (!store) throw new Error('Parámetro requerido: store');
|
|
41
|
+
if (!bank_account) throw new Error('Parámetro requerido: bank_account');
|
|
42
|
+
if (!date_from || !date_to) throw new Error('Parámetros requeridos: date_from, date_to');
|
|
43
|
+
|
|
44
|
+
const stores = resolveStores([store]);
|
|
45
|
+
const storeInfo = stores[0];
|
|
46
|
+
|
|
47
|
+
const filterParts = [
|
|
48
|
+
`Bank_Account_No eq '${bank_account}'`,
|
|
49
|
+
`Posting_Date ge ${date_from}`,
|
|
50
|
+
`Posting_Date le ${date_to}`,
|
|
51
|
+
];
|
|
52
|
+
if (openOnly) {
|
|
53
|
+
filterParts.push('Open eq true');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const url = bcClient.buildODataUrl(store, 'BankAccountLedgerEntries', {
|
|
57
|
+
$filter: filterParts.join(' and '),
|
|
58
|
+
$orderby: 'Posting_Date asc',
|
|
59
|
+
});
|
|
60
|
+
const entries = await bcClient.odataCallAllPages(url);
|
|
61
|
+
|
|
62
|
+
const formatted = entries.map((e) => {
|
|
63
|
+
const amount = e.Amount || 0;
|
|
64
|
+
const postingDate = e.Posting_Date || null;
|
|
65
|
+
const isOpen = e.Open === true || e.Open === 'true';
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
entry_no: e.Entry_No || e.Entry_No_ || 0,
|
|
69
|
+
posting_date: postingDate,
|
|
70
|
+
document_type: e.Document_Type || '',
|
|
71
|
+
document_no: e.Document_No || '',
|
|
72
|
+
external_document_no: e.External_Document_No || '',
|
|
73
|
+
description: e.Description || '',
|
|
74
|
+
amount: Math.round(amount * 100) / 100,
|
|
75
|
+
remaining_amount: Math.round((e.Remaining_Amount || 0) * 100) / 100,
|
|
76
|
+
open: isOpen,
|
|
77
|
+
bal_account_no: e.Bal_Account_No || '',
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const openEntries = formatted.filter((e) => e.open);
|
|
82
|
+
const closedEntries = formatted.filter((e) => !e.open);
|
|
83
|
+
const totalAmount = Math.round(formatted.reduce((s, e) => s + e.amount, 0) * 100) / 100;
|
|
84
|
+
const deposits = formatted.filter((e) => e.amount > 0);
|
|
85
|
+
const payments = formatted.filter((e) => e.amount < 0);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
store,
|
|
89
|
+
store_name: storeInfo.name,
|
|
90
|
+
bank_account,
|
|
91
|
+
period: { from: date_from, to: date_to },
|
|
92
|
+
entries: formatted,
|
|
93
|
+
summary: {
|
|
94
|
+
total_entries: formatted.length,
|
|
95
|
+
open_entries: openEntries.length,
|
|
96
|
+
closed_entries: closedEntries.length,
|
|
97
|
+
total_deposits: deposits.length,
|
|
98
|
+
total_deposit_amount: Math.round(deposits.reduce((s, e) => s + e.amount, 0) * 100) / 100,
|
|
99
|
+
total_payments: payments.length,
|
|
100
|
+
total_payment_amount: Math.round(payments.reduce((s, e) => s + e.amount, 0) * 100) / 100,
|
|
101
|
+
net_amount: totalAmount,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { resolveStores } from '../../config/company-config.js';
|
|
2
|
+
|
|
3
|
+
export const glAccountEntriesTool = {
|
|
4
|
+
name: 'get_gl_account_entries',
|
|
5
|
+
description:
|
|
6
|
+
'Movimientos del libro mayor (G/L) para una cuenta específica. Retorna todas las entradas contables con débitos y créditos. Útil para ver los registros de ventas diarias por cuenta bancaria (ej: GL 18280 = Pago Móvil FQ01-MN0008).',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
store: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
enum: ['FQ01', 'FQ28', 'FQ88'],
|
|
13
|
+
description: 'Tienda a consultar.',
|
|
14
|
+
},
|
|
15
|
+
gl_account: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Número de cuenta G/L (ej: "18280" para BDV Pago Móvil).',
|
|
18
|
+
},
|
|
19
|
+
date_from: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Fecha inicio (YYYY-MM-DD).',
|
|
22
|
+
},
|
|
23
|
+
date_to: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Fecha fin (YYYY-MM-DD).',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ['store', 'gl_account', 'date_from', 'date_to'],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export async function handleGLAccountEntries(bcClient, args) {
|
|
33
|
+
const { store, gl_account, date_from, date_to } = args;
|
|
34
|
+
if (!store) throw new Error('Parámetro requerido: store');
|
|
35
|
+
if (!gl_account) throw new Error('Parámetro requerido: gl_account');
|
|
36
|
+
if (!date_from || !date_to) throw new Error('Parámetros requeridos: date_from, date_to');
|
|
37
|
+
|
|
38
|
+
const stores = resolveStores([store]);
|
|
39
|
+
const storeInfo = stores[0];
|
|
40
|
+
|
|
41
|
+
const url = bcClient.buildApiUrl(storeInfo.companyId, 'generalLedgerEntries', {
|
|
42
|
+
$filter: `postingDate ge ${date_from} and postingDate le ${date_to} and accountNumber eq '${gl_account}'`,
|
|
43
|
+
$select: 'entryNumber,postingDate,documentNumber,accountNumber,description,debitAmount,creditAmount',
|
|
44
|
+
$orderby: 'postingDate asc',
|
|
45
|
+
});
|
|
46
|
+
const entries = await bcClient.apiCallAllPages(url);
|
|
47
|
+
|
|
48
|
+
const formatted = entries.map((e) => ({
|
|
49
|
+
entry_no: e.entryNumber || 0,
|
|
50
|
+
posting_date: e.postingDate || '',
|
|
51
|
+
document_no: e.documentNumber || '',
|
|
52
|
+
gl_account: e.accountNumber || '',
|
|
53
|
+
description: e.description || '',
|
|
54
|
+
debit: Math.round((e.debitAmount || 0) * 100) / 100,
|
|
55
|
+
credit: Math.round((e.creditAmount || 0) * 100) / 100,
|
|
56
|
+
amount: Math.round(((e.creditAmount || 0) - (e.debitAmount || 0)) * 100) / 100,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const totalDebit = Math.round(formatted.reduce((s, e) => s + e.debit, 0) * 100) / 100;
|
|
60
|
+
const totalCredit = Math.round(formatted.reduce((s, e) => s + e.credit, 0) * 100) / 100;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
store,
|
|
64
|
+
store_name: storeInfo.name,
|
|
65
|
+
gl_account,
|
|
66
|
+
period: { from: date_from, to: date_to },
|
|
67
|
+
entries: formatted,
|
|
68
|
+
summary: {
|
|
69
|
+
total_entries: formatted.length,
|
|
70
|
+
total_debit: totalDebit,
|
|
71
|
+
total_credit: totalCredit,
|
|
72
|
+
net: Math.round((totalCredit - totalDebit) * 100) / 100,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|