Inventory & Products | Stock Transfers | Set Stock Transfer

Stock Transfer Creation and Update Endpoint

This document provides guidance on using the API to create or update stock transfers in the API server's database through the set_stock_transfer endpoint. This involves two steps: API authentication and stock transfer creation/update.

API ENDPOINT FOR STOCK TRANSFER CREATION AND UPDATE

This API call is essential for creating new stock transfers or updating existing ones in the system.

Stock Transfer Creation/Update Process

To create or update a stock transfer, follow the steps below using the set_stock_transfer API call. The process involves providing credentials for API login and specific details for the stock transfer.

Endpoint and Method

  • Endpoint: https://easycms.fi/public_api/set_stock_transfer/
  • Method: POST
  • Content-Type: application/json

Parameters | Payload

For stock transfer operations, you need to provide two sets of parameters - one for API authentication and the other for the stock transfer details.

API Authentication Parameters

  • TOKEN (api_key): Your unique API key for authentication. How to Create API Credentials.
  • username: Your login username.
  • password: Your login password.
  • account: Your specific account ID.

Stock Transfer Parameters

For CREATE (id NOT provided):
  • from_location_id: Source Location ID (Integer) - Required. The ID of the location where stock is being transferred from.
  • to_location_id: Destination Location ID (Integer) - Required. The ID of the location where stock is being transferred to.
  • transfer_date: Transfer Date (String) - The date of the transfer. Defaults to current date if not provided.
  • status: Status (Integer) - The status of the transfer. Defaults to 1 (draft) if not provided.
  • transfer_lines: Array of transfer line data - Required for creation.
  • Other optional transfer fields listed below.
For UPDATE (id provided):
  • id: Transfer ID (Integer) - Required for update. The ID of the transfer to update.
  • Any transfer fields to update (only provided fields will be updated).
  • transfer_lines: Array of transfer line data (optional for update).

Stock Transfer Header Fields

  • id: Transfer ID (Integer) - The unique identifier for the transfer (for updates only).
  • reference: Reference Number (Integer) - The reference number for the transfer (auto-generated for new transfers).
  • from_location_id: Source Location ID (Integer) - The ID of the location where stock is being transferred from.
  • to_location_id: Destination Location ID (Integer) - The ID of the location where stock is being transferred to.
  • title: Title (String) - The title of the transfer.
  • note: Note (String) - General notes for the transfer.
  • admin_note: Admin Note (String) - Administrative notes.
  • delivery_instructions: Delivery Instructions (String) - Special delivery instructions.
  • transfer_date: Transfer Date (String) - The date of the transfer.
  • delivery_date: Delivery Date (String) - The expected or actual delivery date.
  • total: Total (Float) - The total quantity of items transferred.
  • discount: Discount (Float) - Any discount applied.
  • status: Status (Integer) - The status of the transfer:
    • 1 = Draft
    • 100 = Sent
    • 110 = Preparing
    • 120 = Shipped
    • 200 = Received
    • -1 = Cancelled

Stock Transfer Lines Fields

Each item in the transfer_lines array can contain:

  • pid: Product ID (Integer) - Required. The identifier of the product.
  • quantity: Ordered Quantity (Float) - Required. The quantity to transfer. Read-only once the transfer is received (status 200) — re-sending the current value is fine, but changing it skips the line with an error. Use received_quantity to report actual received amounts.
  • received_quantity: Received Quantity (Float) - Optional (v3.16+). The actual amount received, for transfers in received status (200). Received ≠ ordered is fully supported (over/under receipt); the confirm step (set_transfer_confirm) moves this amount, not the ordered quantity.
  • unit_price: Unit Price (Float) - The unit price for the product.
  • unit_price_buy: Unit Price Buy (Float) - The purchase price.
  • product_name: Product Name (String) - The name of the product.
  • product_title: Product Title (String) - The title of the product.
  • line_note: Line Note (String) - Notes specific to this line.
  • best_before_date or bbd: Best Before Date (String) - The expiry date if applicable. Accepted formats: YYYY-MM-DD (recommended), DD.MM.YYYY, MM/DD/YYYY, YYYY-MM-DD HH:MM:SS, ISO-8601. The value is normalized to a date literal before storing (v3.16 — previously raw epoch values were silently stored as 0000-00-00). Empty or unparseable values are ignored (the existing line value is kept). Send the bbd before confirming the line — the confirm step copies the line's bbd into the stock records.
  • barcode: Barcode (String) - The product barcode.
  • vat_percent: VAT Percent (Float) - The VAT percentage.
  • vat_percent_buy: VAT Percent Buy (Float) - The VAT percentage for buying.
  • discount: Discount (Float) - Line-level discount.



