Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add service for stock endpoints #38

Merged
merged 8 commits into from
May 27, 2024
19 changes: 19 additions & 0 deletions src/common/utils/queryParamBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function buildQueryFromArray(paramName: string, arrayToBuildFrom: string[], maxSize?: number): string {
if (arrayToBuildFrom.length <= 0) {
throw new Error('Tried to work with an unsupported array size.');
}

if (maxSize && maxSize >= 1) {
arrayToBuildFrom = arrayToBuildFrom.slice(0, maxSize - 1);
}

if (arrayToBuildFrom.length > 1) {
let query = `${arrayToBuildFrom[0]}&${paramName}`;
arrayToBuildFrom = arrayToBuildFrom.slice(1);
query += arrayToBuildFrom.join(`&${paramName}=`);

return query;
} else {
return arrayToBuildFrom[0];
}
}
1 change: 1 addition & 0 deletions src/fft-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './routing-plan';
export * from './shipment';
export * from './subscription';
export * from './types';
export * from './stock';
267 changes: 267 additions & 0 deletions src/fft-api/stock/fftStockService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import { Logger } from 'tslog';
import { FftApiClient } from '../common';
import {
FacilityServiceType,
FacilityStatus,
Stock,
StockDistribution,
StockForCreation,
StockForUpdate,
StockPaginatedResult,
StocksForUpsert,
StockSummaries,
StockUpsertOperationResult,
} from '../types';
import { CustomLogger } from '../../common';
import { ResponseError } from 'superagent';
import { buildQueryFromArray } from '../../common/utils/queryParamBuilder';

export class FftStockService {
private readonly path: string = 'stocks';
private readonly logger: Logger<FftStockService> = new CustomLogger<FftStockService>();

constructor(private readonly apiClient: FftApiClient) {}

public async createStock(stockForCreation: StockForCreation): Promise<Stock> {
try {
return await this.apiClient.post<Stock>(this.path, { ...stockForCreation });
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Could not create stock. Failed with status ${httpError.status}, error: ${
httpError.response ? JSON.stringify(httpError.response.body) : ''
}`
);

throw error;
}
}

public async getAll(
facilityRef?: string,
tenantArticleIds?: string[],
locationRefs?: string[],
size?: number,
startAfterId?: string
): Promise<StockPaginatedResult> {
try {
const queryParams: Record<string, string> = {};

if (facilityRef) {
queryParams['facilityRef'] = facilityRef;
}

if (tenantArticleIds) {
tenantArticleIds = tenantArticleIds.slice(0, 499);
saschaheinl-fft marked this conversation as resolved.
Show resolved Hide resolved

if (tenantArticleIds.length == 1) {
queryParams['tenantArticleId'] = tenantArticleIds[0];
}

if (tenantArticleIds.length > 1) {
queryParams['tenantArticleIds'] = buildQueryFromArray('tenantArticleIds', tenantArticleIds, 500);
arnoerpenbeck marked this conversation as resolved.
Show resolved Hide resolved
}
}

saschaheinl-fft marked this conversation as resolved.
Show resolved Hide resolved
if (locationRefs) {
locationRefs = locationRefs.slice(0, 499);

if (locationRefs.length == 1) {
queryParams['locationRef'] = locationRefs[0];
}

if (locationRefs.length > 1) {
let query = `${locationRefs[0]}&locationRef=`;
saschaheinl-fft marked this conversation as resolved.
Show resolved Hide resolved
locationRefs = locationRefs.slice(1, 499);
query += locationRefs.join('&locationRef=');

queryParams['locationRef'] = query;
}
}

if (size && size <= 100) {
queryParams['size'] = size.toString();
}

if (startAfterId) {
queryParams['startAfterId'] = startAfterId;
}
return await this.apiClient.get<StockPaginatedResult>(this.path, queryParams);
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Fetching all stock failed with status ${httpError.status}, error: ${
httpError.response ? JSON.stringify(httpError.response.body) : ''
}`
);

throw error;
}
}

public async upsertStocks(stocksForUpsert: StocksForUpsert): Promise<StockUpsertOperationResult> {
try {
return await this.apiClient.put<StockUpsertOperationResult>(this.path, { ...stocksForUpsert });
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Could not upsert stock. Failed with status ${httpError.status}, error: ${
httpError.response ? JSON.stringify(httpError.response.body) : ''
}`
);

throw error;
}
}

public async updateById(stockId: string, stockForUpdate: StockForUpdate): Promise<Stock> {
try {
return await this.apiClient.put<Stock>(`${this.path}/${stockId}`, { ...stockForUpdate });
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Could not update stock. Failed with status ${httpError.status}, error: ${
httpError.response ? JSON.stringify(httpError.response.body) : ''
}`
);

throw error;
}
}

public async deleteById(stockId: string): Promise<void> {
try {
return await this.apiClient.delete(`${this.path}/${stockId}`);
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Could not delete stock with id ${stockId}. Failed with status ${httpError.status}, error: ${
httpError.response ? JSON.stringify(httpError.response.body) : ''
}`
);

throw error;
}
}

public async getById(stockId: string): Promise<Stock> {
try {
return await this.apiClient.get<Stock>(`${this.path}/${stockId}`);
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Could not get stock with id '${stockId}'. Failed with status ${httpError.status}, error: ${
httpError.response ? JSON.stringify(httpError.response.body) : ''
}`
);

throw error;
}
}

