Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,206 @@
<?php
namespace Xentral\Modules\FiskalyApi\Service;
use Exception;
use FiskalyClient\errors\exceptions\FiskalyClientException;
use FiskalyClient\errors\exceptions\FiskalyHttpException;
use FiskalyClient\errors\exceptions\FiskalyHttpTimeoutException;
use FiskalyClient\FiskalyClient;
use FiskalyClient\responses\SelfTestResponse;
use Xentral\Components\HttpClient\Exception\ClientErrorException;
use Xentral\Modules\FiskalyApi\Data\TechnicalSecuritySystem;
use Xentral\Modules\FiskalyApi\Data\Client;
use Xentral\Modules\FiskalyApi\Exception\InvalidCredentialsException;
use Xentral\Modules\FiskalyApi\Exception\InvalidTransactionException;
use Xentral\Modules\FiskalyApi\Exception\SmaEndpointNotFoundException;
use Xentral\Modules\FiskalyApi\Exception\SmaEndpointNotReachableException;
use Xentral\Modules\FiskalyApi\Transaction\Transaction;
/**
* Class FiskalyApi
*
* @package Xentral\Modules\FiskalyApi\Service
*/
class FiskalyApi
{
/** @var string */
private $apiKey;
/** @var string */
private $apiSecret;
/** @var string */
private $accessToken = null;
/** @var FiskalyClient */
private $fiskalyClient;
const DEFAULT_SMA_ENDPOINT = 'http://localhost:8080/invoke';
/**
* FiskalyApi constructor.
*
* @param string $smaEndpoint
* @param string $apiKey
* @param string $apiSecret
* @param string $endpoint
*
* @throws Exception
*/
public function __construct(string $smaEndpoint, string $apiKey, string $apiSecret, string $endpoint)
{
if(empty($smaEndpoint)) {
$smaEndpoint = self::DEFAULT_SMA_ENDPOINT;
}
try {
$this->fiskalyClient = FiskalyClient::createUsingCredentials(
$smaEndpoint,
$apiKey,
$apiSecret,
$endpoint
);
}
catch (Exception $e) {
if(strpos($e->getMessage(), '404') === 0) {
throw new SmaEndpointNotFoundException($e->getMessage());
}
if($e->getMessage() === 'Undefined variable: http_response_header') {
throw new SmaEndpointNotReachableException($e->getMessage());
}
throw $e;
}
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
}
/**
* @param string $apiKey
* @param string $apiSecret
*
* @throws ClientErrorException
*
* @return string
*/
protected function generateAccessToken(string $apiKey, string $apiSecret): string
{
$result = $this->callApiPost(
'auth',
json_encode(
[
'api_key' => $apiKey,
'api_secret' => $apiSecret,
]
),
false
);
return $result->access_token;
}
/**
* @param $endpoint
* @param null $body
* @param null $query
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return mixed
*/
protected function callApiGet($endpoint, $body = null, $query = null)
{
return $this->callApi('GET', $endpoint, $body, $query);
}
/**
* @param $endpoint
* @param null $body
* @param null $query
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return mixed
*/
protected function callApiPost($endpoint, $body = null, $query = null)
{
return $this->callApi('POST', $endpoint, $body, $query);
}
/**
* @param $endpoint
* @param null $body
* @param null $query
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return mixed
*/
protected function callApiPut($endpoint, $body = null, $query = null)
{
return $this->callApi('PUT', $endpoint, $body, $query);
}
/**
* @param $method
* @param $endpoint
* @param null $body
* @param null $query
*
* @throws FiskalyClientException
* @throws FiskalyHttpTimeoutException
* @return mixed
*/
private function callApi($method, $endpoint, $body = null, $query = null)
{
if (!empty($body)) {
$body = base64_encode($body);
}
try {
$response = $this->fiskalyClient->request(
$method,
$endpoint,
$query,
null,
$body
);
return json_decode(base64_decode($response->getResponse()['body']));
} catch (ClientErrorException | FiskalyHttpException $e) {
$this->handleClientException($e);
}
}
/**
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return SelfTestResponse
*/
public function selfTest(): SelfTestResponse
{
return $this->fiskalyClient->selfTest();
}
/**
* @param Exception $e
*
* @throws Exception
* @return void
*/
private function handleClientException(Exception $e): void
{
if ($e->getStatus() === 401 || $e->getCode() == 401) {
throw new InvalidCredentialsException('Falsche Zugangsdaten');
}
if ($e->getStatus() === 403 || $e->getCode() == 403) {
throw new InvalidCredentialsException('Nutzer nicht berechtigt');
}
throw $e;
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosingApiResponse;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
interface FiskalyCashPointClosingDBInterface
{
public function create(CashPointClosingApiResponse $cashPointClosingApiResponse): int;
public function update(CashPointClosingApiResponse $cashPointClosingApiResponse): void;
public function get(int $id): ?CashPointClosingApiResponse;
public function getIdByClosingId(string $closingId): ?int;
public function getByClosingId(string $closingId): ?CashPointClosingApiResponse;
public function createTransactionMapping(
CashPointClosingApiResponse $cashPointClosing,
TransactionReponse $transaction
): int;
public function getClosingIdsByState(string $clientId, string $state): array;
}
@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use DateTime;
use DateTimeZone;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosingApiResponse;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
use Xentral\Modules\FiskalyApi\Exception\InvalidArgumentException;
final class FiskalyCashPointClosingDBService implements FiskalyCashPointClosingDBInterface
{
/** @var Database $db */
private $db;
/**
* CashPointClosingDBService constructor.
*
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param CashPointClosingApiResponse $cashPointClosingApiResponse
*
* @throws Exception
* @return int
*/
public function create(CashPointClosingApiResponse $cashPointClosingApiResponse): int
{
$this->db->perform(
'INSERT INTO `fiskaly_cash_point_closing`
(`closing_id`, `client_id`, `cash_point_closing_export_id`, `state`,
`export_creation_date`, `time_start`, `time_end`, `trx_start`, `trx_end`)
VALUES (:closing_id, :client_id, :cash_point_closing_export_id, :state,
:export_creation_date, :time_start, :time_end, :trx_start, :trx_end )',
[
'closing_id' => $cashPointClosingApiResponse->getClosingId(),
'client_id' => $cashPointClosingApiResponse->getClientId(),
'cash_point_closing_export_id' => $cashPointClosingApiResponse->getCashPointClosingExportId(),
'state' => $cashPointClosingApiResponse->getState(),
'export_creation_date' => (new Datetime('now', new DateTimeZone('UTC')))->setTimeStamp(
$cashPointClosingApiResponse->getExportCreationDate()
)->format('Y-m-d H:i:s'),
'time_start' => null,
'time_end' => null,
'trx_start' => $cashPointClosingApiResponse->getFirstTransactionExportId(),
'trx_end' => $cashPointClosingApiResponse->getLastTransactionExportId(),
]
);
return $this->db->lastInsertId();
}
/**
* @param string $closingId
*
* @return CashPointClosingApiResponse|null
*/
public function getByClosingId(string $closingId): ?CashPointClosingApiResponse
{
$id = $this->getIdByClosingId($closingId);
if ($id === null) {
return null;
}
return $this->get($id);
}
/**
* @param int $id
*
* @return CashPointClosingApiResponse|null
*/
public function get(int $id): ?CashPointClosingApiResponse
{
$row = $this->db->fetchRow(
'SELECT * FROM `fiskaly_cash_point_closing` WHERE `id` = :id',
['id' => $id]
);
if (empty($row)) {
return null;
}
return CashPointClosingApiResponse::fromDbState($row);
}
/**
* @param string $closingId
*
* @return int|null
*/
public function getIdByClosingId(string $closingId): ?int
{
$id = $this->db->fetchValue(
'SELECT `id` FROM `fiskaly_cash_point_closing` WHERE `closing_id` = :closing_id',
['closing_id' => $closingId]
);
if ($id === false) {
return null;
}
return (int)$id;
}
/**
* @param CashPointClosingApiResponse $cashPointClosing
* @param TransactionReponse $transaction
*
* @return int
*/
public function createTransactionMapping(
CashPointClosingApiResponse $cashPointClosing,
TransactionReponse $transaction
): int {
$cashPointClosingId = $this->getIdByClosingId($cashPointClosing->getClosingId());
if ($cashPointClosingId === null) {
throw new InvalidArgumentException("cashPointClosingId {$cashPointClosing->getClosingId()} not found");
}
$transactionId = $transaction->getId();
$transactionDbId = $this->getTransactionDbId($transactionId);
if ($transactionDbId === null) {
throw new InvalidArgumentException("Transaction {$transactionId} not found");
}
$this->db->perform(
'INSERT INTO `fiskaly_cash_point_closing_transaction`
(`fiskaly_cash_point_closing_id`, `fiskaly_transaction_id`)
VALUES (:cash_point_closing_id, :transaction_id)',
[
'cash_point_closing_id' => $cashPointClosingId,
'transaction_id' => $transactionDbId,
]
);
return $this->db->lastInsertId();
}
/**
* @param CashPointClosingApiResponse $cashPointClosingApiResponse
*/
public function update(CashPointClosingApiResponse $cashPointClosingApiResponse): void
{
$cashPointClosingId = $this->getIdByClosingId($cashPointClosingApiResponse->getClosingId());
if ($cashPointClosingId === null) {
throw new InvalidArgumentException(
"cashPointClosingId {$cashPointClosingApiResponse->getClosingId()} not found"
);
}
$this->db->perform(
'UPDATE `fiskaly_cash_point_closing` SET `state` = :state WHERE `id` = :id',
['state' => $cashPointClosingApiResponse->getState(), 'id' => $cashPointClosingId]
);
}
/**
* @param string $clientId
* @param string $state
*
* @return array
*/
public function getClosingIdsByState(string $clientId, string $state): array
{
return $this->db->fetchCol(
'SELECT `closing_id` FROM `fiskaly_cash_point_closing` WHERE `client_id` = :client_id AND `state` = :state',
[
'client_id' => $clientId,
'state' => $state,
]
);
}
/**
* @param string $transactionId
*
* @return int|null
*/
private function getTransactionDbId(string $transactionId): ?int
{
$result = $this->db->fetchValue(
'SELECT `id` FROM `fiskaly_transaction` WHERE `trx_id` = :trx_id',
['trx_id' => $transactionId]
);
if ($result === false) {
return null;
}
return (int)$result;
}
}
@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Exception;
use FiskalyClient\errors\exceptions\FiskalyClientException;
use FiskalyClient\errors\exceptions\FiskalyHttpException;
use FiskalyClient\errors\exceptions\FiskalyHttpTimeoutException;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosing;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosingApiResponse;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosingApiResponseCollection;
use Xentral\Modules\FiskalyApi\Data\CashRegister;
use Xentral\Modules\FiskalyApi\Data\VatDefinition;
class FiskalyDSFinVKApi extends FiskalyApi
{
/** @var string */
private const ENDPOINT_BASE = 'https://dsfinvk.fiskaly.com/api/v0/';
/**
* FiskalyDSFinVKApi constructor.
*
* @param string $smaEndpoint
* @param string $apiKey
* @param string $apiSecret
*
* @throws Exception
*/
public function __construct(string $smaEndpoint, string $apiKey, string $apiSecret)
{
parent::__construct($smaEndpoint, $apiKey, $apiSecret, self::ENDPOINT_BASE);
}
/**
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return array
*/
public function getVatDefinitions(): array
{
$result = $this->callApiGet('vat_definitions');
return array_map([VatDefinition::class, 'fromApiResult'], $result->data);
}
/**
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return array
*/
public function getCashRegisters(): array
{
$result = $this->callApiGet('cash_registers');
return array_map([CashRegister::class,'fromApiResult'], $result->data);
}
/**
* @param string $clientId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return CashRegister|null
*/
public function getCashRegister(string $clientId): ?CashRegister
{
$result = $this->callApiGet("cash_registers/{$clientId}");
if(empty($result)) {
return null;
}
return CashRegister::fromApiResult($result);
}
/**
* @param string|null $purchaserAgencyId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
*
* @return CashRegister[]
*/
public function getPurchaserAgencies(?string $purchaserAgencyId = null): array
{
if($purchaserAgencyId === null) {
$result = $this->callApiGet('purchaser_agencies');
return array_map([CashRegister::class, 'fromApiResult'] , $result->data);
}
$result = $this->callApiGet("purchaser_agencies/{$purchaserAgencyId}");
return [
CashRegister::fromApiResult($result)
];
}
/**
* @param CashRegister $cashRegister
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return CashRegister
*/
public function putRegister(CashRegister $cashRegister): CashRegister
{
$clientId = $cashRegister->getClientId();
$body = $cashRegister->toArray();
$result = $this->callApiPut("cash_registers/{$clientId}", json_encode($body));
return CashRegister::fromApiResult($result);
}
/**
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
*
* @return mixed
*/
public function getCashPointClosings(): CashPointClosingApiResponseCollection
{
$result = $this->callApiGet('cash_point_closings');
return CashPointClosingApiResponseCollection::fromApiResult($result->data);
}
/**
* @param string $closingId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return CashPointClosingApiResponse
*/
public function getCashPointClosing(string $closingId): CashPointClosingApiResponse
{
$result = $this->callApiGet("cash_point_closings/{$closingId}");
return CashPointClosingApiResponse::fromApiResult($result);
}
/**
* @param string $closingId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return mixed
*/
public function getCashPointClosingDetails(string $closingId) {
$result = $this->callApiGet("cash_point_closings/{$closingId}/details");
return $result;
}
/**
* @param CashPointClosing $cashPointClosing
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
*
* @return CashPointClosingApiResponse
*/
public function sendCashPointClosings(CashPointClosing $cashPointClosing): CashPointClosingApiResponse
{
$result = $this->callApiPut(
"cash_point_closings/{$cashPointClosing->getClosingId()}", json_encode($cashPointClosing->toApiResult())
);
return CashPointClosingApiResponse::fromApiResult($result);
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Exception;
use FiskalyClient\errors\exceptions\FiskalyClientException;
use FiskalyClient\errors\exceptions\FiskalyHttpException;
use FiskalyClient\errors\exceptions\FiskalyHttpTimeoutException;
class FiskalyEReceiptApi extends FiskalyApi
{
/** @var string */
private const ENDPOINT_BASE = 'https://ereceipt.fiskaly.dev/api/v0/';
/**
* FiskalyEReceiptApi constructor.
*
* @param string $smaEndpoint
* @param string $apiKey
* @param string $apiSecret
*
* @throws Exception
*/
public function __construct(string $smaEndpoint, string $apiKey, string $apiSecret)
{
parent::__construct($smaEndpoint, $apiKey, $apiSecret, self::ENDPOINT_BASE);
}
/**
* @param int $limit
* @param int $offset
* @param string|null $tssId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return mixed
*/
public function listEReceipts(int $limit = 100, int $offset = 0, ?string $tssId = null)
{
if($tssId === null) {
$result = $this->callApiGet("issuer/e_receipts");
//$result = $this->callApiGet("issuer/e_receipts?limit={$limit}&offset={$offset}");
}
else {
$result = $this->callApiGet("issuer/e_receipts?limit={$limit}&offset={$offset}&tss_id={$tssId}");
}
return $result;
}
}
@@ -0,0 +1,469 @@
<?php
namespace Xentral\Modules\FiskalyApi\Service;
use Exception;
use FiskalyClient\errors\exceptions\FiskalyClientException;
use FiskalyClient\errors\exceptions\FiskalyHttpException;
use FiskalyClient\errors\exceptions\FiskalyHttpTimeoutException;
use Xentral\Modules\FiskalyApi\Data\Client;
use Xentral\Modules\FiskalyApi\Data\Export;
use Xentral\Modules\FiskalyApi\Data\TechnicalSecuritySystem;
use Xentral\Modules\FiskalyApi\Data\Transaction\AmountsPerPaymentTypeCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\AmountsPerVatTypeCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\SchemaReceipt;
use Xentral\Modules\FiskalyApi\Data\Transaction\SchemaStandardV1;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponseCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionRequest;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionSchema;
use Xentral\Modules\FiskalyApi\Exception\InvalidArgumentException;
use Xentral\Modules\FiskalyApi\Exception\InvalidTransactionException;
use Xentral\Modules\FiskalyApi\Transaction\Transaction;
use Xentral\Modules\FiskalyApi\UuidTool;
class FiskalyKassenSichVApi extends FiskalyApi
{
/** @var string */
private const ENDPOINT_BASE = 'https://kassensichv.io/api/v1/';
/**
* FiskalyKassenSichVApi constructor.
*
* @param string $smaEndpoint
* @param string $apiKey
* @param string $apiSecret
*
* @throws Exception
*/
public function __construct(string $smaEndpoint, string $apiKey, string $apiSecret)
{
parent::__construct($smaEndpoint, $apiKey, $apiSecret, self::ENDPOINT_BASE);
}
/**
* @return TechnicalSecuritySystem[]
*/
public function getTechnicalSecuritySystemList(): array
{
$result = $this->callApiGet('tss');
return array_map([TechnicalSecuritySystem::class, 'fromApiResult'], $result->data);
}
/**
* @param string $tssUuid
*
* @return TechnicalSecuritySystem
*/
public function getTechnicalSecuritySystemByUuid(string $tssUuid): TechnicalSecuritySystem
{
$result = $this->callApiGet("tss/{$tssUuid}");
return TechnicalSecuritySystem::fromApiResult($result);
}
/**
* @param string $tssUuid
* @param string $state
* @param string|null $description
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TechnicalSecuritySystem
*/
public function changeSecuritySystem(
string $tssUuid,
string $state = 'INITIALIZED',
?string $description = null
): TechnicalSecuritySystem {
if (!in_array($state, ['UNINITIALIZED', 'INITIALIZED', 'DISABLED'])) {
throw new InvalidArgumentException("unknown state '{$state}'");
}
$body = ['state' => $state];
if ($description !== null) {
$body['description'] = $description;
}
$result = $this->callApiPut("tss/{$tssUuid}", json_encode($body));
return TechnicalSecuritySystem::fromApiResult($result);
}
/**
* @param null|string $tssUuid
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
*
* @return Client[]
*/
public function getClients($tssUuid = null): array
{
if (empty($tssUuid)) {
$result = $this->callApiGet("client");
} else {
$result = $this->callApiGet("tss/{$tssUuid}/client");
}
return array_map([Client::class, 'fromApiResult'], $result->data);
}
/**
* @param string $tssUuid
* @param string|null $clientId
* @param string|null $exportId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
*/
public function triggerExport(string $tssUuid, ?string $clientId = null, ?string $exportId = null): Export
{
if ($exportId === null) {
$exportId = UuidTool::generateUuid();
}
if ($clientId !== null) {
Export::fromApiResult(
$this->callApiPut("tss/{$tssUuid}/export/{$exportId}", '{}', ['client_id' => $clientId])
);
}
return Export::fromApiResult($this->callApiPut("tss/{$tssUuid}/export/{$exportId}", '{}'));
}
/**
* @param string $tssUuid
* @param string $serialNumber
* @param string $clientId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return Client
*/
public function createClient(string $tssUuid, string $serialNumber, string $clientId): Client
{
$result = $this->callApiPut(
"tss/{$tssUuid}/client/{$clientId}",
json_encode(['serial_number' => $serialNumber])
);
return Client::fromApiResult($result);
}
/**
* @param $clientUuid
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
*
* @return Client
*/
public function getClientByUuid($tssUuid, $clientUuid): Client
{
$result = $this->callApiGet("tss/{$tssUuid}/client/{$clientUuid}");
return Client::fromApiResult($result);
}
/**
* @param string|null $tssUuid
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TransactionReponseCollection
*/
public function getTransactions(
?string $tssUuid = null,
int $offset = 0,
int $limit = 100
): TransactionReponseCollection {
if ($tssUuid === null) {
$result = $this->callApiGet("tx", null, ['offset' => $offset, 'limit' => $limit,]);
} else {
$result = $this->callApiGet("tss/{$tssUuid}/tx", null, ['offset' => $offset, 'limit' => $limit,]);
}
return TransactionReponseCollection::fromApiResult($result->data);
}
/**
* @param string $tssUuid
* @param string $txId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TransactionReponse
*/
public function getTransaction(string $tssUuid, string $txId): TransactionReponse
{
$result = $this->callApiGet("tss/{$tssUuid}/tx/{$txId}");
return TransactionReponse::fromApiResult($result);
}
/**
* @param Transaction $transaction
* @param TechnicalSecuritySystem $technicalSecuritySystem
*
* @return Transaction
*/
public function uploadTransaction(
Transaction $transaction,
TechnicalSecuritySystem $technicalSecuritySystem
): Transaction {
$transaction = $this->startTransaction($transaction, $technicalSecuritySystem);
return $this->finishTransactionOld($transaction, $technicalSecuritySystem);
}
/**
* @param Transaction $transaction
* @param TechnicalSecuritySystem $tss
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return Transaction
*/
public function startTransaction(Transaction $transaction, TechnicalSecuritySystem $tss)
{
$tssId = $tss->getUuid();
$body = json_encode(
[
'state' => 'ACTIVE',
'client_id' => $transaction->getClientUuid(),
]
);
$result = $this->callApiPut("tss/{$tssId}/tx/" . $transaction->getUuid(), $body);
$transaction->setLastRevision($result->revision);
return $transaction;
}
/**
* @param TransactionRequest $transactionRequest
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TransactionReponse
*/
public function createTransaction(TransactionRequest $transactionRequest): TransactionReponse
{
return TransactionReponse::fromApiResult(
$this->callApiPut(
"tss/{$transactionRequest->getTssId()}/tx/{$transactionRequest->getId()}",
json_encode($transactionRequest->toApiResult())
)
);
}
/**
* @param TransactionRequest $transactionRequest
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TransactionReponse
*/
public function updateTransaction(TransactionRequest $transactionRequest): TransactionReponse
{
return TransactionReponse::fromApiResult(
$this->callApiPut(
"tss/{$transactionRequest->getTssId()}/tx/{$transactionRequest->getId()}",
json_encode($transactionRequest->toApiResult()),
['last_revision' => $transactionRequest->getRevision()]
)
);
}
/**
* @param TransactionReponse $reponse
* @param string $receiptType
* @param AmountsPerVatTypeCollection $amountsPerVatTypeCollection
* @param AmountsPerPaymentTypeCollection $amountsPerPaymentTypeCollection
*
* @return TransactionRequest
*/
public function getFinishTransactionRequest(
TransactionReponse $reponse,
string $receiptType,
AmountsPerVatTypeCollection $amountsPerVatTypeCollection,
AmountsPerPaymentTypeCollection $amountsPerPaymentTypeCollection
): TransactionRequest {
return (new TransactionRequest(
'FINISHED',
$reponse->getClientId(),
new TransactionSchema(
new SchemaStandardV1(
new SchemaReceipt(
$receiptType,
$amountsPerVatTypeCollection,
$amountsPerPaymentTypeCollection
)
)
), $reponse->getMetaData()
)
)->setTssId($reponse->getTssId())
->setId($reponse->getId())
->setRevision($reponse->getLatestRevision());
}
/**
* @param TransactionRequest $request
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @throws Exception
* @return TransactionReponse
*/
public function finishTransaction(TransactionRequest $request): TransactionReponse
{
$result = $this->callApiPut(
"tss/{$request->getTssId()}/tx/{$request->getId()}",
json_encode($request->toArray()),
['last_revision' => $request->getRevision()]
);
return TransactionReponse::fromApiResult($result);
}
/**
* @param Transaction $transaction
* @param TechnicalSecuritySystem $tss
*
* @return Transaction
*/
public function finishTransactionOld(Transaction $transaction, TechnicalSecuritySystem $tss)
{
$tssId = $tss->getUuid();
$vatTypeAmounts = [];
$paymentTypeAmounts = [];
foreach ($transaction->getAmountsPerVatRate() as $vatTypeAmount) {
$vatTypeAmounts[] = [
'vat_rate' => $vatTypeAmount->getVatType(),
'amount' => (string)number_format($vatTypeAmount->getAmount(), 2, '.', ''),
];
}
foreach ($transaction->getAmountsPerPaymentType() as $paymentTypeAmount) {
$paymentTypeAmounts[] = [
'payment_type' => $paymentTypeAmount->getPaymentType(),
'amount' => (string)number_format($paymentTypeAmount->getAmount(), 2, '.', ''),
'currency_code' => 'EUR',
];
}
$hasOrderLineItems = count($transaction->getOrderLineItems()) > 0;
$body =
[
'state' => 'FINISHED',
'client_id' => $transaction->getClientUuid(),
'schema' => [
'standard_v1' => [],
],
];
$body['schema']['standard_v1'] = [
'receipt' => [
'receipt_type' => 'RECEIPT',
'amounts_per_vat_rate' => $vatTypeAmounts,
'amounts_per_payment_type' => $paymentTypeAmounts,
],
];
if ($hasOrderLineItems) {
foreach ($transaction->getOrderLineItems() as $orderLineItem) {
$body['schema']['standard_v1']['receipt']['line_items'][] = [
'quantity' => $orderLineItem->getQuantity(),
'text' => $orderLineItem->getText(),
'price_per_unit' => $orderLineItem->getPricePerUnit(),
];
}
}
if (!$transaction->isLastRevisionSet()) {
throw new InvalidTransactionException("Transaction last_revision not set");
}
$query = ['last_revision' => $transaction->getLastRevision()];
$uuid = $transaction->getUuid();
$result = $this->callApiPut("tss/{$tssId}/tx/{$uuid}", json_encode($body), $query);
$transaction->setLastRevision($result->revision);
$transaction->setTransactionNumber($result->number);
$transaction->setStartTime($result->time_start);
$transaction->setEndTime($result->time_end);
$transaction->setClientSerialNumber($result->client_serial_number);
$transaction->setCertificateSerial($result->certificate_serial);
$transaction->setSignature($result->signature->value);
$transaction->setSignatureAlgorithm($result->signature->algorithm);
$transaction->setSignatureCounter($result->signature->counter);
$transaction->setPublicKey($result->signature->public_key);
return $transaction;
}
/**
* @param string|null $tssId
* @param bool $orderIsDesc
* @param int $offset
* @param int $limit
* @param string $orderBy
* @param array $states
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return array
*/
public function listExports(
?string $tssId = null,
bool $orderIsDesc = false,
int $offset = 0,
int $limit = 100,
string $orderBy = 'time_request',
array $states = []
): array {
$query = null;
if ($orderIsDesc) {
$query['order'] = 'desc';
}
if (!empty($states)) {
$query['states'] = '';
foreach ($states as $keyState => $state) {
$query['states'] .= ($keyState > 0 ? '&' : '') . "states%5B{$keyState}%5D={$state}";
}
}
$query['order_by'] = $orderBy;
$query['limit'] = $limit;
$query['offset'] = $offset;
$endPoint = 'export';
if ($tssId !== null) {
$endPoint = "tss/{$tssId}/export";
}
$result = $this->callApiGet($endPoint, null, $query);
return array_map([Export::class, 'fromApiResult'], $result->data);
}
}
@@ -0,0 +1,60 @@
<?php
namespace Xentral\Modules\FiskalyApi\Service;
use FiskalyClient\errors\exceptions\FiskalyClientException;
use FiskalyClient\errors\exceptions\FiskalyHttpException;
use FiskalyClient\errors\exceptions\FiskalyHttpTimeoutException;
use Xentral\Modules\FiskalyApi\Data\BillingAddress;
use Xentral\Modules\FiskalyApi\Data\Organisation;
use Xentral\Modules\FiskalyApi\Data\User;
class FiskalyManagementApi extends FiskalyApi
{
private const ENDPOINT_BASE = 'https://dashboard.fiskaly.com/api/v0/';
public function __construct(string $smaEndpoint, string $apiKey, string $apiSecret)
{
parent::__construct($smaEndpoint, $apiKey, $apiSecret, self::ENDPOINT_BASE);
}
/**
* @param string|null $uuId
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
*
* @return Organisation[]
*/
public function getOrganisations(?string $uuId = null): array
{
if ($uuId === null) {
$organisations = $this->callApiGet('organizations');
return array_map([Organisation::class, 'fromApiResult'], $organisations->data);
}
$organisation = $this->callApiGet("organizations/{$uuId}");
return [Organisation::fromApiResult($organisation)];
}
public function getUsers(string $organisationUuId): array
{
$result = $this->callApiGet("/organizations/{$organisationUuId}/users");
return array_map([User::class, 'fromApiResult'], $result->data);
}
public function getBillingAddresses(): array
{
$result = $this->callApiGet('billing-addresses');
return array_map([BillingAddress::class, 'fromApiResult'], $result->data);
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponseCollection;
interface FiskalyPosClosingInterface
{
public function getNextCashPointClosingExportId(string $clientId): int;
public function getOpenTransactions(string $clientId): TransactionReponseCollection;
}
@@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use erpAPI;
use Exception;
use DateTime;
use DateTimeZone;
use Xentral\Components\Database\Database;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\BusinessCaseCollection;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosing;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosingCashStatement;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosingHead;
use Xentral\Modules\FiskalyApi\Data\CashPointClosing\CashPointClosingPayment;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponseCollection;
use Xentral\Modules\FiskalyApi\Factory\FiskalyCashPointClosingFactory;
use Xentral\Modules\FiskalyApi\Wrapper\TaxSettingWrapper;
class FiskalyPosClosingService implements FiskalyPosClosingInterface
{
/** @var Database $db */
private $db;
/** @var FiskalyCashPointClosingFactory $cashPointFactory */
private $cashPointFactory;
/** @var TaxSettingWrapper $taxSettingWrapper */
private $taxSettingWrapper;
/**
* FiskalyPosClosingService constructor.
*
* @param Database $db
* @param FiskalyCashPointClosingFactory $cashPointFactory
* @param TaxSettingWrapper $taxSettingWrapper
*/
public function __construct(
Database $db,
FiskalyCashPointClosingFactory $cashPointFactory,
TaxSettingWrapper $taxSettingWrapper
) {
$this->db = $db;
$this->cashPointFactory = $cashPointFactory;
$this->taxSettingWrapper = $taxSettingWrapper;
}
public function getNextCashPointClosingExportId(string $clientId): int
{
return 1 + (int)$this->db->fetchValue(
'SELECT MAX(`cash_point_closing_export_id`)
FROM `fiskaly_cash_point_closing`
WHERE `client_id` = :client_id',
['client_id' => $clientId]
);
}
/**
* @param string $clientId
* @param string|null $date
*
* @throws Exception
* @return TransactionReponseCollection
*/
public function getOpenTransactions(string $clientId, ?string $date = null): TransactionReponseCollection
{
$transactions = $this->db->fetchCol(
'SELECT ft.json_response
FROM `fiskaly_transaction` AS `ft`
INNER JOIN `fiskaly_tranaction_mapping` AS `ftm` ON ft.id = ftm.fiskaly_transaction_id
LEFT JOIN `fiskaly_cash_point_closing_transaction` AS `fcpct` ON ft.id = fcpct.fiskaly_transaction_id
WHERE ft.client_id = :client_id AND fcpct.id IS NULL
GROUP BY ft.id',
['client_id' => $clientId]
);
$instance = new TransactionReponseCollection();
foreach ($transactions as $transactionJson) {
$instance->addTransactionResponse(TransactionReponse::fromApiResult(json_decode($transactionJson, false)));
}
if ($date !== null) {
return $instance->filterDate($date);
}
return $instance;
}
/**
* @param string $clientId
*
* @throws Exception
* @return array
*/
public function getOpenPointClosingDates(string $clientId): array
{
$openTransactions = $this->getOpenTransactions($clientId);
return $openTransactions->getTransactionDates();
}
/**
* @param int $projectId
* @param string $date
*
* @return bool
*/
public function isPosCountingExistsForDate(int $projectId, string $date): bool
{
return $this->db->fetchValue(
'SELECT `id`
FROM `pos_zaehlungen`
WHERE `projekt` = :project_id AND DATE(`zeitstempel`) = :date
LIMIT 1',
[
'project_id' => $projectId,
'date' => $date,
]
) !== false;
}
/**
* @param string $clientId
* @param string|null $date
*
* @throws Exception
* @return CashPointClosing
*/
public function getNextCashPointClosing(string $clientId, ?string $date = null): CashPointClosing
{
if ($date === null) {
$openTransactions = $this->getOpenTransactions(
$clientId,
(new DateTime('now', new DateTimeZone('UTC')))->format(
'Y-m-d'
)
);
} else {
$openTransactions = $this->getOpenTransactions($clientId, $date);
}
$trxs = $openTransactions->getTrxIds();
$firstTransaction = $openTransactions->getBoundedTransactionWithClientId($clientId, true);
$lastLastTransaction = $openTransactions->getBoundedTransactionWithClientId($clientId, false);
$this->cashPointFactory->setTaxNormal($this->getNormalTaxForClientId($clientId));
$paymentCollection = $this->cashPointFactory->getPaymentTypesFromTransactionCollection($openTransactions);
$sum = $paymentCollection->getSum();
$cashCollection = $paymentCollection->filterByType('CASH');
$cashSum = $cashCollection->getSum();
$posJournalEntries = $this->getPosJounralEntriesByTrxs($trxs);
$posSessions = $this->getPosSessionsByTrxs($trxs);
$businessCases = new BusinessCaseCollection();
foreach ($posJournalEntries as $posJournalArray) {
$businessCases = $businessCases->combine(
$this->cashPointFactory->createBusinessCaseCollection($posJournalArray)
);
}
$payment = new CashPointClosingPayment(
$sum,
$cashSum,
$this->cashPointFactory->getCashAmountByCurrencyCollection($paymentCollection),
$this->cashPointFactory->getCashPointClosingPaymentTypeCollectionByPosJournalCollection($posJournalEntries)
);
$instance = new CashPointClosing(
$clientId, $this->getNextCashPointClosingExportId($clientId),
new CashPointClosingHead(
new DateTime('now', new DateTimeZone('UTC')),
$firstTransaction->getId(),
$lastLastTransaction->getId(),
$date === null ? null : (new DateTime($date, new DateTimeZone('UTC')))
),
new CashPointClosingCashStatement($businessCases, $payment),
$this->cashPointFactory->getCashPointClosingTransactionCollection(
$openTransactions,
$posJournalEntries,
$posSessions
)
);
return $instance;
}
/**
* @param array $trxs
*
* @return array
*/
private function getPosJounralEntriesByTrxs(array $trxs): array
{
return $this->db->fetchGroup(
"SELECT ft.trx_id, pj.*
FROM `fiskaly_transaction` AS `ft`
INNER JOIN `fiskaly_tranaction_mapping` AS `ftm` ON ft.id = ftm.fiskaly_transaction_id
INNER JOIN `pos_journal` AS `pj` ON ftm.document = 'pos_journal' AND ftm.document_id = pj.id
WHERE ft.trx_id IN (:trx_ids)",
[
'trx_ids' => $trxs,
]
);
}
/**
* @param array $trxs
*
* @return array
*/
private function getPosSessionsByTrxs(array $trxs): array
{
$posSessions = $this->db->fetchPairs(
"SELECT ft.trx_id, ps.data
FROM `fiskaly_transaction` AS `ft`
INNER JOIN `fiskaly_tranaction_mapping` AS `ftm` ON ft.id = ftm.fiskaly_transaction_id
INNER JOIN `pos_sessions` AS `ps` ON ftm.document = 'pos_session' AND ftm.document_id = ps.id
WHERE ft.trx_id IN (:trx_ids)",
[
'trx_ids' => $trxs,
]
);
$posSessions = array_map(
static function ($posSession) {
return unserialize($posSession, ['allowed_classes' => false]);
},
$posSessions
);
$addressIds = [];
$cashierIds = [];
foreach ($posSessions as $posSession) {
$cashierId = $posSession['kassiererId'] ?? null;
$addressId = $posSession['addrid'] ?? null;
if ($addressId !== null && !in_array($addressId, $addressIds, true)) {
$addressIds[] = $addressId;
}
if ($cashierId !== null && !in_array($cashierId, $cashierIds, true)) {
$cashierIds[] = $cashierId;
}
}
$cashierAddresses = $this->getAddressesFromCashierIds($cashierIds);
$addesses = $this->getAddressesFromIds($addressIds);
foreach ($posSessions as $positionKey => $posSession) {
$posSessions[$positionKey]['address'] = !empty($addesses[$posSession['addrid'] ?? '']) ? reset(
$addesses[$posSession['addrid']]
) : null;
}
foreach ($posSessions as $positionKey => $posSession) {
if (!empty($posSessions[$positionKey]['address']) || empty($posSession['kassiererId'])) {
continue;
}
if (empty($cashierAddresses[$posSession['kassiererId']])) {
continue;
}
$posSessions[$positionKey]['address'] = reset($cashierAddresses[$posSession['kassiererId']]);
$posSessions[$positionKey]['addrid'] = $posSessions[$positionKey]['address']['id'];
$posSessions[$positionKey]['addr']['name'] = $posSessions[$positionKey]['address']['name'];
}
return $posSessions;
}
/**
* @param array $addressIds
*
* @return array
*/
private function getAddressesFromIds(array $addressIds): array
{
if (empty($addressIds)) {
return [];
}
return $this->db->fetchGroup(
"SELECT adr.id, IF(l.iso3 IS NULL OR l.iso3 = '', IF(l.iso = 'AT', 'AUT', 'DEU'), l.iso3) AS `land_iso3`,
adr.*
FROM `adresse` AS `adr`
LEFT JOIN `laender` AS `l` ON adr.land = l.iso
WHERE adr.id IN (:address_ids)",
['address_ids' => $addressIds]
);
}
/**
* @param array $cashierIds
*
* @return array
*/
private function getAddressesFromCashierIds(array $cashierIds): array
{
if (empty($cashierIds)) {
return [];
}
return $this->db->fetchGroup(
"SELECT pk.kassenkennung,
IF(l.iso3 IS NULL OR l.iso3 = '', IF(l.iso = 'AT', 'AUT', 'DEU'), l.iso3) AS `land_iso3`, adr.*
FROM `adresse` AS `adr`
INNER JOIN `pos_kassierer` AS `pk` ON adr.id = pk.adresse
LEFT JOIN `laender` AS `l` ON adr.land = l.iso
WHERE pk.kassenkennung IN (:cashier_ids)",
['cashier_ids' => $cashierIds]
);
}
/**
* @param string $clientId
*
* @return float
*/
private function getNormalTaxForClientId(string $clientId): float
{
$projectId = $this->db->fetchValue(
'SELECT pr.id
FROM `fiskaly_pos_mapping` AS `fpm`
INNER JOIN `projekt` AS `pr`
WHERE fpm.client_uuid = :client_id
LIMIT 1',
['client_id' => $clientId]
);
return $this->taxSettingWrapper->getStandardTaxRate($projectId);
}
}
@@ -0,0 +1,373 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Aura\SqlQuery\Exception;
use Xentral\Components\Database\Database;
use Xentral\Modules\FiskalyApi\Data\Organisation;
final class FiskalyPosMappingService
{
/** @var Database $db */
private $db;
/**
* FiskalyPosMappingService constructor.
*
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @return array
*/
public function listProjects(): array
{
return $this->db->fetchAll(
$this->db->select()
->from('projekt')
->where('geloescht = 0 AND kasse_konto > 0')
->cols(['id', 'name', 'abkuerzung'])
->getStatement()
);
}
/**
* @return array
*/
public function list(): array
{
$query = $this->db->select()
->from('fiskaly_pos_mapping AS f')
->cols(['f.id', 'f.tss_uuid', 'f.client_uuid', 'f.pos_id']);
return $this->db->fetchAll($query->getStatement(), $query->getBindValues());
}
/**
* @param string $cashierId
*
* @throws Exception
* @return array
*/
public function getByCashierId(string $cashierId): array
{
$posProjectQuery = $this->db->select()
->from('pos_kassierer AS p')
->cols(['f.tss_uuid', 'f.client_uuid', 'f.pos_id'])
->where('p.kassenkennung=:kennung')
->leftJoin('fiskaly_pos_mapping AS f', 'f.pos_id = p.projekt')
->bindValue('kennung', $cashierId);
return $this->db->fetchRow($posProjectQuery->getStatement(), $posProjectQuery->getBindValues());
}
/**
* @param int $cashId
*
* @throws Exception
* @return array|null
*/
public function getByCashId(int $cashId): ?array
{
$query = $this->db->select()
->from('fiskaly_pos_mapping AS f')
->innerJoin('projekt AS p', 'f.pos_id = p.id')
->where('p.kasse_konto=:kasse')
->bindValue('kasse', $cashId)
->cols(['f.tss_uuid', 'f.organization_id', 'f.client_uuid', 'p.id']);
return $this->db->fetchRow($query->getStatement(), $query->getBindValues());
}
/**
* @param int $fiskalyPosMappingId
*/
public function delete(int $fiskalyPosMappingId): void
{
$query = $this->db->delete()
->from('fiskaly_pos_mapping')
->where('id=:id')
->bindValue('id', $fiskalyPosMappingId);
$this->db->perform($query->getStatement(), $query->getBindValues());
}
/**
* @param int $projectId
*
* @return array
*/
public function getTssFromProjectId(int $projectId): array
{
$query = $this->db->select()
->from('fiskaly_pos_mapping AS f')
->where('pos_id=:pos_id')
->bindValue('pos_id', $projectId)
->cols(['f.tss_uuid', 'f.organization_id', 'f.client_uuid']);
return $this->db->fetchRow($query->getStatement(), $query->getBindValues());
}
/**
* @param int $projectId
*
* @return string|null
*/
public function getTssIdFromProjectId(int $projectId): ?string
{
$query = $this->db->select()
->from('fiskaly_pos_mapping AS f')
->where('pos_id=:pos_id')
->bindValue('pos_id', $projectId)
->cols(['f.tss_uuid']);
$tssId = $this->db->fetchValue($query->getStatement(), $query->getBindValues());
if ($tssId === false) {
return null;
}
return $tssId;
}
/**
* @param Organisation $organisation
*/
public function tryCreateOrUpdateOrganization(Organisation $organisation): void
{
if ($this->getOrganizationByUuId($organisation->getUuid()) === null) {
$this->createOrganization($organisation);
return;
}
$this->updateOrganization($organisation);
}
/**
* @param int $id
*
* @return Organisation|null
*/
public function getOrganizationById(int $id): ?Organisation
{
$organizationRow = $this->db->fetchRow(
'SELECT * FROM `fiskaly_organization` WHERE `id` = :id',
[
'id' => $id,
]
);
if (empty($organizationRow)) {
return null;
}
return $this->getOrganizationFromDbEntry($organizationRow);
}
/**
* @param string $uuId
*
* @return Organisation|null
*/
public function getOrganizationByUuId(string $uuId): ?Organisation
{
$organizationRow = $this->db->fetchRow(
'SELECT * FROM `fiskaly_organization` WHERE `fiskaly_organization_id` = :uuid',
[
'uuid' => $uuId,
]
);
if (empty($organizationRow)) {
return null;
}
return $this->getOrganizationFromDbEntry($organizationRow);
}
/**
* @param array $organizationRow
*
* @return Organisation
*/
private function getOrganizationFromDbEntry(array $organizationRow): Organisation
{
$envs = [];
if (!empty($organizationRow['is_environment_live'])) {
$envs[] = 'LIVE';
}
if (!empty($organizationRow['is_environment_test'])) {
$envs[] = 'TEST';
}
$organizationRow['_id'] = $organizationRow['fiskaly_organization_id'];
$organizationRow['_type'] = $organizationRow['type'];
$organizationRow['_envs'] = $envs;
if (!empty($organizationRow['gln'])) {
$organizationRow['billing_options']['gln'] = $organizationRow['gln'];
}
if (!empty($organizationRow['withhold_billing'])) {
$organizationRow['billing_options']['withhold_billing'] = $organizationRow['withhold_billing'];
}
if (!empty($organizationRow['bill_to_organization'])) {
$organizationRow['billing_options']['bill_to_organization'] = $organizationRow['bill_to_organization'];
}
return Organisation::fromDbState($organizationRow);
}
/**
* @param Organisation $organisation
*
* @return int
*/
public function createOrganization(Organisation $organisation): int
{
$query = $this->db->insert()
->into('fiskaly_organization')
->cols(
[
'fiskaly_organization_id' => $organisation->getUuid(),
'managed_by_organization_id' => $organisation->getManagedByOrganizationId(),
'type' => $organisation->getType(),
'name' => $organisation->getName(),
'display_name' => $organisation->getDisplayName(),
'address_line1' => $organisation->getAddressLine1(),
'address_line2' => $organisation->getAddressLine2(),
'state' => $organisation->getState(),
'zip' => $organisation->getZip(),
'town' => $organisation->getTown(),
'tax_number' => $organisation->getTaxNumber(),
'vat_id' => $organisation->getVatId(),
'economy_id' => $organisation->getEconomyId(),
'country_code' => $organisation->getCountryCode(),
'is_environment_live' => (int)in_array('LIVE', $organisation->getEnvs()),
'is_environment_test' => (int)in_array('TEST', $organisation->getEnvs()),
]
);
$this->db->perform(
$query->getStatement(),
$query->getBindValues()
);
return $this->db->lastInsertId();
}
/**
* @param Organisation $organisation
*/
public function updateOrganization(Organisation $organisation): void
{
$query = $this->db->update()
->table('fiskaly_organization')
->where('fiskaly_organization_id=:fiskaly_organization_id')
->bindValue('fiskaly_organization_id', $organisation->getUuid())
->cols(
[
'managed_by_organization_id' => $organisation->getManagedByOrganizationId(),
'type' => $organisation->getType(),
'name' => $organisation->getName(),
'display_name' => $organisation->getDisplayName(),
'address_line1' => $organisation->getAddressLine1(),
'address_line2' => $organisation->getAddressLine2(),
'state' => $organisation->getState(),
'zip' => $organisation->getZip(),
'town' => $organisation->getTown(),
'tax_number' => $organisation->getTaxNumber(),
'vat_id' => $organisation->getVatId(),
'economy_id' => $organisation->getEconomyId(),
'country_code' => $organisation->getCountryCode(),
'is_environment_live' => (int)in_array('LIVE', $organisation->getEnvs()),
'is_environment_test' => (int)in_array('TEST', $organisation->getEnvs()),
]
);
$this->db->perform(
$query->getStatement(),
$query->getBindValues()
);
}
/**
* @param int $projectId
* @param string $tseUuid
* @param string $tseDescription
* @param string $clientUuid
* @param string $clientDescription
* @param bool|null $istTestEnvironment
* @param string|null $organizationId
*
* @return int
*/
public function create(
int $projectId,
string $tseUuid,
string $tseDescription,
string $clientUuid,
string $clientDescription,
?bool $istTestEnvironment = null,
?string $organizationId = null
): int {
$query = $this->db->insert()
->into('fiskaly_pos_mapping')
->cols(
[
'pos_id' => $projectId,
'tss_uuid' => $tseUuid,
'tss_description' => $tseDescription,
'client_uuid' => $clientUuid,
'client_description' => $clientDescription,
'is_test_environment' => $istTestEnvironment === null ? null : (int)$istTestEnvironment,
'organization_id' => $organizationId ?? null,
]
);
$this->db->perform(
$query->getStatement(),
$query->getBindValues()
);
return $this->db->lastInsertId();
}
/**
* @param int $fiskalyPosMappingId
* @param int $projectId
* @param string $tseUuid
* @param string $tseDescription
* @param string $clientUuid
* @param string $clientDescription
* @param bool|null $istTestEnvironment
* @param string|null $organizationId
*/
public function update(
int $fiskalyPosMappingId,
int $projectId,
string $tseUuid,
string $tseDescription,
string $clientUuid,
string $clientDescription,
?bool $istTestEnvironment = null,
?string $organizationId = null
): void {
$query = $this->db->update()
->table('fiskaly_pos_mapping')
->where('id=:id')
->bindValue('id', $fiskalyPosMappingId)
->cols(
[
'pos_id' => $projectId,
'tss_uuid' => $tseUuid,
'tss_description' => $tseDescription,
'client_uuid' => $clientUuid,
'client_description' => $clientDescription,
'is_test_environment' => $istTestEnvironment === null ? null : (int)$istTestEnvironment,
'organization_id' => $organizationId ?? null,
]
);
$this->db->perform(
$query->getStatement(),
$query->getBindValues()
);
}
}
@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Aura\SqlQuery\Exception;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
use Xentral\Modules\FiskalyApi\Transaction\Transaction;
class FiskalyTransactionCacheService
{
/** @var array */
private $transactions = [];
/** @var array $transactionResponse */
private $transactionResponse = [];
/** @var array $documentMappings */
private $documentMappings = [];
/** @var array $error */
private $error = [];
public function __construct()
{
}
/**
* @param int $id
* @param string $document
* @param int $documentId
*/
public function addDocumentMapping(int $id, string $document, int $documentId): void
{
$this->documentMappings[$id][] = ['document' => $document, 'document_id' => $documentId];
}
/**
* @param int $id
*
* @return array
*/
public function getDocumentMappings(int $id): array
{
return $this->documentMappings[$id] ?? [];
}
/**
* @param int $id
* @param Transaction $transaction
*
* @return void
*/
public function put(int $id, Transaction $transaction): void
{
$this->transactions[$id] = $transaction;
}
/**
* @param int $id
* @param TransactionReponse $transactionResponse
*/
public function putTransactionResponse(int $id, TransactionReponse $transactionResponse): void
{
$this->transactionResponse[$id] = TransactionReponse::fromDbState($transactionResponse->toArray());
}
/**
* @param int $id
*
* @return TransactionReponse
*/
public function getTransactionResponse(int $id): TransactionReponse
{
return TransactionReponse::fromDbState($this->transactionResponse[$id]->toArray());
}
/**
* @param int $id
* @param string $errorMessage
* @param string $sma
*/
public function putErrorMessage(int $id, string $errorMessage, string $sma): void
{
$this->error[$id] = ['sma' => $sma, 'error_message' => $errorMessage];
}
/**
* @param int $id
*
* @return array|null
*/
public function getErrorMessage(int $id): ?array
{
if (!isset($this->error[$id])) {
return null;
}
return $this->error[$id];
}
/**
* @param int $id
*
* @return Transaction
*/
public function get(int $id): Transaction
{
return $this->transactions[$id];
}
/**
* @param int $id
*/
public function reset(int $id): void
{
if (isset($this->documentMappings[$id])) {
unset($this->documentMappings[$id]);
}
if (isset($this->transactionResponse[$id])) {
unset($this->transactionResponse[$id]);
}
if (isset($this->error[$id])) {
unset($this->error[$id]);
}
}
/**
* @param int $id
*
* @return bool
*/
public function hasTransaction(int $id): bool
{
return array_key_exists($id, $this->transactionResponse);
}
/**
* @param int $id
*
* @return bool
*/
public function hasError(int $id): bool
{
return array_key_exists($id, $this->error);
}
public function getNextOpenKey(): int
{
if (empty($this->transactionResponse)) {
return 0;
}
$keys = array_diff(range(0, count($this->transactionResponse)), array_keys($this->transactionResponse));
return reset($keys);
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Xentral\Modules\FiskalyApi\Data\Export;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponseCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionRequest;
interface FiskalyTransactionPosSessionInterface
{
public function get(string $trxId): ?array;
public function insertTransactions(TransactionReponseCollection $transactionResponseCollection): void;
public function getTransactionIdFromTrxId(string $trxId): ?int;
public function tryMapDocument(string $trxId, string $document, int $documentId): int;
public function create(
?TransactionRequest $request,
?TransactionReponse $response
): int;
public function update(
int $fiskalyTransactionPosSessionId,
TransactionRequest $request,
TransactionReponse $response
): void;
public function createOrUpdateExport(Export $export): void;
public function updateExport(Export $export): void;
public function getExportUrlsNotInDms(string $tssId): array;
public function getUuIdsByState(string $state, ?string $tssId = null): array;
}
@@ -0,0 +1,383 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Datetime;
use DateTimeZone;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Modules\FiskalyApi\Data\Export;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponseCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionRequest;
use Xentral\Modules\FiskalyApi\Exception\InvalidArgumentException;
use Xentral\Modules\FiskalyApi\Exception\InvalidTransactionException;
final class FiskalyTransactionPosSessionService implements FiskalyTransactionPosSessionInterface
{
/** @var Database $db */
private $db;
/**
* FiskalyTransactionPosSessionService constructor.
*
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $trxId
*
* @return array|null
*/
public function get(string $trxId): ?array
{
return $this->db->fetchRow(
'SELECT * FROM `fiskaly_transaction` WHERE `trx_id` = :trx_id',
['trx_id' => $trxId]
);
}
/**
* @param TransactionReponseCollection $transactionResponseCollection
*/
public function insertTransactions(TransactionReponseCollection $transactionResponseCollection): void
{
foreach ($transactionResponseCollection as $transactionResponse) {
$trxId = $transactionResponse->getId();
if (!empty($this->get($trxId))) {
continue;
}
$this->create(null, $transactionResponse);
}
}
/**
* @param string $trxId
*
* @return int|null
*/
public function getTransactionIdFromTrxId(string $trxId): ?int
{
$fiskalyTransactionId = $this->db->fetchValue(
'SELECT `id` FROM `fiskaly_transaction` WHERE `trx_id` = :trx_id',
[
'trx_id' => $trxId,
]
);
return $fiskalyTransactionId === false ? null : (int)$fiskalyTransactionId;
}
/**
* @param string $document
* @param int $documentId
*
* @return array
*/
public function getTransactionFromDocument(string $document, int $documentId): array
{
return $this->db->fetchRow(
'SELECT ft.*
FROM `fiskaly_transaction` AS `ft`
INNER JOIN `fiskaly_tranaction_mapping` AS `ftm` ON ft.id = ftm.fiskaly_transaction_id
WHERE ftm.document = :document AND ftm.document_id = :document_id',
[
'document' => $document,
'document_id' => $documentId,
]
);
}
/**
* @param string $trxId
* @param string $document
* @param int $documentId
*
* @return int
*/
public function tryMapDocument(string $trxId, string $document, int $documentId): int
{
$fiskalyTransactionId = $this->getTransactionIdFromTrxId($trxId);
$mappingId = $fiskalyTransactionId === null ? false : $this->db->fetchValue(
'SELECT ftm.id
FROM `fiskaly_tranaction_mapping` AS `ftm`
WHERE ftm.fiskaly_transaction_id = :fiskaly_transaction_id
AND ftm.document = :document
AND ftm.document_id = :document_id',
[
'fiskaly_transaction_id' => $fiskalyTransactionId,
'document' => $document,
'document_id' => $documentId,
]
);
if ($mappingId !== false) {
return (int)$mappingId;
}
$this->db->perform(
'INSERT INTO `fiskaly_tranaction_mapping` (`fiskaly_transaction_id`, `document`, `document_id`)
VALUES (:fiskaly_transaction_id, :document, :document_id)',
[
'fiskaly_transaction_id' => $fiskalyTransactionId,
'document' => $document,
'document_id' => $documentId,
]
);
return $this->db->lastInsertId();
}
/**
* @param TransactionRequest|null $request
* @param TransactionReponse|null $response
*
* @return int
*/
public function create(
?TransactionRequest $request,
?TransactionReponse $response
): int {
if ($request === null && $response === null) {
throw new InvalidArgumentException('response or request required');
}
$trxId = $request === null ? $response->getId() : $request->getId();
if (!empty($this->get($trxId))) {
throw new InvalidTransactionException('Transaction already exists');
}
$this->db->perform(
'INSERT INTO `fiskaly_transaction`
(`tss_id`, `client_id`, `trx_id`, `state`,
`time_start`, `time_end`, `json_request`, `json_response`)
VALUES (:tss_id, :client_id, :trx_id, :state,
NULL, NULL, :json_request, :json_response)',
[
'tss_id' => $request === null ? $response->getTssId() : $request->getTssId(),
'client_id' => $request === null ? $response->getClientId() : $request->getClientId(),
'trx_id' => $trxId,
'state' => $response === null ? null : $response->getState(),
'json_request' => $request === null ? null : json_encode($request->toArray()),
'json_response' => $response === null ? null : json_encode($response->toArray()),
]
);
$fiskalyTransactionId = $this->db->lastInsertId();
if ($response === null) {
return $fiskalyTransactionId;
}
if ($response->getTimeStart() !== null) {
$this->db->perform(
'UPDATE `fiskaly_transaction`
SET `time_start` = FROM_UNIXTIME(:time_start)
WHERE `id` = :id',
[
'time_start' => $response->getTimeStart()->getTimestamp(),
'id' => $fiskalyTransactionId,
]
);
}
if ($response->getTimeEnd() !== null) {
$this->db->perform(
'UPDATE `fiskaly_transaction`
SET `time_end` = FROM_UNIXTIME(:time_end)
WHERE `id` = :id',
[
'time_end' => $response->getTimeEnd()->getTimestamp(),
'id' => $fiskalyTransactionId,
]
);
}
return $fiskalyTransactionId;
}
/**
* @param int $fiskalyTransactionPosSessionId
* @param TransactionRequest $request
* @param TransactionReponse $response
*/
public function update(
int $fiskalyTransactionPosSessionId,
TransactionRequest $request,
TransactionReponse $response
): void {
if ($response->getTimeEnd() === null) {
$this->db->perform(
'UPDATE `fiskaly_transaction`
SET `state` = :state,
`time_end` = NULL,
`json_request` = :json_request,
`json_response` = :json_response
WHERE `id` = :id',
[
'state' => $response->getState(),
'json_request' => json_encode($request->toApiResult()),
'json_response' => json_encode($response->toApiResult()),
'id' => $fiskalyTransactionPosSessionId,
]
);
return;
}
$this->db->perform(
'UPDATE `fiskaly_transaction`
SET `state` = :state,
`time_end` = FROM_UNIXTIME(:time_end),
`json_request` = :json_request,
`json_response` = :json_response
WHERE `id` = :id',
[
'state' => $response->getState(),
'time_end' => $response->getTimeEnd()->getTimestamp(),
'json_request' => json_encode($request->toApiResult()),
'json_response' => json_encode($response->toApiResult()),
'id' => $fiskalyTransactionPosSessionId,
]
);
}
/**
* @param Export $export
*
* @throws Exception
*/
public function createOrUpdateExport(Export $export): void
{
if ($this->getExportIdFromUuid($export->getUuId()) === null) {
$this->createExport($export);
return;
}
$this->updateExport($export);
}
/**
* @param Export $export
*
* @throws Exception
* @return int
*/
public function createExport(Export $export): int
{
$this->db->perform(
'INSERT INTO `fiskaly_kassensichv_export`
(`uuid`, `type`, `env`, `tssid`, `state`, `href`, `time_request`, `time_start`, `time_end`)
VALUES (:uuid, :type, :env, :tssid, :state, :href, :time_request, :time_start, :time_end)',
[
'uuid' => $export->getUuId(),
'type' => $export->getType(),
'env' => $export->getEnv(),
'tssid' => $export->getTssId(),
'state' => $export->getState(),
'href' => $export->getHref(),
'time_request' => $export->getTimeRequest() === null ? null : (new Datetime(
'now',
new DateTimeZone('UTC')
))->setTimeStamp(
$export->getTimeRequest()
)->format('Y-m-d H:i:s'),
'time_start' => $export->getTimeStart() === null ? null : (new Datetime(
'now', new DateTimeZone('UTC')
))->setTimeStamp($export->getTimeStart())
->format('Y-m-d H:i:s'),
'time_end' => $export->getTimeEnd() === null ? null : (new Datetime(
'now', new DateTimeZone('UTC')
))->setTimeStamp($export->getTimeEnd())
->format('Y-m-d H:i:s'),
]
);
return $this->db->lastInsertId();
}
/**
* @param Export $export
*
* @throws Exception
*/
public function updateExport(Export $export): void
{
$this->db->perform(
'UPDATE `fiskaly_kassensichv_export`
SET `state` = :state,
`href` = :href,
`time_request` = :time_request,
`time_start` = :time_start,
`time_end` = :time_end
WHERE `uuid` = :uuid',
[
'uuid' => $export->getUuId(),
'state' => $export->getState(),
'href' => $export->getHref(),
'time_request' => (new Datetime('now', new DateTimeZone('UTC')))->setTimeStamp(
$export->getTimeRequest()
)->format('Y-m-d H:i:s'),
'time_start' => (new Datetime('now', new DateTimeZone('UTC')))->setTimeStamp($export->getTimeStart())
->format('Y-m-d H:i:s'),
'time_end' => (new Datetime('now', new DateTimeZone('UTC')))->setTimeStamp($export->getTimeEnd())
->format('Y-m-d H:i:s'),
]
);
}
/**
* @param string $tssId
*
* @return array
*/
public function getExportUrlsNotInDms(string $tssId): array
{
return $this->db->fetchAll(
"SELECT fke.id, fke.href, fke.uuid
FROM `fiskaly_kassensichv_export` AS `fke`
LEFT JOIN `datei_stichwoerter` AS `ds` ON fke.id = ds.parameter AND ds.objekt = 'fiskaly_kassensichv_export'
WHERE `fke`.state = 'COMPLETED' AND fke.tssid = :tssid AND ds.id IS NULL",
['tssid' => $tssId]
);
}
/**
* @param string $state
* @param string|null $tssId
*
* @return array
*/
public function getUuIdsByState(string $state, ?string $tssId = null): array
{
if ($tssId === null) {
return $this->db->fetchCol(
'SELECT `uuid` FROM `fiskaly_kassensichv_export` WHERE `state` = :state',
[
'state' => $state,
]
);
}
return $this->db->fetchCol(
'SELECT `uuid` FROM `fiskaly_kassensichv_export` WHERE `tssid` = :tssid AND `state` = :state',
[
'tssid' => $tssId,
'state' => $state,
]
);
}
/**
* @param string $uuid
*
* @return int|null
*/
private function getExportIdFromUuid(string $uuid): ?int
{
$id = $this->db->fetchValue(
'SELECT `id` FROM `fiskaly_kassensichv_export` WHERE `uuid` = :uuid',
['uuid' => $uuid]
);
return $id === false ? null : (int)$id;
}
}
@@ -0,0 +1,340 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\FiskalyApi\Service;
use Aura\SqlQuery\Exception;
use FiskalyClient\errors\exceptions\FiskalyClientException;
use FiskalyClient\errors\exceptions\FiskalyHttpException;
use FiskalyClient\errors\exceptions\FiskalyHttpTimeoutException;
use Xentral\Components\Database\Database;
use Xentral\Modules\FiskalyApi\Data\TechnicalSecuritySystem;
use Xentral\Modules\FiskalyApi\Data\Transaction\AmountsPerPaymentType;
use Xentral\Modules\FiskalyApi\Data\Transaction\AmountsPerPaymentTypeCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\AmountsPerVatType;
use Xentral\Modules\FiskalyApi\Data\Transaction\AmountsPerVatTypeCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponse;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionReponseCollection;
use Xentral\Modules\FiskalyApi\Data\Transaction\TransactionRequest;
use Xentral\Modules\FiskalyApi\Factory\FiskalyApiFactory;
use Xentral\Modules\FiskalyApi\Factory\FiskalyTransactionFactory;
use Xentral\Modules\FiskalyApi\Transaction\Payment\CashPayment;
use Xentral\Modules\FiskalyApi\Transaction\Payment\NonCashPayment;
use Xentral\Modules\FiskalyApi\Transaction\Payment\OrderLineItem;
use Xentral\Modules\FiskalyApi\Transaction\Transaction;
use Xentral\Modules\FiskalyApi\Transaction\VatAmount\BaseVatAmount;
class FiskalyTransferService
{
/** @var FiskalyKassenSichVApi */
private $fiskalyApi;
/** @var FiskalyTransactionFactory $transactionFactory */
private $transactionFactory;
/** @var Database */
private $database;
/** @var FiskalyApiFactory $fiskalyApiFactory */
private $fiskalyApiFactory;
/**
* FiskalyTransferService constructor.
*
* @param FiskalyApiFactory $fiskalyApiFactory
* @param Database $database
*/
public function __construct(
FiskalyApiFactory $fiskalyApiFactory,
FiskalyTransactionFactory $transactionFactory,
Database $database
) {
$this->fiskalyApiFactory = $fiskalyApiFactory;
$this->transactionFactory = $transactionFactory;
$this->database = $database;
}
/**
* @param string $organizationId
*
* @throws \Exception
*/
public function loadOrganization(string $organizationId): self
{
$this->fiskalyApi = $this->fiskalyApiFactory->createFiskalyKassenSichVApiFromSystemSettings($organizationId);
return $this;
}
/**
* @param int $cashierId
* @param bool $incoming
* @param float $amount
* @param bool $isCash
*
* @throws Exception
* @return Transaction
*
* @depracated
*/
public function createTransactionFromSingleJournal(
int $cashierId,
bool $incoming,
float $amount,
bool $isCash
): Transaction {
$posProjectQuery = $this->database->select()
->from('pos_kassierer AS p')
->cols(['f.tss_uuid', 'f.client_uuid'])
->where('p.kassenkennung=:kennung')
->leftJoin('fiskaly_pos_mapping AS f', 'f.pos_id = p.projekt')
->bindValue('kennung', $cashierId);
$result = $this->database->fetchRow($posProjectQuery->getStatement(), $posProjectQuery->getBindValues());
$tssUuid = $result['tss_uuid'];
$clientId = $result['client_uuid'];
$tssDescription = $result['tss_description'];
if ($isCash) {
$paymentTypePayment = new CashPayment($amount);
} else {
$paymentTypePayment = new NonCashPayment($amount);
}
$vat = (float)0;
$sum = $amount * ($incoming ? 1 : -1);
$vatTypePayment = BaseVatAmount::fromPercentage($vat, $sum);
$transaction = new Transaction([$paymentTypePayment], [$vatTypePayment], [], $clientId);
$tss = new TechnicalSecuritySystem($tssUuid, $tssDescription);
return $this->fiskalyApi->uploadTransaction($transaction, $tss);
}
/**
* @param TransactionRequest $transactionRequest
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TransactionReponse
*/
public function startTransaction(TransactionRequest $transactionRequest): TransactionReponse
{
return $this->fiskalyApi->createTransaction($transactionRequest);
}
/**
* @param string|null $tssUuid
* @param int $offset
* @param int $limit
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TransactionReponseCollection
*/
public function getTransactions(
?string $tssUuid = null,
int $offset = 0,
int $limit = 100
): TransactionReponseCollection {
return $this->fiskalyApi->getTransactions($tssUuid, $offset, $limit);
}
/**
* @param TransactionRequest $transactionRequest
*
* @throws FiskalyClientException
* @throws FiskalyHttpException
* @throws FiskalyHttpTimeoutException
* @return TransactionReponse
*/
public function updateTransaction(TransactionRequest $transactionRequest): TransactionReponse
{
return $this->fiskalyApi->updateTransaction($transactionRequest);
}
/**
* @param TransactionReponse $transactionResponse
* @param bool $incoming
* @param float $value
* @param bool $isTraining
*
* @return TransactionRequest
*/
public function createTransactionRequestFromPosCounting(
TransactionReponse $transactionResponse,
bool $incoming,
float $value,
bool $isTraining = false
): TransactionRequest {
$negativeMultiplier = $incoming ? 1 : -1;
$vatTypePayment = BaseVatAmount::fromPercentage(0, $value);
$amountsPerVatTypeCollection = new AmountsPerVatTypeCollection(
[
new AmountsPerVatType(
$vatTypePayment->getVatType(), number_format($negativeMultiplier * $value, 2, '.', '')
),
]
);
$amountsPerPaymentTypeCollection = new AmountsPerPaymentTypeCollection(
[new AmountsPerPaymentType('CASH', number_format($negativeMultiplier * $value, 2, '.', ''), 'EUR')]
);
$receiptType = $isTraining ? 'TRAINING' : 'TRANSFER';
return $this->fiskalyApi->getFinishTransactionRequest(
$transactionResponse,
!empty($posSession['training']) ? 'TRAINING' : $receiptType,
$amountsPerVatTypeCollection,
$amountsPerPaymentTypeCollection
);
}
/**
* @param TransactionReponse $transactionResponse
* @param array $posSession
*
* @return TransactionRequest
*/
public function createFinishTransactionFromPosSession(
TransactionReponse $transactionResponse,
array $posSession
): TransactionRequest {
$amountsPerVatTypeCollection = new AmountsPerVatTypeCollection();
$amountsPerPaymentTypeCollection = new AmountsPerPaymentTypeCollection();
$paymentType = $posSession['ptype'];
$receiptType = 'RECEIPT';
$type = $posSession['rtype'];
if (in_array($type, ['einlage', 'entnahme'])) {
$type = 'TRANSFER';
}
$negativeMultiplier = 1;
if ($type === 'entnahme' || in_array($posSession['cmd'], ['stornieren', 'teilstornieren'])) {
$negativeMultiplier = -1;
}
$tip = isset($posSession['tip']) ? (float)round(str_replace(',', '.', $posSession['tip']), 2) : 0.;
$amount = (float)$posSession['soll'] * $negativeMultiplier;
if ($paymentType === 'bar') {
$amountsPerPaymentTypeCollection->addPaymentType(
new AmountsPerPaymentType('CASH', number_format($amount + $tip, 2, '.', ''), 'EUR')
);
} else {
$amountsPerPaymentTypeCollection->addPaymentType(
new AmountsPerPaymentType('NON_CASH', number_format($amount, 2, '.', ''), 'EUR')
);
if ($tip > 0) {
$amountsPerPaymentTypeCollection->addPaymentType(
new AmountsPerPaymentType('CASH', number_format($tip, 2, '.', ''), 'EUR')
);
}
}
foreach ($posSession['wk'] as $position) {
$vat = str_replace('%', '', $position['tax']);
$vat = (float)str_replace(',', '.', $vat);
$sum = (float)str_replace(',', '.', $position['preis']) * (float)str_replace(',', '.', $position['amount'])
* (1 - (float)str_replace(',', '.', $position['rabatt']) / 100);
$vatTypePayment = BaseVatAmount::fromPercentage($vat, $sum);
$amountsPerVatTypeCollection->combine(
new AmountsPerVatTypeCollection(
[
new AmountsPerVatType($vatTypePayment->getVatType(), number_format($sum, 2, '.', '')),
]
)
);
}
if ($tip > 0) {
$vatTypePayment = BaseVatAmount::fromPercentage(0, $tip);
$amountsPerVatTypeCollection->combine(
new AmountsPerVatTypeCollection(
[
new AmountsPerVatType($vatTypePayment->getVatType(), number_format($tip, 2, '.', '')),
]
)
);
}
return $this->fiskalyApi->getFinishTransactionRequest(
$transactionResponse,
!empty($posSession['training']) ? 'TRAINING' : $receiptType,
$amountsPerVatTypeCollection,
$amountsPerPaymentTypeCollection
);
}
/**
* @param $posSession
*
* @throws Exception
* @return Transaction
*
* @depracated
*/
public function transferPosSession($posSession): Transaction
{
$cashierId = $posSession['kassiererId'];
$result = $this->transactionFactory->getClientAndTssInfoFromCashierId((string)$cashierId);
$tssUuid = $result['tss_uuid'];
$clientId = $result['client_uuid'];
$tssDescription = $result['tss_description'];
$paymentType = $posSession['ptype'];
$negativeMultiplier = 1;
if (in_array($posSession['cmd'], ['stornieren', 'teilstornieren'])) {
$negativeMultiplier = -1;
}
$tip = isset($posSession['tip']) ? (float)round(str_replace(',', '.', $posSession['tip']), 2) : 0.;
$amount = (float)$posSession['soll'] * $negativeMultiplier;
$paymentTypePayments = [];
if ($paymentType === 'bar') {
$paymentTypePayments[] = new CashPayment($amount + $tip);
} else {
$paymentTypePayments[] = new NonCashPayment($amount);
if ($tip > 0) {
$paymentTypePayments[] = new CashPayment($tip);
}
}
$vatTypeAmounts = [];
$oderLineItems = [];
foreach ($posSession['wk'] as $position) {
$vat = str_replace('%', '', $position['tax']);
$vat = (float)str_replace(',', '.', $vat);
$sum = (float)str_replace(',', '.', $position['preis']);
$amount = (float)$position['amount'];
$oderLineItems[] = new OrderLineItem($amount, (string)$position['artikel'], $sum * $negativeMultiplier);
$sum *= $amount * $negativeMultiplier;
$vatTypePayment = BaseVatAmount::fromPercentage($vat, $sum);
$vatTypeClass = get_class($vatTypePayment);
/** @var BaseVatAmount $vatTypeAmount */
$cachedVatTypePayment = $vatTypeAmounts[$vatTypeClass] ?? null;
if (empty($cachedVatTypePayment)) {
$vatTypeAmounts[$vatTypeClass] = $vatTypePayment;
} else {
$cachedVatTypePayment->add($sum);
}
}
if ($tip > 0) {
$vatTypePayment = BaseVatAmount::fromPercentage(0., $tip);
$vatTypeClass = get_class($vatTypePayment);
/** @var BaseVatAmount $vatTypeAmount */
$cachedVatTypePayment = $vatTypeAmounts[$vatTypeClass] ?? null;
if ($cachedVatTypePayment === null) {
$vatTypeAmounts[$vatTypeClass] = $vatTypePayment;
} else {
$cachedVatTypePayment->add($tip);
}
$oderLineItems[] = new OrderLineItem(1, 'Trinkgeld', $tip);
}
$transaction = new Transaction($paymentTypePayments, array_values($vatTypeAmounts), $oderLineItems, $clientId);
$tss = new TechnicalSecuritySystem($tssUuid, $tssDescription);
return $this->fiskalyApi->uploadTransaction($transaction, $tss);
}
}