Skip to content
This repository has been archived by the owner on Feb 6, 2024. It is now read-only.

Commit

Permalink
feat: create createInboundShipmentPlan
Browse files Browse the repository at this point in the history
  • Loading branch information
justinemmanuelmercado committed Jul 9, 2020
1 parent e1802c5 commit 4e67963
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 30 deletions.
178 changes: 176 additions & 2 deletions src/sections/fulfillment-inbound-shipment.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Codec, enumeration, GetInterface, optional, string } from 'purify-ts'
import { Codec, enumeration, GetInterface, number, optional, string } from 'purify-ts'

import { ParsingError } from '../error'
import { HttpClient, RequestMeta, Resource } from '../http'
import { ensureArray } from '../parsing'
import { ensureArray, ensureString } from '../parsing'
import { getServiceStatusByResource } from './shared'

const FULFILLMENT_INBOUND_SHIPMENT_API_VERSION = '2010-10-01'
Expand Down Expand Up @@ -79,9 +79,183 @@ interface GetInboundGuidanceForASINParameters {
MarketplaceId: string
}

interface AddressFIS {
Name: string
AddressLine1: string
AddressLine2?: string
City: string
DistrictOrCounty?: string
StateOrProvinceCode?: string
CountryCode: string
PostalCode?: string
[key: string]: string | undefined
}

type LabelPrepPreference = 'SELLER_LABEL' | 'AMAZON_LABEL_ONLY' | 'AMAZON_LABEL_PREFERRED'
type ConditionFIS =
| 'NewItem'
| 'NewWithWarranty'
| 'NewOEM'
| 'NewOpenBox'
| 'UsedLikeNew'
| 'UsedVeryGood'
| 'UsedGood'
| 'UsedAcceptable'
| 'UsedPoor'
| 'UsedRefurbished'
| 'CollectibleLikeNew'
| 'CollectibleVeryGood'
| 'CollectibleGood'
| 'CollectibleAcceptable'
| 'CollectiblePoor'
| 'RefurbishedWithWarranty'
| 'Refurbished'
| 'Club'

enum PrepInstructionEnum {
Polybagging = 'Polybagging',
BubbleWrapping = 'BubbleWrapping',
Taping = 'Taping',
BlackShrinkWrapping = 'BlackShrinkWrapping',
Labeling = 'Labeling',
HangGarment = 'HangGarment',
}

type PrepInstruction = keyof typeof PrepInstructionEnum

enum PrepOwnerEnum {
AMAZON = 'AMAZON',
SELLER = 'SELLER',
}

type PrepOwner = keyof typeof PrepOwnerEnum

interface PrepDetails {
PrepInstruction: PrepInstruction
PrepOwner: PrepOwner
}

interface InboundShipmentPlanRequestItem {
SellerSKU: string
ASIN?: string
Condition?: ConditionFIS
Quantity: number
QuantityInCase?: number
PrepDetailsList?: PrepDetails[]
}

interface CreateInboundShipmentPlanParameters {
ShipFromAddress: AddressFIS
ShipToCountryCode?: string
ShipToCountrySubdivisionCode?: string
LabelPrepPreference?: LabelPrepPreference
InboundShipmentPlanRequestItems: InboundShipmentPlanRequestItem[]
}

const canonicalizeInboundShipmentPlanRequestItems = (
requestItem: InboundShipmentPlanRequestItem,
) => {
return {
SellerSKU: requestItem.SellerSKU,
ASIN: requestItem.ASIN,
Condition: requestItem.Condition,
Quantity: requestItem.Quantity,
QuantityInCase: requestItem.QuantityInCase,
'PrepDetailsList.PrepDetails': requestItem.PrepDetailsList,
}
}

const canonicalizeParametersCreateInboundShipmentPlan = (
parameters: CreateInboundShipmentPlanParameters,
) => {
const fixedInboundShipmentPlanRequestItems = parameters.InboundShipmentPlanRequestItems.map(
(requestItem) => canonicalizeInboundShipmentPlanRequestItems(requestItem),
)
return {
ShipToCountryCode: parameters.ShipToCountryCode,
ShipFromAddress: parameters.ShipFromAddress,
ShipToCountrySubdivisionCode: parameters.ShipToCountrySubdivisionCode,
LabelPrepPreference: parameters.LabelPrepPreference,
'InboundShipmentPlanRequestItems.member': fixedInboundShipmentPlanRequestItems,
}
}

const AddressFIS = Codec.interface({
Name: string,
AddressLine1: string,
AddressLine2: optional(string),
City: string,
DistrictOrCounty: optional(string),
StateOrProvinceCode: optional(string),
CountryCode: string,
PostalCode: optional(string),
})

