Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Next »

This article contains information about main flows that may be used in scope of web shop integration process.


We have type safe proxies for C#, typescript (JS), Kotlin, swift - this guide is however aimed at developers who do not implement for native IOS, Android or typescript (e.g. php developers).

If you need access to our proxies please contact us

General info

CRS public API is exposed via Swagger by following URL:

{host_url}/swagger/{microservice_name}/ui/index

where {host_url}- URL of CRS instance to integrate (for example, https://prodshadow.cloudretailsystems.dk/),

{microservice_name} - name of microservice, which feature is supposed to be used. In scope of web shop integration following microservice names will be used:

"security",

"inventory",

"financial",

"media",

"actors"

Further I will provide only minimal request body that is needed for specific query/operation, extra request fields are exposed in Swagger UI.

Also it is worth to read following document in order to get short CRS API reference: /wiki/spaces/MBSG/pages/44512

Work with Get[Something]Page requests


in CRS often used way  possibility to fetch some entities page-by-page. All requests for such cases use some generic properties:

[offset] - current page start (for first page - 0),

[count] - current page size,

[includeOverallEntryCount] - optional fields that indicates that along with entities of current page response should contain information about how many entities exists at all for filter and sort info described in request.

Following approach may be used to fetch all entities by filter page-by-page:

  • should be considered  [page_size]  (for example 100).

  • in first products page request [offset] should be 0, [count] should be [page_size] and [includeOverallEntryCount] should be true. Then in response value along with 100 entities of first page in property "entries" will be available property [overallEntryCount]" with entire count of products that can be fetched by this request parameters.

  • For next request [offset] should be amount of already fetched entities, page size should be the same and [includeOverallEntryCount]  should be false because we already know count for example: request 2: [offset] = 1*page_size, [count] = page_size, request 3: [offset] = 2*page_size, [count] = page_size, and so on till all products will be fetched.

I suggest to set [includeOverallEntryCount]  only for request of first page because for other pages with same filter overall count will remains immutable and its' calculation is time-consuming.

This offset/count management approach may be used in all cases when data should be fetched by pages.

Next will be enlisted set of operations that will be needed for web shop integration.

Authentication 

Login/get user token

Word: POST
URL: {host_url}/api/security/Authentication/Login
Minimal Request:

{
	"userNameOrEMail": "your_username",
	"password": "your_password",
	"tenantDomainName": "your_tenant"
}

In case of happy flow response will contain token   
Response:
All CRS responces contains errorOrValue property, which field [error] will be null in happy flow and field value will contain operation result.
In case of successful authentication  response will contain  ["token"] field that need to be used as [Authorization] header of all next requests.

Actors integration

Create actor

Actor creation flow is described in How to create/update Actor

Please note that customer need to have one of the customer roles (that have system name "CustomerPerson" or "CustomerOrganization");
Response will contain list of variants of product.

Get Actor

Word: POST
URL: {host_url}/api/actors/Actor/GetActor
Minimal request:

{  
   {  
      "id":actor_id,
      "propertiesToLoad":  { "value": 11111111111111 }
   }
}

[propertiesToLoad] - flag enumeration with following options:

FieldSet = 1,
WebAddresses = 2,
Emails = 4,
Addresses = 8,
Fields = 16,
Groups = 32,
Phones = 64,
Roles = 128,
User = 256,
Relations = 512,
PaymentInfo = 1024,
WorkingHours = 2048,
ExternalDataRecord = 4096

If all actor properties needed properties to load should be FieldSet | WebAddresses | Emails  and so on  - 1111111111111 in result.

[propertiesToLoad] is an optional property, if it will not be defined, all actor properties will be loaded.

Get actors(paged)

Word: POST
URL: {host_url}/api/actors/actor/GetActorsPage
Minimal request:

{ 
   "sortInfo":[
   ],
   "propertiesToLoad":  11111111111111,
   "offset":0,
   "count":page_size,
   "includeOverallEntryCount":true
}

[propertiesToLoad] - flag enumeration with same options that also used in GetActor call (see previous chapter).

Following optional filters are available and need to be mentioned:

[RoleSystemName]- allows to filter actors by specific role
[SearchPattern] - allows to filter actor whcich person or organization name, phone, email or customer identifier satisfies search pattern
[ModifiedAtFrom] - allows to filter actors modified after specific timestamp
[ModifiedAtTo] - allows to filter actors modified before specific timestamp


Examples:

Get customers page:

{
   "sortInfo":[
   ],
   "propertiesToLoad":  111111111111,
   "offset":0,
   "count":50,
   "includeOverallEntryCount":true,
   "roleSystemName":{
       "value":"Customer"
    }
}


Get suppliers page:

{
   "sortInfo":[
   ],
   "propertiesToLoad":  111111111111,
   "offset":0,
   "count":50,
   "includeOverallEntryCount":true,
   "roleSystemName":{
       "value":"Supplier"
    }
}


Get page of actors by search pattern that were modified in date range:

{
    "sortInfo": [],
    "propertiesToLoad": 1111111111111,
    "offset": 0,
    "count": 50,
    "includeOverallEntryCount": true,
    "modifiedAtFrom": {
        "value": "2020-01-01T18:25:43.511Z"
    },
    "modifiedAtTo": {
        "value": "2021-01-01T18:25:43.511Z"
    },
    "searchPattern": {
        "value": "aa"
    }
}


Inventory integration 

Get product categories 

Word: POST
URL: {host_url}/api/inventory/ProductCategory/GetProductCategories
Minimal request:

{
}

Response will contain list of all product categories.

Get products (paged)

Word: POST
URL: {host_url}/api/inventory/Product/GetProductPage

Detailed usage of GetProductPage method is described in  GetProductPage usage document. Please read this document first.

Minimal request:


{
	"propertiesToLoad": {
		"value": 1111111111111
	},
	"offset": 0,
	"count": page_size,
	"includeOverallEntryCount": true,
	"modifiedAfter": {
		"value": "2017-09-26T12:52:29.960Z"
	}
}

[modifiedAfter] indicate that we need all products that were modified after specific date. In case when we are need all products - this field should be null.

Each product have properties [isProductInstancesSupported][isProductVariantsSupported], and [productImages] that need to be analyzed in order to fetch additional product data.

If [isProductInstancesSupported]  is true, variants for products need to be fetched,
If [isProductVariantsSupported]  is true, product instances need to be fetched,
If collection of [productImages] is not empty - product image files may be fetched.

Get product variants

Word: POST
URL: {host_url}/api/inventory/ProductVariant/GetProductVariants
Minimal request:

{
	"productId": {
		"value": product_id
	}
}

Response will contain list of variants of product.

Please note that product variant entity does not contain overridable properties  [isEnabled] and [CostPrice] and property [SalesPrice] of ProductVariant entity does does not contain overridable value for current stock organizational unit, it contains root sales price value.

To get actual values of  [isEnabled] and [CostPrice] and [SalesPrice] properties of variants need to be considered ProductVariantInfo entity

Get product variant infos

Product variant in

Word: POST
URL: {host_url}/api/inventory/ProductVariant/GetProductVariantInfos
Minimal request:

{
	"productId": {
		"value": product_id
	}
}

Response will contain list of variants of product in ProductVariantInfo format.

Also may be specified parameter [OrganizationUnitId] to be sure that  [isEnabled] and [CostPrice] and [SalesPrice] overridable properties will be got for specific organizational unit. In case when [OrganizationUnitId]  is not defined, overridable properties will be filled for Organizational unit to which user is logged in.

Get product variant infos (paged)

Word: POS
URL: {host_url}/api/inventory/ProductInstance/GetProductVariantInfoPage
Minimal request:

{
	"offset": 0,
	"count": page_size,
	"includeOverallEntryCount": true
}

Response will contain pages with product product variants according to filter.

[productIds] and [modifiedAfter] may be specified in order to narrow paged result.

Get product instances (paged)

Word: POS
URL: {host_url}/api/inventory/ProductInstance/GetProductInstancePage
Minimal request:

{
	"productIds": {
		"value": [
			product_id
		]
	},
	"offset": 0,
	"count": page_size,
	"includeOverallEntryCount": true
}

Response will contain pages with product instances according to filter. Multiple products may be enlisted in request.

Get product /product variant image

Product and ProductVariant entities contains collection named ProductImages.
Each product image along with other properties contain property [mediaItemId] using which product image can be fetched from media storage

First need to be fetched media item information:

Word: POST
URL: {host_url}/api/media/MediaItem/GetMediaItem

Minimal request:

{
	"id": media_item_id
}


Response will contain information about mime type, file name and extension, creation information, file name etc.

Then need to be fetched media item itself:

Word: GET
URL: {host_url}/api/media/MediaItem/GetMediaItemStream
Parameters: Id:mediaItemId

Response will contain media item stream.

Get product availability


Word: POST
URL: {host_url}/api/inventory/StockEntry/GetProductAvailability
Minimal request:

{
	"productIdentifier": {
		"productId": product_id,
	},
	"organizationalUnitId": {
		"value": organizational_unit_id
	}
}

Response will be list of availability records for product in selected organizational unit.
In [productIdentifier] parameter may be also set [variantId] and [instanceId] (if product support variants and instances and we need to know availability for specific variant and instance).

In case when [instanceId] is not set- in result will be availability rows for all instances for product/variant combination.
In case when [variantId] is not set - in result will be availability rows for all variants /instances for product.

Get product availability (paged)


Word: POST
URL: {host_url}/api/inventory/StockEntry/GetProductAvalibilityPage
Minimal request:

{
  "organizationalUnitId": organizational_unit_id,
  "offset": 0,
  "count": page_size,
  "includeOverallEntryCount": true
}

 [updatedAtFrom] and [updatedAtTo] may be specified to retrieve product availability results for products which availability was changed within specific time interval.

organizational_unit_id is organizational unit id (Root organizational unit id = -1) for which availability need to be fetched.

[productIds] and [categoryIds] may be specified in order to narrow paged result for specific product categories or products.

Paged result will contain page of entities of type GetProductAvailabilityResult each of them will contain availability items for specific organizational unit in Items collection and StockItem property in which ProductId is product Id and properties VariantId and InstanceId will be always null for this request.

Please note following:

a) For products that supports variants and instances availability items in response will be grouped by product so GetProductAvailabilityResult  will contain availability entries for all variants and all instance of specific product in Items  collection and they need to be grouped by variant and instance on client side in case of need.

