Changeset 3306740
- Timestamp:
- 06/04/2025 09:33:48 PM (9 months ago)
- Location:
- ultimate-business-dashboard/trunk
- Files:
-
- 10 edited
-
assets/js/admin.js (modified) (1 diff)
-
includes/categories.php (modified) (8 diffs)
-
includes/dashboard.php (modified) (6 diffs)
-
includes/imports.php (modified) (2 diffs)
-
includes/invoices.php (modified) (9 diffs)
-
includes/reports.php (modified) (1 diff)
-
includes/settings.php (modified) (3 diffs)
-
includes/webhook-dext.php (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
-
ultimate-business-dashboard.php (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ultimate-business-dashboard/trunk/assets/js/admin.js
r3300693 r3306740 34 34 }); 35 35 36 // Invoices: Select all checkboxes 36 // Invoices: Select all checkboxes for categories and invoices 37 37 if ($('#select-all-categories').length) { 38 38 $('#select-all-categories').on('change', function() { 39 39 $('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); 40 45 }); 41 46 } -
ultimate-business-dashboard/trunk/includes/categories.php
r3301653 r3306740 19 19 20 20 $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 } 21 30 $category_types = get_option('ultibuda_category_types', []); 22 31 23 // Handle category type updates24 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')) { 25 34 $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') : []; 26 36 $updated = false; 27 37 38 // Update category types 28 39 foreach ($updated_types as $cat_id => $type) { 29 // Validate category type30 40 if (in_array($type, ['Fixed', 'Variable', 'None'], true) && $type !== ($category_types[$cat_id] ?? 'None')) { 31 41 $category_types[$cat_id] = $type; 32 42 $updated = true; 33 43 } 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 } 35 56 } 36 57 } … … 38 59 if ($updated) { 39 60 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>'; 41 63 } else { 42 64 echo '<div class="error"><p>' . esc_html__('No changes detected.', 'ultimate-business-dashboard') . '</p></div>'; … … 45 67 46 68 // 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')) { 48 70 $ids_to_delete = array_map('sanitize_text_field', wp_unslash($_POST['category_ids'])); 49 71 $invoices_data = get_option('ultibuda_invoices_data', []); … … 73 95 update_option('ultibuda_category_types', $category_types); 74 96 update_option('ultibuda_invoices_data', $invoices_data); 75 // translators: %d is the number of categories deleted76 97 echo '<div class="updated"><p>' . sprintf( 77 98 esc_html__('%d category(ies) deleted successfully! Associated invoices reassigned to Uncategorized.', 'ultimate-business-dashboard'), … … 83 104 } elseif (isset($_POST['ultibuda_delete_categories']) && empty($_POST['category_ids'])) { 84 105 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 } 89 152 if (empty($categories)) { 90 153 ?> … … 101 164 <div class="wrap"> 102 165 <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'); ?> 108 173 <div class="category-table-container"> 109 174 <table class="wp-list-table widefat striped" style="margin-top: 10px;"> … … 111 176 <tr> 112 177 <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> 115 181 </tr> 116 182 </thead> 117 183 <tbody> 118 <?php foreach ($categories as $cat_id => $cat_ name) : ?>184 <?php foreach ($categories as $cat_id => $cat_data) : ?> 119 185 <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> 122 191 <td> 123 192 <select name="category_types[<?php echo esc_attr($cat_id); ?>]"> … … 133 202 </div> 134 203 <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'); ?>');"> 136 216 </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> 137 218 </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 147 219 </div> 148 220 <?php -
ultimate-business-dashboard/trunk/includes/dashboard.php
r3301653 r3306740 20 20 $invoices_data = get_option('ultibuda_invoices_data', []); 21 21 $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 } 22 31 $all_revenues = get_option('ultibuda_revenues', []); 23 32 $all_other_costs = get_option('ultibuda_other_costs', []); … … 120 129 <?php foreach ($monthly_data[$month]['categories'] as $cat => $amount) : ?> 121 130 <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> 123 132 <span class="value"><?php echo esc_html(number_format($amount, 2, '.', ',')); ?></span> 124 133 </div> … … 241 250 <?php foreach ($annual_data['categories'] as $cat => $amount) : ?> 242 251 <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> 244 253 <span class="value"><?php echo esc_html(number_format($amount, 2, '.', ',')); ?></span> 245 254 </div> … … 541 550 542 551 $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 } 544 561 $monthly_revenues = $all_revenues[$year] ?? array_fill(1, 12, 0); 545 562 $monthly_amortizations = $all_amortizations[$year] ?? array_fill(1, 12, 0); … … 570 587 foreach ($invoices_data as $entry) { 571 588 $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'); 573 590 $cat_type = $category_types[$category_id] ?? 'None'; 574 591 … … 640 657 } 641 658 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 642 668 $annual_data['category_breakdown'] = $annual_data['categories']; 643 669 if ($annual_data['other_cost'] > 0) { -
ultimate-business-dashboard/trunk/includes/imports.php
r3301653 r3306740 145 145 $existing_data = get_option('ultibuda_invoices_data', []); 146 146 $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 } 147 156 $invoices_by_receipt_id = array_column($existing_data, null, 'receipt_id'); 148 157 $stats = ['imported' => 0, 'skipped' => 0, 'errors' => []]; … … 268 277 $invoice[$field] = ultibuda_parse_date($value); 269 278 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; 279 case '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; 291 334 case 'image_url': 292 335 if (!empty($value) && filter_var($value, FILTER_VALIDATE_URL)) { -
ultimate-business-dashboard/trunk/includes/invoices.php
r3303076 r3306740 88 88 $invoices_data = get_option('ultibuda_invoices_data', []); 89 89 $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 } 90 105 $category_types = get_option('ultibuda_category_types', []); 91 106 … … 162 177 if (isset($updates['category']) && $updates['category'] !== $invoice['category_id']) { 163 178 $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']); 164 185 $updated = true; 165 186 } … … 229 250 echo '<div class="error"><p>' . esc_html__('Failed to update database!', 'ultimate-business-dashboard') . '</p></div>'; 230 251 } 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>'; 232 253 } 233 254 } else { 234 255 $error_message = $has_critical_error ? 235 256 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'); 237 258 echo '<div class="error"><p>' . $error_message . '</p></div>'; 238 259 } … … 309 330 310 331 $types = array_unique(array_map(function($entry) { 311 return $entry['type'] ;332 return $entry['type'] ?: 'Unknown'; 312 333 }, $invoices_data)); 334 if (!in_array('Unknown', $types)) { 335 $types[] = 'Unknown'; 336 } 313 337 sort($types); 314 338 … … 326 350 $filtered_data = array_filter($filtered_data, function($invoice) use ($search_query, $categories) { 327 351 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; 333 357 }); 334 358 } … … 406 430 </div> 407 431 <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> 409 438 </div> 410 439 </td> … … 419 448 <td class="invoice-cell"> 420 449 <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> 423 452 <?php endforeach; ?> 424 453 <option value="uncategorized" <?php selected($invoice['category_id'], 'uncategorized'); ?>><?php esc_html_e('Uncategorized', 'ultimate-business-dashboard'); ?></option> … … 430 459 </button> 431 460 <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']); ?>"> 435 464 </div> 436 465 </td> … … 508 537 $sanitized[$key] = sanitize_text_field($value); 509 538 break; 539 case 'type': 540 $sanitized[$key] = sanitize_text_field($value); 541 break; 510 542 case 'spread_months': 511 543 $sanitized[$key] = absint($value); -
ultimate-business-dashboard/trunk/includes/reports.php
r3301653 r3306740 20 20 $invoices_data = get_option('ultibuda_invoices_data', []); 21 21 $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 } 22 31 $all_revenues = get_option('ultibuda_revenues', []); 23 32 $all_other_costs = get_option('ultibuda_other_costs', []); -
ultimate-business-dashboard/trunk/includes/settings.php
r3301653 r3306740 9 9 exit; 10 10 } 11 12 /** 13 * Register plugin settings. 14 */ 15 function 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 } 25 add_action('admin_init', 'ultibuda_register_settings'); 11 26 12 27 /** … … 24 39 if (isset($_POST['ultibuda_save_settings']) && check_admin_referer('ultibuda_settings')) { 25 40 $delete_on_uninstall = isset($_POST['ultibuda_delete_data_on_uninstall']) ? 1 : 0; 41 $group_by_code = isset($_POST['ultibuda_group_by_code']) ? 1 : 0; 26 42 update_option('ultibuda_delete_data_on_uninstall', $delete_on_uninstall); 43 update_option('ultibuda_group_by_code', $group_by_code); 27 44 $message = esc_html__('Settings saved successfully.', 'ultimate-business-dashboard'); 28 45 $status = 'success'; … … 52 69 <form method="post"> 53 70 <?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> 54 77 <h3><?php esc_html_e('Uninstall Settings', 'ultimate-business-dashboard'); ?></h3> 55 78 <p> -
ultimate-business-dashboard/trunk/includes/webhook-dext.php
r3300693 r3306740 84 84 // Category 85 85 $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 } 92 96 } 93 97 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 } 101 129 } 102 130 -
ultimate-business-dashboard/trunk/readme.txt
r3303076 r3306740 5 5 Requires at least: 5.5 6 6 Tested up to: 6.8 7 Stable tag: 1. 27 Stable tag: 1.3 8 8 Requires PHP: 7.4 9 9 License: GPLv2 or later … … 96 96 97 97 == 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 98 105 = 1.2 - 29 May 2025 = 99 106 * Added: Editable fields for "Tax" and "Total Amount" on the Invoices page, with automatic calculation of "Net Amount". … … 116 123 117 124 == Upgrade Notice == 125 = 1.3 = 126 This release introduces significant improvements to category management, including merging categories, grouping categories by accounting code, and editable codes. 127 118 128 = 1.2 = 119 129 This 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 3 3 * Plugin Name: Ultimate Business Dashboard 4 4 * 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. 25 * Description: Turn your WordPress dashboard into a Financial Powerhouse. 6 * Version: 1.3 7 7 * Author: Ziyad Bachalany 8 8 * Author URI: https://tulipemedia.com … … 300 300 'ultibuda-admin-scripts', 301 301 ULTIBUDA_PLUGIN_URL . 'assets/js/admin.js', 302 ['jquery', 'chart-js'], // Ajouter chart-js comme dépendance302 ['jquery', 'chart-js'], 303 303 $js_version, 304 304 true … … 308 308 $invoices_data = get_option('ultibuda_invoices_data', []); 309 309 $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 } 310 319 $all_revenues = get_option('ultibuda_revenues', []); 311 320 $all_other_costs = get_option('ultibuda_other_costs', []); … … 322 331 } else { 323 332 $selected_period = isset($_GET['period']) ? sanitize_text_field($_GET['period']) : 'month'; 324 // Validate YYYY-MM format for month325 333 $selected_month = isset($_GET['month']) ? sanitize_text_field($_GET['month']) : gmdate('Y-m'); 326 334 if (!preg_match('/^\d{4}-\d{2}$/', $selected_month)) { … … 339 347 } 340 348 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 341 360 wp_localize_script( 342 361 'ultibuda-admin-scripts', 343 362 'ultibudaReportData', 344 363 [ 345 'categoryLabels' => array_keys($data['main']['category_breakdown'] ?: []),364 'categoryLabels' => $category_labels, 346 365 'categoryData' => array_values($data['main']['category_breakdown'] ?: []), 347 366 'fixedCosts' => $data['main']['fixed_costs'] ?? 0,
Note: See TracChangeset
for help on using the changeset viewer.