Plugin Directory

Changeset 3306740


Ignore:
Timestamp:
06/04/2025 09:33:48 PM (9 months ago)
Author:
tulipwork
Message:

Mise à jour du plugin UBD vers la version 1.3

Location:
ultimate-business-dashboard/trunk
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • ultimate-business-dashboard/trunk/assets/js/admin.js

    r3300693 r3306740  
    3434        });
    3535
    36         // Invoices: Select all checkboxes
     36        // Invoices: Select all checkboxes for categories and invoices
    3737        if ($('#select-all-categories').length) {
    3838            $('#select-all-categories').on('change', function() {
    3939                $('input[name="category_ids[]"]:not(:disabled)').prop('checked', this.checked);
     40            });
     41        }
     42        if ($('#select-all').length) {
     43            $('#select-all').on('change', function() {
     44                $('input[name="invoice_ids[]"]:not(:disabled)').prop('checked', this.checked);
    4045            });
    4146        }
  • ultimate-business-dashboard/trunk/includes/categories.php

    r3301653 r3306740  
    1919
    2020    $categories = get_option('ultibuda_invoice_categories', []);
     21    // Convertir les anciennes catégories (nom seul) en nouvelle structure (name + code)
     22    foreach ($categories as $cat_id => $cat_data) {
     23        if (is_string($cat_data)) {
     24            $categories[$cat_id] = [
     25                'name' => $cat_data,
     26                'code' => '',
     27            ];
     28        }
     29    }
    2130    $category_types = get_option('ultibuda_category_types', []);
    2231
    23     // Handle category type updates
    24     if (isset($_POST['ultibuda_save_categories']) && check_admin_referer('ultibuda_save_categories')) {
     32    // Handle category type and code updates
     33    if (isset($_POST['ultibuda_save_categories']) && check_admin_referer('ultibuda_save_categories', 'ultibuda_save_categories_nonce')) {
    2534        $updated_types = isset($_POST['category_types']) ? map_deep($_POST['category_types'], 'sanitize_text_field') : [];
     35        $updated_codes = isset($_POST['category_codes']) ? map_deep($_POST['category_codes'], 'sanitize_text_field') : [];
    2636        $updated = false;
    2737
     38        // Update category types
    2839        foreach ($updated_types as $cat_id => $type) {
    29             // Validate category type
    3040            if (in_array($type, ['Fixed', 'Variable', 'None'], true) && $type !== ($category_types[$cat_id] ?? 'None')) {
    3141                $category_types[$cat_id] = $type;
    3242                $updated = true;
    3343            } else {
    34                 unset($updated_types[$cat_id]); // Remove invalid types
     44                unset($updated_types[$cat_id]);
     45            }
     46        }
     47
     48        // Update category codes
     49        foreach ($updated_codes as $cat_id => $code) {
     50            if (isset($categories[$cat_id])) {
     51                $code = trim($code);
     52                if ($code !== ($categories[$cat_id]['code'] ?? '')) {
     53                    $categories[$cat_id]['code'] = $code ?: '';
     54                    $updated = true;
     55                }
    3556            }
    3657        }
     
    3859        if ($updated) {
    3960            update_option('ultibuda_category_types', $category_types);
    40             echo '<div class="updated"><p>' . esc_html__('Category types updated successfully!', 'ultimate-business-dashboard') . '</p></div>';
     61            update_option('ultibuda_invoice_categories', $categories);
     62            echo '<div class="updated"><p>' . esc_html__('Category types and codes updated successfully!', 'ultimate-business-dashboard') . '</p></div>';
    4163        } else {
    4264            echo '<div class="error"><p>' . esc_html__('No changes detected.', 'ultimate-business-dashboard') . '</p></div>';
     
    4567
    4668    // Handle category deletion
    47     if (isset($_POST['ultibuda_delete_categories']) && !empty($_POST['category_ids']) && check_admin_referer('ultibuda_delete_categories')) {
     69    if (isset($_POST['ultibuda_delete_categories']) && !empty($_POST['category_ids']) && check_admin_referer('ultibuda_delete_categories', 'ultibuda_delete_categories_nonce')) {
    4870        $ids_to_delete = array_map('sanitize_text_field', wp_unslash($_POST['category_ids']));
    4971        $invoices_data = get_option('ultibuda_invoices_data', []);
     
    7395            update_option('ultibuda_category_types', $category_types);
    7496            update_option('ultibuda_invoices_data', $invoices_data);
    75             // translators: %d is the number of categories deleted
    7697            echo '<div class="updated"><p>' . sprintf(
    7798                esc_html__('%d category(ies) deleted successfully! Associated invoices reassigned to Uncategorized.', 'ultimate-business-dashboard'),
     
    83104    } elseif (isset($_POST['ultibuda_delete_categories']) && empty($_POST['category_ids'])) {
    84105        echo '<div class="error"><p>' . esc_html__('Please select at least one category to delete.', 'ultimate-business-dashboard') . '</p></div>';
    85     } elseif (isset($_POST['ultibuda_delete_categories'])) {
    86         echo '<div class="error"><p>' . esc_html__('Category deletion failed due to invalid nonce. Please try again.', 'ultimate-business-dashboard') . '</p></div>';
    87     }
    88 
     106    }
     107    // Handle category merge
     108    if (isset($_POST['ultibuda_merge_categories']) && !empty($_POST['category_ids']) && !empty($_POST['target_category']) && check_admin_referer('ultibuda_merge_categories', 'ultibuda_merge_categories_nonce')) {
     109        $ids_to_merge = array_map('sanitize_text_field', wp_unslash($_POST['category_ids']));
     110        $target_category_id = sanitize_text_field(wp_unslash($_POST['target_category']));
     111        $invoices_data = get_option('ultibuda_invoices_data', []);
     112        $merged_count = 0;
     113
     114        // Validate target category
     115        if (!isset($categories[$target_category_id]) || $target_category_id === 'uncategorized') {
     116            echo '<div class="error"><p>' . esc_html__('Invalid target category selected.', 'ultimate-business-dashboard') . '</p></div>';
     117        } else {
     118            foreach ($ids_to_merge as $cat_id) {
     119                if ($cat_id === 'uncategorized' || $cat_id === $target_category_id) {
     120                    continue;
     121                }
     122                if (isset($categories[$cat_id])) {
     123                    // Reassign invoices to target category
     124                    foreach ($invoices_data as &$invoice) {
     125                        if ($invoice['category_id'] === $cat_id) {
     126                            $invoice['category_id'] = $target_category_id;
     127                        }
     128                    }
     129                    unset($categories[$cat_id]);
     130                    unset($category_types[$cat_id]);
     131                    $merged_count++;
     132                }
     133            }
     134            unset($invoice);
     135
     136            if ($merged_count > 0) {
     137                update_option('ultibuda_invoice_categories', $categories);
     138                update_option('ultibuda_category_types', $category_types);
     139                update_option('ultibuda_invoices_data', $invoices_data);
     140                echo '<div class="updated"><p>' . sprintf(
     141                    esc_html__('%d category(ies) merged successfully into %s.', 'ultimate-business-dashboard'),
     142                    esc_html((int)$merged_count),
     143                    esc_html($categories[$target_category_id]['name'])
     144                ) . '</p></div>';
     145            } else {
     146                echo '<div class="error"><p>' . esc_html__('No categories were merged. Please select valid categories.', 'ultimate-business-dashboard') . '</p></div>';
     147            }
     148        }
     149    } elseif (isset($_POST['ultibuda_merge_categories']) && (empty($_POST['category_ids']) || empty($_POST['target_category']))) {
     150        echo '<div class="error"><p>' . esc_html__('Please select at least one category to merge and a target category.', 'ultimate-business-dashboard') . '</p></div>';
     151    }
    89152    if (empty($categories)) {
    90153        ?>
     
    101164    <div class="wrap">
    102165        <h1><?php esc_html_e('Categories', 'ultimate-business-dashboard'); ?></h1>
    103         <p><?php esc_html_e('Manage categories imported from your Dext or other CSV files. Category names are taken directly from the CSV (e.g., "Emballages perdus" from "602610 - Emballages perdus" for Dext exports). The Uncategorized category is the default for unassigned invoices.', 'ultimate-business-dashboard'); ?></p>
    104 
    105         <!-- Form for updating category types -->
    106         <form method="post" id="ultibuda-update-categories-form">
    107             <?php wp_nonce_field('ultibuda_save_categories'); ?>
     166        <p><?php esc_html_e('Manage categories imported from your Dext or other CSV files. Category names are taken directly from the CSV. The Uncategorized category is the default for unassigned invoices.', 'ultimate-business-dashboard'); ?></p>
     167
     168       <!-- Combined form for updating types, codes, and merging categories -->
     169        <form method="post" id="ultibuda-categories-form">
     170            <?php wp_nonce_field('ultibuda_save_categories', 'ultibuda_save_categories_nonce'); ?>
     171            <?php wp_nonce_field('ultibuda_delete_categories', 'ultibuda_delete_categories_nonce'); ?>
     172            <?php wp_nonce_field('ultibuda_merge_categories', 'ultibuda_merge_categories_nonce'); ?>
    108173            <div class="category-table-container">
    109174                <table class="wp-list-table widefat striped" style="margin-top: 10px;">
     
    111176                        <tr>
    112177                            <th style="width: 5%;"><input type="checkbox" id="select-all-categories"></th>
    113                             <th style="width: 45%;"><?php esc_html_e('Category Name', 'ultimate-business-dashboard'); ?></th>
    114                             <th style="width: 50%;"><?php esc_html_e('Type', 'ultimate-business-dashboard'); ?></th>
     178                            <th style="width: 40%;"><?php esc_html_e('Category Name', 'ultimate-business-dashboard'); ?></th>
     179                            <th style="width: 20%;"><?php esc_html_e('Accounting Code', 'ultimate-business-dashboard'); ?></th>
     180                            <th style="width: 35%;"><?php esc_html_e('Type', 'ultimate-business-dashboard'); ?></th>
    115181                        </tr>
    116182                    </thead>
    117183                    <tbody>
    118                         <?php foreach ($categories as $cat_id => $cat_name) : ?>
     184                        <?php foreach ($categories as $cat_id => $cat_data) : ?>
    119185                            <tr>
    120                                 <td><input type="checkbox" name="category_ids[]" value="<?php echo esc_attr($cat_id); ?>" form="ultibuda-delete-categories-form" <?php echo $cat_id === 'uncategorized' ? 'disabled' : ''; ?>></td>
    121                                 <td><?php echo esc_html($cat_name); ?></td>
     186                                <td><input type="checkbox" name="category_ids[]" value="<?php echo esc_attr($cat_id); ?>" <?php echo $cat_id === 'uncategorized' ? 'disabled' : ''; ?>></td>
     187                                <td><?php echo esc_html($cat_data['name']); ?></td>
     188                                <td>
     189                                    <input type="text" name="category_codes[<?php echo esc_attr($cat_id); ?>]" value="<?php echo esc_attr($cat_data['code'] ?: ''); ?>" style="width: 100%;" placeholder="<?php esc_attr_e('-', 'ultimate-business-dashboard'); ?>">
     190                                </td>
    122191                                <td>
    123192                                    <select name="category_types[<?php echo esc_attr($cat_id); ?>]">
     
    133202            </div>
    134203            <p class="submit">
    135                 <input type="submit" name="ultibuda_save_categories" class="button-primary" value="<?php esc_attr_e('Save Changes', 'ultimate-business-dashboard'); ?>">
     204                <input type="submit" name="ultibuda_save_categories" class="button button-primary" value="<?php esc_attr_e('Save Changes', 'ultimate-business-dashboard'); ?>">
     205                <input type="submit" name="ultibuda_delete_categories" class="button button-secondary" value="<?php esc_attr_e('Delete Selected', 'ultimate-business-dashboard'); ?>" onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete the selected categories? Associated invoices will be reassigned to Uncategorized.', 'ultimate-business-dashboard'); ?>');">
     206                <label for="target_category" style="margin-left: 10px;"><?php esc_html_e('Merge into:', 'ultimate-business-dashboard'); ?></label>
     207                <select name="target_category" id="target_category">
     208                    <option value=""><?php esc_html_e('Select target category', 'ultimate-business-dashboard'); ?></option>
     209                    <?php foreach ($categories as $cat_id => $cat_data) : ?>
     210                        <?php if ($cat_id !== 'uncategorized') : ?>
     211                            <option value="<?php echo esc_attr($cat_id); ?>"><?php echo esc_html($cat_data['name']); ?><?php echo !empty($cat_data['code']) ? ' (' . esc_html($cat_data['code']) . ')' : ''; ?></option>
     212                        <?php endif; ?>
     213                    <?php endforeach; ?>
     214                </select>
     215                <input type="submit" name="ultibuda_merge_categories" class="button button-secondary" value="<?php esc_attr_e('Merge Selected', 'ultimate-business-dashboard'); ?>" onclick="return confirm('<?php esc_attr_e('Are you sure you want to merge the selected categories? Their invoices will be reassigned to the target category, and the selected categories will be deleted.', 'ultimate-business-dashboard'); ?>');">
    136216            </p>
     217            <p class="description"><?php esc_html_e('Edit accounting codes to assign or update codes for existing categories. This helps group new imports with matching codes.', 'ultimate-business-dashboard'); ?></p>
    137218        </form>
    138 
    139         <!-- Form for deleting categories -->
    140         <form method="post" id="ultibuda-delete-categories-form">
    141             <?php wp_nonce_field('ultibuda_delete_categories'); ?>
    142             <p class="submit">
    143                 <input type="submit" name="ultibuda_delete_categories" class="button button-secondary" value="<?php esc_attr_e('Delete Selected', 'ultimate-business-dashboard'); ?>" onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete the selected categories? Associated invoices will be reassigned to Uncategorized.', 'ultimate-business-dashboard'); ?>');">
    144             </p>
    145         </form>
    146 
    147219    </div>
    148220    <?php
  • ultimate-business-dashboard/trunk/includes/dashboard.php

    r3301653 r3306740  
    2020    $invoices_data = get_option('ultibuda_invoices_data', []);
    2121    $categories = get_option('ultibuda_invoice_categories', []);
     22    // Convertir les anciennes catégories (chaîne) en nouvelle structure (tableau)
     23    foreach ($categories as $cat_id => $cat_data) {
     24        if (is_string($cat_data)) {
     25            $categories[$cat_id] = [
     26                'name' => $cat_data,
     27                'code' => '',
     28            ];
     29        }
     30    }
    2231    $all_revenues = get_option('ultibuda_revenues', []);
    2332    $all_other_costs = get_option('ultibuda_other_costs', []);
     
    120129                                        <?php foreach ($monthly_data[$month]['categories'] as $cat => $amount) : ?>
    121130                                            <div class="detail-row sub-item">
    122                                                 <span class="label">- <?php echo esc_html($cat); ?></span>
     131                                                <span class="label">- <?php echo esc_html($cat); ?><?php echo isset($categories[array_search($cat, array_column($categories, 'name'))]['code']) && !empty($categories[array_search($cat, array_column($categories, 'name'))]['code']) ? ' (' . esc_html($categories[array_search($cat, array_column($categories, 'name'))]['code']) . ')' : ''; ?></span>
    123132                                                <span class="value"><?php echo esc_html(number_format($amount, 2, '.', ',')); ?></span>
    124133                                            </div>
     
    241250                                    <?php foreach ($annual_data['categories'] as $cat => $amount) : ?>
    242251                                        <div class="detail-row sub-item">
    243                                             <span class="label">- <?php echo esc_html($cat); ?></span>
     252                                            <span class="label">- <?php echo esc_html($cat); ?><?php echo isset($categories[array_search($cat, array_column($categories, 'name'))]['code']) && !empty($categories[array_search($cat, array_column($categories, 'name'))]['code']) ? ' (' . esc_html($categories[array_search($cat, array_column($categories, 'name'))]['code']) . ')' : ''; ?></span>
    244253                                            <span class="value"><?php echo esc_html(number_format($amount, 2, '.', ',')); ?></span>
    245254                                        </div>
     
    541550
    542551    $category_types = get_option('ultibuda_category_types', []);
    543 
     552    // Convertir les anciennes catégories (chaîne) en nouvelle structure (tableau)
     553    foreach ($categories as $cat_id => $cat_data) {
     554        if (is_string($cat_data)) {
     555            $categories[$cat_id] = [
     556                'name' => $cat_data,
     557                'code' => '',
     558            ];
     559        }
     560    }
    544561    $monthly_revenues = $all_revenues[$year] ?? array_fill(1, 12, 0);
    545562    $monthly_amortizations = $all_amortizations[$year] ?? array_fill(1, 12, 0);
     
    570587    foreach ($invoices_data as $entry) {
    571588        $category_id = $entry['category_id'] ?? 'uncategorized';
    572         $category_name = $categories[$category_id] ?? esc_html__('Uncategorized', 'ultimate-business-dashboard');
     589        $category_name = isset($categories[$category_id]['name']) ? $categories[$category_id]['name'] : esc_html__('Uncategorized', 'ultimate-business-dashboard');
    573590        $cat_type = $category_types[$category_id] ?? 'None';
    574591
     
    640657    }
    641658
     659    // Sort categories alphabetically by name
     660    foreach ($monthly_data as &$month_data) {
     661        ksort($month_data['categories']);
     662    }
     663    unset($month_data);
     664    ksort($annual_data['categories']);
     665
     666    $annual_data['category_breakdown'] = $annual_data['categories'];
     667
    642668    $annual_data['category_breakdown'] = $annual_data['categories'];
    643669    if ($annual_data['other_cost'] > 0) {
  • ultimate-business-dashboard/trunk/includes/imports.php

    r3301653 r3306740  
    145145    $existing_data = get_option('ultibuda_invoices_data', []);
    146146    $categories = get_option('ultibuda_invoice_categories', []);
     147    // Convertir les anciennes catégories (chaîne) en nouvelle structure (tableau)
     148    foreach ($categories as $cat_id => $cat_data) {
     149        if (is_string($cat_data)) {
     150            $categories[$cat_id] = [
     151                'name' => $cat_data,
     152                'code' => '',
     153            ];
     154        }
     155    }
    147156    $invoices_by_receipt_id = array_column($existing_data, null, 'receipt_id');
    148157    $stats = ['imported' => 0, 'skipped' => 0, 'errors' => []];
     
    268277                        $invoice[$field] = ultibuda_parse_date($value);
    269278                        break;
    270                     case 'category':
    271                         $category_full = sanitize_text_field($value);
    272                         if ($is_dext) {
    273                             // Extract name for Category (e.g., 'Emballages perdus' from '602610 - Emballages perdus')
    274                             $category_name = ultibuda_extract_category_name($category_full);
    275                         } else {
    276                             $category_name = $category_full;
    277                         }
    278                         if (empty($category_name)) {
    279                             $category_name = 'Uncategorized';
    280                         }
    281                         $normalized_name = ultibuda_normalize_category_name($category_name);
    282                         $category_id = 'cat_' . md5($normalized_name);
    283                         $existing_id = array_search($category_name, $categories, true);
    284                         if ($existing_id !== false) {
    285                             $category_id = $existing_id;
    286                         } else {
    287                             $categories[$category_id] = $category_name;
    288                         }
    289                         $invoice['category_id'] = $category_id;
    290                         break;
     279case 'category':
     280    $category_full = sanitize_text_field($value);
     281    $category_code = '';
     282    $category_name = 'Uncategorized';
     283
     284    // Extraire le code et le nom si le format est <code> - <nom>
     285    if (preg_match('/^(\d+)\s*-\s*(.+)$/', $category_full, $matches)) {
     286        $category_code = sanitize_text_field($matches[1]); // Ex. : "613546"
     287        $category_name = trim($matches[2]); // Ex. : "Applications et Logiciels"
     288    } else {
     289        $category_name = $category_full;
     290    }
     291
     292    if (empty($category_name)) {
     293        $category_name = 'Uncategorized';
     294    }
     295
     296    // Si l'option de regroupement par code est activée et qu'un code existe
     297    $group_by_code = get_option('ultibuda_group_by_code', 0);
     298    $category_id = null;
     299    if ($group_by_code && !empty($category_code)) {
     300        foreach ($categories as $id => $cat_data) {
     301            // Vérifier que $cat_data est un tableau
     302            if (is_array($cat_data) && isset($cat_data['code']) && $cat_data['code'] === $category_code) {
     303                $category_id = $id;
     304                break;
     305            }
     306        }
     307    }
     308
     309    // Si aucun code ou pas de correspondance, utiliser le nom normalisé
     310    if ($category_id === null) {
     311        $normalized_name = ultibuda_normalize_category_name($category_name);
     312        $category_id = 'cat_' . md5($normalized_name);
     313        $existing_id = false;
     314        foreach ($categories as $id => $cat_data) {
     315            // Vérifier que $cat_data est un tableau
     316            $cat_name = is_array($cat_data) ? $cat_data['name'] : $cat_data;
     317            if ($cat_name === $category_name) {
     318                $existing_id = $id;
     319                break;
     320            }
     321        }
     322        if ($existing_id !== false) {
     323            $category_id = $existing_id;
     324        } else {
     325            $categories[$category_id] = [
     326                'name' => $category_name,
     327                'code' => $category_code,
     328            ];
     329        }
     330    }
     331
     332    $invoice['category_id'] = $category_id;
     333    break;
    291334                    case 'image_url':
    292335                        if (!empty($value) && filter_var($value, FILTER_VALIDATE_URL)) {
  • ultimate-business-dashboard/trunk/includes/invoices.php

    r3303076 r3306740  
    8888    $invoices_data = get_option('ultibuda_invoices_data', []);
    8989    $categories = get_option('ultibuda_invoice_categories', []);
     90    // Convertir les anciennes catégories (chaîne) en nouvelle structure (tableau)
     91    $categories_updated = false;
     92    foreach ($categories as $cat_id => $cat_data) {
     93        if (is_string($cat_data)) {
     94            $categories[$cat_id] = [
     95                'name' => $cat_data,
     96                'code' => '',
     97            ];
     98            $categories_updated = true;
     99        }
     100    }
     101    // Mettre à jour l'option si des catégories ont été converties
     102    if ($categories_updated) {
     103        update_option('ultibuda_invoice_categories', $categories);
     104    }
    90105    $category_types = get_option('ultibuda_category_types', []);
    91106
     
    162177            if (isset($updates['category']) && $updates['category'] !== $invoice['category_id']) {
    163178                $invoice['category_id'] = sanitize_text_field($updates['category']);
     179                $updated = true;
     180            }
     181
     182            // Update type
     183            if (isset($updates['type']) && $updates['type'] !== ($invoice['type'] ?? 'Unknown')) {
     184                $invoice['type'] = sanitize_text_field($updates['type']);
    164185                $updated = true;
    165186            }
     
    229250            echo '<div class="error"><p>' . esc_html__('Failed to update database!', 'ultimate-business-dashboard') . '</p></div>';
    230251        } else {
    231             echo '<div class="updated"><p>' . esc_html__('Changes saved successfully, including split settings, tax, and total amounts!', 'ultimate-business-dashboard') . '</p></div>';
     252            echo '<div class="updated"><p>' . esc_html__('Changes saved successfully, including type, category, split settings, tax, and total amounts!', 'ultimate-business-dashboard') . '</p></div>';
    232253        }
    233254    } else {
    234255        $error_message = $has_critical_error ?
    235256            esc_html__('No changes saved due to critical errors in tax or total amounts.', 'ultimate-business-dashboard') :
    236             esc_html__('No changes detected. Ensure split settings, categories, tax, or total amounts are modified.', 'ultimate-business-dashboard');
     257            esc_html__('No changes detected. Ensure type, category, split settings, tax, or total amounts are modified.', 'ultimate-business-dashboard');
    237258        echo '<div class="error"><p>' . $error_message . '</p></div>';
    238259    }
     
    309330
    310331    $types = array_unique(array_map(function($entry) {
    311         return $entry['type'];
     332        return $entry['type'] ?: 'Unknown';
    312333    }, $invoices_data));
     334    if (!in_array('Unknown', $types)) {
     335        $types[] = 'Unknown';
     336    }
    313337    sort($types);
    314338
     
    326350        $filtered_data = array_filter($filtered_data, function($invoice) use ($search_query, $categories) {
    327351            return stripos($invoice['receipt_id'], $search_query) !== false ||
    328                    stripos($invoice['supplier'], $search_query) !== false ||
    329                    stripos($invoice['invoice_number'], $search_query) !== false ||
    330                    stripos($categories[$invoice['category_id']] ?? '', $search_query) !== false ||
    331                    stripos((string)$invoice['total_amount'], $search_query) !== false ||
    332                    stripos((string)$invoice['tax_amount'], $search_query) !== false;
     352                stripos($invoice['supplier'], $search_query) !== false ||
     353                stripos($invoice['invoice_number'], $search_query) !== false ||
     354                stripos(isset($categories[$invoice['category_id']]['name']) ? $categories[$invoice['category_id']]['name'] : '', $search_query) !== false ||
     355                stripos((string)$invoice['total_amount'], $search_query) !== false ||
     356                stripos((string)$invoice['tax_amount'], $search_query) !== false;
    333357        });
    334358    }
     
    406430                                    </div>
    407431                                    <div class="invoice-detail">
    408                                         <?php esc_html_e('Type:', 'ultimate-business-dashboard'); ?> <?php echo esc_html($invoice['type'] ?? 'Unknown'); ?>
     432                                        <?php esc_html_e('Type:', 'ultimate-business-dashboard'); ?>
     433                                       <select name="invoices[<?php echo esc_attr($invoice['id']); ?>][type]">
     434                                            <?php foreach ($types as $type_option) : ?>
     435                                                <option value="<?php echo esc_attr($type_option); ?>" <?php selected($invoice['type'] ?? 'Unknown', $type_option); ?>><?php echo esc_html($type_option); ?></option>
     436                                            <?php endforeach; ?>
     437                                        </select>
    409438                                    </div>
    410439                                </td>
     
    419448                                <td class="invoice-cell">
    420449                                    <select name="invoices[<?php echo esc_attr($invoice['id']); ?>][category]">
    421                                         <?php foreach ($categories as $cat_id => $cat_name) : ?>
    422                                             <option value="<?php echo esc_attr($cat_id); ?>" <?php selected($invoice['category_id'], $cat_id); ?>><?php echo esc_html($cat_name); ?></option>
     450                                        <?php foreach ($categories as $cat_id => $cat_data) : ?>
     451                                            <option value="<?php echo esc_attr($cat_id); ?>" <?php selected($invoice['category_id'], $cat_id); ?>><?php echo esc_html($cat_data['name']); ?><?php echo !empty($cat_data['code']) ? ' (' . esc_html($cat_data['code']) . ')' : ''; ?></option>
    423452                                        <?php endforeach; ?>
    424453                                        <option value="uncategorized" <?php selected($invoice['category_id'], 'uncategorized'); ?>><?php esc_html_e('Uncategorized', 'ultimate-business-dashboard'); ?></option>
     
    430459                                    </button>
    431460                                    <div class="split-details" style="display: none;"
    432                                          data-spread-type="<?php echo esc_attr(isset($invoice['spread']) ? $invoice['spread']['type'] : 'standard'); ?>"
    433                                          data-spread-months="<?php echo esc_attr(isset($invoice['spread']) ? (int)$invoice['spread']['months'] : 1); ?>"
    434                                          data-start-date="<?php echo esc_attr(isset($invoice['spread']) ? $invoice['spread']['start_date'] : $invoice['date']); ?>">
     461                                        data-spread-type="<?php echo esc_attr(isset($invoice['spread']) ? $invoice['spread']['type'] : 'standard'); ?>"
     462                                        data-spread-months="<?php echo esc_attr(isset($invoice['spread']) ? (int)$invoice['spread']['months'] : 1); ?>"
     463                                        data-start-date="<?php echo esc_attr(isset($invoice['spread']) ? $invoice['spread']['start_date'] : $invoice['date']); ?>">
    435464                                    </div>
    436465                                </td>
     
    508537                $sanitized[$key] = sanitize_text_field($value);
    509538                break;
     539            case 'type':
     540                $sanitized[$key] = sanitize_text_field($value);
     541                break;
    510542            case 'spread_months':
    511543                $sanitized[$key] = absint($value);
  • ultimate-business-dashboard/trunk/includes/reports.php

    r3301653 r3306740  
    2020    $invoices_data = get_option('ultibuda_invoices_data', []);
    2121    $categories = get_option('ultibuda_invoice_categories', []);
     22    // Convertir les anciennes catégories (chaîne) en nouvelle structure (tableau)
     23    foreach ($categories as $cat_id => $cat_data) {
     24        if (is_string($cat_data)) {
     25            $categories[$cat_id] = [
     26                'name' => $cat_data,
     27                'code' => '',
     28            ];
     29        }
     30    }
    2231    $all_revenues = get_option('ultibuda_revenues', []);
    2332    $all_other_costs = get_option('ultibuda_other_costs', []);
  • ultimate-business-dashboard/trunk/includes/settings.php

    r3301653 r3306740  
    99    exit;
    1010}
     11
     12/**
     13 * Register plugin settings.
     14 */
     15function ultibuda_register_settings() {
     16    register_setting('ultibuda_settings', 'ultibuda_delete_data_on_uninstall', [
     17        'sanitize_callback' => 'absint',
     18        'default' => 0,
     19    ]);
     20    register_setting('ultibuda_settings', 'ultibuda_group_by_code', [
     21        'sanitize_callback' => 'absint',
     22        'default' => 0,
     23    ]);
     24}
     25add_action('admin_init', 'ultibuda_register_settings');
    1126
    1227/**
     
    2439    if (isset($_POST['ultibuda_save_settings']) && check_admin_referer('ultibuda_settings')) {
    2540        $delete_on_uninstall = isset($_POST['ultibuda_delete_data_on_uninstall']) ? 1 : 0;
     41        $group_by_code = isset($_POST['ultibuda_group_by_code']) ? 1 : 0;
    2642        update_option('ultibuda_delete_data_on_uninstall', $delete_on_uninstall);
     43        update_option('ultibuda_group_by_code', $group_by_code);
    2744        $message = esc_html__('Settings saved successfully.', 'ultimate-business-dashboard');
    2845        $status = 'success';
     
    5269            <form method="post">
    5370                <?php wp_nonce_field('ultibuda_settings'); ?>
     71                <h3><?php esc_html_e('General Settings', 'ultimate-business-dashboard'); ?></h3>
     72                <p>
     73                    <input type="checkbox" name="ultibuda_group_by_code" id="ultibuda_group_by_code" value="1" <?php checked(get_option('ultibuda_group_by_code', 0), 1); ?>>
     74                    <label for="ultibuda_group_by_code"><?php esc_html_e('Group Categories by Accounting Code', 'ultimate-business-dashboard'); ?></label>
     75                    <p class="description"><?php esc_html_e('Enable to group categories with the same accounting code (e.g., "613546 - Applications and Software" and "613546 - Softwares" will use the same category). Recommended for standardized accounting plans.', 'ultimate-business-dashboard'); ?></p>
     76                </p>
    5477                <h3><?php esc_html_e('Uninstall Settings', 'ultimate-business-dashboard'); ?></h3>
    5578                <p>
  • ultimate-business-dashboard/trunk/includes/webhook-dext.php

    r3300693 r3306740  
    8484    // Category
    8585    $category_full = trim($data['Category'] ?? $data['category'] ?? $data['Compte'] ?? '');
    86     if (empty($category_full)) {
    87         $category_name = 'Uncategorized';
    88     } elseif (preg_match('/^(\d+)\s*-\s*(.+)$/', $category_full, $matches)) {
    89         $category_name = trim($matches[2]);
    90     } else {
    91         $category_name = $category_full;
     86    $category_code = '';
     87    $category_name = 'Uncategorized';
     88
     89    if (!empty($category_full)) {
     90        if (preg_match('/^(\d+)\s*-\s*(.+)$/', $category_full, $matches)) {
     91            $category_code = sanitize_text_field($matches[1]); // Ex. : "613546"
     92            $category_name = trim($matches[2]); // Ex. : "Applications et Logiciels"
     93        } else {
     94            $category_name = $category_full;
     95        }
    9296    }
    9397
    94     $normalized_name = ultibuda_normalize_category_name($category_name);
    95     $category_id = 'cat_' . md5($normalized_name);
    96     $existing_id = array_search($category_name, $categories, true);
    97     if (false !== $existing_id) {
    98         $category_id = $existing_id;
    99     } else {
    100         $categories[$category_id] = $category_name;
     98    // Si l'option de regroupement par code est activée et qu'un code existe
     99    $group_by_code = get_option('ultibuda_group_by_code', 0);
     100    $category_id = null;
     101    if ($group_by_code && !empty($category_code)) {
     102        foreach ($categories as $id => $cat_data) {
     103            if (isset($cat_data['code']) && $cat_data['code'] === $category_code) {
     104                $category_id = $id;
     105                break;
     106            }
     107        }
     108    }
     109
     110    // Si aucun code ou pas de correspondance, utiliser le nom normalisé
     111    if ($category_id === null) {
     112        $normalized_name = ultibuda_normalize_category_name($category_name);
     113        $category_id = 'cat_' . md5($normalized_name);
     114        $existing_id = false;
     115        foreach ($categories as $id => $cat_data) {
     116            if ($cat_data['name'] === $category_name) {
     117                $existing_id = $id;
     118                break;
     119            }
     120        }
     121        if ($existing_id !== false) {
     122            $category_id = $existing_id;
     123        } else {
     124            $categories[$category_id] = [
     125                'name' => $category_name,
     126                'code' => $category_code,
     127            ];
     128        }
    101129    }
    102130
  • ultimate-business-dashboard/trunk/readme.txt

    r3303076 r3306740  
    55Requires at least: 5.5
    66Tested up to: 6.8
    7 Stable tag: 1.2
     7Stable tag: 1.3
    88Requires PHP: 7.4
    99License: GPLv2 or later
     
    9696
    9797== Changelog ==
     98= 1.3 - 4 June 2025 =
     99* Added: New option to group categories by accounting code in Settings, ensuring invoices from the same expense account are assigned to a single category, regardless of the category name, facilitating standardized accounting plans.
     100* Added: A new dropdown to edit the type field for invoices.
     101* Added: Merge multiple categories to consolidate invoices.
     102* Improved: Manual (Dext/Other CSV) and automatic (Dext via Zapier) imports to support accounting code grouping, ensuring new invoices use existing categories with matching codes when the option is enabled.
     103* Improved: Categories in the financial dashboard for monthly and annual tables are now sorted alphabetically by name, enhancing readability.
     104
    98105= 1.2 - 29 May 2025 =
    99106* Added: Editable fields for "Tax" and "Total Amount" on the Invoices page, with automatic calculation of "Net Amount".
     
    116123
    117124== Upgrade Notice ==
     125= 1.3 =
     126This release introduces significant improvements to category management, including merging categories, grouping categories by accounting code, and editable codes.
     127
    118128= 1.2 =
    119129This release introduces editable Tax and Total Amount fields for invoices, enhances accounting accuracy with negative Tax validation for credits.
  • ultimate-business-dashboard/trunk/ultimate-business-dashboard.php

    r3303076 r3306740  
    33 * Plugin Name: Ultimate Business Dashboard
    44 * Plugin URI: https://tulipemedia.com/en/ultimate-business-dashboard/
    5  * Description: A simple dashboard to manage your business with Dext via Zapier.
    6  * Version: 1.2
     5 * Description: Turn your WordPress dashboard into a Financial Powerhouse.
     6 * Version: 1.3
    77 * Author: Ziyad Bachalany
    88 * Author URI: https://tulipemedia.com
     
    300300            'ultibuda-admin-scripts',
    301301            ULTIBUDA_PLUGIN_URL . 'assets/js/admin.js',
    302             ['jquery', 'chart-js'], // Ajouter chart-js comme dépendance
     302            ['jquery', 'chart-js'],
    303303            $js_version,
    304304            true
     
    308308        $invoices_data = get_option('ultibuda_invoices_data', []);
    309309        $categories = get_option('ultibuda_invoice_categories', []);
     310        // Convertir les anciennes catégories (chaîne) en nouvelle structure (tableau)
     311        foreach ($categories as $cat_id => $cat_data) {
     312            if (is_string($cat_data)) {
     313                $categories[$cat_id] = [
     314                    'name' => $cat_data,
     315                    'code' => '',
     316                ];
     317            }
     318        }
    310319        $all_revenues = get_option('ultibuda_revenues', []);
    311320        $all_other_costs = get_option('ultibuda_other_costs', []);
     
    322331        } else {
    323332            $selected_period = isset($_GET['period']) ? sanitize_text_field($_GET['period']) : 'month';
    324             // Validate YYYY-MM format for month
    325333            $selected_month = isset($_GET['month']) ? sanitize_text_field($_GET['month']) : gmdate('Y-m');
    326334            if (!preg_match('/^\d{4}-\d{2}$/', $selected_month)) {
     
    339347        }
    340348
     349        // Préparer les étiquettes et les données pour le graphique
     350        $category_labels = array_map(function($key) use ($categories) {
     351            // Rechercher le code comptable correspondant au nom
     352            foreach ($categories as $cat_data) {
     353                if ($cat_data['name'] === $key && !empty($cat_data['code'])) {
     354                    return $key . ' (' . $cat_data['code'] . ')';
     355                }
     356            }
     357            return $key;
     358        }, array_keys($data['main']['category_breakdown'] ?: []));
     359       
    341360        wp_localize_script(
    342361            'ultibuda-admin-scripts',
    343362            'ultibudaReportData',
    344363            [
    345                 'categoryLabels' => array_keys($data['main']['category_breakdown'] ?: []),
     364                'categoryLabels' => $category_labels,
    346365                'categoryData' => array_values($data['main']['category_breakdown'] ?: []),
    347366                'fixedCosts' => $data['main']['fixed_costs'] ?? 0,
Note: See TracChangeset for help on using the changeset viewer.