UNPKG

minimax-client

Version:

TypeScript client library for the Minimax accounting API (https://moj.minimax.rs/RS/API/)

968 lines (953 loc) 30.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var axios = require('axios'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios); /** * Base resource module * * Base class for all API resource modules */ /** * Base resource module */ class BaseResource { /** * Create a new resource module * * @param client Minimax client instance */ constructor(client) { this.client = client; } /** * Get the full endpoint URL for a resource * * @param path Optional path to append to the base endpoint * @returns Full endpoint URL */ getEndpoint(path) { // Construct the base path with the optional path parameter const basePath = path ? `${this.endpoint}/${path}` : this.endpoint; // If the endpoint already starts with 'api/', return it as is if (basePath.startsWith('api/')) { console.log(`Base path already starts with 'api/': ${basePath}`); return basePath; } // For organization-specific endpoints that require an organization ID const organizationId = this.client.getOrganizationId(); if (organizationId && !basePath.includes('/orgs/')) { // Use the correct pattern: /api/orgs/{orgId}/... console.log(`Using organization ID: ${organizationId}`, `Base path: ${basePath}`); return `api/orgs/${organizationId}/${basePath}`; } // For non-organization-specific endpoints return `api/${basePath}`; } } /** * Received Invoices module * * Module for interacting with received invoices in the Minimax API */ /** * Received Invoices module */ class ReceivedInvoicesModule extends BaseResource { constructor() { super(...arguments); /** * Base endpoint for received invoices * The actual endpoint will be constructed as api/orgs/{organizationId}/receivedInvoices */ this.endpoint = 'receivedinvoices'; } /** * Get all received invoices * * @param options Filter options * @returns Promise resolving to an array of received invoices */ async getAll(options = {}) { const params = {}; // Add pagination if (options.limit) { params.pageSize = options.limit; } if (options.offset) { const page = Math.floor(options.offset / (options.limit || 10)) + 1; params.page = page; } // Add count if (options.count) { params.$count = true; } const response = await this.client.get(this.getEndpoint(), { params }); return response.Rows; } /** * Get a received invoice by ID * * @param id Received invoice ID * @returns Promise resolving to the received invoice */ async get(id) { return this.client.get(this.getEndpoint(id)); } /** * Create a new received invoice * * @param data Invoice data * @returns Promise resolving to the created received invoice */ async create(data) { return this.client.post(this.getEndpoint(), data); } /** * Update a received invoice * * @param id Received invoice ID * @param data Invoice data with RowVersion * @returns Promise resolving to the updated received invoice */ async update(id, data) { return this.client.put(this.getEndpoint(id), data); } /** * Delete a received invoice * * @param id Received invoice ID * @param rowVersion RowVersion for concurrency control * @returns Promise resolving when the received invoice is deleted */ async delete(id, rowVersion) { return this.client.delete(this.getEndpoint(id), { params: { RowVersion: rowVersion } }); } /** * Issue a received invoice * * @param id Received invoice ID * @returns Promise resolving to the issued received invoice */ async issue(id) { return this.client.post(this.getEndpoint(id) + '/issue'); } /** * Mark a received invoice as paid * * @param id Received invoice ID * @param paymentDate Payment date (YYYY-MM-DD) * @returns Promise resolving to the paid received invoice */ async markAsPaid(id, paymentDate) { return this.client.post(this.getEndpoint(id) + '/pay', { paymentDate }); } /** * Cancel a received invoice * * @param id Received invoice ID * @returns Promise resolving to the cancelled received invoice */ async cancel(id) { return this.client.post(this.getEndpoint(id) + '/cancel'); } } /** * Customers module * * Module for interacting with customers in the Minimax API */ /** * Customers module */ class CustomersModule extends BaseResource { constructor() { super(...arguments); /** * Base endpoint for customers */ this.endpoint = 'customers'; } /** * Get all customers * * @param options Filter options * @returns Promise resolving to an array of customers */ async getAll(options = {}) { const params = {}; // Build filter string const filters = []; if (options.name) { filters.push(`contains(Name, '${options.name}')`); } if (options.taxNumber) { filters.push(`TaxNumber eq '${options.taxNumber}'`); } if (options.type) { filters.push(`Type eq '${options.type}'`); } if (options.isActive !== undefined) { filters.push(`IsActive eq ${options.isActive}`); } if (filters.length > 0) { params.$filter = filters.join(' and '); } // Add pagination if (options.limit) { params.$top = options.limit; } if (options.offset) { params.$skip = options.offset; } // Add count if (options.count) { params.$count = true; } const response = await this.client.get(this.endpoint, { params }); return response.value; } /** * Get a customer by ID * * @param id Customer ID * @returns Promise resolving to the customer */ async get(id) { return this.client.get(this.getEndpoint(id)); } /** * Create a new customer * * @param params Customer creation parameters * @returns Promise resolving to the created customer */ async create(params) { return this.client.post(this.endpoint, params); } /** * Update a customer * * @param id Customer ID * @param params Customer update parameters * @returns Promise resolving to the updated customer */ async update(id, params) { return this.client.put(this.getEndpoint(id), params); } /** * Delete a customer * * @param id Customer ID * @returns Promise resolving when the customer is deleted */ async delete(id) { await this.client.delete(this.getEndpoint(id)); } } /** * Organizations module for the Minimax client * Handles operations related to organizations */ /** * Organizations module */ class OrganizationsModule extends BaseResource { constructor() { super(...arguments); /** * Endpoint for organizations */ this.endpoint = 'api/currentuser/orgs'; } /** * Get all organizations for the current user * * @returns Promise that resolves to a list of organizations */ async getAll() { const response = await this.client.get(this.endpoint); // Extract organizations from the response return response.Rows.map(row => row.Organisation); } /** * Find organization by name * * @param name Name to find * @returns Promise that resolves to the organization or null if not found */ async findByName(name) { const organizations = await this.getAll(); return organizations.find(org => org.Name === name) || null; } /** * Find organization by identifier (alias for findByName) * * @param identifier Organization identifier (registration number) to find * @returns Promise that resolves to the organization or null if not found */ async findByIdentifier(identifier) { return this.findByName(identifier); } } /** * Employees module * * Module for interacting with employees in the Minimax API */ /** * Employees module */ class EmployeesModule extends BaseResource { constructor() { super(...arguments); /** * Base endpoint for employees * The actual endpoint will be constructed as api/orgs/{organizationId}/employees */ this.endpoint = 'employees'; } /** * Get all employees * * @param options Filter options * @returns Promise resolving to an array of employees */ async getAll(options = {}) { const params = {}; // Add pagination if (options.limit) { params.pageSize = options.limit; } if (options.offset) { const page = Math.floor(options.offset / (options.limit || 10)) + 1; params.page = page; } // Add count if (options.count) { params.$count = true; } // Add filters if (options.status) { params.status = options.status; } if (options.department) { params.department = options.department; } const response = await this.client.get(this.getEndpoint(), { params }); return response.Rows; } /** * Get an employee by ID * * @param id Employee ID * @returns Promise resolving to the employee */ async get(id) { return this.client.get(this.getEndpoint(id)); } /** * Create a new employee * * @param data Employee data * @returns Promise resolving to the created employee */ async create(data) { return this.client.post(this.getEndpoint(), data); } /** * Update an employee * * @param id Employee ID * @param data Employee data with RowVersion * @returns Promise resolving to the updated employee */ async update(id, data) { return this.client.put(this.getEndpoint(id), data); } /** * Delete an employee * * @param id Employee ID * @param rowVersion RowVersion for concurrency control * @returns Promise resolving when the employee is deleted */ async delete(id, rowVersion) { return this.client.delete(this.getEndpoint(id), { params: { RowVersion: rowVersion } }); } } /** * Journals module * * Module for interacting with journals and journal entries in the Minimax API */ /** * Journals module */ class JournalsModule extends BaseResource { constructor() { super(...arguments); /** * Base endpoint for journals */ this.endpoint = 'journals'; } /** * Get all journals * * @param options Filter options * @returns Promise resolving to an array of journal search results */ async getAll(options = {}) { const params = {}; // Add filter parameters directly to the query if (options.dateTo) { params.DateTo = options.dateTo; } if (options.dateFrom) { params.DateFrom = options.dateFrom; } if (options.journalId) { params.JournalId = options.journalId; } if (options.journalType) { params.JournalType = options.journalType; } if (options.description) { params.Description = options.description; } if (options.status) { params.Status = options.status; } // Add pagination if (options.currentPage) { params.CurrentPage = options.currentPage; } if (options.pageSize) { params.PageSize = options.pageSize; } // Add sorting if (options.sortField) { params.SortField = options.sortField; } if (options.order) { params.Order = options.order; } const response = await this.client.get(this.getEndpoint(), { params }); return response.Rows; } /** * Get a journal by ID * * @param id Journal ID * @returns Promise resolving to the journal */ async get(id) { return this.client.get(this.getEndpoint(id)); } /** * Create a new journal * * @param params Journal creation parameters * @returns Promise resolving to the created journal */ async create(params) { return this.client.post(this.endpoint, params); } /** * Update a journal * * @param id Journal ID * @param params Journal update parameters * @returns Promise resolving to the updated journal */ async update(id, params) { return this.client.put(this.getEndpoint(id), params); } /** * Delete a journal * * @param id Journal ID * @returns Promise resolving when the journal is deleted */ async delete(id) { await this.client.delete(this.getEndpoint(id)); } /** * Get a VAT entry by ID * * @param journalId Journal ID * @param vatId VAT entry ID * @returns Promise resolving to the VAT entry */ async getVATEntry(journalId, vatId) { return this.client.get(this.getEndpoint(`${journalId}/vat/${vatId}`)); } /** * Create a new VAT entry * * @param journalId Journal ID * @param vatEntry VAT entry * @returns Promise resolving when the VAT entry is created */ async createVATEntry(journalId, vatEntry) { await this.client.post(this.getEndpoint(`${journalId}/vat`), vatEntry); } /** * Update a VAT entry * * @param journalId Journal ID * @param vatId VAT entry ID * @param vatEntry VAT entry * @returns Promise resolving when the VAT entry is updated */ async updateVATEntry(journalId, vatId, vatEntry) { await this.client.put(this.getEndpoint(`${journalId}/vat/${vatId}`), vatEntry); } /** * Delete a VAT entry * * @param journalId Journal ID * @param vatId VAT entry ID * @returns Promise resolving when the VAT entry is deleted */ async deleteVATEntry(journalId, vatId) { await this.client.delete(this.getEndpoint(`${journalId}/vat/${vatId}`)); } /** * Get all journal entries * * @param options Filter options * @returns Promise resolving to an array of journal entries search results */ async getAllJournalEntries(options = {}) { const params = {}; // Add filter parameters directly to the query if (options.journalType) { params.JournalType = options.journalType; } if (options.description) { params.Description = options.description; } if (options.analyticId) { params.AnalyticID = options.analyticId; } if (options.customerId) { params.CustomerID = options.customerId; } if (options.employeeId) { params.EmployeeId = options.employeeId; } if (options.status) { params.Status = options.status; } if (options.currency) { params.Currency = options.currency; } if (options.dateTo) { params.DateTo = options.dateTo; } if (options.dateFrom) { params.DateFrom = options.dateFrom; } if (options.account) { params.Account = options.account; } // Add pagination if (options.currentPage) { params.CurrentPage = options.currentPage; } if (options.pageSize) { params.PageSize = options.pageSize; } // Add sorting if (options.sortField) { params.SortField = options.sortField; } if (options.order) { params.Order = options.order; } const response = await this.client.get(this.getEndpoint('journal-entries'), { params }); return response.Rows; } } /** * Journal Types module * * Module for interacting with journal types in the Minimax API */ /** * Journal types module */ class JournalTypesModule extends BaseResource { constructor() { super(...arguments); /** * Base endpoint for journal types */ this.endpoint = 'journaltypes'; } /** * Get all journal types * * @param options Filter options * @returns Promise resolving to an array of journal types */ async getAll(options = {}) { const params = {}; // Add search string parameter if (options.searchString) { params.SearchString = options.searchString; } // Add pagination parameters if (options.currentPage) { params.CurrentPage = options.currentPage; } if (options.pageSize) { params.PageSize = options.pageSize; } // Add sorting parameters if (options.sortField) { params.SortField = options.sortField; } if (options.order) { params.Order = options.order; } const response = await this.client.get(this.getEndpoint(), { params }); return response.Rows; } /** * Get a journal type by ID * * @param id Journal type ID * @returns Promise resolving to the journal type */ async get(id) { return this.client.get(this.getEndpoint(id)); } /** * Get a journal type by code * * @param code Journal type code * @returns Promise resolving to the journal type */ async getByCode(code) { return this.client.get(this.getEndpoint(`code(${code})`)); } } /** * Minimax Client * * Main client class for interacting with the Minimax API */ /** * Main client for interacting with the Minimax API */ class MinimaxClient { /** * Create a new Minimax client * * @param config Client configuration */ constructor(config) { this.token = null; this.organizationId = null; this.config = config; // Create HTTP client this.httpClient = axios__default["default"].create({ baseURL: config.baseUrl || MinimaxClient.DEFAULT_BASE_URL, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); // Set organization ID if provided if (config.organizationId) { this.organizationId = config.organizationId; } // Initialize resource modules this.receivedInvoices = new ReceivedInvoicesModule(this); this.customers = new CustomersModule(this); this.organizations = new OrganizationsModule(this); this.employees = new EmployeesModule(this); this.journals = new JournalsModule(this); this.journalTypes = new JournalTypesModule(this); } /** * Authenticate with the Minimax API * * @returns Promise that resolves when authentication is complete */ async authenticate() { const authUrl = this.config.authUrl || MinimaxClient.DEFAULT_AUTH_URL; const params = new URLSearchParams(); params.append('grant_type', 'password'); params.append('client_id', this.config.clientId); params.append('client_secret', this.config.clientSecret); params.append('username', this.config.username); params.append('password', this.config.password); params.append('scope', 'minimax.rs'); try { const response = await axios__default["default"].post(authUrl, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); this.token = { ...response.data, obtained_at: Date.now() }; // Set organization based on registration number or ID await this.setOrganizationFromConfig(); } catch (error) { throw new Error(`Authentication failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Set the organization based on the configuration * If organizationIdentifier is provided, find and set the organization with that registration number * Otherwise, use the organizationId if provided * * @returns Promise that resolves when the organization is set */ async setOrganizationFromConfig() { // If organization identifier is provided, find and set the organization if (this.config.organizationIdentifier) { try { console.log(`Finding organization by identifier: ${this.config.organizationIdentifier}`); const organization = await this.organizations.findByIdentifier(this.config.organizationIdentifier); if (organization) { this.organizationId = organization.ID; console.log(`Organization set to ${organization.Name} (ID: ${organization.ID}) based on identifier ${this.config.organizationIdentifier}`); } else { console.warn(`No organization found with identifier ${this.config.organizationIdentifier}`); // Fall back to organizationId if provided if (this.config.organizationId) { this.organizationId = this.config.organizationId; console.log(`Falling back to provided organization ID: ${this.config.organizationId}`); } } } catch (error) { console.warn(`Error finding organization by identifier: ${error instanceof Error ? error.message : String(error)}`); // Fall back to organizationId if provided if (this.config.organizationId) { this.organizationId = this.config.organizationId; console.log(`Falling back to provided organization ID: ${this.config.organizationId}`); } } } // Otherwise, use the organizationId if provided else if (this.config.organizationId) { this.organizationId = this.config.organizationId; console.log(`Organization ID set to ${this.config.organizationId}`); } } /** * Check if the token is expired * * @returns True if the token is expired or doesn't exist */ isTokenExpired() { if (!this.token) { return true; } const expiresAt = this.token.obtained_at + (this.token.expires_in * 1000); const now = Date.now(); // Consider token expired if it expires in less than 30 seconds return expiresAt - now < 30000; } /** * Refresh the authentication token * * @returns Promise that resolves when the token is refreshed */ async refreshToken() { if (!this.token) { await this.authenticate(); return; } const authUrl = this.config.authUrl || MinimaxClient.DEFAULT_AUTH_URL; const params = new URLSearchParams(); params.append('grant_type', 'refresh_token'); params.append('client_id', this.config.clientId); params.append('client_secret', this.config.clientSecret); params.append('refresh_token', this.token.refresh_token); params.append('scope', 'minimax.rs'); try { const response = await axios__default["default"].post(authUrl, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); this.token = { ...response.data, obtained_at: Date.now() }; } catch (error) { // If refresh fails, try full authentication this.token = null; await this.authenticate(); } } /** * Get a valid access token * * @returns Promise that resolves to the access token */ async getAccessToken() { if (this.isTokenExpired()) { await this.refreshToken(); } return this.token.access_token; } /** * Set the organization ID for API calls * * @param organizationId Organization ID */ setOrganizationId(organizationId) { this.organizationId = organizationId; } /** * Get the current organization ID * * @returns Organization ID or null if not set */ getOrganizationId() { return this.organizationId; } /** * Make a request to the Minimax API * * @param method HTTP method * @param endpoint API endpoint * @param options Request options * @returns Promise that resolves to the response data */ async request(method, endpoint, options = {}) { // Ensure we have a valid token const token = await this.getAccessToken(); // Prepare headers const headers = { 'Authorization': `Bearer ${token}`, ...options.headers }; // Add organization ID header if set if (this.organizationId) { headers['Organization-Id'] = this.organizationId; } try { // The endpoint should already be properly constructed by the resource module's getEndpoint method // No need to modify it here const url = endpoint; const response = await this.httpClient.request({ method, url, data: options.data, params: options.params, headers }); return response.data; } catch (error) { if (axios__default["default"].isAxiosError(error) && error.response) { const status = error.response.status; const data = error.response.data; // Handle specific error cases if (status === 401) { // Token might be invalid, try to refresh and retry once this.token = null; await this.authenticate(); // The endpoint should already be properly constructed by the resource module's getEndpoint method // No need to modify it here const url = endpoint; // Retry the request const retryResponse = await this.httpClient.request({ method, url, data: options.data, params: options.params, headers: { 'Authorization': `Bearer ${this.token.access_token}`, ...options.headers } }); return retryResponse.data; } // Handle other error cases throw new Error(`API request failed: ${data.error_description || data.message || error.message}`); } // Handle non-Axios errors throw new Error(`Request failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Make a GET request to the Minimax API * * @param endpoint API endpoint * @param options Request options * @returns Promise that resolves to the response data */ async get(endpoint, options = {}) { return this.request('GET', endpoint, options); } /** * Make a POST request to the Minimax API * * @param endpoint API endpoint * @param data Request body * @param options Request options * @returns Promise that resolves to the response data */ async post(endpoint, data, options = {}) { return this.request('POST', endpoint, { ...options, data }); } /** * Make a PUT request to the Minimax API * * @param endpoint API endpoint * @param data Request body * @param options Request options * @returns Promise that resolves to the response data */ async put(endpoint, data, options = {}) { return this.request('PUT', endpoint, { ...options, data }); } /** * Make a DELETE request to the Minimax API * * @param endpoint API endpoint * @param options Request options * @returns Promise that resolves to the response data */ async delete(endpoint, options = {}) { return this.request('DELETE', endpoint, options); } } /** * Default API URLs */ MinimaxClient.DEFAULT_BASE_URL = 'https://moj.minimax.rs/RS/api/'; MinimaxClient.DEFAULT_AUTH_URL = 'https://moj.minimax.rs/RS/AUT/oauth20/token'; /** * Minimax API Client * A TypeScript client for the Minimax accounting API */ // Export version const VERSION = '0.1.0'; // Note: The old implementation (auth, http, api, types) has been removed // in favor of the simplified client implementation. exports.CustomersModule = CustomersModule; exports.EmployeesModule = EmployeesModule; exports.MinimaxClient = MinimaxClient; exports.ReceivedInvoicesModule = ReceivedInvoicesModule; exports.VERSION = VERSION; //# sourceMappingURL=index.js.map