216 lines
7.1 KiB
PHP
216 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Import\Handlers;
|
|
|
|
use App\Models\Account;
|
|
use App\Models\Import;
|
|
use App\Services\Import\BaseEntityHandler;
|
|
use App\Services\Import\DecimalNormalizer;
|
|
|
|
class AccountHandler extends BaseEntityHandler
|
|
{
|
|
public function getEntityClass(): string
|
|
{
|
|
return Account::class;
|
|
}
|
|
|
|
/**
|
|
* Override validate to handle contract_id and reference from context.
|
|
* Both contract_id and reference are populated in process() (reference defaults to contract reference).
|
|
*/
|
|
public function validate(array $mapped): array
|
|
{
|
|
// Remove contract_id and reference from validation - both will be populated in process()
|
|
// Reference defaults to contract.reference if not set (matching v1 behavior)
|
|
$rules = $this->entityConfig?->validation_rules ?? [];
|
|
|
|
unset($rules['contract_id'], $rules['reference']);
|
|
|
|
if (empty($rules)) {
|
|
return ['valid' => true, 'errors' => []];
|
|
}
|
|
|
|
$validator = \Illuminate\Support\Facades\Validator::make($mapped, $rules);
|
|
|
|
if ($validator->fails()) {
|
|
return [
|
|
'valid' => false,
|
|
'errors' => $validator->errors()->all(),
|
|
];
|
|
}
|
|
|
|
return ['valid' => true, 'errors' => []];
|
|
}
|
|
|
|
public function resolve(array $mapped, array $context = []): mixed
|
|
{
|
|
$reference = $mapped['reference'] ?? null;
|
|
$contractId = $mapped['contract_id'] ?? $context['contract']['entity']->id ?? null;
|
|
|
|
if (! $reference || ! $contractId) {
|
|
return null;
|
|
}
|
|
|
|
return Account::where('contract_id', $contractId)
|
|
->where('reference', $reference)
|
|
->first();
|
|
}
|
|
|
|
public function process(Import $import, array $mapped, array $raw, array $context = []): array
|
|
{
|
|
// Ensure contract context
|
|
if (! isset($context['contract'])) {
|
|
return [
|
|
'action' => 'skipped',
|
|
'message' => 'Account requires contract context',
|
|
];
|
|
}
|
|
|
|
// Fallback: if account.reference is empty, use contract.reference (matching v1 behavior)
|
|
if (empty($mapped['reference'])) {
|
|
$contractReference = $context['contract']['entity']->reference ?? null;
|
|
if ($contractReference) {
|
|
$mapped['reference'] = preg_replace('/\s+/', '', trim((string) $contractReference));
|
|
}
|
|
}
|
|
|
|
$contractId = $context['contract']['entity']->id;
|
|
$mapped['contract_id'] = $contractId;
|
|
|
|
$existing = $this->resolve($mapped, $context);
|
|
|
|
if ($existing) {
|
|
// Track old balance for activity creation
|
|
$oldBalance = (float) ($existing->balance_amount ?? 0);
|
|
|
|
$payload = $this->buildPayload($mapped, $existing);
|
|
$appliedFields = $this->trackAppliedFields($existing, $payload);
|
|
|
|
if (empty($appliedFields)) {
|
|
return [
|
|
'action' => 'skipped',
|
|
'entity' => $existing,
|
|
'message' => 'No changes detected',
|
|
];
|
|
}
|
|
|
|
$existing->fill($payload);
|
|
$existing->save();
|
|
|
|
// Create activity if balance changed and tracking is enabled
|
|
if ($this->getOption('track_balance_changes', true) && array_key_exists('balance_amount', $appliedFields)) {
|
|
$this->createBalanceChangeActivity($existing, $oldBalance, $import, $context);
|
|
}
|
|
|
|
return [
|
|
'action' => 'updated',
|
|
'entity' => $existing,
|
|
'applied_fields' => $appliedFields,
|
|
];
|
|
}
|
|
|
|
// Create new account
|
|
$account = new Account;
|
|
$payload = $this->buildPayload($mapped, $account);
|
|
|
|
// Ensure required defaults for new accounts
|
|
if (!isset($payload['type_id'])) {
|
|
$payload['type_id'] = $this->getDefaultAccountTypeId();
|
|
}
|
|
|
|
$account->fill($payload);
|
|
$account->save();
|
|
|
|
return [
|
|
'action' => 'inserted',
|
|
'entity' => $account,
|
|
'applied_fields' => array_keys($payload),
|
|
];
|
|
}
|
|
|
|
protected function buildPayload(array $mapped, $model): array
|
|
{
|
|
$payload = [];
|
|
|
|
$fieldMap = [
|
|
'contract_id' => 'contract_id',
|
|
'reference' => 'reference',
|
|
'title' => 'title',
|
|
'description' => 'description',
|
|
'balance_amount' => 'balance_amount',
|
|
'currency' => 'currency',
|
|
];
|
|
|
|
foreach ($fieldMap as $source => $target) {
|
|
if (array_key_exists($source, $mapped)) {
|
|
$value = $mapped[$source];
|
|
|
|
// Normalize decimal fields (convert comma to period)
|
|
if (in_array($source, ['balance_amount', 'initial_amount']) && is_string($value)) {
|
|
$value = DecimalNormalizer::normalize($value);
|
|
}
|
|
|
|
$payload[$target] = $value;
|
|
}
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
/**
|
|
* Create activity when account balance changes.
|
|
*/
|
|
protected function createBalanceChangeActivity(Account $account, float $oldBalance, Import $import, array $context): void
|
|
{
|
|
if (! $this->getOption('create_activity_on_balance_change', true)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$newBalance = (float) ($account->balance_amount ?? 0);
|
|
|
|
// Skip if balance didn't actually change
|
|
if ($newBalance === $oldBalance) {
|
|
return;
|
|
}
|
|
|
|
$currency = \App\Models\PaymentSetting::first()?->default_currency ?? 'EUR';
|
|
$beforeStr = number_format($oldBalance, 2, ',', '.').' '.$currency;
|
|
$afterStr = number_format($newBalance, 2, ',', '.').' '.$currency;
|
|
$note = 'Sprememba stanja (Stanje pred: '.$beforeStr.', Stanje po: '.$afterStr.'; Izvor: sprememba)';
|
|
|
|
// Get client_case_id
|
|
$clientCaseId = $account->contract?->client_case_id;
|
|
|
|
if ($clientCaseId) {
|
|
// Use action_id from import meta if available
|
|
$metaActionId = (int) ($import->meta['action_id'] ?? 0);
|
|
|
|
if ($metaActionId > 0) {
|
|
\App\Models\Activity::create([
|
|
'due_date' => null,
|
|
'amount' => null,
|
|
'note' => $note,
|
|
'action_id' => $metaActionId,
|
|
'decision_id' => $import->meta['decision_id'] ?? null,
|
|
'client_case_id' => $clientCaseId,
|
|
'contract_id' => $account->contract_id,
|
|
]);
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
\Log::warning('Failed to create balance change activity', [
|
|
'account_id' => $account->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get default account type ID.
|
|
*/
|
|
protected function getDefaultAccountTypeId(): int
|
|
{
|
|
return (int) (\App\Models\AccountType::min('id') ?? 1);
|
|
}
|
|
} |