PHPackages                             arturas88/finvalda-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. arturas88/finvalda-sdk

ActiveLibrary[API Development](/categories/api)

arturas88/finvalda-sdk
======================

PHP SDK for Finvalda (FVS) accounting software web service API

v2.8.0(2mo ago)0221MITPHPPHP ^8.3CI passing

Since Mar 22Pushed 3d agoCompare

[ Source](https://github.com/arturas88/finvalda-sdk)[ Packagist](https://packagist.org/packages/arturas88/finvalda-sdk)[ Docs](https://github.com/arturas88/finvalda-sdk)[ RSS](/packages/arturas88-finvalda-sdk/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (2)Dependencies (7)Versions (19)Used By (0)

Finvalda PHP SDK
================

[](#finvalda-php-sdk)

PHP SDK for the [Finvalda (FVS)](https://www.finvalda.lt/) accounting software web service API.

Built from the official [Finvalda API documentation](https://documenter.getpostman.com/view/7208231/2s8YmRMLvd).

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
    - [Basic Configuration](#basic-configuration)
    - [Laravel Integration](#laravel-integration)
    - [Logging](#logging)
    - [Retry Policy](#retry-policy)
    - [Custom HTTP Client](#custom-http-client-testing)
- [Typed DTOs &amp; Collections](#typed-dtos--collections)
    - [Finding Entities](#finding-entities)
    - [Working with Collections](#working-with-collections)
- [Fluent Operation Builders](#fluent-operation-builders)
    - [Sales](#creating-a-sale)
    - [Purchases](#creating-a-purchase)
    - [Internal Transfers](#creating-an-internal-transfer)
    - [Returns](#creating-returns)
    - [Payments](#creating-payments)
    - [Write-Offs &amp; Capitalization](#write-offs--capitalization)
    - [Production](#creating-a-production-operation)
    - [Non-Analytical Operations](#non-analytical-operations)
    - [Inventory Count](#inventory-count)
    - [Clearing / Set-Off](#clearing--set-off)
    - [UVM (Order Management)](#uvm-order-management)
    - [Short / Simplified Operations](#short--simplified-operations)
- [Query Builders](#query-builders)
    - [Transaction Query](#transaction-query)
    - [Operation Query](#operation-query)
- [Validation](#validation)
- [Field Reference](#field-reference)
- [Resources](#resources)
    - [Stock / Inventory](#stock--inventory)
    - [Clients](#clients)
    - [Products](#products)
    - [Services](#services)
    - [Objects (6 Levels)](#objects-6-levels)
    - [Transactions](#transactions-financial-detail-data)
    - [Operations](#operations-create-update-delete)
    - [Order Management (UVM)](#order-management-uvm)
    - [Pricing &amp; Discounts](#pricing--discounts)
    - [Documents](#documents)
    - [Reports &amp; Invoices](#reports--invoices)
    - [Descriptions (Universal Query)](#descriptions-universal-query)
    - [Reference Data](#reference-data)
    - [User Permissions](#user-permissions)
- [Pagination](#pagination)
- [Error Handling](#error-handling)
- [Server-Configured Parameters](#server-configured-parameters)
- [API Versions](#api-versions)
- [License](#license)

Requirements
------------

[](#requirements)

- PHP &gt;= 8.3
- Guzzle HTTP client

Installation
------------

[](#installation)

```
composer require arturas88/finvalda-sdk
```

Quick Start
-----------

[](#quick-start)

```
use Finvalda\Finvalda;
use Finvalda\FinvaldaConfig;

// Configure the client
$config = new FinvaldaConfig(
    baseUrl: 'https://your-server.com/FvsServicePure.svc',
    username: 'your-username',
    password: 'your-password',
);

$finvalda = new Finvalda($config);

// Test connection
if (! $finvalda->ping()) {
    die('Connection failed — check credentials and server URL');
}

// Fetch all clients as a typed collection
$clients = $finvalda->clients()->collect();

foreach ($clients as $client) {
    echo "{$client->code}: {$client->name}\n";
}

// Create a sale using the fluent builder
$result = $finvalda->sale()
    ->client('CLI001')
    ->date('2024-01-15')
    ->warehouse('MAIN')
    ->addProduct('PRD001', quantity: 10, price: 19.99)
    ->addProduct('PRD002', quantity: 5, amount: 49.95)
    ->save('STANDARD');

if ($result->success) {
    echo "Created: {$result->journal} #{$result->number}";
}
```

Configuration
-------------

[](#configuration)

### Basic Configuration

[](#basic-configuration)

```
use Finvalda\Finvalda;
use Finvalda\FinvaldaConfig;
use Finvalda\Enums\Language;
use Finvalda\Retry\RetryPolicy;

$config = new FinvaldaConfig(
    baseUrl: 'https://your-server.com/FvsServicePure.svc',
    username: 'your-username',
    password: 'your-password',
    // Optional parameters:
    connString: null,                    // Database connection string
    companyId: null,                     // Company ID for multi-database setups
    language: Language::Lithuanian,      // or Language::English
    removeEmptyStringTags: false,
    removeZeroNumberTags: false,
    removeNewLines: false,
    timeout: 30,
    logger: null,                        // PSR-3 logger instance
    retry: null,                         // RetryPolicy instance
);

$finvalda = new Finvalda($config);
```

### Laravel Integration

[](#laravel-integration)

The package auto-registers via Laravel package discovery. Add your credentials to `.env`:

```
FINVALDA_BASE_URL=https://your-server.com/FvsServicePure.svc
FINVALDA_USERNAME=your-username
FINVALDA_PASSWORD=your-password
FINVALDA_COMPANY_ID=your-company-id

# Optional: route SDK debug logs to a Laravel log channel
FINVALDA_LOG_CHANNEL=stack

# Optional: retry transient failures with exponential backoff
FINVALDA_RETRY_ENABLED=true
FINVALDA_RETRY_MAX_ATTEMPTS=3
```

Publish the config file (optional):

```
php artisan vendor:publish --tag=finvalda-config
```

Then inject or use the facade:

```
// Dependency injection
public function index(Finvalda\Finvalda $finvalda)
{
    $clients = $finvalda->clients()->collect();
}

// Facade
use Finvalda\Laravel\Facades\Finvalda;

$clients = Finvalda::clients()->collect();
```

### Logging

[](#logging)

Enable PSR-3 logging for request/response debugging:

```
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Create a logger
$logger = new Logger('finvalda');
$logger->pushHandler(new StreamHandler('path/to/finvalda.log', Logger::DEBUG));

// Option 1: Pass in config
$config = new FinvaldaConfig(
    // ...
    logger: $logger,
);

// Option 2: Set after initialization
$finvalda->setLogger($logger);
```

Both records are logged at `debug` level. `Finvalda API request` includes method, endpoint, parameters, and the full request body (`body`, string or null for GET). `Finvalda API response` includes method, endpoint, status code, response time, and the full response body (`body`). Bodies larger than 100 KB are truncated with a `... [truncated N bytes]` marker — route the SDK's debug-level records to a suitable handler if log volume is a concern.

### Debug Mode

[](#debug-mode)

Capture full request/response details for troubleshooting:

```
$finvalda->setDebug(true);

// Make any API call
$result = $finvalda->operations()->create(OperationClass::Sale, $data, 'PARAM');

// Inspect what was sent and received
$debug = $finvalda->getLastDebugInfo();
print_r($debug['request']);   // method, url, headers, body
print_r($debug['response']);  // status_code, headers, body

// Disable debug mode (clears stored info)
$finvalda->setDebug(false);
```

### Retry Policy

[](#retry-policy)

Configure automatic retries for transient failures:

```
use Finvalda\Retry\RetryPolicy;

// Default retry policy (3 attempts, 100ms initial delay, exponential backoff)
$config = new FinvaldaConfig(
    // ...
    retry: RetryPolicy::default(),
);

// Custom retry policy
$config = new FinvaldaConfig(
    // ...
    retry: new RetryPolicy(
        maxAttempts: 5,
        delayMs: 200,
        multiplier: 2.0,
        maxDelayMs: 10000,
        retryableStatusCodes: [429, 500, 502, 503, 504],
        retryOnNetworkError: true,
    ),
);

// Conservative policy (longer delays)
$config = new FinvaldaConfig(
    // ...
    retry: RetryPolicy::conservative(),
);

// Disable retries
$config = new FinvaldaConfig(
    // ...
    retry: RetryPolicy::noRetry(),
);
```

### Custom HTTP Client (Testing)

[](#custom-http-client-testing)

Inject a custom Guzzle client for testing or custom configuration:

```
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

$mock = new MockHandler([
    new Response(200, [], json_encode(['AccessResult' => 'Success', 'items' => []])),
]);

$httpClient = new \Finvalda\HttpClient($config, new Client(['handler' => HandlerStack::create($mock)]));
$finvalda = new Finvalda($config, $httpClient);
```

Typed DTOs &amp; Collections
----------------------------

[](#typed-dtos--collections)

The SDK provides typed Data Transfer Objects for better IDE support and type safety.

### Finding Entities

[](#finding-entities)

Use `find()` to get a single entity as a typed DTO with full IDE autocomplete:

```
use Finvalda\Data\Client;
use Finvalda\Data\Product;
use Finvalda\Data\Service;
use Finvalda\Exceptions\NotFoundException;

// Find a client - returns typed Client DTO
$client = $finvalda->clients()->find('CLI001');
echo $client->name;           // Full IDE autocomplete
echo $client->email;
echo $client->vatCode;
echo $client->debt;

// Find a product
$product = $finvalda->products()->find('PRD001');
echo $product->name;
echo $product->price1;
echo $product->barcode;
echo $product->supplier1;

// Find a service
$service = $finvalda->services()->find('SVC001');
echo $service->name;
echo $service->price;

// Handle not found
try {
    $client = $finvalda->clients()->find('NONEXISTENT');
} catch (NotFoundException $e) {
    echo "Client not found";
}

// Access raw API data if needed
$rawData = $client->raw;
$specificField = $client['sSpecialField']; // ArrayAccess supported
```

### Working with Collections

[](#working-with-collections)

Use `collect()` to get typed collections with powerful filtering and transformation methods:

```
use Finvalda\Collections\ClientCollection;
use Finvalda\Collections\ProductCollection;

// Get all clients as a typed collection
$clients = $finvalda->clients()->collect();

// Filter clients with debt
$debtors = $clients->withDebt();
$totalDebt = $clients->totalDebt();

// Filter by type
$vipClients = $clients->whereType('VIP');

// Find by code within collection
$client = $clients->findByCode('CLI001');

// Get products
$products = $finvalda->products()->collect();

// Filter by type
$electronics = $products->whereType('ELECTRONICS');

// Filter by supplier
$fromSupplier = $products->whereSupplier('SUP001');

// Products with stock
$inStock = $products->withStock();

// Filter by tag
$tagged = $products->whereTag(1, 'FEATURED');

// Find by barcode
$product = $products->findByBarcode('1234567890123');

// Collection methods work on all collections
$clients->count();                    // Count items
$clients->isEmpty();                  // Check if empty
$clients->isNotEmpty();               // Check if not empty
$clients->first();                    // Get first item
$clients->last();                     // Get last item
$clients->get(5);                     // Get by index
$clients->all();                      // Get as array

// Filtering and mapping
$filtered = $clients->filter(fn($c) => $c->debt > 1000);
$names = $clients->map(fn($c) => $c->name);
$codes = $clients->pluck('code');

// Iteration
foreach ($clients as $client) {
    echo "{$client->code}: {$client->name}\n";
}

// Execute callback for each
$clients->each(function($client) {
    sendReminder($client);
});

// Group by field
$byType = $clients->groupBy(fn($c) => $c->type);
foreach ($byType as $type => $typeClients) {
    echo "{$type}: {$typeClients->count()} clients\n";
}

// Convert to array
$array = $clients->toArray();

// Date filtering
$recentClients = $finvalda->clients()->collect(modifiedSince: '2024-01-01');
$newProducts = $finvalda->products()->collect(createdSince: '2024-06-01');
```

Fluent Operation Builders
-------------------------

[](#fluent-operation-builders)

Create operations using an intuitive fluent interface instead of complex nested arrays.

### Creating a Sale

[](#creating-a-sale)

```
// Fluent builder (new way)
$result = $finvalda->sale()
    ->client('CLI001')
    ->date('2024-01-15')
    ->warehouse('MAIN')
    ->currency('EUR')
    ->description('January order')
    ->documentNumber('ORD-2024-001')
    ->paymentDays(30)
    ->priceType(1)
    ->discount(5.0)
    ->object1('DEPT01')
    ->object2('PROJ01')
    ->employee('JONAS')
    ->exportToIvaz()
    ->roundingAmount(0.01)
    ->addProduct('PRD001', quantity: 10, price: 19.99)
    ->addProduct('PRD002', quantity: 5, amount: 49.95)
    ->addService('SVC001', quantity: 2, price: 50.00)
    ->save('STANDARD');

// Equivalent array-based approach (old way - still supported)
$result = $finvalda->operations()->create(OperationClass::Sale, [
    'sKlientas' => 'CLI001',
    'tData' => '2024-01-15',
    'sSandelis' => 'MAIN',
    'sValiuta' => 'EUR',
    'sAprasymas' => 'January order',
    'sDokumentas' => 'ORD-2024-001',
    'nAtsiskDien' => 30,
    'nKainosTipas' => 1,
    'dNuolaida' => 5.0,
    'sObjektas1' => 'DEPT01',
    'sObjektas2' => 'PROJ01',
    'PardDokPrekeDetEil' => [
        ['sKodas' => 'PRD001', 'nKiekis' => 10, 'dKaina' => 19.99],
        ['sKodas' => 'PRD002', 'nKiekis' => 5, 'dSumaV' => 49.95],
    ],
    'PardDokPaslaugaDetEil' => [
        ['sKodas' => 'SVC001', 'nKiekis' => 2, 'dKaina' => 50.00],
    ],
], 'STANDARD');
```

### Using Line DTOs (Recommended for Accounting)

[](#using-line-dtos-recommended-for-accounting)

For operations that need VAT, amounts in EUR, objects per line, or other detail fields, use `ProductLine` and `ServiceLine` DTOs for full IDE discoverability:

```
use Finvalda\Builders\ProductLine;
use Finvalda\Builders\ServiceLine;

$result = $finvalda->sale()
    ->client('CLI001')
    ->date('2024-01-15')
    ->currency('EUR')
    ->series('SF')
    ->employee('JONAS')
    ->product(
        ProductLine::make('MILTAI', 12.25)
            ->warehouse('CENTR.')
            ->amount(161.16, local: 161.16)
            ->vat(percent: 21, amount: 33.84, amountLocal: 33.84)
    )
    ->product(
        ProductLine::make('PIENAS', 5)
            ->warehouse('CENTR.')
            ->price(5.00)
            ->vat(percent: 21)
            ->discount(percent: 5.0)
            ->object(1, 'DEPT01')
            ->object(4, '1234567')        // sparse objects — level 2,3 skipped
    )
    ->service(
        ServiceLine::make('TRANSPORT', 1)
            ->amount(50.00, local: 50.00)
            ->vat(percent: 21, amount: 10.50, amountLocal: 10.50)
            ->object(1, 'DEPT01')
    )
    ->save('STANDARD');
```

The `product()` / `service()` methods accept line DTOs. The existing `addProduct()` / `addService()` / `addProductLine()` / `addServiceLine()` methods still work — you can mix both styles in the same builder.

**Available ProductLine methods:** `price()`, `amount()`, `vat()`, `discount()`, `warehouse()`, `object()`, `objects()`, `vatCode()`, `intrastat()`, `weight()`, `firstMeasurement()`, `secondMeasurement()`, `info()`, `marked()`, `set()`

**Available ServiceLine methods:** `price()`, `amount()`, `vat()`, `discount()`, `object()`, `objects()`, `vatCode()`, `description()`, `firstMeasurement()`, `info()`, `marked()`, `set()`

#### Quantity convention (important)

[](#quantity-convention-important)

Every product/service code (`sKodas`) is configured in Finvalda with a measurement unit that has **two dimensions** — a *first* (primary) unit, a *second* unit, and a **first/second ratio** (`pirm_antr_sant`, from `references()->measurementUnits()`). Examples: `M` → first = m, second = cm, ratio 100; `KG` → first = kg, second = g, ratio 1000; `VNT` → ratio 1. The `nPirmasMat` flag selects which dimension `nKiekis`is read in:

`nPirmasMat`How Finvalda reads `nKiekis``1` (sent)In the **first (primary)** unit, **verbatim**. `250` on an "M" product = 250 m.absentIn the **second** unit, then **rescaled by the unit's ratio**. `250` on an "M" product = 250 cm = **2.5 m**.The second-measurement default is **not a no-op** — Finvalda divides by the ratio. `VNT` products (ratio 1) are unaffected, which is why piece quantities never exposed this. All official Finvalda API examples send product lines with `"nPirmasMat":"1"`.

Services additionally use a **×100 fixed-point** encoding for the second measurement (`1 → 100`, `0.5 → 50`); products do not. From the spec:

> **Service** `nKiekis` — *paslaugos kiekis antru matavimu (integer) arba pirmu jei nurodyta nPirmasMat=1. **Kiekis padaugintas iš 100.** Jeigu reikalingas kiekis 0.5 tada nKiekis = 50, jeigu reikalingas kiekis 1 tada nKiekis = 100. Jeigu naudojamas pirmas matavimas dauginti nereikia.*
>
> **Product** `nKiekis` — *prekės kiekis antru matavimu (integer) arba pirmu jei nurodyta nPirmasMat=1.* (no ×100)

##### What the SDK does for you

[](#what-the-sdk-does-for-you)

Builder helperDefaultOpt-out`ProductLine::make($code, $qty)`**First measurement**: emits `nKiekis = $qty` verbatim and `nPirmasMat = 1``->secondMeasurement()` (or `->firstMeasurement(false)`) drops `nPirmasMat` so Finvalda rescales by the unit ratio`ServiceLine::make($code, $qty)`**Second measurement**: emits `nKiekis = round($qty × 100)``->firstMeasurement()` emits `nKiekis = $qty` as-is and `nPirmasMat = 1``addProduct()`Emits `nKiekis` **verbatim** and `nPirmasMat = 1`Pass `additionalData: ['nPirmasMat' => 0]`, or use `addProductLine()` for raw control`addService()`Emits `nKiekis` **verbatim** (no scaling, no `nPirmasMat`)Pass `nPirmasMat` via `additionalData``addProductLine()` / `addServiceLine()`Emit the line array **verbatim** (no defaults)—> **Why product lines default to `nPirmasMat=1`:** real quantities are expressed in the primary unit (you book "250 metres", not "25000 cm"). Omitting the flag silently divided M/KG quantities by their ratio. Both `ProductLine::make()` and the `addProduct()` helper default to the primary unit; `addProductLine()` stays a raw passthrough for full control.

```
// Product (default, first measurement): qty sent verbatim in the primary unit
$finvalda->sale()->client('CLI001')->product(
    ProductLine::make('1141817', 250.0)        // nKiekis = 250, nPirmasMat = 1 → 250 m
)->save('STANDARD');

// Product, second measurement: Finvalda rescales by the unit ratio (rarely what you want)
$finvalda->sale()->client('CLI001')->product(
    ProductLine::make('1141817', 250.0)->secondMeasurement()  // nKiekis = 250 → 2.5 m on an "M" product
)->save('STANDARD');

// Service, second measurement (default): qty 1 → nKiekis 100, 0.5 → 50
$finvalda->sale()->client('CLI001')->service(
    ServiceLine::make('TRANSPORT', 0.5)        // nKiekis = 50
)->save('STANDARD');

// Service, first measurement: qty sent as-is
$finvalda->sale()->client('CLI001')->service(
    ServiceLine::make('TRANSPORT', 1)->firstMeasurement()   // nKiekis = 1, nPirmasMat = 1
)->save('STANDARD');
```

For rare/niche API fields not covered by named methods, use the `set()` escape hatch:

```
ProductLine::make('SPECIAL', 1)
    ->warehouse('CENTR.')
    ->vat(percent: 21)
    ->set('sAtitSer', 'CERT-001')     // conformity certificate
    ->set('tGalData', '2025-12-31')   // expiry date
```

### Creating a Purchase

[](#creating-a-purchase)

```
use Finvalda\Enums\DocumentType;

$result = $finvalda->purchase()
    ->client('SUP001')
    ->date('2024-01-15')
    ->warehouse('MAIN')
    ->currency('EUR')
    ->series('SF')                          // sSerija — document series
    ->documentType(DocumentType::VatInvoice) // sDokRusis — or pass 'SF'
    ->supplierInvoice('INV-2024-001')
    ->supplierInvoiceDate('2024-01-14')
    ->paymentDays(60)
    ->addProduct('PRD001', quantity: 100, price: 9.99)
    ->addProduct('PRD002', quantity: 50, price: 14.99)
    ->addService('FREIGHT', quantity: 1, amount: 150.00)
    ->save('STANDARD');
```

#### Document type, series &amp; the operation parameter

[](#document-type-series--the-operation-parameter)

`series()`, `documentType()`, and the `save()` parameter are **three independent inputs** to a create call — none of them is derived from the others:

Builder methodAPI fieldControls`documentType()``sDokRusis`The document type (see codes below)`series()``sSerija`The document series`save('STANDARD')``sParametras`The server-configured import/journal profile`documentType()` accepts a `DocumentType` enum case or a raw 2-character code:

Code`DocumentType` caseMeaning`S``Invoice`Sąskaita faktūra`SF``VatInvoice`PVM sąskaita faktūra`D``DebitInvoice`Debetinė sąskaita`DS``DebitVatInvoice`Debetinė PVM sąskaita`K``CreditInvoice`Kreditinė sąskaita`KS``CreditVatInvoice`Kreditinė PVM sąskaita faktūra`KT``Other`Kita`VS``LawyerVatInvoice`Advokatų PVM sąskaita faktūra`VD``LawyerVatInvoiceDebit`Advokatų PVM sąskaita faktūra debetinė`VK``LawyerVatInvoiceCredit`Advokatų PVM sąskaita faktūra kreditinėThese methods are available on `sale()`, `salesReservation()`, `salesReturn()`, `purchase()`, `purchaseOrder()`, and `purchaseReturn()`.

> **`sParametras` is required and cannot be bypassed.** The operation *type*(purchase vs. sale, i.e. `ItemClassName`) is what you pick by choosing the builder, and you set `sDokRusis`/`sSerija` yourself — but the journal an operation lands in is resolved server-side from the `sParametras` profile you pass to `save()`. There is no header field to specify the journal directly on create; the resulting journal/number come back on the `OperationResult`.

### Creating an Internal Transfer

[](#creating-an-internal-transfer)

```
$result = $finvalda->internalTransfer()
    ->date('2024-01-15')
    ->fromWarehouse('MAIN')      // header field sIsSandelio
    ->toWarehouse('BRANCH')      // header field sISandeli
    ->description('Restock branch warehouse')
    ->addTransfer('PRD001', quantity: 50)
    ->addTransfer('PRD002', quantity: 25)
    ->save('TRANSFER');
```

An internal transfer carries a **single** source/destination warehouse pair at the header level — the detail rows have no per-line warehouse fields. To move stock between different warehouse pairs, create separate transfer operations.

### Creating Returns

[](#creating-returns)

```
// Sales return
$result = $finvalda->salesReturn()
    ->client('CLI001')
    ->date('2024-01-20')
    ->warehouse('MAIN')
    ->originalDocument('SF-001', 'PARD', 123)
    ->reason('Defective product')
    ->addProduct('PRD001', quantity: 2, price: 19.99)
    ->save('RETURN');

// Purchase return
$result = $finvalda->purchaseReturn()
    ->client('SUP001')
    ->date('2024-01-20')
    ->warehouse('MAIN')
    ->originalDocument('PO-001', 'PIRK', 456)
    ->reason('Wrong items delivered')
    ->addProduct('PRD001', quantity: 10, price: 9.99)
    ->save('RETURN');
```

### Creating Payments

[](#creating-payments)

```
// Payment received (inflow)
$result = $finvalda->inflow()
    ->client('CLI001')
    ->date('2024-01-15')
    ->amount(500.00)
    ->currency('EUR')
    ->bankAccount('BANK01')
    ->description('Payment for invoice SF-001')
    ->forDocument('SF-001', 'PARD', 123, amount: 500.00)
    ->save('INFLOW');

// Payment out (disbursement)
$result = $finvalda->disbursement()
    ->client('SUP001')
    ->date('2024-01-15')
    ->amount(1000.00)
    ->currency('EUR')
    ->bankAccount('BANK01')
    ->description('Payment for purchase PO-001')
    ->forDocument('PO-001', 'PIRK', 456, amount: 1000.00)
    ->save('DISBURSEMENT');
```

### Builder Advanced Usage

[](#builder-advanced-usage)

```
// Set the parameter once
$sale = $finvalda->sale()->parameter('STANDARD');

// Add custom header fields
$sale->setHeader('sCustomField', 'value');

// Add product lines with additional data
$sale->addProduct('PRD001', quantity: 10, price: 19.99, warehouse: 'WH01', additionalData: [
    'sLot' => 'LOT001',
    'tExpiryDate' => '2025-12-31',
]);

// Add raw product line
$sale->addProductLine([
    'sKodas' => 'PRD002',
    'nKiekis' => 5,
    'dKaina' => 29.99,
    'sSandelis' => 'WH01',
    'sSerialNumber' => 'SN12345',
]);

// Build without saving (for inspection)
$data = $sale->build();
print_r($data);

// Save
$result = $sale->save();
```

### Write-Offs &amp; Capitalization

[](#write-offs--capitalization)

```
// Write-off (disposal of inventory)
$result = $finvalda->writeOff()
    ->date('2024-01-15')
    ->name('Monthly write-off')
    ->note('Damaged goods')
    ->employee('Jonas')
    ->addItem('PRD001', quantity: 5, warehouse: 'MAIN', account: '6110')
    ->addItem('PRD002', quantity: 3, warehouse: 'MAIN', account: '6110')
    ->save('WRITEOFF');

// Capitalization (receiving inventory)
$result = $finvalda->capitalization()
    ->date('2024-01-15')
    ->name('Inventory receiving')
    ->addItem('PRD001', quantity: 10, amount: 199.90, warehouse: 'MAIN', account: '2010')
    ->save('CAPITALIZE');
```

### Creating a Production Operation

[](#creating-a-production-operation)

```
$result = $finvalda->production()
    ->date('2024-01-15')
    ->finishedProduct('FINISHED001')
    ->documentNumber('PROD-001')
    ->description('Daily production run')
    ->addFinishedGood('FINISHED001', warehouse: 'MAIN', quantity: 100, amount: 500.00)
    ->addRawMaterial('RAW001', warehouse: 'MAIN', quantity: 200)
    ->addRawMaterial('RAW002', warehouse: 'MAIN', quantity: 50)
    ->addProductionService('SVC001', amount: 100.00, quantity: 1)
    ->save('PRODUCTION');
```

### Non-Analytical Operations

[](#non-analytical-operations)

```
$result = $finvalda->nonAnalytical()
    ->date('2024-01-15')
    ->currency('EUR')
    ->documentNumber('DEP-001')
    ->description1('Depreciation entry')
    ->addEntry('6110', 'Equipment depreciation', debitLocal: 500.00, creditLocal: 0)
    ->addEntry('1240', 'Accumulated depreciation', debitLocal: 0, creditLocal: 500.00)
    ->save('JOURNAL');
```

### Inventory Count

[](#inventory-count)

```
$result = $finvalda->inventoryCount()
    ->journal('INVENT')
    ->warehouse('01')
    ->date('2024-03-03')
    ->addItem('B.BENZINAS', quantity: 15.45, account: '1275')
    ->addItem('B.DYZELINAS', quantity: 20.00, account: '1275')
    ->save('INVENTORY');

// Append to an existing inventory count
$result = $finvalda->inventoryCount()
    ->mode(1)
    ->journal('INVENT')
    ->warehouse('01')
    ->date('2024-03-03')
    ->addItem('B.PROPANAS', quantity: 8.00, account: '1275')
    ->save('INVENTORY');
```

### Clearing / Set-Off

[](#clearing--set-off)

```
$result = $finvalda->clearing()
    ->date('2024-01-15')
    ->name('Monthly clearing')
    ->debtor('CLI001')
    ->creditor('CLI002')
    ->addDebitLine(amount: 270.00, series: 'SF', document: '001', type: 3)
    ->addCreditLine(amount: 270.00, series: 'PF', document: '002', type: 2)
    ->save('CLEARING');

// Using account entries (type 6)
$result = $finvalda->clearing()
    ->date('2024-01-15')
    ->debtor('CLI001')
    ->creditor('CLI002')
    ->addDebitAccount(amount: 270.00, account: '241000')
    ->addCreditAccount(amount: 270.00, account: '241001')
    ->save('CLEARING');
```

### UVM (Order Management)

[](#uvm-order-management)

```
// UVM sales reservation (workshop/service order)
$result = $finvalda->uvmSalesReservation()
    ->client('HTNT')
    ->date('2024-01-15')
    ->operationType('PARDSERV')
    ->fulfillmentDate('2024-01-20')
    ->currency('EUR')
    ->object1('SERVISAS')
    ->description('Workshop order #30608')
    ->addService('5054', quantity: 1, price: 0, additionalData: [
        'sPavadinimas' => 'Service description',
    ])
    ->save('WORKSHOP');

// UVM purchase order
$result = $finvalda->uvmPurchaseOrder()
    ->client('SUP001')
    ->date('2024-01-15')
    ->currency('EUR')
    ->operationType('PIRK')
    ->addProduct('PRD001', quantity: 24, price: 3.50, warehouse: 'CENTR.')
    ->save('ORDER');

// UVM cancellation
$result = $finvalda->uvmCancellation()
    ->date('2024-01-15')
    ->name('Cancel reservations')
    ->documentNumber('ANUL-001')
    ->addCancellation(journal: 'UVMPARD', number: 123)
    ->addCancellation(journal: 'UVMPARD', number: 124)
    ->save('CANCEL');
```

### Short / Simplified Operations

[](#short--simplified-operations)

All sales, purchase, and return builders support a `short()` mode that uses simplified operation variants. Short operations send minimal headers and let the server fill in defaults.

```
// Short sale — server applies default settings
$result = $finvalda->sale()
    ->short()
    ->client('CLI001')
    ->date('2024-01-15')
    ->series('SF')
    ->currency('EUR')
    ->addProduct('PRD001', quantity: 10, price: 19.99)
    ->save('STANDARD');

// Short purchase return
$result = $finvalda->purchaseReturn()
    ->short()
    ->client('SUP001')
    ->currency('EUR')
    ->series('GR')
    ->addProduct('PRD001', quantity: 10, price: 9.99)
    ->save('RETURN');
```

Builders supporting `short()`: `sale()`, `salesReservation()`, `salesReturn()`, `purchase()`, `purchaseOrder()`, `purchaseReturn()`.

Query Builders
--------------

[](#query-builders)

Build queries fluently for better readability and IDE support.

### Transaction Query

[](#transaction-query)

```
use Finvalda\Query\TransactionQuery;

// Create a fluent query
$query = TransactionQuery::create()
    ->journal('PARD')
    ->series('AA')
    ->dateRange('2024-01-01', '2024-12-31')
    ->modifiedSince('2024-06-01');

// Use with transactions resource
$response = $finvalda->transactions()->sales($query->toFilter());
$response = $finvalda->transactions()->salesDetail($query->toFilter());

// Query methods
$query = TransactionQuery::create()
    ->journal('PARD')              // Filter by journal code
    ->operationNumber(123)         // Filter by operation number
    ->series('AA')                 // Filter by document series
    ->orderNumber('SF-001')        // Filter by order/document number
    ->journalGroup('SALES_GRP')    // Filter by journal group
    ->dateFrom('2024-01-01')       // Operation date from
    ->dateTo('2024-12-31')         // Operation date to
    ->dateRange('2024-01-01', '2024-12-31')  // Both dates at once
    ->modifiedSince('2024-06-01'); // Only modified since
```

### Operation Query

[](#operation-query)

```
use Finvalda\Query\OperationQuery;

// Factory methods for common operation types
$query = OperationQuery::sales()
    ->journal('PARD')
    ->dateRange('2024-01-01', '2024-12-31')
    ->client('CLI001');

// Use with operations resource
$response = $finvalda->operations()->query($query->opClass(), $query->build());

// All factory methods
$query = OperationQuery::sales();
$query = OperationQuery::salesDetail();
$query = OperationQuery::purchases();
$query = OperationQuery::purchasesDetail();
$query = OperationQuery::inflows();
$query = OperationQuery::inflowsDetail();
$query = OperationQuery::disbursement();
$query = OperationQuery::disbursementDetail();
$query = OperationQuery::internalTransactions();
$query = OperationQuery::internalTransactionsDetail();
$query = OperationQuery::forClass(OpClass::SalesReturns);

// Query methods
$query = OperationQuery::sales()
    ->journal('PARD')
    ->number(123)
    ->series('AA')
    ->client('CLI001')
    ->warehouse('WH01')
    ->product('PRD001')
    ->dateFrom('2024-01-01')
    ->dateTo('2024-12-31')
    ->modifiedSince('2024-06-01')
    ->journalGroup('SALES_GRP')
    ->object1('DEPT01')
    ->object2('PROJ01');
```

Validation
----------

[](#validation)

Validate data before sending to the API to catch errors early:

```
use Finvalda\Validation\Validator;
use Finvalda\Validation\Rules\Required;
use Finvalda\Validation\Rules\StringLength;
use Finvalda\Validation\Rules\NumericRange;
use Finvalda\Validation\Rules\DateFormat;
use Finvalda\Exceptions\ValidationException;

// Define validation rules
$validator = new Validator([
    'sKodas' => [new Required(), StringLength::max(50)],
    'sPavadinimas' => [new Required(), StringLength::max(200)],
    'dKaina' => [NumericRange::positive()],
    'tData' => [DateFormat::ymd()],
]);

// Validate data
$result = $validator->validate([
    'sKodas' => 'PRD001',
    'sPavadinimas' => 'Product Name',
    'dKaina' => 19.99,
    'tData' => '2024-01-15',
]);

if ($result->fails()) {
    foreach ($result->errors as $field => $errors) {
        echo "{$field}: " . implode(', ', $errors) . "\n";
    }
}

// Or validate and throw exception
try {
    $validator->validateOrFail($data);
} catch (ValidationException $e) {
    $errors = $e->getErrors();
    $allMessages = $e->getAllErrors();
}

// Quick validation
$result = Validator::check($data, [
    'sKodas' => [new Required()],
    'sPavadinimas' => [new Required(), StringLength::between(3, 200)],
]);

// Available rules
new Required();                          // Field is required
new Required('Custom message');          // With custom message
StringLength::max(50);                   // Max 50 characters
StringLength::min(3);                    // Min 3 characters
StringLength::between(3, 50);            // Between 3 and 50
StringLength::exact(10);                 // Exactly 10 characters
NumericRange::positive();                // >= 0
NumericRange::positiveNonZero();         // > 0
NumericRange::min(10);                   // >= 10
NumericRange::max(100);                  //  The lengths above are illustrative. For the **real** per-field maximum lengths, see the [Field Reference](#field-reference) — e.g. a client `sKodas` is max 15 chars, `sPavadinimas` max 100.

Field Reference
---------------

[](#field-reference)

The API enforces per-field **maximum text lengths** and **numeric precision**, and marks each field as mandatory or auto-filled from the server parameter profile. This is documented exhaustively in the official spec, which is included in this repo:

**→ [`docs/FVS_Webservice.md`](docs/FVS_Webservice.md)** (the single source of truth)

Look up the write payload you're building:

- **Master data** (`InsertNewItem`) — `Fvs.Preke` (products), `Fvs.Paslauga`(services), `Fvs.Klientas` (clients), objects, banks, warehouses, types/tags.
- **Operations** (`InsertNewOperation` / `UpdateOperation`) — `PardDok` (sales), `PirkDok` (purchases), `IplDok`/`IsmDok` (payments), `VidPerkDok` (transfers), `NurasymasDok`/`PajamavimasDok` (write-off/capitalization), `GamybaDok` (production), `UzskaitaDok` (clearing), `KtNeanalitDok` (non-analytical), UVM, and their `*DetEil`detail lines.

Each field is a table row with these columns: **type · field name · description · max length · required · auto-filled-from-parameter · notes**. A `+` in the *required* column means mandatory; a `+` in the *auto-filled* column means the webservice supplies it from the parameter profile when omitted. For example, client `sKodas` is `String` max **15**, required; `sPavadinimas` max **100**; `sEMail` max **30**, optional. Money amounts are `Numeric (14,2)`.

Resources
---------

[](#resources)

All read methods return a `Response` object:

```
$response = $finvalda->clients()->list();

$response->successful();  // bool - whether the request succeeded
$response->failed();      // bool - whether the request failed
$response->data;          // array - the response data
$response->error;         // ?string - error message if failed
$response->raw;           // array - full raw response
```

All write methods return an `OperationResult` object:

```
$result = $finvalda->clients()->create($data);

$result->success;     // bool
$result->series;      // ?string
$result->document;    // ?string
$result->journal;     // ?string
$result->number;      // ?int
$result->error;       // ?string
$result->errorCode;   // ?int
```

### Stock / Inventory

[](#stock--inventory)

```
// Current stock balances
$response = $finvalda->stock()->balances();
$response = $finvalda->stock()->balances(productCode: 'PROD001', warehouseCode: 'WH01');

// Extended balances (includes product type, tags)
$response = $finvalda->stock()->balancesExtended();

// Balances with selling prices
$response = $finvalda->stock()->balancesWithPrices(includeZeroQuantity: true);

// Balances by warehouse group
$response = $finvalda->stock()->balancesByGroup(warehouseGroupCode: 'GROUP1');

// Ordered products
$response = $finvalda->stock()->orderedProducts();
```

### Clients

[](#clients)

```
use Finvalda\Enums\ClientTypeId;

// List / find / collect
$response = $finvalda->clients()->list();
$response = $finvalda->clients()->list(modifiedSince: '2024-01-01');
$response = $finvalda->clients()->get('CLIENT001');
$client = $finvalda->clients()->find('CLIENT001');      // Returns typed Client DTO
$clients = $finvalda->clients()->collect();             // Returns ClientCollection

// All clients (with optional date filters)
$response = $finvalda->clients()->all();
$response = $finvalda->clients()->all(modifiedSince: '2024-01-01');

// Find client by email
$response = $finvalda->clients()->findByEmail('client@example.com');

// Client types and tags (use enum for type-safety)
$response = $finvalda->clients()->typesAndTags(ClientTypeId::Type);   // Client types
$response = $finvalda->clients()->typesAndTags(ClientTypeId::Tag1);   // Tag 1 options
$response = $finvalda->clients()->typesAndTags(ClientTypeId::Tag2);   // Tag 2 options
$response = $finvalda->clients()->typesAndTags(ClientTypeId::Tag3);   // Tag 3 options
$response = $finvalda->clients()->typesAndTags(22);                   // Also accepts int

// Clients by type
$response = $finvalda->clients()->byType('VIP');

// Accounts (with full filter support)
$response = $finvalda->clients()->accounts(clientCode: 'CLIENT001');
$response = $finvalda->clients()->accounts(
    clientCode: 'CLIENT001',
    journalGroup: 'PARD',
    debtType: 1,
    documentDateFrom: '2024-01-01',
    documentDateTo: '2024-12-31',
);

// Unpaid documents (sales and purchases)
$response = $finvalda->clients()->unpaidDocuments('CLIENT001');
$response = $finvalda->clients()->unpaidPurchaseDocuments('CLIENT001');

// Client debt condition
$response = $finvalda->clients()->debtCondition('CLIENT001', journalGroup: 'PARD');

// Settlements
$response = $finvalda->clients()->settlements(series: 'SER', document: 'DOC001');
$response = $finvalda->clients()->settlements(journal: 'PARD', number: 123);
$response = $finvalda->clients()->settlementsDetailed(series: 'SER', document: 'DOC001');
$response = $finvalda->clients()->settlementsFromDate(series: 'SER', modifiedSince: '2024-01-01');
$response = $finvalda->clients()->settlementsFromDateParam($xmlParam); // raw XML param variant

// CRUD operations
$result = $finvalda->clients()->create([
    'sKodas' => 'NEW001',
    'sPavadinimas' => 'New Client Ltd',
    'sDebtSask' => '2410',
    'sKredSask' => '5001',
]);

$result = $finvalda->clients()->update([
    'sKodas' => 'NEW001',
    'sPavadinimas' => 'Updated Client Name',
]);

$result = $finvalda->clients()->delete('CLIENT001');

// Invoices related to a customer
$response = $finvalda->clients()->invoicesRelatedToCustomer('CLIENT001', debtType: 0);
```

### Products

[](#products)

```
use Finvalda\Enums\ProductTypeId;

// List / find / collect
$response = $finvalda->products()->list();
$response = $finvalda->products()->get('PROD001');
$product = $finvalda->products()->find('PROD001');      // Returns typed Product DTO
$products = $finvalda->products()->collect();           // Returns ProductCollection

// Extended list with filters
$response = $finvalda->products()->listExtended(
    type: 'ELECTRONICS',
    supplier1: 'SUPP01',
    modifiedSince: '2024-01-01',
);

// All products
$response = $finvalda->products()->all(modifiedSince: '2024-01-01');

// Product image (envelope with base64 `fileContents`)
$response = $finvalda->products()->image('PROD001');

// Product image as decoded JPG bytes
$jpg = $finvalda->products()->imageJpeg('PROD001');

// Products in warehouse
$response = $finvalda->products()->inWarehouse('WH01', modifiedSince: '2024-01-01');
$response = $finvalda->products()->inWarehouseOrdered('WH01', order: 1);

// Types and tags (use enum for type-safety)
$response = $finvalda->products()->typesAndTags(ProductTypeId::Type);  // Product types
$response = $finvalda->products()->typesAndTags(ProductTypeId::Tag1);  // Tag 1 options
$response = $finvalda->products()->typesAndTags(ProductTypeId::Tag2);  // Tag 2-6...
$response = $finvalda->products()->typeGroups();
$response = $finvalda->products()->typeGroupComposition('GRP01');
$response = $finvalda->products()->byType('ELECTRONICS');

// Product history
$response = $finvalda->products()->history('PROD001', dateFrom: '2024-01-01');

// Sold products per period
$response = $finvalda->products()->soldPerPeriod(
    productCode: 'PROD001',
    warehouseCode: 'WH01',
    dateFrom: '2024-01-01',
    dateTo: '2024-12-31',
);

// CRUD operations
$result = $finvalda->products()->create([
    'sKodas' => 'NEWPROD',
    'sPavadinimas' => 'New Product',
    'sRysysSuSask' => '2414',
    'sMatavimoVnt' => 'vnt',
]);

$result = $finvalda->products()->update([
    'sKodas' => 'PROD001',
    'sPavadinimas' => 'Updated Product Name',
]);

// Bulk edit product properties (applies to multiple products at once)
$result = $finvalda->products()->editProperties([
    'Kodas' => ['PROD001', 'PROD002', 'PROD003'],
    'pardKaina1' => '19.99',
    'pardVal' => 'EUR',
]);

$result = $finvalda->products()->delete('PROD001');
```

### Services

[](#services)

```
use Finvalda\Enums\ServiceTypeId;

// List / find / collect
$response = $finvalda->services()->list();
$response = $finvalda->services()->get('SVC001');
$service = $finvalda->services()->find('SVC001');       // Returns typed Service DTO
$services = $finvalda->services()->collect();           // Returns ServiceCollection

// All services
$response = $finvalda->services()->all(modifiedSince: '2024-01-01');

// Types and tags (use enum for type-safety)
$response = $finvalda->services()->typesAndTags(ServiceTypeId::Type);  // Service types
$response = $finvalda->services()->typesAndTags(ServiceTypeId::Tag1);  // Tag 1 options
$response = $finvalda->services()->byType('CONSULTING');

// CRUD operations
$result = $finvalda->services()->create([
    'sKodas' => 'NEWSVC',
    'sPavadinimas' => 'New Service',
    'sRysysSuSask' => '5001',
]);

$result = $finvalda->services()->update(['sKodas' => 'SVC001', 'sPavadinimas' => 'Updated']);
$result = $finvalda->services()->delete('SVC001');
```

### Objects (6 Levels)

[](#objects-6-levels)

```
// List objects at level 1-6
$response = $finvalda->objects()->list(level: 1);
$response = $finvalda->objects()->list(level: 2, objectCode: 'OBJ001');

// Get single object
$response = $finvalda->objects()->get(level: 1, objectCode: 'OBJ001');

// Create / update
$result = $finvalda->objects()->create(level: 1, data: [
    'sKodas' => 'DEPT01',
    'sPavadinimas' => 'Sales Department',
]);

$result = $finvalda->objects()->update(level: 1, data: [
    'sKodas' => 'DEPT01',
    'sPavadinimas' => 'Updated Department',
]);
```

### Transactions (Financial Detail Data)

[](#transactions-financial-detail-data)

```
use Finvalda\Filters\TransactionFilter;
use Finvalda\Filters\PaymentFilter;
use Finvalda\Query\TransactionQuery;

// Using filter DTO
$filter = new TransactionFilter(
    dateFrom: '2024-01-01',
    dateTo: '2024-12-31',
    journalGroup: 'PARD_GRP',
);

// Or using fluent query builder
$filter = TransactionQuery::create()
    ->dateRange('2024-01-01', '2024-12-31')
    ->journalGroup('PARD_GRP')
    ->toFilter();

// Sales
$response = $finvalda->transactions()->sales($filter);
$response = $finvalda->transactions()->salesDetail($filter);
$response = $finvalda->transactions()->salesDetailWithPrimeCost($filter);

// Sale Reservations
$response = $finvalda->transactions()->saleReservations($filter);
$response = $finvalda->transactions()->saleReservationsDetail($filter);

// Sales Returns
$response = $finvalda->transactions()->salesReturns($filter);
$response = $finvalda->transactions()->salesReturnsDetail($filter);

// Purchases
$response = $finvalda->transactions()->purchases($filter);
$response = $finvalda->transactions()->purchasesDetail($filter);
$response = $finvalda->transactions()->purchasesExtendedDetail($filter);

// Purchase Orders & Returns
$response = $finvalda->transactions()->purchaseOrders($filter);
$response = $finvalda->transactions()->purchaseOrdersDetail($filter);
$response = $finvalda->transactions()->purchaseReturns($filter);
$response = $finvalda->transactions()->purchaseReturnsDetail($filter);

// Inflows with payment reference
$response = $finvalda->transactions()->inflowsDetail(
    filter: $filter,
    paymentFilter: new PaymentFilter(
        payedForDocSeries: 'AA',
        payedForDocOrderNumber: 'SF-001',
    ),
);

// Advance Payments
$response = $finvalda->transactions()->advancedPaymentsDetail(
    filter: $filter,
    client: 'CLIENT001',
    offsetStatus: 0,
);

// Disbursements & Clearing
$response = $finvalda->transactions()->disbursementsDetail($filter);
$response = $finvalda->transactions()->clearingOffsDetail($filter);

// OMM (Order Management Module)
$response = $finvalda->transactions()->ommSales($filter);
$response = $finvalda->transactions()->ommSalesDetail($filter);
$response = $finvalda->transactions()->ommPurchases($filter);
$response = $finvalda->transactions()->ommPurchasesDetail($filter);

// OMM sales filtered by a raw XML condition
$response = $finvalda->transactions()->ommSalesXmlCondition($xmlData);
$response = $finvalda->transactions()->ommSalesXmlConditionWithTitle($xmlData);

// Advance payments (extended)
$response = $finvalda->transactions()->advancedPaymentsDetailExtended(
    filter: $filter,
    client: 'CLIENT001',
    offsetStatus: 0,
);

// Fixed Assets & Currency
$response = $finvalda->transactions()->depreciationOfFixedAssets(year: 2024, month: 6);
$response = $finvalda->transactions()->depreciationOfFixedAssetsObjects(year: 2024, month: 6);
$response = $finvalda->transactions()->currencyDebtRecount($filter);

// Low Value Inventory
$response = $finvalda->transactions()->lowValueInventory();
```

### Operations (Create, Update, Delete)

[](#operations-create-update-delete)

Operations require a `$parameter` argument which is server-configured. See [Server-Configured Parameters](#server-configured-parameters).

```
use Finvalda\Enums\OperationClass;
use Finvalda\Enums\DeleteOperationClass;
use Finvalda\Enums\UpdateOperationClass;
use Finvalda\Enums\OpClass;
use Finvalda\Query\OperationQuery;

$parameter = 'STANDARD'; // Server-configured

// Create operations (prefer fluent builders - see above)
$result = $finvalda->operations()->create(OperationClass::Sale, $data, $parameter);
$result = $finvalda->operations()->create(OperationClass::Purchase, $data, $parameter);
$result = $finvalda->operations()->create(OperationClass::InternalTransfer, $data, $parameter);

// Delete an operation
$result = $finvalda->operations()->delete(
    DeleteOperationClass::Sale,
    journal: 'PARD',
    number: 123,
    parameter: $parameter,
);

// Update an operation
$result = $finvalda->operations()->update(UpdateOperationClass::Sale, [
    'sZurnalas' => 'PARD',
    'nNumeris' => 123,
    'PardDokHeadEil' => ['sPastaba' => 'Updated comment'],
], $parameter);

// Read operations with query builder
$query = OperationQuery::sales()
    ->dateRange('2024-01-01', '2024-12-31')
    ->client('CLIENT001');

$response = $finvalda->operations()->query($query->opClass(), $query->build());

// Or with arrays (sent as opReadParams, filter keys must be nested under `filter`)
$response = $finvalda->operations()->get(OpClass::Sales, [
    'filter' => [
        'OpDateFrom' => '2024-01-01',
        'OpDateTill' => '2024-12-31',
    ],
]);

// Lock / unlock operations
$finvalda->operations()->lock('PARD', 123, parameter: 'STANDARD');
$finvalda->operations()->unlock('PARD', 123);
$finvalda->operations()->unlock('PARD', 123, newJournal: 'PARD2'); // move to new journal on unlock
$response = $finvalda->operations()->isLocked('PARD', 123);

// Change journal
$result = $finvalda->operations()->changeJournal([
    'sJournal' => 'PARD',
    'nOpNumber' => 123,
    'sJournalNew' => 'PARD2',
]);

// Copy operation
$result = $finvalda->operations()->copy([
    'sParameter' => 'STANDARD',
    'sJournal' => 'PARD',
    'nOpNumber' => 123,
    'sJournalNew' => 'PARD2',
    'bDeleteSourceOp' => false,
    'bKeepDocument' => false,
]);

// Activity by analytical objects (GetVeiklaPagalObjektus)
$response = $finvalda->operations()->activityByObjects([
    'tDataNuo' => '2024-01-01',
    'tDataIki' => '2024-12-31',
    // ...object/journal filters
]);
```

### Order Management (UVM)

[](#order-management-uvm)

```
$response = $finvalda->orderManagement()->salesReservationStatus('PARD', 123);

$response = $finvalda->orderManagement()->completedReservations(
    journalGroup: 'PARD_GRP',
    dateFrom: '2024-01-01',
    dateTo: '2024-12-31',
);
$response = $finvalda->orderManagement()->pendingReservations();
$response = $finvalda->orderManagement()->cancelledReservations();
$response = $finvalda->orderManagement()->orderedProducts(dateFrom: '2024-01-01');
```

### Pricing &amp; Discounts

[](#pricing--discounts)

```
// Combined client + item prices
$response = $finvalda->pricing()->clientItemPrices(clientCode: 'CLI001', itemCode: 'PROD001');
$response = $finvalda->pricing()->clientTypeItemPrices(clientTypeCode: 'VIP', itemCode: 'PROD001');
$response = $finvalda->pricing()->clientItemTypePrices(clientCode: 'CLI001', itemTypeCode: 'ELECTRONICS');

// Product discounts and additional prices
$response = $finvalda->pricing()->clientProductDiscounts('CLI001');
$response = $finvalda->pricing()->clientProductAdditionalPrices('CLI001');
$response = $finvalda->pricing()->clientProductTypeDiscounts('CLI001');

// Service pricing
$response = $finvalda->pricing()->clientServiceDiscounts('CLI001');
$response = $finvalda->pricing()->clientServiceAdditionalPrices('CLI001');

// Client type pricing
$response = $finvalda->pricing()->clientTypeProductDiscounts('VIP');
$response = $finvalda->pricing()->clientTypeServiceDiscounts('VIP');

// The full pricing matrix follows a consistent naming scheme:
//   client[Type]  ×  Product|Service[Type]  ×  Discounts|AdditionalPrices
// All of the following are available (each takes the relevant code plus
// optional modifiedSince / createdSince date filters):
$finvalda->pricing()->clientItemTypePrices(clientCode: 'CLI001', itemTypeCode: 'ELECTRONICS');
$finvalda->pricing()->clientTypeItemPrices(clientTypeCode: 'VIP', itemCode: 'PROD001');
$finvalda->pricing()->clientTypeItemTypePrices(clientTypeCode: 'VIP', itemTypeCode: 'ELECTRONICS');
$finvalda->pricing()->clientProductTypeAdditionalPrices('CLI001');
$finvalda->pricing()->clientServiceTypeDiscounts('CLI001');
$finvalda->pricing()->clientServiceTypeAdditionalPrices('CLI001');
$finvalda->pricing()->clientTypeProductAdditionalPrices('VIP');
$finvalda->pricing()->clientTypeProductTypeDiscounts('VIP');
$finvalda->pricing()->clientTypeProductTypeAdditionalPrices('VIP');
$finvalda->pricing()->clientTypeServiceAdditionalPrices('VIP');
$finvalda->pricing()->clientTypeServiceTypeDiscounts('VIP');
$finvalda->pricing()->clientTypeServiceTypeAdditionalPrices('VIP');

// Recommended price calculation
$response = $finvalda->pricing()->recommendedPrice([
    'invoiceType' => 0,
    'invoiceDate' => ['year' => 2024, 'month' => 6, 'day' => 15],
    'itemType' => 1,
    'itemCode' => 'PROD001',
    'itemAmount' => 10,
    'warehouseCode' => 'WH01',
    'clientCode' => 'CLI001',
]);
```

### Documents

[](#documents)

```
use Finvalda\Enums\DocumentEntityType;

// Upload
$result = $finvalda->documents()->uploadFile('invoice.pdf', '/path/to/invoice.pdf');
$result = $finvalda->documents()->upload('doc.pdf', $hexContent);

// Attach to entity
$result = $finvalda->documents()->attach(
    DocumentEntityType::Sale,
    entityCode: 'CLI001',
    filename: 'invoice.pdf',
    journal: 'PARD',
    number: 123,
);

// Get attached documents
$response = $finvalda->documents()->attached(DocumentEntityType::Client, 'CLI001');

// Delete
$result = $finvalda->documents()->delete('invoice.pdf');
```

### Reports &amp; Invoices

[](#reports--invoices)

```
// Recommended: pass params as an array and get decoded PDF bytes back
$pdf = $finvalda->reports()->makeInvoicePdf([
    'FakturosKodas' => 'PARD_01',
    'sSerija' => 'AAA',
    'sDokumentas' => '123',
    'sZurnalas' => '$PARD.',
    'nNumeris' => 45151,
]);

$pdf = $finvalda->reports()->makeReportPdf([
    'code' => 'PARDSAR_01',
    'DateFrom' => '2024-01-01',
    'DateTo' => '2024-01-31',
]);

// Low-level response methods remain available when you need the API envelope
$response = $finvalda->reports()->makeInvoice(['FakturosKodas' => 'PARD_01']);
$response = $finvalda->reports()->makeReport(['code' => 'PARDSAR_01']);
$response = $finvalda->reports()->autoReports();
$response = $finvalda->reports()->autoReport('report_filename.pdf');
$pdf = $finvalda->reports()->autoReportPdf('report_filename.pdf');
```

### Descriptions (Universal Query)

[](#descriptions-universal-query)

```
use Finvalda\Enums\DescriptionType;

// The SDK nests filters under the correct key per description type. Pass only
// the inner filter contents; the wrapping (StockOnDate, Products, Series, ...)
// is handled for you.
$response = $finvalda->descriptions()->get(DescriptionType::Products, [
    'Codes' => ['PROD001', 'PROD002'],
], page: 1, limit: 50);

// Convenience methods
$response = $finvalda->descriptions()->stockOnDate('2024-06-15', ['Warehouse' => 'WH01']);
$response = $finvalda->descriptions()->products(['Type' => 'ELECTRONICS']);
$response = $finvalda->descriptions()->clients(['Email' => 'client@example.com']);
$response = $finvalda->descriptions()->services();
$response = $finvalda->descriptions()->currentStock(['Warehouse' => 'WH01']);
$response = $finvalda->descriptions()->fixedAssets();
$response = $finvalda->descriptions()->barCodes(['Codes' => ['PROD001']]);
$response = $finvalda->descriptions()->prices(['Client' => 'CLI001']);
$response = $finvalda->descriptions()->currencyRates('2024-01-01', '2024-12-31', ['USD', 'GBP']);

// Additional description types
$response = $finvalda->descriptions()->get(DescriptionType::OperationStatuses);
$response = $finvalda->descriptions()->get(DescriptionType::Accounts);
$response = $finvalda->descriptions()->get(DescriptionType::Vehicles);
$response = $finvalda->descriptions()->get(DescriptionType::ProductionItem, [
    'Codes' => ['PROD001'],
]);
$response = $finvalda->descriptions()->get(DescriptionType::PartnerProducts, [
    'Codes' => ['PROD001'],
    'Client' => 'CLI001',
]);

// Convenience helpers for grouping/reference description types
$response = $finvalda->descriptions()->typesAndTags('product', number: 1); // 'product'|'service'|'client'
$response = $finvalda->descriptions()->clientGroups();
$response = $finvalda->descriptions()->warehouseGroups();
$response = $finvalda->descriptions()->logbookGroups();      // journal (logbook) groups
$response = $finvalda->descriptions()->opTypeGroups();       // operation-type groups
$response = $finvalda->descriptions()->documentSeries(type: 1);
$response = $finvalda->descriptions()->calendarEvents('USERNAME', ['DateFrom' => '2024-01-01']);
$response = $finvalda->descriptions()->vehicles();
$response = $finvalda->descriptions()->invoiceList(opClass: 'PARD');
$response = $finvalda->descriptions()->reportList(class: 'PARDSAR');
```

### Reference Data

[](#reference-data)

```
$response = $finvalda->references()->measurementUnits();
$response = $finvalda->references()->warehouses();
$response = $finvalda->references()->taxes();
$response = $finvalda->references()->paymentTerms();
$response = $finvalda->references()->user();
$response = $finvalda->references()->materiallyResponsiblePersons();        // optional code filter

// Update existing reference entities
$result = $finvalda->references()->updateWarehouse(['sKodas' => 'WH03', 'sPavadinimas' => 'Renamed']);
$result = $finvalda->references()->updatePaymentTerm(['sKodas' => 'NET30', 'sPavadinimas' => 'Net 30 days']);

// Append an item to a group (AppendGroup)
$result = $finvalda->references()->addToGroup(
    itemClassName: 'Fvs.Preke',
    groupCode: 'GRP01',
    itemCode: 'PROD001',
);

// Create reference entities
$result = $finvalda->references()->createBank(['sKodas' => 'BNK01', 'sPavadinimas' => 'My Bank']);
$result = $finvalda->references()->createWarehouse(['sKodas' => 'WH03', 'sPavadinimas' => 'Warehouse 3']);
$result = $finvalda->references()->createPaymentTerm(['sKodas' => 'NET30', 'sPavadinimas' => 'Net 30']);
$result = $finvalda->references()->createClientType(['sKodas' => 'VIP', 'sPavadinimas' => 'VIP Clients']);
$result = $finvalda->references()->createProductType(['sKodas' => 'ELEC', 'sPavadinimas' => 'Electronics']);

// Create product tag values (tags 1-20)
$result = $finvalda->references()->createProductTag(1, ['sKodas' => 'FEAT', 'sPavadinimas' => 'Featured']);
$result = $finvalda->references()->createProductTag(7, ['sKodas' => 'NEW', 'sPavadinimas' => 'New Arrival']);
```

### User Permissions

[](#user-permissions)

```
$response = $finvalda->permissions()->warehouses();
$response = $finvalda->permissions()->clients();
$response = $finvalda->permissions()->operationTypes();
$response = $finvalda->permissions()->operationJournals();
```

Pagination
----------

[](#pagination)

For large datasets, use lazy pagination with the `Cursor` class:

```
use Finvalda\Pagination\Cursor;
use Finvalda\Pagination\LazyCollection;

// Create a cursor for clients
$cursor = new Cursor(
    fetcher: fn($modifiedSince, $createdSince) =>
        $finvalda->clients()->all($modifiedSince, $createdSince)->data,
    dateExtractor: fn($item) => isset($item['tKoregavimoData'])
        ? new \DateTime($item['tKoregavimoData'])
        : null,
    // Recommended: a stable identity per record so duplicates from
    // overlapping date ranges are skipped reliably. Without it, items
    // are compared by full content.
    idExtractor: fn($item) => $item['sKodas'],
);

// Iterate lazily (memory efficient)
foreach ($cursor->modifiedSince('2024-01-01')->getIterator() as $clientData) {
    echo $clientData['sPavadinimas'] . "\n";
}

// Take first N items
$first100 = $cursor->take(100);

// Get all as array
$allClients = $cursor->all();

// LazyCollection for generator-based iteration
$lazy = LazyCollection::make($finvalda->clients()->all()->data);

$filtered = $lazy
    ->filter(fn($c) => ($c['dSkola'] ?? 0) > 0)
    ->map(fn($c) => $c['sPavadinimas'])
    ->take(10)
    ->all();
```

Error Handling
--------------

[](#error-handling)

```
use Finvalda\Exceptions\FinvaldaException;
use Finvalda\Exceptions\AccessDeniedException;
use Finvalda\Exceptions\ValidationException;
use Finvalda\Exceptions\NotFoundException;
use Finvalda\Exceptions\NetworkException;
use Finvalda\Exceptions\ServerException;
use Finvalda\Exceptions\RetryExhaustedException;

try {
    $client = $finvalda->clients()->find('CLI001');
} catch (NotFoundException $e) {
    echo "Client not found";
} catch (AccessDeniedException $e) {
    echo "Access denied: {$e->getMessage()}";
} catch (NetworkException $e) {
    echo "Network error (connection failed, timeout): {$e->getMessage()}";
} catch (ServerException $e) {
    echo "Server error (5xx): {$e->getMessage()}";
} catch (RetryExhaustedException $e) {
    echo "All {$e->attempts} retry attempts failed: {$e->getMessage()}";
} catch (ValidationException $e) {
    $errors = $e->getErrors();
    $allMessages = $e->getAllErrors();
} catch (FinvaldaException $e) {
    echo "API error: {$e->getMessage()}";
}

// Check response status
$response = $finvalda->clients()->list();

if ($response->failed()) {
    echo "Error: {$response->error}";
}

// Check operation result
$result = $finvalda->clients()->create($data);

if ($result->success) {
    echo "Created: {$result->journal} #{$result->number}";
} else {
    echo "Error #{$result->errorCode}: {$result->error}";
}
```

Server-Configured Parameters
----------------------------

[](#server-configured-parameters)

Finvalda uses server-configured parameters that depend on your installation.

### sParametras (Operations)

[](#sparametras-operations)

Required for operation methods (`create`, `update`, `delete`). Tells the server which journal configuration to use.

```
$parameter = 'STANDARD'; // Your server-configured value

$result = $finvalda->operations()->create(OperationClass::Sale, $data, $parameter);
$result = $finvalda->sale()->client('CLI001')->addProduct('PRD001', 10, 19.99)->save($parameter);
```

> **Deeper reference — [`docs/parameters/`](docs/parameters/):** explains what a `sParametras` profile actually contains (journal, operation type, series, document type, accounts, VAT, division, employee, Intrastat data, flags), how it is configured in the `FvsNETParamKonfig` tool, and a YAML format ([`parameters.example.yaml`](docs/parameters/parameters.example.yaml)) for cataloguing your own profiles. With that catalog filled in, an AI assistant can match a transaction to the right profile, explain a profile, or draft `FvsNETParamKonfig` setup instructions for a new one. Keep deployment-specific values out of version control (the catalog file is git-ignored).

### sFvsImportoParametras (Items)

[](#sfvsimportoparametras-items)

Optional data field for item methods. Include in data array if required:

```
$result = $finvalda->clients()->create([
    'sKodas' => 'NEW001',
    'sPavadinimas' => 'New Client Ltd',
    'sFvsImportoParametras' => 'STANDARD', // Server-configured
]);
```

### Troubleshooting Parameter Errors

[](#troubleshooting-parameter-errors)

If you receive an error like:

```
{
  "nResult": 1036,
  "sError": "Parameter 'NET_DELSPINIGIAI_SUPVM' not found in database!"
}
```

This means the parameter is not configured on your server. Contact your Finvalda administrator for valid parameter values.

API Versions
------------

[](#api-versions)

This SDK targets **V2 (FvsServicePure)** - the recommended REST interface.

VersionURL PatternDescriptionV2 (recommended)`.../FvsServicePure.svc`Clean REST JSON/XMLV1`.../FvsServiceR.svc/rest`REST with string-wrapped responsesV0`.../FvsService.asmx`SOAP + REST XMLKeeping Up to Date
------------------

[](#keeping-up-to-date)

The SDK is built from the [official Postman collection](https://documenter.getpostman.com/view/7208231/2s8YmRMLvd). To check for new endpoints:

```
bin/sync-postman-collection
```

> **Note on parameter names.** The legacy method signatures in `docs/FVS_Webservice.txt`describe the older V0 (SOAP) interface and do **not** always match the V2 `FvsServicePure` endpoint this SDK targets. For example, `GetPrekesSandelyje`is documented with `sSanKod` but the V2 endpoint actually honors `sSandKod`(verified against a live server). When a query filter appears to be silently ignored, confirm the exact parameter name against a live server rather than trusting the `.txt` signature.

License
-------

[](#license)

MIT

###  Health Score

44

—

FairBetter than 91% of packages

Maintenance94

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

18

Last Release

63d ago

Major Versions

v0.1.0 → v1.0.02026-04-02

v1.1.1 → v2.0.02026-04-09

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4507937?v=4)[Artūras Šeršniovas](/maintainers/arturas88)[@arturas88](https://github.com/arturas88)

---

Top Contributors

[![arturas88](https://avatars.githubusercontent.com/u/4507937?v=4)](https://github.com/arturas88 "arturas88 (59 commits)")

---

Tags

phpapilaravelsdkAccountingERPfinvaldafvs

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/arturas88-finvalda-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/arturas88-finvalda-sdk/health.svg)](https://phpackages.com/packages/arturas88-finvalda-sdk)
```

###  Alternatives

[openai-php/laravel

OpenAI PHP for Laravel is a supercharged PHP API client that allows you to interact with the Open AI API

3.7k8.8M83](/packages/openai-php-laravel)[theodo-group/llphant

LLPhant is a library to help you build Generative AI applications.

1.7k371.6k6](/packages/theodo-group-llphant)[mozex/anthropic-laravel

Laravel integration for the Anthropic API: facade, config publishing, install command, testing fakes, messages, streaming, tool use, thinking, and batches.

72287.1k1](/packages/mozex-anthropic-laravel)[resend/resend-php

Resend PHP library.

596.2M35](/packages/resend-resend-php)[clicksend/clicksend-php

301.6M11](/packages/clicksend-clicksend-php)[bushlanov-dev/max-bot-api-client-php

Max Bot API Client library

464.7k](/packages/bushlanov-dev-max-bot-api-client-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