Two-Phase Receive Confirmation (v3.08+)

When a stock transfer's status is changed to 200 (received):

  • Stock is NO LONGER automatically moved on the status change
  • All lines have received_quantity = quantity pre-filled
  • Report actual received amounts by re-sending the line with received_quantity (over/under receipt is supported)
  • Each line must be individually confirmed via set_transfer_confirm to move stock
  • Alternatively, use set_transfer_confirm without transfer_line_id to confirm all lines at once

This ensures accurate stock tracking -- the person receiving goods explicitly confirms what was actually received.

Confirmed-line safety rules (v3.16+): re-sending transfer_lines on a received (status 200) transfer follows these rules, so duplicate/idempotent submissions are safe:

  • Already-confirmed line re-sent unchanged → SKIPPED (idempotent no-op; reported in changes.transfer_lines.lines_skipped_confirmed). The line's confirmed stock is never touched by a re-send.
  • Already-confirmed line with a NEW received_quantity → the previous confirmed stock movement is reversed, the line is un-confirmed and staged with the new quantity — call set_transfer_confirm again to apply the corrected amount.
  • Changing quantity (the ordered amount) on a received transfer → rejected per line with an error: ordered quantity is read-only once received. Use received_quantity.
  • Pending (unconfirmed) line → an explicit received_quantity wins; without it, any already-entered received quantity is preserved (the ordered quantity never overwrites an entered actual count).


Status Changes & Stock Movement (v3.37+)

Changing the status field on an UPDATE now runs the same stock-movement engine as the CMS editor. The API and CMS behave identically — there is no longer a difference between "changing status via the admin UI" and "changing status via API".

Validation: Only the canonical status values are accepted on UPDATE: 1, 100, 110, 120, 200, -1. Any other value returns 400 INVALID_STATUS.

What each transition does to stock:

Transition Stock effect
Draft → Sent (1 → 100/110/120) Reserves stock at source: writes tbl_stockcoming rows (stock leaves source's availability, expected at destination). Live stock (tbl_stockcurrent) is not touched yet.
Sent → Received (100/110/120 → 200) Pre-fills received_quantity = quantity on all lines, sets arrival_date = today. Stock movement is deferred — call set_transfer_confirm to move it.
Draft → Received (1 → 200) Same as Sent → Received: pre-fills lines, defers movement to set_transfer_confirm.
Received → Draft/Sent (200 → 1/100) Reverses the confirmed stock movements (puts stock back at source, removes from destination), resets received_confirmed = 0.
Sent → Draft (100 → 1) Removes the tbl_stockcoming reservations.
Any → Cancelled (→ -1) Rolls back all stock effects as if returning to draft, then marks the transfer as deleted.

Reason codes used in stock diary: +4 (IN_MOVEMENT, stock arrives at destination) and -4 (OUT_MOVEMENT, stock leaves source). These are the same codes the CMS uses.

Recommended mobile flow:

  1. Create draft transfer: set_stock_transfer (status defaults to 1) — no stock touched
  2. Send it: set_stock_transfer UPDATE with status: 100 — stock reserved at source (in transit)
  3. Mark received: set_stock_transfer UPDATE with status: 200 — lines pre-filled (received_quantity = quantity), movement deferred
  4. Report actual counts (if received ≠ ordered): set_stock_transfer UPDATE with transfer_lines: [{pid, quantity: <ordered>, received_quantity: <actual>}] — keep quantity at the ordered value; only set received_quantity to what physically arrived. Omit lines you are not changing, and never re-send a line after confirming it.
  5. Confirm receipt: set_transfer_confirm with id — stock actually moves to destination (per line with transfer_line_id, or all pending lines at once)

Steps 2 and 3 can be combined if the destination confirms receipt immediately, but the confirm step (set_transfer_confirm) is always required to move stock to tbl_stockcurrent.


Call Examples in Different Languages


# Create a new stock transfer
curl -X POST 'https://easycms.fi/public_api/set_stock_transfer' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
  "username": "USERNAME",
  "password": "PASSWORD",
  "account": "ACCOUNT_ID",
  "from_location_id": 1,
  "to_location_id": 2,
  "transfer_date": "2024-01-15 10:30:00",
  "status": 1,
  "title": "Monthly Stock Transfer",
  "note": "Regular monthly transfer",
  "delivery_instructions": "Handle with care",
  "transfer_lines": [
    {
      "pid": 12345,
      "quantity": 100,
      "unit_price": 15.50,
      "product_name": "Product Name",
      "barcode": "1234567890123",
      "best_before_date": "2025-12-31"
    },
    {
      "pid": 12346,
      "quantity": 50,
      "unit_price": 22.00,
      "product_name": "Another Product",
      "boxcode": "BOX001"
    }
  ]
}'

