minimax-client
Version:
TypeScript client library for the Minimax accounting API (https://moj.minimax.rs/RS/API/)
968 lines (953 loc) • 30.1 kB
JavaScript
'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