Financial
This section covers 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
Sales prices and amounts are considered to include VAT part by default, e.g. price, discount or total amount on invoice.
Get Sales Orders (Paged)
Method: POST
URL: {host_url}/api/financial/SalesOrder/GetSalesOrdersPage
Minimal Request:
{
"offset": 0,
"count": page_size,
"includeOverallEntryCount": true
}
Sales Order Properties to Load:
Use propertiesToLoad
to specify which properties should be included in the response. The available flags are:
Flag Name | Value |
---|---|
Basic | 0 |
SalesOrderItems | 1 |
Tasks | 2 |
ConvertRecord | 4 |
ConvertSource | 8 |
Type | 16 |
EavFieldValues | 32 |
SalesOrderItemState | 64 |
SalesOrderItemDiscounts | 128 |
TypeDetails | 256 |
Multiple flags can be combined using |
operator in order to fetch more than one property.
So a minimal request to specify sales order properties to load will be:
{
"propertiesToLoad":{
"value":32 // Eav field values in this case
},
"offset":0,
"count":10,
}
Optional Filters:
createdAt
: Filter by creation date.updatedAt
: Filter by update date.numericIdentifier
: Filter by identifier range.salesOrderStates
: Filter by state.salesOrderDispatchStates
: Filter by dispatch state.
Example request with filters:
{
"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 type 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 retrieve financial account details:
Method: POST
URL: {host_url}/api/accounting/FinancialAccount/GetFinancialAccountByNumber
Minimal Request:
{
"number": account_number
}
An account may or may not have a VAT type.
Prerequisites for Creating a Sales Order
Before create of sales order we need to have:
customer
- taken from actor microserviceaddress
(optional) - user input, defaulted by actoremail
(optional)- user input, defaulted by actorphone
(optional)- user input, defaulted by actor
Required Product Data
To compose a sales order item, the following product-related information is needed:
product
→ Selected product from the inventory.product_category
→ Categorization of the selected product.product_variant
(if applicable) → Required if the product supports variants.product_instance
(if applicable) → Required if the product supports instances.financial_account_number
→ Retrieved based on predefined rules.vat_type
→ Fetched from the Accounting microservice based on the financial account number.sales_price
:- If a product instance is selected and has a sales price, use that.
- Otherwise, check the variant for a defined sales price.
- If no price is found, use the product's sales price.
sales_price
include VAT by default.
quantity
→ User input; must be1
for items with a defined product instance.manual_discount
→ User input; calculated as(sales_price * quantity) - manual_discount
.Cost Price Calculation
:- Use cost price from the instance if available.
- Otherwise, use cost price from the variant.
- If still undefined, use the product's cost price. If
product.isDefaultCostPriceApplicable = true
, applyproduct.defaultCostPrice
.
API Request: Create Sales Order
Method: POST
URL: {host_url}/api/financial/SalesOrder/CreateSalesOrder
Supported Model Updates
A collection of modelUpdates
is used for all modifications:
ChangeSalesOrderModelUpdate
→ Manage sales order properties.CreateSalesOrderItemModelUpdate
→ Add sales order items.ChangeSalesOrderItemModelUpdate
→ Modify sales order items.DeleteSalesOrderItemsModelUpdate
→ Remove sales order items.SetSalesOrderCustomerDataModelUpdate
→ Specify actor and contact information.
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;
}
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 the following document EAV Fields.
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
"isProductInstanceRequired" from product.isProductInstancesSupported
},
"price" - [sales_price]
},
{
"modelType":"SetSalesOrderCustomerDataModelUpdate",
"actor":{ // this entire block may be undefined if [customer] is not defined
"value":{
"actorId"// from [customer]
"fullName"// Property [ActorText] from [customer]
"identification// Customer Identifier (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"
}
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,
"isProductInstanceRequired": true
},
"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
Some customers may need to modify a sales order even after it is marked as ready or invoiced (e.g., adding work hours and invoicing separately). This behavior can be enabled by setting:
"AllowModifyingWhenReady": { "value": true }
When enabled, the client app will prompt at invoice time whether to complete the sales order. Users can choose whether to finalize it or keep it open for modifications.
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.
Method: POST
URL: {host_url}/api/financial/SalesOrder/UpdateSalesOrder
{
"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
Method: POST
URL: {host_url}/api/financial/SalesOrder/AddProductToSalesOrder
{
"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
Method: POST
URL: {host_url}/api/financial/SalesOrder/SetSalesOrderReady
Minimal Request:
{
"salesOrderId": sales order id
}
Create Invoice
Invoices can be created either as blank or based on a sales order and its items. This section covers the necessary data and API requests for invoice creation.
Prerequisites for Creating an Invoice
Before creating an invoice, the following data must be collected:
customer
→ Retrieved from theActors
microservice based on invoice customer data.- Contact Information (Optional) → User input, defaulted by actor (
adress
,email
,phone
).
To compose an invoice item, the following product-related details are required:
product
→ Selected product from the inventory.product_category
→ Categorization of the selected product.product_variant
(if applicable) → Required if the product supports variants.product_instance
(if applicable) → Required if the product supports instances.financial_account_number
→ Retrieved based on predefined rules ([Get product financial and VAT data]).vat_type
→ Retrieved from the Accounting microservice based on the financial account number.sales_price
→ Calculated from the following rules:- If a product instance is selected and has a sales price, use that.
- Otherwise, check the variant for a defined sales price.
- If no price is found, use the product's sales price.
- All sales prices include VAT by default.
quantity
→ User input must be1
for invoice items with a defined product instance.manual_discount
→ User input; calculated assales_price * quantity - manual_discount
. All enlisted entities will be used in example below.manual_discount
is considered to include VAT part.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
andproduct.isDefaultCostPriceApplicable = true
thenproduct.defaultCostPrice
should be used.
API Request: Create Invoice
Method: POST
URL: {host_url}/api/financial/Invoice/CreateInvoiceAndReserve
Supported Model Updates
A collection of modelUpdates
is used for all invoice modifications:
CreateInvoiceItemModelUpdate
→ Add invoice items.ChangeInvoiceItemModelUpdate
→ Modify invoice items.DeleteInvoiceItemsModelUpdate
→ Remove invoice items.SetInvoiceCustomerDataModelUpdate
→ Specify actor and contact information.
There may be many model updates of any type in command. Here are these model updates in detail:
{
"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
"isProductInstanceRequired" from product.isProductInstancesSupported
},
"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,
"isProductInstanceRequired": true
},
"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"
}
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 possible to generate multiple invoices from a single sales order, allowing you to allocate specific items to different invoices as needed. However, before doing so, the sales order must first be retrieved from storage.
For fetching sales order from storage use the following:
Method: 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.
To create an invoice, use the following request:
Method: POST
URL: {host_url}/api/financial/Invoice/CreateInvoiceAndReserve
Commands contain a collection of modelUpdates
and all invoice manipulation should be done via model updates. When creating an invoice based on a sales order, the process involves retrieving relevant data from the sales order and applying it to the invoice.
Before create of invoice we need to have:
sale_order
→ The order from which the invoice will be generated.
customer
→ Retrieved from the actors microservice using the customer data in the sale_order
.
To compose sales order item we need to have:
sale_order_item
from sales order.
These fields will be used in the 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",
"isProductInstanceRequired"
},
"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,
"isProductInstanceRequired": true
},
"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
Once an invoice has been created, it must be booked to finalize the transaction and officially record it in the system. Booking an invoice ensures that no further modifications can be made, and it becomes ready for financial processing, including payments and accounting.
The booking process locks the invoice, preventing any changes, and integrates it into the financial records. It may also trigger additional processes such as inventory updates, payment handling, or synchronization with external accounting systems. Before booking, it is essential to verify that all details are accurate, as any errors will require issuing a credit note or creating a new invoice.
To book an invoice, the following request should be used:
Method: POST
URL: {host_url}/api/financial/Invoice/BookInvoice
Minimal Request:
{
"invoiceId": sales order id
}
After booking, the invoice is considered finalized, and the sales order and invoice creation flow is complete.