# Update an existing stock transfer
curl -X POST 'https://easycms.fi/public_api/set_stock_transfer' \
-H 'Authorization1: TOKEN' \
-H 'Content-Type: application/json' \
-d '{
  "username": "USERNAME",
  "password": "PASSWORD",
  "account": "ACCOUNT_ID",
  "id": 123,
  "status": 200,
  "note": "Transfer completed"
}'

$curl = curl_init();

$payload = [
  'username' => 'USERNAME',
  'password' => 'PASSWORD',
  'account' => 'ACCOUNT_ID',
  'from_location_id' => 1,
  'to_location_id' => 2,
  'transfer_date' => '2024-01-15 10:30:00',
  'status' => 1,
  'title' => 'Monthly Stock Transfer',
  'note' => 'Regular monthly transfer',
  'delivery_instructions' => 'Handle with care',
  'transfer_lines' => [
    [
      'pid' => 12345,
      'quantity' => 100,
      'unit_price' => 15.50,
      'product_name' => 'Product Name',
      'barcode' => '1234567890123',
      'best_before_date' => '2025-12-31'
    ],
    [
      'pid' => 12346,
      'quantity' => 50,
      'unit_price' => 22.00,
      'product_name' => 'Another Product',
      'boxcode' => 'BOX001'
    ]
  ]
];

curl_setopt_array($curl, [
  CURLOPT_URL => "https://easycms.fi/public_api/set_stock_transfer",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode($payload),
  CURLOPT_HTTPHEADER => [
    "Authorization1: TOKEN",
    "Content-Type: application/json"
  ],
]);

$response = curl_exec($curl);
curl_close($curl);
echo $response;

import requests
import json

url = "https://easycms.fi/public_api/set_stock_transfer"
headers = {
    "Authorization1": "TOKEN",
    "Content-Type": "application/json"
}

payload = {
    "username": "USERNAME",
    "password": "PASSWORD",
    "account": "ACCOUNT_ID",
    "from_location_id": 1,
    "to_location_id": 2,
    "transfer_date": "2024-01-15 10:30:00",
    "status": 1,
    "title": "Monthly Stock Transfer",
    "note": "Regular monthly transfer",
    "delivery_instructions": "Handle with care",
    "transfer_lines": [
        {
            "pid": 12345,
            "quantity": 100,
            "unit_price": 15.50,
            "product_name": "Product Name",
            "barcode": "1234567890123",
            "best_before_date": "2025-12-31"
        },
        {
            "pid": 12346,
            "quantity": 50,
            "unit_price": 22.00,
            "product_name": "Another Product",
            "boxcode": "BOX001"
        }
    ]
}

