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:
@@ -222,13 +222,21 @@ class FinancesController
|
||||
|
||||
$repo = self::repo();
|
||||
|
||||
$operationId = (int)\S::get( 'operation-id' );
|
||||
$fakturowniaContext = null;
|
||||
|
||||
if ( $operationId > 0 )
|
||||
$fakturowniaContext = $repo -> fakturowniaOperationContext( $operationId );
|
||||
|
||||
return \Tpl::view( 'finances/operation-edit', [
|
||||
'operation' => $repo -> operationDetails( \S::get( 'operation-id' ) ),
|
||||
'category_id' => \S::get( 'category-id' ),
|
||||
'tags' => $repo -> tagsList( \S::get_session( 'finance-group' ) ),
|
||||
'tags_json' => $repo -> tagsJson( \S::get_session( 'finance-group' ) ),
|
||||
'operation_date' => \S::get_session( 'operation-date' ),
|
||||
'clients' => $repo -> clientsList()
|
||||
'clients' => $repo -> clientsList(),
|
||||
'fakturownia_operation_context' => $fakturowniaContext,
|
||||
'fakturownia_categories' => self::prepareCategoryOptions( $repo -> categoriesFlatList() )
|
||||
] );
|
||||
}
|
||||
|
||||
@@ -448,4 +456,53 @@ class FinancesController
|
||||
header( 'Location: /finances/main_view/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function fakturowniaOperationMappingSave()
|
||||
{
|
||||
if ( !self::requireAuth() )
|
||||
return false;
|
||||
|
||||
if ( !\S::csrf_verify() )
|
||||
{
|
||||
\S::alert( 'Nieprawidlowy token bezpieczenstwa. Odswiez strone i sproboj ponownie.' );
|
||||
header( 'Location: /finances/main_view/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$operationId = (int)\S::get( 'operation_id' );
|
||||
$financeCategoryId = (int)\S::get( 'finance_category_id' );
|
||||
$applyToAll = (int)\S::get( 'apply_to_all' ) === 1;
|
||||
$returnCategoryId = (int)\S::get( 'return_category_id' );
|
||||
|
||||
$repo = self::repo();
|
||||
if ( $operationId <= 0 || $financeCategoryId <= 0 || !$repo -> categoryExists( $financeCategoryId ) )
|
||||
{
|
||||
\S::alert( 'Nie udalo sie zapisac dopasowania. Sprawdz dane formularza.' );
|
||||
header( 'Location: /finances/main_view/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$context = $repo -> fakturowniaOperationContext( $operationId );
|
||||
if ( !$context )
|
||||
{
|
||||
\S::alert( 'Ta operacja nie jest powiazana z mapowaniem Fakturownia.' );
|
||||
header( 'Location: /finances/operations_list/category-id=' . $returnCategoryId );
|
||||
exit;
|
||||
}
|
||||
|
||||
$importRepo = self::importRepo();
|
||||
$importRepo -> ensureTables();
|
||||
$importRepo -> saveItemMapping( $context['external_item_key'], $context['external_name'], $financeCategoryId );
|
||||
|
||||
$repo -> updateOperationCategory( $operationId, $financeCategoryId );
|
||||
$updatedCount = $repo -> updateFakturowniaOperationsCategoryByItemName( $context['item_name'], $financeCategoryId, $applyToAll );
|
||||
|
||||
if ( $applyToAll )
|
||||
\S::alert( 'Dopasowanie zmienione. Zaktualizowano operacje: ' . (int)$updatedCount . '.' );
|
||||
else
|
||||
\S::alert( 'Dopasowanie zmienione dla tej operacji.' );
|
||||
|
||||
header( 'Location: /finances/operations_list/category-id=' . $financeCategoryId );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user