const PrepDetails = Codec.interface({
PrepInstruction: enumeration(PrepInstructionEnum),
PrepOwner: enumeration(PrepOwnerEnum),
})

const InboundShipmentPlanItem = Codec.interface({
SellerSKU: string,
FulfillmentNetworkSKU: string,
Quantity: number,
PrepDetailsList: optional(ensureArray('PrepDetails', PrepDetails)),
})

const Amount = Codec.interface({
CurrencyCode: string,
Value: ensureString,
})

const BoxContentsFeeDetails = Codec.interface({
TotalUnits: optional(number),
FeePerUnit: optional(Amount),
TotalFee: optional(Amount),
})

const InboundShipmentPlan = Codec.interface({
ShipmentId: string,
DestinationFulfillmentCenterId: string,
ShipToAddress: AddressFIS,
LabelPrepType: string,
Items: ensureArray('member', InboundShipmentPlanItem),
EstimatedBoxContentsFee: optional(BoxContentsFeeDetails),
})

const CreateInboundShipmentPlan = Codec.interface({
InboundShipmentPlans: ensureArray('member', InboundShipmentPlan),
})

type CreateInboundShipmentPlan = GetInterface<typeof CreateInboundShipmentPlan>

const CreateInboundShipmentPlanResponse = Codec.interface({
CreateInboundShipmentPlanResponse: Codec.interface({
CreateInboundShipmentPlanResult: CreateInboundShipmentPlan,
}),
})

export class FulfillmentInboundShipment {
constructor(private httpClient: HttpClient) {}

async createInboundShipmentPlan(
parameters: CreateInboundShipmentPlanParameters,
): Promise<[CreateInboundShipmentPlan, RequestMeta]> {
const [response, meta] = await this.httpClient.request('POST', {
resource: Resource.FulfillmentInboundShipment,
version: FULFILLMENT_INBOUND_SHIPMENT_API_VERSION,
action: 'CreateInboundShipmentPlan',
parameters: canonicalizeParametersCreateInboundShipmentPlan(parameters),
})

return CreateInboundShipmentPlanResponse.decode(response).caseOf({
Right: (x) => [x.CreateInboundShipmentPlanResponse.CreateInboundShipmentPlanResult, meta],
Left: (error) => {
throw new ParsingError(error)
},
})
}

async getInboundGuidanceForAsin(
parameters: GetInboundGuidanceForASINParameters,
): Promise<[GetInboundGuidanceForASIN, RequestMeta]> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<CreateInboundShipmentPlanResponse xmlns="http://mws.amazonaws.com/FulfillmentInboundShipment/2010-10-01/">
<CreateInboundShipmentPlanResult>
<InboundShipmentPlans>
<member>
<DestinationFulfillmentCenterId>ABE2</DestinationFulfillmentCenterId>
<LabelPrepType>SELLER_LABEL</LabelPrepType>
<ShipmentId>String</ShipmentId>
<DestinationFulfillmentCenterId>String</DestinationFulfillmentCenterId>
<ShipToAddress>
<City>Breinigsville</City>
<CountryCode>US</CountryCode>
<PostalCode>18031</PostalCode>
<Name>Amazon.com</Name>
<AddressLine1>705 Boulder Drive</AddressLine1>
<StateOrProvinceCode>PA</StateOrProvinceCode>
<Name>String</Name>
<AddressLine1>String</AddressLine1>
<AddressLine2>String</AddressLine2>
<DistrictOrCounty>String</DistrictOrCounty>
<City>String</City>
<StateOrProvinceCode>String</StateOrProvinceCode>
<CountryCode>String</CountryCode>
<PostalCode>String</PostalCode>
</ShipToAddress>
<EstimatedBoxContentsFee>
<TotalUnits>10</TotalUnits>
<FeePerUnit>
<CurrencyCode>USD</CurrencyCode>
<Value>0.10</Value>
</FeePerUnit>
<TotalFee>
<CurrencyCode>USD</CurrencyCode>
<Value>10.0</Value>
</TotalFee>
</EstimatedBoxContentsFee>
<LabelPrepType>String</LabelPrepType>
<Items>
<member>
<FulfillmentNetworkSKU>FNSKU00001</FulfillmentNetworkSKU>
<SellerSKU>String</SellerSKU>
<FulfillmentNetworkSKU>String</FulfillmentNetworkSKU>
<Quantity>1</Quantity>
<SellerSKU>SKU00001</SellerSKU>
<PrepDetailsList>
<PrepDetails>
<PrepInstruction>Taping</PrepInstruction>
<PrepOwner>AMAZON</PrepOwner>
<PrepInstruction>String</PrepInstruction>
<PrepOwner>String</PrepOwner>
</PrepDetails>
</PrepDetailsList>
</member>
</Items>
<ShipmentId>FBA0000001</ShipmentId>
<EstimatedBoxContentsFee>
<TotalUnits>1</TotalUnits>
<FeePerUnit>
<CurrencyCode>String</CurrencyCode>
<Value>100</Value>
</FeePerUnit>
<TotalFee>
<CurrencyCode>String</CurrencyCode>
<Value>100</Value>
</TotalFee>
</EstimatedBoxContentsFee>
</member>
</InboundShipmentPlans>
</CreateInboundShipmentPlanResult>
<ResponseMetadata>
<RequestId>babd156d-8b2f-40b1-a770-d117f9ccafef</RequestId>
<RequestId>String</RequestId>
</ResponseMetadata>
</CreateInboundShipmentPlanResponse>
</CreateInboundShipmentPlanResponse>
55 changes: 55 additions & 0 deletions test/unit/__snapshots__/fulfillment-inbound-shipment.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`fulfillmentInboundShipment createInboundShipmentPlan returns inbound shipment plans if succesful 1`] = `
Array [
Object {
"InboundShipmentPlans": Array [
Object {
"DestinationFulfillmentCenterId": "String",
"EstimatedBoxContentsFee": Object {
"FeePerUnit": Object {
"CurrencyCode": "String",
"Value": "100",
},
"TotalFee": Object {
"CurrencyCode": "String",
"Value": "100",
},
"TotalUnits": 1,
},
"Items": Array [
Object {
"FulfillmentNetworkSKU": "String",
"PrepDetailsList": Array [
Object {
"PrepInstruction": "String",
"PrepOwner": "String",
},
],
"Quantity": 1,
"SellerSKU": "String",
},
],
"LabelPrepType": "String",
"ShipToAddress": Object {
"AddressLine1": "String",
"AddressLine2": "String",
"City": "String",
"CountryCode": "String",
"DistrictOrCounty": "String",
"Name": "String",
"PostalCode": "String",
"StateOrProvinceCode": "String",
},
"ShipmentId": "String",
},
],
},
Object {
"quotaMax": 1000,
"quotaRemaining": 999,
"quotaResetOn": 2020-04-06T10:22:23.582Z,
"requestId": "0",
"timestamp": 2020-05-06T09:22:23.582Z,
},
]
`;

