feat(05-finances-fakturownia-import): complete Fakturownia mapping corrections

Phase 5 complete:
- add category mapping edit from operation edit for Fakturownia operations
- update current operation category immediately after mapping change
- support optional bulk update for matching imported operations
- close 05-05 and 05-06 PAUL summaries

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
Codex
2026-05-04 22:57:55 +02:00
parent d0ab2a4f5f
commit 7acf22c71a
10 changed files with 672 additions and 27 deletions

View File

@@ -155,6 +155,9 @@ class FinanceRepository
public function operationDetails( $operation_id )
{
$operation = $this -> mdb -> get( 'finance_operations', '*', [ 'id' => $operation_id ] );
if ( !$operation )
return [];
$operation['tags'] = $this -> mdb -> query(
'SELECT tag, tag_id FROM finance_operation_tags AS fot '
. 'INNER JOIN finance_tags AS ft ON fot.tag_id = ft.id '
@@ -164,6 +167,103 @@ class FinanceRepository
return $operation;
}
public function fakturowniaOperationContext( $operationId )
{
$operationId = (int)$operationId;
if ( $operationId <= 0 )
return null;
$operation = $this -> mdb -> get( 'finance_operations', '*', [ 'id' => $operationId ] );
if ( !$operation )
return null;
$imported = $this -> mdb -> query(
'SELECT id FROM fakturownia_imported_documents '
. 'WHERE FIND_IN_SET( :operation_id, finance_operation_ids ) LIMIT 1',
[ ':operation_id' => (string)$operationId ]
) -> fetch( \PDO::FETCH_ASSOC );
if ( !$imported )
return null;
$itemName = $this -> extractItemNameFromDescription( (string)( $operation['description'] ?? '' ) );
if ( $itemName === '' )
return null;
$externalItemKey = $this -> buildNameItemKey( $itemName );
$mapping = $this -> mdb -> get( 'fakturownia_item_mappings', '*', [
'external_item_key' => $externalItemKey
] );
if ( !$mapping )
$mapping = $this -> findItemMappingByExternalName( $itemName );
if ( !$mapping )
return null;
return [
'operation_id' => $operationId,
'item_name' => $itemName,
'external_item_key' => (string)$mapping['external_item_key'],
'external_name' => (string)$mapping['external_name'],
'finance_category_id' => (int)$mapping['finance_category_id']
];
}
public function updateOperationCategory( $operationId, $categoryId )
{
$this -> mdb -> update( 'finance_operations', [
'category_id' => (int)$categoryId
], [
'id' => (int)$operationId
] );
}
public function updateFakturowniaOperationsCategoryByItemName( $itemName, $categoryId, $includeAll )
{
$itemName = trim( (string)$itemName );
if ( $itemName === '' )
return 0;
if ( !$includeAll )
return 0;
$pattern = '%| ' . $itemName . ' |%';
$stmt = $this -> mdb -> query(
'UPDATE finance_operations AS fo '
. 'INNER JOIN fakturownia_imported_documents AS fid ON FIND_IN_SET( fo.id, fid.finance_operation_ids ) '
. 'INNER JOIN finance_categories AS fc_old ON fc_old.id = fo.category_id '
. 'INNER JOIN finance_categories AS fc_new ON fc_new.id = :category_id '
. 'SET fo.category_id = :category_id '
. 'WHERE fo.description LIKE :pattern AND fc_old.group_id = fc_new.group_id',
[ ':category_id' => (int)$categoryId, ':pattern' => $pattern ]
);
if ( method_exists( $stmt, 'rowCount' ) )
return (int)$stmt -> rowCount();
return 0;
}
public function operationGroupId( $operationId )
{
$row = $this -> mdb -> query(
'SELECT fc.group_id '
. 'FROM finance_operations AS fo '
. 'INNER JOIN finance_categories AS fc ON fc.id = fo.category_id '
. 'WHERE fo.id = :operation_id LIMIT 1',
[ ':operation_id' => (int)$operationId ]
) -> fetch( \PDO::FETCH_ASSOC );
return isset( $row['group_id'] ) ? (int)$row['group_id'] : 0;
}
public function categoryGroupId( $categoryId )
{
return (int)$this -> mdb -> get( 'finance_categories', 'group_id', [ 'id' => (int)$categoryId ] );
}
public function saveOperation( $operation_id, $category_id, $date, $amount, $description, $tags, $group_id, $client_id = null )
{
$data = [
@@ -434,4 +534,44 @@ class FinanceRepository
) -> fetchAll();
return $results[0][0];
}
private function extractItemNameFromDescription( $description )
{
$description = trim( (string)$description );
if ( $description === '' || strpos( $description, 'Fakturownia |' ) !== 0 )
return '';
$parts = explode( '|', $description );
if ( count( $parts ) < 3 )
return '';
return trim( (string)$parts[2] );
}
private function buildNameItemKey( $name )
{
$normalized = trim( (string)$name );
if ( function_exists( 'mb_strtolower' ) )
$normalized = mb_strtolower( $normalized, 'UTF-8' );
else
$normalized = strtolower( $normalized );
return 'name:' . $normalized;
}
private function findItemMappingByExternalName( $itemName )
{
$itemName = trim( (string)$itemName );
if ( $itemName === '' )
return null;
$row = $this -> mdb -> query(
'SELECT * FROM fakturownia_item_mappings '
. 'WHERE LOWER( external_name ) = LOWER( :external_name ) '
. 'ORDER BY id DESC LIMIT 1',
[ ':external_name' => $itemName ]
) -> fetch( \PDO::FETCH_ASSOC );
return $row ? $row : null;
}
}