UNPKG

sap-b1-mcp-server

Version:

SAP Business One Service Layer MCP Server

646 lines 26.5 kB
import { SessionManager } from './session.js'; export class SAPClient { sessionManager; constructor(config) { this.sessionManager = SessionManager.getInstance(config); } /** * Login to SAP B1 */ async login() { return await this.sessionManager.login(); } /** * Logout from SAP B1 */ async logout() { await this.sessionManager.logout(); } /** * Check session status */ async getSessionStatus() { const valid = await this.sessionManager.isSessionValid(); const sessionInfo = this.sessionManager.getSessionInfo(); return { valid, sessionInfo }; } /** * Get business partners with optional filters */ async getBusinessPartners(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/BusinessPartners${queryString}`); return response.value; } /** * Get business partners with full OData response including pagination info */ async getBusinessPartnersWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/BusinessPartners${queryString}`); return response; } /** * Get a single business partner by code */ async getBusinessPartner(cardCode, params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/BusinessPartners('${encodeURIComponent(cardCode)}')${queryString}`); return response; } /** * Create a new business partner */ async createBusinessPartner(businessPartner) { const response = await this.sessionManager.executeRequest('POST', '/b1s/v1/BusinessPartners', businessPartner); return response; } /** * Update an existing business partner */ async updateBusinessPartner(cardCode, updates) { await this.sessionManager.executeRequest('PATCH', `/b1s/v1/BusinessPartners('${encodeURIComponent(cardCode)}')`, updates); } /** * Get items with optional filters */ async getItems(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Items${queryString}`); return response.value; } /** * Get items with full OData response including pagination info */ async getItemsWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Items${queryString}`); return response; } /** * Get item groups (product categories) with optional filters */ async getItemGroups(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/ItemGroups${queryString}`); return response.value; } /** * Get item groups with full OData response including pagination info */ async getItemGroupsWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/ItemGroups${queryString}`); return response; } /** * Get a single item by code */ async getItem(itemCode, params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Items('${encodeURIComponent(itemCode)}')${queryString}`); return response; } /** * Create a new item */ async createItem(item) { const response = await this.sessionManager.executeRequest('POST', '/b1s/v1/Items', item); return response; } /** * Update an existing item */ async updateItem(itemCode, updates) { await this.sessionManager.executeRequest('PATCH', `/b1s/v1/Items('${encodeURIComponent(itemCode)}')`, updates); } /** * Get sales orders with optional filters */ async getSalesOrders(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Orders${queryString}`); return response.value; } /** * Get sales orders with full OData response including pagination info */ async getSalesOrdersWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Orders${queryString}`); return response; } /** * Get a single sales order by DocEntry or DocNum */ async getSalesOrder(identifier, params) { const queryString = this.buildQueryString(params); if (identifier.docEntry) { // Use direct DocEntry access const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Orders(${identifier.docEntry})${queryString}`); return response; } else if (identifier.docNum) { // Use filter by DocNum const filterParams = { ...params, $filter: `DocNum eq ${identifier.docNum}`, $top: 1 }; const queryStringWithFilter = this.buildQueryString(filterParams); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Orders${queryStringWithFilter}`); if (response.value.length === 0) { throw new Error(`Sales order with DocNum ${identifier.docNum} not found`); } return response.value[0]; } else { throw new Error('Either docEntry or docNum must be provided'); } } /** * Create a new sales order */ async createSalesOrder(order) { const response = await this.sessionManager.executeRequest('POST', '/b1s/v1/Orders', order); return response; } /** * Update an existing sales order */ async updateSalesOrder(docEntry, updates) { await this.sessionManager.executeRequest('PATCH', `/b1s/v1/Orders(${docEntry})`, updates); } /** * Get purchase orders with optional filters */ async getPurchaseOrders(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseOrders${queryString}`); return response.value; } /** * Get purchase orders with full OData response including pagination info */ async getPurchaseOrdersWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseOrders${queryString}`); return response; } /** * Get a single purchase order by DocEntry or DocNum */ async getPurchaseOrder(identifier, params) { const queryString = this.buildQueryString(params); if (identifier.docEntry) { // Use direct DocEntry access const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseOrders(${identifier.docEntry})${queryString}`); return response; } else if (identifier.docNum) { // Use filter by DocNum const filterParams = { ...params, $filter: `DocNum eq ${identifier.docNum}`, $top: 1 }; const queryStringWithFilter = this.buildQueryString(filterParams); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseOrders${queryStringWithFilter}`); if (response.value.length === 0) { throw new Error(`Purchase order with DocNum ${identifier.docNum} not found`); } return response.value[0]; } else { throw new Error('Either docEntry or docNum must be provided'); } } /** * Create a new purchase order */ async createPurchaseOrder(order) { const response = await this.sessionManager.executeRequest('POST', '/b1s/v1/PurchaseOrders', order); return response; } /** * Update an existing purchase order */ async updatePurchaseOrder(docEntry, updates) { await this.sessionManager.executeRequest('PATCH', `/b1s/v1/PurchaseOrders(${docEntry})`, updates); } /** * Get goods receipts with optional filters */ async getGoodsReceipts(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseDeliveryNotes${queryString}`); return response.value; } /** * Get goods receipts with full OData response including pagination info */ async getGoodsReceiptsWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseDeliveryNotes${queryString}`); return response; } /** * Get a single goods receipt by DocEntry or DocNum */ async getGoodsReceipt(identifier, params) { const queryString = this.buildQueryString(params); if (identifier.docEntry) { // Use direct DocEntry access const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseDeliveryNotes(${identifier.docEntry})${queryString}`); return response; } else if (identifier.docNum) { // Use filter by DocNum const filterParams = { ...params, $filter: `DocNum eq ${identifier.docNum}`, $top: 1 }; const queryStringWithFilter = this.buildQueryString(filterParams); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PurchaseDeliveryNotes${queryStringWithFilter}`); if (response.value.length === 0) { throw new Error(`Goods receipt with DocNum ${identifier.docNum} not found`); } return response.value[0]; } else { throw new Error('Either docEntry or docNum must be provided'); } } /** * Get invoices with optional filters */ async getInvoices(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Invoices${queryString}`); return response.value; } /** * Get invoices with full OData response including pagination info */ async getInvoicesWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Invoices${queryString}`); return response; } /** * Get a single invoice by DocEntry or DocNum */ async getInvoice(identifier, params) { const queryString = this.buildQueryString(params); if (identifier.docEntry) { // Use direct DocEntry access const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Invoices(${identifier.docEntry})${queryString}`); return response; } else if (identifier.docNum) { // Use filter by DocNum const filterParams = { ...params, $filter: `DocNum eq ${identifier.docNum}`, $top: 1 }; const queryStringWithFilter = this.buildQueryString(filterParams); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Invoices${queryStringWithFilter}`); if (response.value.length === 0) { throw new Error(`Invoice with DocNum ${identifier.docNum} not found`); } return response.value[0]; } else { throw new Error('Either docEntry or docNum must be provided'); } } /** * Create a new invoice */ async createInvoice(invoice) { const response = await this.sessionManager.executeRequest('POST', '/b1s/v1/Invoices', invoice); return response; } /** * Update an existing invoice */ async updateInvoice(docEntry, updates) { await this.sessionManager.executeRequest('PATCH', `/b1s/v1/Invoices(${docEntry})`, updates); } /** * Execute a custom OData query */ async executeQuery(entityType, params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/${entityType}${queryString}`); return response.value; } /** * Execute a cross-join query for complex data relationships */ async executeCrossJoinQuery(query) { // Cross-join queries in SAP B1 typically use the $crossjoin system query option const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/$crossjoin(${encodeURIComponent(query)})`); return response.value; } /** * Test connection to SAP B1 */ async testConnection() { try { const sessionInfo = await this.sessionManager.ensureValidSession(); return { success: true, message: 'Successfully connected to SAP Business One', sessionInfo }; } catch (error) { return { success: false, message: error.message || 'Failed to connect to SAP Business One' }; } } /** * Build query string from parameters */ buildQueryString(params) { // Set default pagination to overcome SAP B1 Service Layer 20-record default limit const defaultParams = { $top: 100, // Default to 100 records instead of SAP's 20-record default ...params }; const queryParams = []; if (defaultParams.$filter) { queryParams.push(`$filter=${encodeURIComponent(defaultParams.$filter)}`); } if (defaultParams.$select) { queryParams.push(`$select=${encodeURIComponent(defaultParams.$select)}`); } if (defaultParams.$expand) { queryParams.push(`$expand=${encodeURIComponent(defaultParams.$expand)}`); } if (defaultParams.$orderby) { queryParams.push(`$orderby=${encodeURIComponent(defaultParams.$orderby)}`); } if (defaultParams.$top !== undefined) { queryParams.push(`$top=${defaultParams.$top}`); } if (defaultParams.$skip !== undefined) { queryParams.push(`$skip=${defaultParams.$skip}`); } if (defaultParams.$count) { queryParams.push(`$count=${defaultParams.$count}`); } return queryParams.length > 0 ? `?${queryParams.join('&')}` : '?$top=100'; } /** * Get warehouses with optional filters */ async getWarehouses(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Warehouses${queryString}`); return response.value; } /** * Get warehouses with full OData response including pagination info */ async getWarehousesWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Warehouses${queryString}`); return response; } /** * Get a single warehouse by WarehouseCode */ async getWarehouse(warehouseCode, params) { const queryString = this.buildQueryString(params); if (!warehouseCode) { throw new Error('WarehouseCode is required'); } // Use direct WarehouseCode access const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Warehouses('${warehouseCode}')${queryString}`); return response; } /** * Get price lists with optional filters */ async getPriceLists(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PriceLists${queryString}`); return response.value; } /** * Get price lists with full OData response including pagination info */ async getPriceListsWithPagination(params) { const queryString = this.buildQueryString(params); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PriceLists${queryString}`); return response; } /** * Get a single price list by PriceListNo */ async getPriceList(priceListNo, params) { const queryString = this.buildQueryString(params); if (!priceListNo) { throw new Error('PriceListNo is required'); } // Use direct PriceListNo access const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/PriceLists(${priceListNo})${queryString}`); return response; } /** * Get item stock levels for a specific item and warehouse */ async getItemStock(itemCode, warehouseCode, params) { if (!itemCode) { throw new Error('ItemCode is required'); } if (!warehouseCode) { throw new Error('WarehouseCode is required'); } // Try alternative approach using ItemWarehouseInfoCollection entity directly const filterParams = { ...params, $filter: `ItemCode eq '${itemCode}' and WarehouseCode eq '${warehouseCode}'`, $top: 1 }; const queryString = this.buildQueryString(filterParams); try { // First try direct entity access const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/ItemWarehouseInfoCollection${queryString}`); if (response.value.length === 0) { throw new Error(`No stock information found for item ${itemCode} in warehouse ${warehouseCode}`); } return response.value[0]; } catch (error) { // Fallback: create simplified stock info from Items entity const itemResponse = await this.sessionManager.executeRequest('GET', `/b1s/v1/Items('${itemCode}')`); // Return basic stock information from Items entity return { ItemCode: itemCode, WarehouseCode: warehouseCode, InStock: itemResponse.QuantityOnStock || 0, Available: itemResponse.QuantityOnStock || 0, Committed: 0, Ordered: itemResponse.QuantityOrderedFromVendors || 0 }; } } /** * Get all stock levels for a specific item across all warehouses */ async getItemStockAll(itemCode, params) { if (!itemCode) { throw new Error('ItemCode is required'); } // Try alternative approach using ItemWarehouseInfoCollection entity directly const filterParams = { ...params, $filter: params?.$filter ? `ItemCode eq '${itemCode}' and (${params.$filter})` : `ItemCode eq '${itemCode}'` }; const queryString = this.buildQueryString(filterParams); try { const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/ItemWarehouseInfoCollection${queryString}`); return response.value; } catch (error) { // Fallback: create stock info from Items entity and all warehouses const itemResponse = await this.sessionManager.executeRequest('GET', `/b1s/v1/Items('${itemCode}')`); const warehousesResponse = await this.sessionManager.executeRequest('GET', '/b1s/v1/Warehouses?$select=WarehouseCode,WarehouseName'); // Return simplified stock information for each warehouse return warehousesResponse.value.map(warehouse => ({ ItemCode: itemCode, WarehouseCode: warehouse.WarehouseCode, WarehouseName: warehouse.WarehouseName, InStock: itemResponse.QuantityOnStock || 0, // This is total, not per warehouse Available: itemResponse.QuantityOnStock || 0, Committed: 0, Ordered: itemResponse.QuantityOrderedFromVendors || 0 })); } } /** * Get item price for a specific item and price list */ async getItemPrice(itemCode, priceListId, params) { if (!itemCode) { throw new Error('ItemCode is required'); } if (!priceListId && priceListId !== 0) { throw new Error('PriceListId is required'); } // Approach 1: Use the same method as getItem - get the item with price expansion try { // Get the item with all price information - this matches how sap_get_item works const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Items('${encodeURIComponent(itemCode)}')`); // SAP B1 items often include price information directly or through navigation properties // Check if the item has price information for the requested price list if (response) { // If ItemPrices navigation property exists if (response.ItemPrices && Array.isArray(response.ItemPrices)) { const priceForList = response.ItemPrices.find((p) => p.PriceList === priceListId); if (priceForList) { return { ItemCode: itemCode, PriceList: priceListId, Price: priceForList.Price || 0, Currency: priceForList.Currency || 'USD', Factor: priceForList.Factor, PriceListName: priceForList.PriceListName }; } } // If price is embedded directly in the item (for default price list) if (priceListId === 1 && (response.Price !== undefined || response.UnitPrice !== undefined)) { return { ItemCode: itemCode, PriceList: 1, Price: response.Price || response.UnitPrice || 0, Currency: response.Currency || 'USD' }; } } } catch (error) { // Continue to other approaches } // Approach 2: Try using ItemPrices entity directly try { const filterParams = { ...params, $filter: `ItemCode eq '${itemCode}' and PriceList eq ${priceListId}`, $top: 1 }; const queryString = this.buildQueryString(filterParams); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/ItemPrices${queryString}`); if (response.value.length > 0) { return response.value[0]; } } catch (error) { // Continue to fallback approach } // Approach 3: Try getting the item with explicit ItemPrices expansion try { const expandParams = { $expand: `ItemPrices($filter=PriceList eq ${priceListId})`, $select: 'ItemCode' }; const queryString = this.buildQueryString(expandParams); const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/Items('${itemCode}')${queryString}`); if (response.ItemPrices && response.ItemPrices.length > 0) { const itemPrice = response.ItemPrices[0]; return { ItemCode: itemCode, PriceList: priceListId, Price: itemPrice.Price || 0, Currency: itemPrice.Currency || 'USD', Factor: itemPrice.Factor, PriceListName: itemPrice.PriceListName }; } } catch (error) { // Continue to final fallback } // Final fallback: throw error if no price found throw new Error(`No price found for item ${itemCode} in price list ${priceListId}. Item exists but no price is configured for this price list. Available approaches tried: direct item access, ItemPrices entity, and item expansion.`); } /** * Get all prices for a specific item across all price lists */ async getItemPricesAll(itemCode, params) { if (!itemCode) { throw new Error('ItemCode is required'); } // Try alternative approach using ItemPrices entity directly const filterParams = { ...params, $filter: params?.$filter ? `ItemCode eq '${itemCode}' and (${params.$filter})` : `ItemCode eq '${itemCode}'` }; const queryString = this.buildQueryString(filterParams); try { const response = await this.sessionManager.executeRequest('GET', `/b1s/v1/ItemPrices${queryString}`); return response.value; } catch (error) { // Fallback: Return basic price information for all price lists const priceListsResponse = await this.sessionManager.executeRequest('GET', '/b1s/v1/PriceLists?$select=PriceListNo,PriceListName'); // Return basic price information for each price list return priceListsResponse.value.map(priceList => ({ ItemCode: itemCode, PriceList: priceList.PriceListNo, Price: 0, // Would need proper price lookup Currency: 'USD', PriceListName: priceList.PriceListName })); } } } //# sourceMappingURL=client.js.map