exports[`fulfillmentInboundShipment getInboundGuidanceForAsin returns sku inbound guidance list if succesful 1`] = `
Array [
Object {
Expand Down
37 changes: 35 additions & 2 deletions test/unit/fulfillment-inbound-shipment.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
import { ParsingError } from '../../src'
import { createMockHttpClient, mockMwsFail, mockMwsServiceStatus, parsingError } from '../utils'

function mockFunctions() {
/**
* Mock everything in purify-ts, restore it back to normal,
* except for `enumeration` which will be stubbed to accept
* https://github.com/facebook/jest/issues/936#issuecomment-265074320
*/
const original = jest.requireActual('purify-ts')

return {
...original, // Pass down all the exported objects
enumeration: <T extends Record<string, string | number>>(enumeration: T) => {
const enumValues = Object.values(enumeration)

return original.Codec.custom({
decode: (input: string | number) => {
if (typeof input !== 'string' && typeof input !== 'number') {
return original.Left(`Expected enum, received ${input}`)
}

const enumIndex = enumValues.indexOf(input)

return enumIndex !== -1 || input === 'String'
? original.Right((enumValues[enumIndex] as T[keyof T]) || 'String')
: original.Left(`Expected enum, received ${input}`)
},
encode: original.identity,
schema: () => ({ enum: enumValues }),
})
},
}
}
jest.mock('purify-ts', () => mockFunctions())

describe('fulfillmentInboundShipment', () => {
describe('createInboundShipmentPlan', () => {
const mockAddress = {
Expand All @@ -20,14 +53,14 @@ describe('fulfillmentInboundShipment', () => {

const parameters = {
ShipFromAddress: mockAddress,
InboundShipmetPlanRequestItems: [mockInboundShipmentPlanRequestItem],
InboundShipmentPlanRequestItems: [mockInboundShipmentPlanRequestItem],
}

it('returns inbound shipment plans if succesful', async () => {
expect.assertions(1)

const mockCreateInboundShipmentPlan = createMockHttpClient(
'fulfillment_inbound_shipmet_create_inbound_shipment_plan',
'fulfillment_inbound_shipment_create_inbound_shipment_plan',
)

expect(
Expand Down

0 comments on commit 4e67963

Please sign in to comment.