response = requests.post(url, headers=headers, data=json.dumps(payload))
print(response.text)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        String jsonPayload = """
            {
              "username": "USERNAME",
              "password": "PASSWORD",
              "account": "ACCOUNT_ID",
              "from_location_id": 1,
              "to_location_id": 2,
              "transfer_date": "2024-01-15 10:30:00",
              "status": 1,
              "title": "Monthly Stock Transfer",
              "note": "Regular monthly transfer",
              "delivery_instructions": "Handle with care",
              "transfer_lines": [
                {
                  "pid": 12345,
                  "quantity": 100,
                  "unit_price": 15.50,
                  "barcode": "1234567890123"
                }
              ]
            }
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://easycms.fi/public_api/set_stock_transfer"))
            .headers("Authorization1", "TOKEN", "Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
            .build();

        HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}

const https = require('https');

const payload = {
  username: 'USERNAME',
  password: 'PASSWORD',
  account: 'ACCOUNT_ID',
  from_location_id: 1,
  to_location_id: 2,
  transfer_date: '2024-01-15 10:30:00',
  status: 1,
  title: 'Monthly Stock Transfer',
  note: 'Regular monthly transfer',
  delivery_instructions: 'Handle with care',
  transfer_lines: [
    {
      pid: 12345,
      quantity: 100,
      unit_price: 15.50,
      product_name: 'Product Name',
      barcode: '1234567890123',
      bbd: '2025-12-31'
    }
  ]
};

const data = JSON.stringify(payload);

const options = {
  hostname: 'easycms.fi',
  path: '/public_api/set_stock_transfer',
  method: 'POST',
  headers: {
    'Authorization1': 'TOKEN',
    'Content-Type': 'application/json',
    'Content-Length': data.length
  }
};

const req = https.request(options, (res) => {
  let responseData = '';
  res.on('data', (chunk) => { responseData += chunk; });
  res.on('end', () => { console.log(responseData); });
});

req.on('error', (e) => { console.error(e); });
req.write(data);
req.end();

import React, { useEffect, useState } from 'react';

function App() {
  const [responseData, setResponseData] = useState('');

  useEffect(() => {
    const createTransfer = async () => {
      try {
        const response = await fetch('https://easycms.fi/public_api/set_stock_transfer', {
          method: 'POST',
          headers: {
            'Authorization1': 'TOKEN',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            username: 'USERNAME',
            password: 'PASSWORD',
            account: 'ACCOUNT_ID',
            from_location_id: 1,
            to_location_id: 2,
            transfer_date: '2024-01-15 10:30:00',
            status: 1,
            title: 'Monthly Stock Transfer',
            note: 'Regular monthly transfer',
            delivery_instructions: 'Handle with care',
            transfer_lines: [
              {
                pid: 12345,
                quantity: 100,
                unit_price: 15.50,
                barcode: '1234567890123'
              }
            ]
          })
        });
        const data = await response.json();
        setResponseData(JSON.stringify(data, null, 2));
      } catch (error) {
        console.error(error);
      }
    };
    createTransfer();
  }, []);

  return <pre>{responseData}</pre>;
}

export default App;

import okhttp3.OkHttpClient
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException

fun main() {
    val client = OkHttpClient()

    val jsonPayload = """
        {
          "username": "USERNAME",
          "password": "PASSWORD",
          "account": "ACCOUNT_ID",
          "from_location_id": 1,
          "to_location_id": 2,
          "transfer_date": "2024-01-15 10:30:00",
          "status": 1,
          "title": "Monthly Stock Transfer",
          "note": "Regular monthly transfer",
          "delivery_instructions": "Handle with care",
          "transfer_lines": [
            {
              "pid": 12345,
              "quantity": 100,
              "unit_price": 15.50,
              "barcode": "1234567890123"
            }
          ]
        }
    """.trimIndent()

    val requestBody = jsonPayload.toRequestBody("application/json".toMediaType())

    val request = Request.Builder()
        .url("https://easycms.fi/public_api/set_stock_transfer")
        .post(requestBody)
        .addHeader("Authorization1", "TOKEN")
        .build()

    client.newCall(request).execute().use { response ->
        if (!response.isSuccessful) throw IOException("Unexpected code $response")
        println(response.body?.string())
    }
}

using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Add("Authorization1", "TOKEN");

        var payload = new
        {
            username = "USERNAME",
            password = "PASSWORD",
            account = "ACCOUNT_ID",
            from_location_id = 1,
            to_location_id = 2,
            transfer_date = "2024-01-15 10:30:00",
            status = 1,
            title = "Monthly Stock Transfer",
            note = "Regular monthly transfer",
            delivery_instructions = "Handle with care",
            transfer_lines = new[]
            {
                new
                {
                    pid = 12345,
                    quantity = 100,
                    unit_price = 15.50,
                    barcode = "1234567890123"
                }
            }
        };

        var jsonContent = new StringContent(
            JsonSerializer.Serialize(payload),
            Encoding.UTF8,
            "application/json"
        );

        var response = await httpClient.PostAsync(
            "https://easycms.fi/public_api/set_stock_transfer",
            jsonContent
        );

        if (response.IsSuccessStatusCode)
        {
            var responseBody = await response.Content.ReadAsStringAsync();
            Console.WriteLine(responseBody);
        }
        else
        {
            Console.WriteLine($"Error: {response.StatusCode}");
        }
    }
}




Handling Endpoint Results

When you make a request to the endpoint, you receive a JSON response containing various keys and values. Here's an explanation of the response keys and their meanings:


Response Structure:

- `status`: Response status - "success" or "error"
- `message`: Human-readable message describing the result
- `data`: Object containing the created/updated transfer data
  - `id`: The transfer ID
  - `transfer`: The complete transfer object with all fields
- `changes`: Object tracking what was changed
  - For CREATE:
    - `transfer_created`: Object with new transfer details (id, reference, from_location_id, to_location_id)
    - `transfer_lines`: Result of line processing
  - For UPDATE:
    - `transfer`: Object with field-level changes (old and new values)
    - `transfer_lines`: Result of line processing (if lines were provided)

Transfer Object Fields:
- `id`: Transfer ID - Unique identifier
- `reference`: Reference Number - Auto-generated reference
- `from_location_id`: Source Location ID - Where stock is being transferred from
- `from_location_name`: Source Location Name - Name of source location
- `to_location_id`: Destination Location ID - Where stock is being transferred to
- `to_location_name`: Destination Location Name - Name of destination location
- `transfer_date`: Transfer Date - When the transfer occurred
- `delivery_date`: Delivery Date - Expected/actual delivery date
- `status`: Status Code - Current transfer status
- `title`: Title - Transfer title
- `note`: Note - General notes
- `admin_note`: Admin Note - Administrative notes
- `delivery_instructions`: Delivery Instructions
- `total`: Total - Total quantity of items transferred
- `discount`: Discount - Applied discount
- `deleted`: Deleted - Whether the transfer is deleted

Transfer Lines Result:
- `success`: Boolean - Whether line processing succeeded (true when at least one line was processed OR skipped as already-confirmed)
- `lines_processed`: Integer - Number of lines written
- `lines_skipped`: Integer - Number of lines skipped due to validation errors
- `lines_skipped_confirmed`: Integer - Number of already-confirmed lines skipped (idempotent re-send, no changes made)
- `errors`: Array of Strings - Per-line validation errors (e.g. attempted ordered-quantity change on a received transfer)
- `notes`: Array of Strings - Per-line informational notes (e.g. confirmed quantity reversed and un-confirmed)
- `message`: String - Description of the result
    

{
  "status": "success",
  "message": "Transfer created successfully",
  "data": {
    "id": 125,
    "transfer": {
      "id": "125",
      "reference": "2024001",
      "from_location_id": "1",
      "from_location_name": "Main Warehouse",
      "to_location_id": "2",
      "to_location_name": "Branch Store",
      "transfer_date": "2024-01-15 10:30:00",
      "delivery_date": "2024-01-20 14:00:00",
      "status": "1",
      "title": "Monthly Stock Transfer",
      "note": "Regular monthly transfer",
      "admin_note": "",
      "delivery_instructions": "Handle with care",
      "total": "100",
      "discount": "0",
      "deleted": "0"
    }
  },
  "changes": {
    "transfer_created": {
      "id": 125,
      "reference": "2024001",
      "from_location_id": 1,
      "to_location_id": 2
    },
    "transfer_lines": {
      "success": true,
      "lines_processed": 2,
      "message": "Transfer lines updated for 2 product(s)"
    }
  }
}
    

{
  "status": "success",
  "message": "Transfer updated successfully",
  "data": {
    "id": 123,
    "transfer": {
      "id": "123",
      "reference": "2024000",
      "from_location_id": "1",
      "from_location_name": "Main Warehouse",
      "to_location_id": "2",
      "to_location_name": "Branch Store",
      "transfer_date": "2024-01-14 09:00:00",
      "delivery_date": "2024-01-20 14:00:00",
      "status": "200",
      "title": "Monthly Stock Transfer",
      "note": "Transfer completed",
      "admin_note": "",
      "delivery_instructions": "Handle with care",
      "total": "200",
      "discount": "0",
      "deleted": "0"
    }
  },
  "changes": {
    "transfer": {
      "status": {
        "old": "120",
        "new": 200
      },
      "note": {
        "old": "Monthly stock transfer",
        "new": "Transfer completed"
      }
    }
  }
}
    

Stock Transfer Status Flow

Stock transfers follow a status flow from creation to completion:

Status Code Title Description
1 draft_transfer Transfer is in draft state, can be modified
100 transfer_sent Transfer has been sent to destination
110 transfer_preparing Transfer is being prepared for shipment
120 transfer_shipped Transfer has been shipped
200 transfer_received Transfer received — stock movement deferred to per-line confirmation (see below)
-1 cancelled Transfer has been cancelled

Two-phase receive (v3.08+): When a transfer status changes to 200 (received):

  • Each line's received_quantity is pre-filled with its ordered quantity
  • Stock is NOT automatically moved at this point — it is deferred
  • Each line must then be confirmed via the set_transfer_confirm endpoint, which performs the actual stock movement (deducts from source, adds to destination)
  • Call set_transfer_confirm without transfer_line_id to confirm all pending lines at once
  • This allows partial receipts: confirm only the lines that actually arrived

Error Handling

Here are the possible error messages and their meanings:

  • UN-AUTHORIZED - _user_name_password_is_set_but_wrong_value!: Incorrect username or password.
  • this_account_does_not_exist_or_your_credentials_do_not_match_this_account: The account doesn't exist or mismatched credentials.
  • UN-AUTHORIZED - header is set but the header value is not correct!: Incorrect authorization header value.
  • METHOD_NOT_ALLOWED: Only POST method is allowed for this endpoint.
  • MISSING_REQUIRED_FIELDS: Missing required fields for creation. Required: from_location_id, to_location_id
  • MISSING_TRANSFER_LINES: Transfer lines are required for creating a stock transfer.
  • TRANSFER_NOT_FOUND: The specified transfer ID was not found (for updates).
  • UPDATE_FAILED: Failed to update the transfer in the database.
  • CREATE_FAILED: Failed to create the transfer in the database.
  • INVALID_LOCATION: The specified location ID was not found.
  • INSUFFICIENT_STOCK: Not enough stock available at source location for transfer.
  • INVALID_STATUS: The status value is not one of the allowed values (1, 100, 110, 120, 200, -1). Returned on UPDATE only.

Per-line errors (in changes.transfer_lines.errors, the line is skipped — the request itself still succeeds):

  • Cannot change ordered quantity for a received (status 200) transfer...: The quantity of an existing line was changed after the transfer was marked received. Ordered quantity is read-only once received; use received_quantity to report actual amounts.

Best Practices

  1. Creating a Transfer: Always provide from_location_id, to_location_id, and transfer_lines when creating a new transfer.
  2. Updating a Transfer: Only include fields that need to be changed. The API uses partial updates.
  3. Status Management: Update status progressively through the transfer lifecycle (1 → 100 → 110 → 120 → 200). Each transition runs the stock engine identically to the CMS editor.
  4. Stock Reservation: Changing status to 100/110/120 (sent/preparing/shipped) reserves stock at the source location (writes tbl_stockcoming). The stock is now "in transit".
  5. Stock Movement: Changing status to 200 (received) pre-fills received_quantity but defers the actual stock movement. Call set_transfer_confirm to move stock to the destination.
  6. Line Items: Each line requires pid and quantity at minimum. Optional line fields: unit_price, unit_price_buy, vat_percent, vat_percent_buy, discount, weight, weight_type, stock_type_id, product_name, product_title, boxcode, line_note, best_before_date (or bbd), barcode, received_quantity (received transfers only).
  7. Stock Validation: Ensure sufficient stock exists at source location before transferring.
  8. Partial Receipts: Confirm individual lines via set_transfer_confirm with transfer_line_id to handle partial receipts.
  9. Actual Received Amounts: Send received_quantity per line (keeping quantity at the ordered value) when more or fewer items physically arrived than ordered. Already-confirmed lines re-sent unchanged are safely skipped.
  10. Do not re-send confirmed lines: Once a line is confirmed (set_transfer_confirm), stop including it in subsequent transfer_lines arrays. It is ignored (idempotent skip), but sending only pending lines keeps responses clean and avoids ambiguity.