b) In case when  [updatedAtFrom] and [updatedAtTo] is specified, will be returned availability for products which availability is changed within time interval but will be presented all availability records, not change (delta) rows. In other worls [updatedAtFrom] and [updatedAtTo]  need to be used to narrow the range of products for which availability will be fetched but always all availability rows for product will be returned.

Get stock entries (paged)


Word: POST
URL: {host_url}/api/inventory/StockEntry/GetStockEntryPage
Minimal request:

{
	"offset": 0,
	"count": page_size,
	"includeOverallEntryCount": true
}

May also be specified product/variant/instance id and organizational_unit_id in order to narrow results.

 [updatedAtFrom] and [updatedAtTo] may be specified to retrieve stock stock entries created or updated within specific time interval.


Missing/planning features :

  • filter [createdAtFrom] in order to get entries created after specific date

  • filter [productCategoryIds] in order to get entries for products of specific category

  • filters [productId], [variantId] and [instanceId] should be plural.


Stock integration

General information

It is may be needed to add/remove products from stock directly. Common flow to do it is :

  • select stock draft;

  • create stock appendix for this stock draft;

  • add set of stock draft entries to created stock appendix;

  • Book stock draft (in case when stock draft have IsAutoBookPossible = true, booking will be performed on moment of stock draft entries creation);

  • Ensure that booking was ended with success.

Get existing stock drafts

Word: POST
URL: {host_url}/api/inventory/StockDraft/GetAllStockDraftsDescriptors
Minimal request:

{
}

Method returns list of descriptors of existing stock drafts. Each descriptor have an Id property that may be used to create an appendix based on stock draft

Create stock appendix

Word: POST
URL: {host_url}/api/inventory/StockAppendicies/CreateStockAppendix

Minimal request:

{
  "stockDraftId": 1,
}