public async getStockSummaries(
size?: number,
startAfterId?: string,
serviceTypes?: FacilityServiceType[],
facilityStatuses?: FacilityStatus[],
facilityRefs?: string[],
allowStale?: boolean,
tenantArticleIds?: string[],
channelRefs?: string[]
): Promise<StockSummaries> {
saschaheinl-fft marked this conversation as resolved.
Show resolved Hide resolved
try {
const queryParams: Record<string, string> = {};

if (size && size <= 100) {
queryParams['size'] = size.toString();
}

if (startAfterId) {
queryParams['startAfterId'] = startAfterId;
}

if (serviceTypes && serviceTypes.length > 0) {
queryParams['facilityServiceTypes'] = buildQueryFromArray('facilityServiceTypes', serviceTypes);
}

if (facilityStatuses) {
queryParams['facilityStatus'] = buildQueryFromArray('facilityStatus', facilityStatuses);
}

if (facilityRefs) {
queryParams['facilityRefs'] = buildQueryFromArray('facilityRefs', facilityRefs, 500);
}

if (allowStale) {
queryParams['allowStale'] = String(allowStale);
}

if (tenantArticleIds) {
queryParams['tenantArticleIds'] = buildQueryFromArray('tenantArticleIds', tenantArticleIds, 500);
}

if (channelRefs) {
queryParams['channelRefs'] = buildQueryFromArray('channelRefs', channelRefs, 50);
}

return await this.apiClient.get<StockSummaries>(`${this.path}/summaries`, queryParams);
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Fetching stock summaries failed with status ${httpError.status}, error: ${
httpError.response ? JSON.stringify(httpError.response.body) : ''
}`
);

throw error;
}
}

public async getStockDistribution(
tenantArticleId: string,
facilityServiceTypes?: FacilityServiceType[],
facilityStatuses?: FacilityStatus[],
facilityName?: string,
facilityIds?: string[],
channelRefs?: string[]
): Promise<StockDistribution> {
if (!tenantArticleId) {
throw new Error('tenantArticleId is missing.');
}

try {
const queryParams: Record<string, string> = {};

if (facilityServiceTypes) {
queryParams['facilityServiceTypes'] = buildQueryFromArray('facilityServiceTypes', facilityServiceTypes);
}

if (facilityStatuses) {
queryParams['facilityStatus'] = buildQueryFromArray('facilityStatus', facilityStatuses);
}

if (facilityName) {
queryParams['facilityName'] = facilityName;
}

if (facilityIds) {
queryParams['facilityIds'] = buildQueryFromArray('facilityIds', facilityIds, 500);
}

if (channelRefs) {
queryParams['channelRefs'] = buildQueryFromArray('channelRefs', channelRefs, 50);
}

return await this.apiClient.get<StockDistribution>(`articles/${tenantArticleId}/stockdistribution`, queryParams);
} catch (error) {
const httpError = error as ResponseError;
this.logger.error(
`Fetching stock distribution for tenantArticleId ${tenantArticleId} failed with status ${
httpError.status
}, error: ${httpError.response ? JSON.stringify(httpError.response.body) : ''}`
);

throw error;
}
}
}
1 change: 1 addition & 0 deletions src/fft-api/stock/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './fftStockService';
38 changes: 38 additions & 0 deletions src/fft-api/types/api.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35809,6 +35809,12 @@ components:
$ref: '#/components/schemas/ServiceJobLink'
type: array
minItems: 1
fullIdentifier:
type: string
description: >-
Full identifier of the service job. Using the full name of the
customer when created from an order.
example: 240429_lorem-42
required:
- id
- serviceJobLinks
Expand All @@ -35834,6 +35840,12 @@ components:
$ref: '#/components/schemas/ServiceJobLinkForCreation'
type: array
minItems: 1
fullIdentifier:
type: string
description: >-
Full identifier of the service job. Using the full name of the
customer when created from an order.
example: 240429_lorem-42
required:
- serviceJobLinks
ServiceJobLinkForCreation:
Expand Down Expand Up @@ -36324,6 +36336,8 @@ components:
properties:
name:
type: string
facilityRef:
type: string
InboundProcessPurchaseOrder:
type: object
properties:
Expand Down Expand Up @@ -36591,6 +36605,12 @@ components:
type: number
available:
type: number
deprecated: true
description: >-
This field is deprecated and replaced by new availability concepts.
Please see
https://docs.fulfillmenttools.com/api-docs/use-cases/inventory-management/global-inventory/availability
for more information.
byTrait:
$ref: '#/components/schemas/InventoryFacilityStockShapeByTrait'
changeReason:
Expand All @@ -36599,8 +36619,20 @@ components:
- UNKNOWN
availableToPromise:
type: number
deprecated: true
description: >-
This field is deprecated and replaced by new availability concepts.
Please see
https://docs.fulfillmenttools.com/api-docs/use-cases/inventory-management/global-inventory/availability
for more information.
readyToPick:
type: number
deprecated: true
description: >-
This field is deprecated and replaced by new availability concepts.
Please see
https://docs.fulfillmenttools.com/api-docs/use-cases/inventory-management/global-inventory/availability
for more information.
availableForPicking:
type: number
delta:
Expand All @@ -36609,6 +36641,10 @@ components:
type: array
items:
$ref: '#/components/schemas/InventoryFacilityStockStaleReason'
stockOnHand:
type: number
availableOnStock:
type: number
required:
- facilityRef
- tenantArticleId
Expand All @@ -36623,6 +36659,8 @@ components:
- availableForPicking
- delta
- staleReasons
- stockOnHand
- availableOnStock
RequestedDate:
type: object
properties:
Expand Down
Loading