Extended request example:

{
  "stockDraftId": 1,
  "description" -- optional: {
    "value": "my appendix"
  },
  "stockAppendixSource" -- optional: {
    "value": {
      "sourceType": "invoice",
      "sourceId": {
        "value": [my_invoice_id]
      },
      "sourceIdentifier": {
        "value": [my_invoice_identifier]
      },
      "alternativeAppendixNumber": {
        "value": [invoice_identifier for example]
      }
    }
  },
  "userDefinedAppendixNumber": -- optional:{
    "value": [my_invoice_identifier]
  }
}

It is possible to mention that stock appendix was created based on invoice, sales order or other entity not from inventory by specifying stockAppendixSource in create command.

Also in some cases it is handy to specify userDefinedAppendixNumber to create appendix with custom number instead of auto-generated.

Please note that userDefinedAppendixNumber should not be in range of any of  stock appendix number series in this metadata record.

Method returns structure with created appendix Id and appendix number. Appendix Id will be used in next methods to create stock draft entries.

Create stock draft entries

Word: POST
URL: {host_url}/api/inventory/StockDraftEntry/CreateStockDraftEntries
Minimal request:

{
  "stockAppendixId": [stock appendix id of previous call result],
  "stockDraftEntrySources": [collection of stock draft entry sources.
  ]
}

Method allows to create multiple stock draft entries. Request property stockDraftEntrySources should contain a collection of StockDraftEntrySource objects of following format:

{
  "stockAppendixId": [stock appendix ,
  "stockDraftEntrySources": [
    {
      "customerActorId" --optional: {
        "value": [my customer actor id]
      },
      "supplierActorId": --optional{
        "value": [my supplier actor id]
      },
      "description": --optional{
        "value": "[my stock draft entry description]"
      },
      "costPrice": --optional{
        "value": [my cost price]
      },
      "salesPrice": --optional{
        "value": [my sales price]
      },
      "source": --optional{
        "value": {
          "kind": "PurchaseDelivery = 1",
          "identifier": "string"
        }
      },
      "organizationalUnitId": -1,
      "stockDate": "2020-06-26T06:04:13.637Z",
      "productId": 1,
      "variantId": [may be null],
      "count": 1,
      "instanceIds": --optional {
        "value": [
         	1
        ]
      },
      "productLocationId": 1,
      "externalReference": --optional{
        "value": {
          "sourceType": "[invoice for example]",
          "sourceId": [invoice id for example]
        }
      },
      "creditNoteExternalReference": --optional: {
        "value": {
          "sourceType": "string",
          "sourceId": 0
        }
      }
    }
  ]
}

Response of this method will contain references for created stock draft entries with their ids.

Some of stock drafts have a property IsAutoBookPossible = true. In this case created stock draft entries will be booked just after creation and response will also contain Book operation result. In "Start booking of stock draft entries" and "Checking stock draft booking operation state" sections below is described how to analyze booking operation result. 

Get stock draft entries (paged)

Word: POST
URL: {host_url}/api/inventory/StockDraftEntry/GetStockDraftEntriesPage
Minimal request:

{
  "stockAppendixId": 0,
  "offset": 0,
  "count": 10,
  "includeOverallEntryCount": false
}

Extended request example:

{
  "stockDraftId": 1,
  "productId": {
    "value": [product id filter]"
  },
  "description": {
    "value": "[description filter]"
  },
  "costPrice": {
    "value": {
      "from": [cost price filter min],
      "to": [cost price filter max]
    }
  },
  "productCategoryIds": {
    "value": [
      product category ids to filter
    ]
  },
  "productLocationIds": {
    "value": [
      product location ids to filter
    ]
  },
  "organizationalUnitIds": {
    "value": [
      organizational unit ids to filter
    ]
  },
  "calculateTotalCostPrice": {
    "value": true
  },
  "offset": 0,
  "count": 10,
  "includeOverallEntryCount": true
}


Method allows to fetch stock draft entries of stock draft page by page. Various filters may by be specified

Delete stock draft entry

Word: POST
URL: {host_url}/api/inventory/StockDraftEntry/RemoveStockDraftEntry
Minimal request:

{
  "stockDraftEntryId": 1
}

Method allows to delete stock draft entry by Id

Delete all stock draft entries from stock draft

Word: POST
URL: {host_url}/api/inventory/StockDraft/ClearStockDraft
Minimal request:

{
  "stockDraftId": 1
}

Method allows to delete all stock draft entries from stock draft.

Start booking of stock draft entries

Word: POST
URL: {host_url}/api/inventory/StockDraft/BookStockDraftEntries
Minimal request:

{
  "stockDraftId": 1
}

Extended request example:

{
  "stockDraftId": 1,
  "productCategoryIds": {
    "value": [
      0
    ]
  },
  "productLocationIds": {
    "value": [
      0
    ]
  },
  "organizationalUnitIds": {
    "value": [
      0
    ]
  }
}

Method is intended to start stock draft booking operation. In response will be following structure:

"requisitionId": 1,
      "state": {
        "state": "1",
        "resultCode": "1",
        "faultedAppendixNo": 0,
        "htmlErrorMessage": "string"
      }

where requisitionId property contains the created stock booking operation requisition using which  it is possible to track booking operation state. 

Property [state]  contains state of booking operation at current moment. It is also contains property named  [state], which is enum of type BookingOperationRequisitionStateKind with following values:

  • InProgress = 1

  • Success = 2

  • Failed = 3,

Checking stock draft booking operation state 

Word: POST
URL: {host_url}/api/inventory/StockDraft/GetStockBookingOperationState
Minimal request:

{
  "id": [requistion id of previous call result]
}

Method allows to check state of stock draft booking operation. As Id should be used requisitionId of result of BookStockDraftEntries call. Response of method have following structure:

{
  "errorOrValue": {
    "value": {
      "resultCode": "11",
      "value": {
        "state": "1",
        "resultCode": "1",
        "faultedAppendixNo": 0,
        "htmlErrorMessage": "error_text"
      }
    }
  }
}

 To make sure that operation was ended well, after BookStockDraftEntries  need to be organized poling of GetStockBookingOperationState till [state] property of result will contain   BookingOperationRequisitionStateKind.Success  or BookingOperationRequisitionStateKind.Failed. In case of BookingOperationRequisitionStateKind.Failed [htmlErrorMessage] property need to be analyzed to get a clue about fail reason.


Financial integration 

General information

In this section will be described integration with financial microservice - creating and processing of sales order and invoice.

In order to create and process sales order following steps need to be performed: 

  • Create sales order,

  • Set sales order as ready.

  • Create Invoice (or set of invoices) based on sales order,

  • Book invoice.

In order to create and process invoice - following steps need to be performed: 

  • Create blank invoice,

  • Book invoice.

Get sales orders (paged)

Word: POST
URL: {host_url}/api/financial/SalesOrder/GetSalesOrdersPage

Minimal request:

{
	"offset": 0,
	"count": page_size,
	"includeOverallEntryCount": true
}

Set of properties of sales order which will be included in response may be defined in propertiesToLoad property of request. This is a flag enum of type SalesOrderProperties with following values:

  • Basic = 0

  • SalesOrderItems = 1

  • Tasks = 2

  • ConvertRecord = 4

  • ConvertSource = 8

  • Type = 16

  • EavFieldValues = 32

  • SalesOrderItemState = 64

  • SalesOrderItemDiscounts = 128

  • TypeDetails = 256

Flags may be combined by “|” operator in order to fetch more then one property.

So minimal request to specify sales order properties to load will be

{
   "propertiesToLoad":{
      "value":32 // Eav field values in this case
   },
   "offset":0,
   "count":10,
}

Following optional filters are available and need to be mentioned:

[createdAt]- filter by creation date;

[updatedAt]- filter by update date;

[numericIdentifier]- filter by numeric identifier range;

[salesOrderStates]- filter by states;

[salesOrderDispatchStates]- filter by dispatch states.

Minimal request which utilizes these filters will be:

{
   "offset":0,
   "count":10,
   "createdAt":{
      "value":{
         "from":"2021-09-07T00:00:00+03:00",
         "to":"2021-09-07T23:59:59+03:00"
      }
   },

   "updatedAt":{
      "value":{
         "from":"2021-09-07T00:00:00+03:00",
         "to":"2021-09-07T23:59:59+03:00"
      }
   },

   "numericIdentifier":{
      "value":{
         "from":100,
         "to":200
      }
   },
   "salesOrderStates":{
      "value":[
         1
      ]
   },
   "salesOrderDispatchStates":{
      "value":[
         3
      ]
   }
}

Create sales order

To create sales order following data is need to be collected to create it:

  • Customer information -  customer should be created/selected in Actors micro service

  • Contact information - optional ( address, email, phone). Contact information is supposed to be entered by user in web shop but can be defaulted by customer data,

  • Sale items information.

Get product financial and VAT data

To compose sales order item along with data presented on product also need to be provided VAT data from VAT type. VAT type to be taken from financial account.

Financial account need to be taken from accounting microservice by [account_number]

In default case - [account_number] = [SalesAccountNumber] taken from product's category

If product instance is specified and it is marked as used - [account_number] = [UsedSalesAccountNumber] taken from product's category

Product category financial accounts data is depends on Stock Organizational Unit in which Web Shop operates and may be retrieved from product (which for example retrieved by GetProductPage method with Category included) by following way:

Product.Category.OverridableProperties.FirstOrDefault(it=>it.OrganizationalUnitId ==StockOrganizationalUnitId).Item

Item is an instance of tyoe ProductCategoryProps which have following properties:

SalesAccountNumber, 
SalesAccountWithoutVatNumber,
DiscountAccountNumber,
UsedSalesAccountNumber,
AdditionStockAccountNumber,
SubstractionStockAccountNumber,
UsedAdditionStockAccountNumber,
UsedSubstractionStockAccountNumber,
CostOfSalesAccountNumber,
UsedCostOfSalesAccountNumber

And among them are presented properties [SalesAccountNumber] and [UsedSalesAccountNumber] that is needed to continue.

To get financial account by number please use:

Word: POST
URL: {host_url}/api/accounting/FinancialAccount/GetFinancialAccountByNumber
Minimal request:

{  
   {  
      "number":account_number
   }
}


Account may have or may not have VAT type.

Create sales order


Before create of sales order we need to have:

  • [customer] - taken from actor microservice

  • [address] (optional) - user input, defaulted by actor

  • [email] (optional)- user input, defaulted by actor

  • [phone] (optional)- user input, defaulted by actor

To compose sales order item we need to have:

  • [product]

  • [product_category]

  • [product_variant] if product supports variants - variant is required

  • [product_instance] if product supports instances - instance is required

  • [financial_account_number]- taken by rules described above

  • [vat_type] - taken from accounting microservice by financial account number

  • [sales_price] - calculated by following rule: If product instance is selected and its sales price is not null- product instance's sales price. If not- check variant. If variant is selected and its sales price is not null- variant's sales price. If sales price still not calculated - should be used sales price from product

  • [quantity] - user input. Please note that quantity should be 1 for sales order items where product instance is defined

  • [manual_discount] - user input. Sale item amount will be calculated as [sales_price] * [quantity]. If it is needed to register sale item with different amount - [manual_discount] should be specified
    All enlisted entities will be used in example below.

  • [cost_price] - the rule is:

    • take cost price from instance, if any

    • take cost price from variant, if any

    • take cost price from product, if it is null and product.isDefaultCostPriceApplicable = true then product.defaultCostPrice should be used


Word: POST
URL: {host_url}/api/financial/SalesOrder/CreateSalesOrder

Command contain collection [modelUpdates] and all sale manipulation should be done via model updates.
Model update type can be specified by placing it's name to [modelType] property.
Following model updates are supported for invoice creation:

ChangeSalesOrderModelUpdate - to manage sales order properties,
CreateSalesOrderItemModelUpdate - to create sales order item,
ChangeSalesOrderItemModelUpdate - to change sales order item,
DeleteSalesOrderItemsModelUpdate - to delete sales order item
SetSalesOrderCustomerDataModelUpdate - to specify actor and contact information.
There may be many model updates of any type in command.

Here are these model updates in detail:

IChangeSalesOrderModelUpdate {
	public static readonly modelTypeName: string = "ChangeSalesOrderModelUpdate";
	customerReferenceNumber: IOptional<string>|undefined;
	internalNoteSource: IOptional<string>|undefined;
	invoiceDate: IOptional<Date|null>|undefined;
	isCostPriceOrder: IOptional<boolean>|undefined;
	isExemptVat: IOptional<boolean>|undefined;
	isPaymentTermChangedManually: IOptional<boolean>|undefined;
	maxTotal: IOptional<number|null>|undefined;
	organizationalUnitId: IOptional<number>|undefined;
	otherPayer: IOptional<IActorDescriptor>|undefined;
	parentSalesOrder: IOptional<IParentSalesOrder>|undefined;
	paymentTerm: IOptional<IPaymentTermInfo>|undefined;
	publicNoteSource: IOptional<string>|undefined;
	requisitionNumber: IOptional<string>|undefined;
	salesOrderTypeId: IOptional<number>|undefined;
	shipmondoShipmentId: IOptional<number|null>|undefined;
	subject: IOptional<string>|undefined;
	modelType = ChangeSalesOrderModelUpdate.modelTypeName;
}

ICreateSalesOrderItemModelUpdate {
	public static readonly modelTypeName: string = "CreateSalesOrderItemModelUpdate";
	calculatedDiscount: IOptional<ISaleItemCalculatedDiscountModel>|undefined;
	contraAccountSettings: IOptional<IContraAccountSettings>|undefined;
	financialData: ISalesItemFinancialData;
	fixedPriceGroup: IOptional<IFixedPriceGroup>|undefined;
	internalNote: IOptional<string>|undefined;
	inventoryItemData: ISalesItemInventoryData;
	isExemptVat: IOptional<boolean>|undefined;
	isFinancialDataChangedManually: IOptional<boolean>|undefined;
	isPriceChangedManually: IOptional<boolean>|undefined;
	isProductInstancePurchase: IOptional<boolean>|undefined;
	location: IOptional<IStockLocation>|undefined;
	manualDiscountAmount: number;
	order: IOptional<number>|undefined;
	price: number;
	publicNote: IOptional<string>|undefined;
	quantity: number;
	relatedItem: IOptional<IRelatedItemData>|undefined;
	stateId: IOptional<number|null>|undefined;
	uuid: IOptional<string>|undefined;
	modelType = CreateSalesOrderItemModelUpdate.modelTypeName;
}

 IChangeSalesOrderItemModelUpdate {
	public static readonly modelTypeName: string = "ChangeSalesOrderItemModelUpdate";
	calculatedDiscount: IOptional<ISaleItemCalculatedDiscountModel>|undefined;
	contraAccountSettings: IOptional<IContraAccountSettings>|undefined;
	displayName: IOptional<string>|undefined;
	financialData: IOptional<ISalesItemFinancialData>|undefined;
	fixedPriceGroup: IOptional<IFixedPriceGroup>|undefined;
	internalNote: IOptional<string>|undefined;
	inventoryItemData: IOptional<ISalesItemInventoryData>|undefined;
	isExemptVat: IOptional<boolean>|undefined;
	isFinancialDataChangedManually: IOptional<boolean>|undefined;
	isPriceChangedManually: IOptional<boolean>|undefined;
	isProductInstancePurchase: IOptional<boolean>|undefined;
	itemId: number;
	location: IOptional<IStockLocation>|undefined;
	manualDiscountAmount: IOptional<number>|undefined;
	order: IOptional<number>|undefined;
	price: IOptional<number>|undefined;
	publicNote: IOptional<string>|undefined;
	quantity: IOptional<number>|undefined;
	relatedItem: IOptional<IRelatedItemData>|undefined;
	stateId: IOptional<number|null>|undefined;
	modelType = ChangeSalesOrderItemModelUpdate.modelTypeName;
}

IDeleteSalesOrderItemsModelUpdate {
	public static readonly modelTypeName: string = "DeleteSalesOrderItemsModelUpdate";
	idsToDelete: number[] = [];
	modelType = DeleteSalesOrderItemsModelUpdate.modelTypeName;
}

ISetSalesOrderCustomerDataModelUpdate {
	actor?: IOptional<IActorDescriptor>;
	address?: IOptional<IAddressDescriptor>;
	email?: IOptional<string>;
	phone?: IOptional<string>;
}


ISetSalesOrderCustomerDataModelUpdate {
	public static readonly modelTypeName: string = "SetSalesOrderCustomerDataModelUpdate";
	actor: IOptional<IActorDescriptor>|undefined;
	address: IOptional<IAddressDescriptor>|undefined;
	email: IOptional<string>|undefined;
	phone: IOptional<string>|undefined;
	modelType = SetSalesOrderCustomerDataModelUpdate.modelTypeName;
}

Note 1 : Sales orders supports EAV fields and EAV field-related model updates may be used in Create/Update sales order command to set sales order-specific EAV field values. These model updates are described in following document : How to manage EAV fields

Note 2 : Grouped products need custom handling. If it needed to add SalesOrderItem with grouped product to SalesOrder and it is expected that this item will be split to composed product parts, this iten meed to be added not via ChangeSalesOrderItemModelUpdate but via AddProductToSalesOrder command which is described below.

Example:


{  
   "modelUpdates":[  
      {  
         "modelType":"CreateSalesOrderItemModelUpdate",
         "financialData":{  
            "priceIncludesVat" // should always be true,
            "saleFinancialAccountNumber" // [financial_account_number],
            "vatAccountNumber" // should be taken from [vat_type] if presented, else - undefined
            "vatRate" // should be taken from [vat_type] if presented, else - undefined
            "discountAccountNumber" // should be taken from [vat_type] if presented, else - undefined
            "usedVatFinancialAccountNumber" // should be taken from [vat_type] else - undefined
            "usedVatRate" // should be taken from [vat_type] if presented, else - undefined
         },
         "manualDiscountAmount" // [manual_discount]
         "quantity" // [quantity],
         "inventoryItemData":{  
            "displayName" // from [product]
            "productId" // from [product]
            "variantId" // from [product_instance] if presented, , else - undefined,
            "instanceId" // from [product_instance] if presented, else - undefined
            "productIdentifier" // from [product_variant]
            "isUserDefinedPriceAllowed // from [product]
            "isCustomDisplayNameAllowed // from [product]
            "costPrice" // [cost_price]
            "isUsed"from [product_instance] if presented, else - undefined
            "purchasePrice" //from [product_instance] if presented, else - undefined
         },
         "price" - [sales_price]
      },
      {  
         "modelType":"SetSalesOrderCustomerDataModelUpdate",
         "actor":{ // this entire block may be undefined if [customer] is not defined 
            "value":{  
               "actorId"// from [customer]
               "fullName"// from [customer]
               "identification// from [customer]
            }
         },
         "address":{  // this entire block may be undefined if [address] is not defined 
            "value":{  
               "addressLine1" // from [address], required
               "addressLine2" // from [address]
               "postalCode" // from [address], required
               "postalDistrict" // from [address]
               "country" // from [address], required
               "latitude" // from [address]
               "longitude" // from [address]
            }
         },
         "email":{   // this entire block may be undefined if [email] is not defined 
            "value" // [email] itself
         },
         "phone":{  // this entire block may be undefined if [phone]  is not defined 
            "value" // [phone] itself
         }
      }
   ],
   "modelType":"CreateSalesOrderCommand"
}

Note: To take customer identifier from customer actor you should get first eav field from [eavFields] collection with eavFieldValue.field.systemName == 'CustomerIdentifier' and get its stringValue


Example with real data based on which sales order may be created in test tenant:

{  
   "modelUpdates":[  
      {  
         "modelType":"CreateSalesOrderItemModelUpdate",
         "financialData":{  
            "priceIncludesVat":true,
            "saleFinancialAccountNumber":1100,
            "vatAccountNumber":14262,
            "vatRate":25,
            "discountAccountNumber":7200,
            "usedVatFinancialAccountNumber":null,
            "usedVatRate":null
         },
         "manualDiscountAmount":300.14,
         "quantity":1,
         "inventoryItemData":{  
            "displayName":"Sony Playstation 4 (cf18-4136-9f92-31764c4)",
            "productId":106,
            "variantId":null,
            "instanceId":165,
            "productIdentifier":"ID_108",
            "isUserDefinedPriceAllowed":false,
            "isCustomDisplayNameAllowed":false,
            "costPrice":3000,
            "isUsed":false,
            "purchasePrice":null
         },
         "price":3001.45
      },
      {  
         "modelType":"SetSalesOrderCustomerDataModelUpdate",
         "actor":{  
            "value":{  
               "actorId":8,
               "fullName":"0000100001 a_person Customer",
               "identification":"0000100001"
            }
         },
         "address":{  
            "value":{  
               "addressLine1":"myStreet",
               "addressLine2":"222",
               "postalCode":"2100",
               "postalDistrict":"København Ø",
               "country":"Denmark",
               "latitude":null,
               "longitude":null,
            }
         },
         "email":{  
            "value":"example@gmail.com"
         },
         "phone":{  
            "value":"77777777"
         }
      }
   ],
   "modelType":"CreateSalesOrderCommand"
}


In response sales order Id will be presented.

Advanced flows:

  1. Some customers need to continue modifying sales order even after it is ready or invoiced (e.g. to add work hours to sales order and invoice it separately.). In this case sapera can be told that sales order should support such a flow by setting AllowModifyingWhenReady = {value: true} at creation time in root of CreateSalesOrderCommand body. When it is set - client app will ask at invoice time if sales order should be completed. User can choose to complete

Update Sales Order

It is possible to update sales order after creation (add or change customer data or contact info, add, change or remove sale items). All model updates described above that are used in CreateSalesOrderCommand may be used in UpdateSalesOrderCommand, they may be reviewed in Swagger.

Word: POST

URL: {host_url}/api/financial/SalesOrder/UpdateSalesOrder

Example body

{
   "modelUpdates":[
      {
         "modelType":"ChangeSalesOrderItemModelUpdate",
         "price":{
            "value":12000
         },
         "isPriceChangedManually":{
            "value":true
         },
         "isProductInstancePurchase":{
            "value":false
         },
         "itemId":17286
      },
      {
         "modelType":"ChangeSalesOrderItemModelUpdate",
         "price":{
            "value":250
         },
         "isPriceChangedManually":{
            "value":true
         },
         "isProductInstancePurchase":{
            "value":false
         },
         "itemId":17287
      },
      {
         "modelType":"SetSalesOrderCustomerDataModelUpdate",
         "actor":{
            "value":{
               "actorId":259,
               "fullName":"Test actor full name",
               "identification":"10000000",
               "organizationIdentifier":"10000000"
            }
         },
         "address":{
            "value":{
               "id":204,
               "addressLine1":"TestAddressLine1",
               "addressLine2":null,
               "postalCode":"1111",
               "postalDistrict":"TestPostalDistict",
               "country":null,
               "latitude":null,
               "longitude":null,
               "addressType":{
                  "id":1,
                  "systemName":"Business",
                  "displayName":"Firma",
                  "isSystem":true
               }
            }
         },
         "email":{
            "value":"test_email@email.dk"
         },
         "phone":{
            
         }
      },
      {
         "modelType":"SetDateTimeEavValueModelUpdate",
         "fieldSystemName":"LeveringDato",
         "value":"2021-09-14T00:00:00+03:00"
      }
   ],
   "modelType":"UpdateSalesOrderCommand",
   "entityId":1115
}

Add product to sales order

Word: POST
URL: {host_url}/api/financial/SalesOrder/AddProductToSalesOrder
Request:

{
  	"salesOrderId": sales order id,
  	"product": { 
    	"kind": product descriptor kind, use 1 for Id, 2 for identifier 
		"id": { // this block should be set only if kind = 1
			"value": "1244"
		},
		"identifier": { // this block should be set only if kind = 2
			"value": "ZXC PRODUCT"
		}
	},
	"variantId": { // this block can be undefined, if no variant is relevant
		"value": "213"
	},
	"organizationalUnitId": { // this block can be undefined, if item(s) should follow sales order's OU
		"value": "1213213"
	},
	"quantity": { // this block can be undefined, 1 is used by default
		"value": "2"
	},
}

Example with test data:

{
	"salesOrderId": 999,
  	"product": { 
    	"kind": 2,
		"identifier": { "value": "GROUPED_PRODUCT_IDENTIFIER" }
	}
}

Set Sales order as ready

Word: POST
URL: {host_url}/api/financial/SalesOrder/SetSalesOrderReady
Minimal request:

{
  "salesOrderId": sales order id
}

Create Invoice

Invoice is created based on sales order and its items or blank.

Create blank invoice

Before create of invoice we need to have:

  • [customer] - retrieved from actors microservice based on [invoice] customer's data

  • [address] (optional) - user input, defaulted by actor

  • [email] (optional)- user input, defaulted by actor

  • [phone] (optional)- user input, defaulted by actor

To compose sales order item we need to have:

  • [product]

  • [product_category]

  • [product_variant] if product supports variants - variant is required

  • [product_instance] if product supports instances - instance is required

  • [financial_account_number] - taken by rules described above ([Get product financial and VAT data])

  • [vat_type] - taken from accounting microservice by financial account number

  • [sales_price] - calculated by following rule: If product instance is selected and its sales price is not null- product instance's sales price. If not- check variant. If variant is selected and its sales price is not null- variant's sales price. If sales price still not calculated - should be used sales price from product

  • [quantity] - user input. Please note that quantity should be 1 for sales order items where product instance is defined

  • [manual_discount] - user input. Sale item amount will be calculated as [sales_price] * [quantity]. If it is needed to register sale item with different amount - [manual_discount] should be specified
    All enlisted entities will be used in example below.

For create invoice use following:

Word: POST
URL: {host_url}/api/financial/Invoice/CreateInvoiceAndReserve

Command contain collection [modelUpdates] and all invoice manipulation should be done via model updates.

Model update type can be specified by placing it's name to [modelType] property.
Following model updates are supported for invoice creation:

  • CreateInvoiceItemModelUpdate - to create sales order item,

  • ChangeInvoiceItemModelUpdate - to change sales order item,

  • DeleteInvoiceItemsModelUpdate - to delete sales order item

  • SetInvoiceCustomerDataModelUpdate - to specify actor and contact information.

There may be many model updates of any type in command.

Example:

{  
   "modelUpdates":[  
      {  
         "modelType":"CreateInvoiceItemModelUpdate",
         "financialData":{  
            "priceIncludesVat" // should always be true,
            "saleFinancialAccountNumber" // [financial_account_number],
            "vatAccountNumber" // should be taken from [vat_type] if presented, else - undefined
            "vatRate" // should be taken from [vat_type] if presented, else - undefined
            "discountAccountNumber" // should be taken from [vat_type] if presented, else - undefined
            "usedVatFinancialAccountNumber" // should be taken from [vat_type] else - undefined
            "usedVatRate" // should be taken from [vat_type] if presented, else - undefined
         },
         "manualDiscountAmount" // [manual_discount]
         "quantity" // [quantity],
         "inventoryItemData":{  
            "displayName" // from [product]
            "productId" // from [product]
            "variantId" // from [product_instance] if presented, , else - undefined,
            "instanceId" // from [product_instance] if presented, else - undefined
            "productIdentifier" // from [product_variant]
            "isUserDefinedPriceAllowed // from [product]
            "isCustomDisplayNameAllowed // from [product]
            "costPrice" // from [product_instance] if presented, else - undefined
            "isUsed"from [product_instance] if presented, else - undefined
            "purchasePrice" //from [product_instance] if presented, else - undefined
         },
         "price" - [sales_price]
      },
      {  
         "modelType":"SetInvoiceCustomerDataModelUpdate",
         "actor":{ // this entire block may be undefined if [customer] is not defined 
            "value":{  
               "actorId"// from [customer]
               "fullName"// from [customer]
               "identification// from [customer]
            }
         },
         "address":{  // this entire block may be undefined if [address] is not defined 
            "value":{  
               "addressLine1" // from [address], required
               "addressLine2" // from [address]
               "postalCode" // from [address], required
               "postalDistrict" // from [address]
               "country" // from [address], required
               "latitude" // from [address]
               "longitude" // from [address]
            }
         },
         "email":{   // this entire block may be undefined if [email] is not defined 
            "value" // [email] itself
         },
         "phone":{  // this entire block may be undefined if [phone]  is not defined 
            "value" // [phone] itself
         }
      }
   ],
   "modelType":"CreateInvoiceAndReserveCommand"
}

Example with real data based on which sales order may be created in test tenant:

{  
   "modelUpdates":[  
      {  
         "modelType":"CreateInvoiceItemModelUpdate",
         "financialData":{  
            "priceIncludesVat":true,
            "saleFinancialAccountNumber":1100,
            "vatAccountNumber":14262,
            "vatRate":25,
            "discountAccountNumber":7200,
            "usedVatFinancialAccountNumber":null,
            "usedVatRate":null
         },
         "manualDiscountAmount":300.14,
         "quantity":1,
         "inventoryItemData":{  
            "displayName":"Sony Playstation 4 (cf18-4136-9f92-31764c4)",
            "productId":106,
            "variantId":null,
            "instanceId":165,
            "productIdentifier":"ID_108",
            "isUserDefinedPriceAllowed":false,
            "isCustomDisplayNameAllowed":false,
            "costPrice":3000,
            "isUsed":false,
            "purchasePrice":null
         },
         "price":3001.45
      },
      {  
         "modelType":"SetInvoiceCustomerDataModelUpdate",
         "actor":{  
            "value":{  
               "actorId":8,
               "fullName":"0000100001 a_person Customer",
               "identification":"0000100001"
            }
         },
         "address":{  
            "value":{  
               "addressLine1":"myStreet",
               "addressLine2":"222",
               "postalCode":"2100",
               "postalDistrict":"København Ø",
               "country":"Denmark",
               "latitude":null,
               "longitude":null,
            }
         },
         "email":{  
            "value":"example@gmail.com"
         },
         "phone":{  
            "value":"77777777"
         }
      }
   ],
   "modelType":"CreateInvoiceAndReserveCommand"
}


In response sales order Id will be presented.
It is possible to update sales order after creation (add or change customer data or contact info, add, change or remove items), to do UpdateInvoice operation may be used with same model updates as CreateInvoiceAndReserve, you may review it in Swagger

Create Invoice based on sales order

It is OK to create more then 1 invoice based on sales order and place some items into one sales order and some items from another.
But first it is needed to fetch sales order from storage.

For fetch sales order from storage use following:

Word: POST

URL: {host_url}/api/financial/SalesOrder/GetSalesOrder
Minimal request:

{
  "id": sales order id
}


In response sales order along with its items will be presented, it is needed for invoice creation.

For create invoice use following:

Word: POST
URL: {host_url}/api/financial/Invoice/CreateInvoiceAndReserve

Command contain collection [modelUpdates] and all invoice manipulation should be done via model updates.
Main idea of creation invoice based on sales order is to fetch data from sales order and place it into invoice

Before create of invoice we need to have:
[sale_order]
[customer] - retrieved from actors microservice based on [sale_order] customer's data

To compose sales order item we need to have :
[sale_order_item] from sales order.

These fields will be used in example below:


{  
   "modelUpdates":[  
      {  
         "modelType":"CreateInvoiceItemModelUpdate",
         "salesOrderItemReference":{  
            "value":{  
               "salesOrderId" // from [sale_order]
               "salesOrderIdentifier // from [sale_order]
               "salesOrderItemId" // from [sale_order_item]
            }
         },
         "financialData":{  // just copy from property with same name from [sale_order_item] or change if needed
            "saleFinancialAccountNumber"
            "vatRate"
            "vatAccountNumber"
            "discountAccountNumber"
            "priceIncludesVat"
            "usedVatRate"
            "usedVatFinancialAccountNumber"
         },
         "manualDiscountAmount"// just copy from property with same name from [sale_order_item] or change if needed
         "quantity"// just copy from property with same name from [sale_order_item] or change if needed
         "inventoryItemData":{  // just copy from property with same name from [sale_order_item] or change if needed
            "displayName"
            "productId"
            "variantId"
            "instanceId"
            "productIdentifier"
            "isUserDefinedPriceAllowed"
            "isCustomDisplayNameAllowed"
            "costPrice"
            "isUsed"
            "purchasePrice"
         },
         "price"// just copy from property with same name from [sale_order_item] or change if needed
      },
      {  
         "modelType":"SetInvoiceCustomerDataModelUpdate",
         "customerDescriptor":{  // copy from property [customer] of [sale_order] or compose based on [customer]
            "value":{  
               "actorId"
               "fullName"
               "identification"
            }
         },
         "firstName":{  
            "value" // take from [customer] if possible or user input
         },
         "lastName":{  
            "value" // take from [customer] if possible or user input
         },
         "organizationName":{  
            "value" // take from [customer] if possible or user input
         },
         "address":{ // take from [customer] if possible or user input  
            "value":{  
               "addressLine1"
               "addressLine2"
               "postalCode"
               "postalDistrict"
               "country"
               "latitude"
               "longitude"
            }
         },
         "email":{  
            "value" // take from [customer] if possible or user input
         },
         "phone":{  
            "value" // take from [customer] if possible or user input
         }
      }
   ],
   "modelType":"CreateInvoiceCommand"
}


Example with real data based on which sales order may be created in test tenant :


{  
   "modelUpdates":[  
      {  
         "modelType":"CreateInvoiceItemModelUpdate",
         "salesOrderItemReference":{  
            "value":{  
               "salesOrderId":1,
               "salesOrderIdentifier":"0000000001",
               "salesOrderItemId":1
            }
         },
         "financialData":{  
            "saleFinancialAccountNumber":1100,
            "vatRate":25,
            "vatAccountNumber":14262,
            "discountAccountNumber":7200,
            "priceIncludesVat":true,
            "usedVatRate":null,
            "usedVatFinancialAccountNumber":null
         },
         "manualDiscountAmount":300.14,
         "quantity":1,
         "inventoryItemData":{  
            "displayName":"Sony Playstation 4 (cf18-4136-9f92-31764c4)",
            "productId":106,
            "variantId":null,
            "instanceId":165,
            "productIdentifier":"ID_108",
            "isUserDefinedPriceAllowed":false,
            "isCustomDisplayNameAllowed":false,
            "costPrice":3000,
            "isUsed":false,
            "purchasePrice":null
         },
         "price":3001.45
      },
      {  
         "modelType":"SetInvoiceCustomerDataModelUpdate",
         "customerDescriptor":{  
            "value":{  
               "actorId":8,
               "fullName":"0000100001 a_person Customer",
               "identification":"0000100001"
            }
         },
         "firstName":{  
            "value":"a_person"
         },
         "lastName":{  
            "value":"Customer"
         },
         "organizationName":{  
            "value":null
         },
         "ean":{  

         },
         "address":{  
            "value":{  
               "addressLine1":"myStreet",
               "addressLine2":"222",
               "postalCode":"2100",
               "postalDistrict":"København Ø",
               "country":"Denmark",
               "latitude":null,
               "longitude":null
            }
         },
         "email":{  
            "value":"example@gmail.com"
         },
         "phone":{  
            "value":"77777777"
         }
      },
      {  
         "modelType":"SetInvoiceProductInstanceModelUpdate",
         "productInstance":{  
            "productInstanceId":null,
            "displayValue":"",
            "productId":null,
            "productVariantId":null
         }
      },
      {  
         "modelType":"SetInvoicePaymentDueDateModelUpdate",
         "paymentDueDate":"2018-09-27T13:38:46+03:00"
      }
   ],
   "modelType":"CreateInvoiceCommand"
}


In response invoice Id will be presented.
It is possible to update invoice after creation (add or change customer data or contact info, add, change or remove sale items), to do UpdateInvoice operation may be used with same model updates as CreateInvoice, you may review it in Swagger.

Book invoice

Word: POST
URL: {host_url}/api/financial/Invoice/BookInvoice


{
  "invoiceId": sales order id
}


After book sales order/invoice creation flow is completed

  • No labels