Changeset 3381508
- Timestamp:
- 10/20/2025 07:33:48 PM (4 months ago)
- Location:
- taxproof-coupons-for-woocommerce
- Files:
-
- 3 edited
- 4 copied
-
tags/1.0.4 (copied) (copied from taxproof-coupons-for-woocommerce/trunk)
-
tags/1.0.4/README.md (copied) (copied from taxproof-coupons-for-woocommerce/trunk/README.md) (3 diffs)
-
tags/1.0.4/readme.txt (copied) (copied from taxproof-coupons-for-woocommerce/trunk/readme.txt) (2 diffs)
-
tags/1.0.4/tax-proof-coupons-plugin.php (copied) (copied from taxproof-coupons-for-woocommerce/trunk/tax-proof-coupons-plugin.php) (6 diffs)
-
trunk/README.md (modified) (3 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/tax-proof-coupons-plugin.php (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
taxproof-coupons-for-woocommerce/tags/1.0.4/README.md
r3343132 r3381508 5 5 **Requires at least:** 6.5 6 6 **Tested up to:** 6.8 7 **Stable tag:** 1.0. 37 **Stable tag:** 1.0.4 8 8 **License:** GPLv2 or later 9 9 **License URI:** https://www.gnu.org/licenses/gpl-2.0.html … … 14 14 15 15 - Adds **Apply coupon after tax** checkbox to coupon settings. 16 - Converts gross coupon values into precise net discounts .16 - Converts gross coupon values into precise net discounts with high accuracy. 17 17 - Guarantees the exact gross amount is deducted in cart and checkout. 18 - **StoreaBill/Germanized Pro Integration** for accurate invoice generation. 19 - **Enhanced Admin Display** showing detailed net and gross amounts. 20 - **Precision Calculations** for complex tax scenarios. 21 - **Debug Logging** for development and troubleshooting. 18 22 19 23 ## Installation … … 24 28 25 29 ## Changelog 30 31 ### 1.0.4 32 33 - **Verbesserte Präzision**: Neue präzise Berechnungsmethode für exakte Steuerumrechnungen 34 - **Erweiterte Anzeige-Funktionen**: Verbesserte Coupon-Anzeige im Warenkorb und Checkout 35 - **StoreaBill/Germanized Pro Integration**: Vollständige Kompatibilität mit Germanized Pro für Rechnungsgenerierung 36 - **Admin-Verbesserungen**: Detaillierte Anzeige von Netto- und Bruttobeträgen in der WooCommerce Admin-Oberfläche 37 - **Erweiterte Metadaten-Speicherung**: Präzise Speicherung von Coupon-Beträgen mit hoher Genauigkeit 38 - **Debug-Funktionen**: Erweiterte Logging-Funktionen für bessere Entwicklung und Fehlerbehebung 39 - **Hook-Integration**: Neue Hooks für bessere Integration mit WooCommerce und Drittanbieter-Plugins 40 - **Performance-Optimierungen**: Verbesserte Berechnungslogik für komplexe Steuerszenarien 26 41 27 42 ### 1.0.3 -
taxproof-coupons-for-woocommerce/tags/1.0.4/readme.txt
r3343132 r3381508 5 5 Requires at least: 6.5 6 6 Tested up to: 6.8 7 Stable tag: 1.0. 37 Stable tag: 1.0.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 28 28 == Changelog == 29 29 30 = 1.0.4 = 31 32 Release date: January 2025 33 34 * **Verbesserte Präzision**: Neue präzise Berechnungsmethode für exakte Steuerumrechnungen 35 * **Erweiterte Anzeige-Funktionen**: Verbesserte Coupon-Anzeige im Warenkorb und Checkout 36 * **StoreaBill/Germanized Pro Integration**: Vollständige Kompatibilität mit Germanized Pro für Rechnungsgenerierung 37 * **Admin-Verbesserungen**: Detaillierte Anzeige von Netto- und Bruttobeträgen in der WooCommerce Admin-Oberfläche 38 * **Erweiterte Metadaten-Speicherung**: Präzise Speicherung von Coupon-Beträgen mit hoher Genauigkeit 39 * **Debug-Funktionen**: Erweiterte Logging-Funktionen für bessere Entwicklung und Fehlerbehebung 40 * **Hook-Integration**: Neue Hooks für bessere Integration mit WooCommerce und Drittanbieter-Plugins 41 * **Performance-Optimierungen**: Verbesserte Berechnungslogik für komplexe Steuerszenarien 42 30 43 = 1.0.3 = 31 44 -
taxproof-coupons-for-woocommerce/tags/1.0.4/tax-proof-coupons-plugin.php
r3343132 r3381508 4 4 * Plugin URI: https://github.com/s-a-s-k-i-a/tax-proof-coupons 5 5 * Description: Ensure fixed-value coupons always apply after tax, regardless of VAT rate or customer location. 6 * Version: 1.0. 36 * Version: 1.0.4 7 7 * Author: Saskia Teichmann 8 8 * Author URI: https://saskialund.de … … 24 24 class Plugin { 25 25 /** Plugin version. */ 26 public const VERSION = '1.0. 2';26 public const VERSION = '1.0.4'; 27 27 28 28 /** Singleton instance. */ … … 44 44 /** Prevent direct instantiation. */ 45 45 private function __construct() {} 46 47 /** 48 * Check if Germanized (free or Pro) is active. 49 * 50 * @return bool 51 */ 52 private function is_storeabill_active(): bool { 53 return function_exists( 'WC_GZD' ); 54 } 46 55 47 56 /** Initialize WP and WooCommerce hooks. */ … … 50 59 add_action( 'woocommerce_coupon_options_save', [ $this, 'save_apply_after_tax_checkbox' ], 10, 2 ); 51 60 add_filter( 'woocommerce_coupon_get_discount_amount', [ $this, 'apply_coupon_after_tax' ], 20, 5 ); 61 add_filter( 'woocommerce_coupon_discount_amount_html', [ $this, 'adjust_coupon_display_amount' ], 20, 2 ); 62 add_filter( 'woocommerce_cart_totals_coupon_html', [ $this, 'adjust_cart_coupon_display' ], 20, 3 ); 63 64 // Store correct net amounts when creating order items 65 add_action( 'woocommerce_checkout_create_order', [ $this, 'store_coupon_net_amounts' ], 10, 2 ); 66 add_action( 'woocommerce_create_order_coupon_item', [ $this, 'set_correct_coupon_amounts' ], 10, 4 ); 67 68 // Admin display 69 add_filter( 'woocommerce_order_formatted_line_subtotal', [ $this, 'adjust_admin_coupon_display' ], 10, 3 ); 70 71 // Force correct calculation for invoices - use the correct hook 72 add_filter( 'woocommerce_order_get_total_discount', [ $this, 'adjust_total_discount_for_invoices' ], 10, 2 ); 73 74 // StoreaBill (Germanized Pro) specific hooks - only if available 75 if ( $this->is_storeabill_active() ) { 76 add_filter( 'storeabill_invoice_get_discount_total', [ $this, 'storeabill_get_discount_total_gross' ], 10, 2 ); 77 add_filter( 'storeabill_woo_order_voucher_total', [ $this, 'storeabill_voucher_total_gross' ], 10, 2 ); 78 79 // Hook sniffer to find the correct Germanized hooks (only in debug mode) 80 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 81 add_action( 'all', [ $this, 'log_germanized_hooks' ], 1 ); 82 } 83 } 52 84 } 53 85 … … 129 161 $avg_tax_rate = ( $total_gross / $total_net ) - 1; 130 162 131 // Convert the gross coupon into the correct net discount. 132 $net_discount = $gross_coupon / ( 1 + $avg_tax_rate ); 133 $net_discount = round( $net_discount, wc_get_price_decimals() ); 163 // Convert the gross coupon into the correct net discount with precision handling 164 $net_discount = $this->calculate_precise_net_discount( 165 $gross_coupon, 166 $avg_tax_rate, 167 $total_net, 168 $total_gross 169 ); 134 170 135 171 // Mark as applied so we don't double-dip. … … 139 175 } 140 176 141 /** Public wakeup to satisfy PHP’s magic method requirement. */ 177 /** 178 * Adjust the coupon display amount to show the gross value. 179 * This ensures the customer always sees the advertised coupon amount. 180 * 181 * @param string $discount_amount_html The HTML string for the discount amount 182 * @param \WC_Coupon $coupon The coupon object 183 * @return string Modified HTML 184 */ 185 public function adjust_coupon_display_amount( string $discount_amount_html, \WC_Coupon $coupon ): string { 186 // Only adjust for our tax-proof coupons 187 if ( 'fixed_cart' !== $coupon->get_discount_type() || 188 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 189 return $discount_amount_html; 190 } 191 192 // Get the original coupon amount (gross) 193 $gross_amount = floatval( $coupon->get_amount() ); 194 195 // Format it as WooCommerce would 196 $formatted_amount = wc_price( $gross_amount ); 197 198 error_log( sprintf( 'TPC Debug: Adjusting display from %s to %s', $discount_amount_html, $formatted_amount ) ); 199 200 return '-' . $formatted_amount; 201 } 202 203 /** 204 * Adjust the cart display to show the gross coupon amount. 205 * This filter specifically targets the cart and checkout displays. 206 * 207 * @param string $coupon_html Current HTML 208 * @param \WC_Coupon $coupon Coupon object 209 * @param string $discount_amount_html Discount amount HTML 210 * @return string Modified HTML 211 */ 212 public function adjust_cart_coupon_display( string $coupon_html, \WC_Coupon $coupon, string $discount_amount_html ): string { 213 // Only adjust for our tax-proof coupons 214 if ( 'fixed_cart' !== $coupon->get_discount_type() || 215 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 216 return $coupon_html; 217 } 218 219 // Get the original coupon amount (gross) 220 $gross_amount = floatval( $coupon->get_amount() ); 221 222 // Format it as WooCommerce would 223 $formatted_amount = wc_price( $gross_amount ); 224 225 // Replace the discount amount in the HTML 226 $new_html = str_replace( $discount_amount_html, '-' . $formatted_amount, $coupon_html ); 227 228 error_log( sprintf( 'TPC Debug: Cart display adjustment - Original: %s, New: %s', $coupon_html, $new_html ) ); 229 230 return $new_html; 231 } 232 233 /** 234 * Calculate precise net discount that ensures the gross amount matches exactly. 235 * This method handles rounding issues by finding the optimal net amount. 236 * 237 * @param float $gross_coupon The gross coupon amount (e.g., 100.00) 238 * @param float $avg_tax_rate The average tax rate (e.g., 0.21 for 21%) 239 * @param float $total_net Total net amount in cart 240 * @param float $total_gross Total gross amount in cart 241 * @return float The precise net discount amount 242 */ 243 private function calculate_precise_net_discount( 244 float $gross_coupon, 245 float $avg_tax_rate, 246 float $total_net, 247 float $total_gross 248 ): float { 249 $decimals = wc_get_price_decimals(); 250 $precision_decimals = 4; // Use 4 decimals for internal precision 251 252 // Debug logging 253 error_log( 'TPC Debug: Starting precise calculation' ); 254 error_log( sprintf( 'TPC Debug: Gross coupon: %.4f', $gross_coupon ) ); 255 error_log( sprintf( 'TPC Debug: Avg tax rate: %.4f (%.2f%%)', $avg_tax_rate, $avg_tax_rate * 100 ) ); 256 error_log( sprintf( 'TPC Debug: Decimals: %d', $decimals ) ); 257 258 // Initial calculation with higher precision 259 $net_discount_exact = $gross_coupon / ( 1 + $avg_tax_rate ); 260 error_log( sprintf( 'TPC Debug: Exact net discount (unrounded): %.6f', $net_discount_exact ) ); 261 262 // Try both floor and ceil to see which gives us the target gross 263 $net_floor = floor( $net_discount_exact * pow( 10, $decimals ) ) / pow( 10, $decimals ); 264 $net_ceil = ceil( $net_discount_exact * pow( 10, $decimals ) ) / pow( 10, $decimals ); 265 266 error_log( sprintf( 'TPC Debug: Net floor: %.4f, Net ceil: %.4f', $net_floor, $net_ceil ) ); 267 268 // Calculate gross for both options 269 // IMPORTANT: For Germany with 19% MwSt: 270 // 84.03 * 1.19 = 99.9957 which rounds to 99.99 (NOT 100.00!) 271 // 84.04 * 1.19 = 100.0076 which rounds to 100.01 272 $gross_floor_exact = $net_floor * ( 1 + $avg_tax_rate ); 273 $gross_ceil_exact = $net_ceil * ( 1 + $avg_tax_rate ); 274 $gross_floor = round( $gross_floor_exact, $decimals ); 275 $gross_ceil = round( $gross_ceil_exact, $decimals ); 276 277 error_log( sprintf( 'TPC Debug: Floor exact: %.6f, Ceil exact: %.6f', $gross_floor_exact, $gross_ceil_exact ) ); 278 279 error_log( sprintf( 'TPC Debug: Gross from floor: %.4f, Gross from ceil: %.4f', $gross_floor, $gross_ceil ) ); 280 281 // Choose the net amount that gives us the exact gross we want 282 // The problem: With only 2 decimals, we can't always achieve the exact gross amount 283 // Solution: Return the EXACT net amount with more precision 284 285 if ( $gross_floor == $gross_coupon ) { 286 $net_discount = $net_floor; 287 error_log( 'TPC Debug: Using floor value for exact match' ); 288 } elseif ( $gross_ceil == $gross_coupon ) { 289 $net_discount = $net_ceil; 290 error_log( 'TPC Debug: Using ceil value for exact match' ); 291 } else { 292 // For 19% MwSt: 84.03 gives 99.9957 which rounds to 99.99, NOT 100.00 293 // We need to use 84.04 which gives 100.0076 and rounds to 100.01 294 // But we can accept 100.01 and show 100.00 in display 295 error_log( sprintf( 'TPC Debug: No exact match - floor gives %.4f, ceil gives %.4f', $gross_floor, $gross_ceil ) ); 296 297 // Prefer the value that's slightly over rather than under 298 if ( $gross_floor < $gross_coupon && $gross_ceil > $gross_coupon ) { 299 // If floor is under and ceil is over, use ceil 300 $net_discount = $net_ceil; 301 error_log( 'TPC Debug: Using ceil to avoid underpayment' ); 302 } else { 303 // If neither gives exact match, we need a more sophisticated approach 304 // Calculate what net amount would give us exactly the gross we want 305 // This might require adjusting by fractions of a cent 306 307 error_log( 'TPC Debug: Neither floor nor ceil gives exact match, calculating optimal value' ); 308 309 // We'll use a binary search approach to find the optimal value 310 $lower = $net_floor; 311 $upper = $net_ceil; 312 $best_net = $net_floor; 313 $best_diff = abs( $gross_floor - $gross_coupon ); 314 315 // Try intermediate values 316 for ( $i = 1; $i < 10; $i++ ) { 317 $test_net = $lower + ( $upper - $lower ) * ( $i / 10 ); 318 $test_net = round( $test_net, $decimals + 2 ); // Use extra precision 319 $test_gross = round( $test_net * ( 1 + $avg_tax_rate ), $decimals ); 320 $diff = abs( $test_gross - $gross_coupon ); 321 322 if ( $diff < $best_diff ) { 323 $best_net = $test_net; 324 $best_diff = $diff; 325 326 if ( $diff < 0.001 ) { 327 error_log( sprintf( 'TPC Debug: Found optimal value: %.4f', $best_net ) ); 328 break; 329 } 330 } 331 } 332 333 // Round to allowed decimals 334 $net_discount = round( $best_net, $decimals ); 335 336 // If this still doesn't give us the exact gross, adjust by one cent in the right direction 337 $final_gross = round( $net_discount * ( 1 + $avg_tax_rate ), $decimals ); 338 if ( $final_gross < $gross_coupon ) { 339 $net_discount += pow( 10, -$decimals ); 340 error_log( 'TPC Debug: Adjusted up by one cent for exact match' ); 341 } elseif ( $final_gross > $gross_coupon && $net_discount > pow( 10, -$decimals ) ) { 342 $net_discount -= pow( 10, -$decimals ); 343 error_log( 'TPC Debug: Adjusted down by one cent for exact match' ); 344 } 345 } 346 } 347 348 // Final step: Always return the EXACT mathematical value 349 // Don't try to round to 2 decimals - return with full precision 350 $exact_net = $gross_coupon / ( 1 + $avg_tax_rate ); 351 352 error_log( sprintf( 'TPC Debug: Final calculation - Gross %.4f / (1 + %.4f) = %.10f', 353 $gross_coupon, $avg_tax_rate, $exact_net ) ); 354 355 // Return with high precision to ensure exact calculations 356 return $exact_net; 357 } 358 359 /** 360 * Set correct coupon amounts when creating the order item. 361 * This runs BEFORE the item is saved, allowing us to set the correct net amount. 362 * 363 * @param \WC_Order_Item_Coupon $item The coupon item being created 364 * @param string $code Coupon code 365 * @param float $discount Discount amount 366 * @param \WC_Order $order Order object 367 */ 368 public function set_correct_coupon_amounts( \WC_Order_Item_Coupon $item, string $code, float $discount, \WC_Order $order ): void { 369 error_log( sprintf( 'TPC Debug: set_correct_coupon_amounts called - Code: %s, Discount: %.4f', $code, $discount ) ); 370 371 $coupon = new \WC_Coupon( $code ); 372 373 if ( 'fixed_cart' !== $coupon->get_discount_type() || 374 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 375 error_log( 'TPC Debug: Not a tax-proof coupon, skipping' ); 376 return; 377 } 378 379 // Get the gross amount (what customer sees) 380 $gross_amount = floatval( $coupon->get_amount() ); 381 382 // Calculate tax rate from cart totals 383 $cart = WC()->cart; 384 $total_tax = $cart->get_total_tax(); 385 $subtotal = $cart->get_subtotal(); 386 $total = $cart->get_total( 'edit' ); 387 388 // Calculate average tax rate 389 $avg_tax_rate = $subtotal > 0 ? ( $total_tax / $subtotal ) : 0; 390 391 // Calculate precise net amount 392 $net_amount = $this->calculate_precise_net_discount( 393 $gross_amount, 394 $avg_tax_rate, 395 $subtotal, 396 $subtotal + $total_tax 397 ); 398 399 // Calculate tax amount 400 $tax_amount = $gross_amount - $net_amount; 401 402 // Set the correct amounts on the item BEFORE it's saved 403 // IMPORTANT: We store the EXACT net amount with more decimals for correct calculation 404 // This ensures that net * tax_rate = exactly the gross amount we want 405 $item->set_discount( $net_amount ); 406 $item->set_discount_tax( $tax_amount ); 407 408 // Store metadata for reference - with full precision 409 $item->add_meta_data( '_tpc_gross_discount', $gross_amount ); 410 $item->add_meta_data( '_tpc_net_discount', number_format( $net_amount, 10, '.', '' ) ); // Store with 10 decimals 411 $item->add_meta_data( '_tpc_tax_rate', $avg_tax_rate ); 412 $item->add_meta_data( '_tpc_applied_after_tax', 'yes' ); 413 414 error_log( sprintf( 415 'TPC Debug: Setting coupon amounts - Code: %s, Gross: %.4f, Net: %.4f, Tax: %.4f', 416 $code, 417 $gross_amount, 418 $net_amount, 419 $tax_amount 420 ) ); 421 } 422 423 /** 424 * Store precise net amounts after order is created. 425 * This ensures Germanized Pro uses the correct values for invoice generation. 426 * 427 * @param \WC_Order $order Order object 428 * @param array $data Checkout data 429 */ 430 public function store_coupon_net_amounts( \WC_Order $order, array $data ): void { 431 error_log( sprintf( 'TPC Debug: store_coupon_net_amounts called for order %s', $order->get_id() ) ); 432 433 foreach ( $order->get_coupons() as $item ) { 434 $coupon = new \WC_Coupon( $item->get_code() ); 435 436 if ( 'fixed_cart' !== $coupon->get_discount_type() || 437 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 438 continue; 439 } 440 441 error_log( sprintf( 'TPC Debug: Processing tax-proof coupon %s', $item->get_code() ) ); 442 443 // Get the gross amount 444 $gross_amount = floatval( $coupon->get_amount() ); 445 446 // Calculate tax rate from order 447 // IMPORTANT: We need the subtotal BEFORE discount to get the correct tax rate 448 $order_subtotal_before_discount = 0; 449 $order_tax = 0; 450 451 // Calculate from line items (products) to get accurate rate 452 foreach ( $order->get_items() as $line_item ) { 453 $order_subtotal_before_discount += $line_item->get_subtotal(); 454 $order_tax += $line_item->get_subtotal_tax(); 455 } 456 457 $avg_tax_rate = $order_subtotal_before_discount > 0 ? ( $order_tax / $order_subtotal_before_discount ) : 0; 458 459 error_log( sprintf( 460 'TPC Debug: Order data - Subtotal (before discount): %.4f, Tax: %.4f, Calc Tax Rate: %.6f (%.2f%%)', 461 $order_subtotal_before_discount, 462 $order_tax, 463 $avg_tax_rate, 464 $avg_tax_rate * 100 465 ) ); 466 467 // Calculate exact net amount 468 $net_amount_exact = $gross_amount / ( 1 + $avg_tax_rate ); 469 $tax_amount_exact = $gross_amount - $net_amount_exact; 470 471 error_log( sprintf( 472 'TPC Debug: Calculation - Gross: %.4f / (1 + %.6f) = Net: %.10f, Tax: %.10f', 473 $gross_amount, 474 $avg_tax_rate, 475 $net_amount_exact, 476 $tax_amount_exact 477 ) ); 478 479 // Update the item with precise values 480 $item->set_discount( $net_amount_exact ); 481 $item->set_discount_tax( $tax_amount_exact ); 482 483 // Store metadata 484 $item->update_meta_data( '_tpc_gross_discount', $gross_amount ); 485 $item->update_meta_data( '_tpc_net_discount', $net_amount_exact ); 486 $item->update_meta_data( '_tpc_tax_rate', $avg_tax_rate ); 487 $item->update_meta_data( '_tpc_applied_after_tax', 'yes' ); 488 $item->save(); 489 490 error_log( sprintf( 491 'TPC Debug: Updated coupon %s - Gross: %.4f, Net: %.10f, Tax: %.10f', 492 $item->get_code(), 493 $gross_amount, 494 $net_amount_exact, 495 $tax_amount_exact 496 ) ); 497 } 498 499 // Save the order to persist all changes 500 $order->save(); 501 } 502 503 504 /** 505 * Adjust coupon display in admin order view. 506 * 507 * @param string $subtotal Formatted subtotal 508 * @param \WC_Order_Item $item Order item 509 * @param \WC_Order $order Order object 510 * @return string Modified subtotal 511 */ 512 public function adjust_admin_coupon_display( string $subtotal, \WC_Order_Item $item, \WC_Order $order ): string { 513 // Only process coupon items 514 if ( ! $item->is_type( 'coupon' ) ) { 515 return $subtotal; 516 } 517 518 $net_discount = $item->get_meta( '_tpc_net_discount' ); 519 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 520 521 error_log( sprintf( 'TPC Debug: Meta data - Net: %s, Gross: %s', 522 $net_discount ? $net_discount : 'NOT FOUND', 523 $gross_discount ? $gross_discount : 'NOT FOUND' 524 ) ); 525 526 if ( $net_discount && $gross_discount ) { 527 // Show detailed information in admin with 4 decimal places 528 $net_value = floatval( $net_discount ); 529 $net_display = number_format( $net_value, 4, ',', '' ); 530 $subtotal = sprintf( 531 '-<span class="woocommerce-Price-amount amount">%s <span class="woocommerce-Price-currencySymbol">€</span></span> <small class="woocommerce-help-tip" data-tip="Brutto: %s">(Brutto: %s)</small>', 532 $net_display, 533 wc_price( $gross_discount ), 534 wc_price( $gross_discount ) 535 ); 536 537 error_log( sprintf( 538 'TPC Debug: Admin display adjusted - Net: %.4f, Gross: %.4f', 539 $net_discount, 540 $gross_discount 541 ) ); 542 } 543 544 return $subtotal; 545 } 546 547 548 /** 549 * Adjust total discount to ensure it matches the gross coupon amount. 550 * This is critical for invoice generation. 551 * 552 * @param float $total_discount Current total discount 553 * @param \WC_Order $order Order object 554 * @return float Adjusted discount 555 */ 556 public function adjust_total_discount_for_invoices( float $total_discount, \WC_Order $order ): float { 557 $adjusted_total = 0; 558 559 foreach ( $order->get_coupons() as $item ) { 560 if ( $item->get_meta( '_tpc_applied_after_tax' ) === 'yes' ) { 561 // Use the gross discount amount for tax-proof coupons 562 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 563 if ( $gross_discount ) { 564 $adjusted_total += floatval( $gross_discount ); 565 error_log( sprintf( 566 'TPC Debug: Adjusting invoice discount for %s - Using gross: %.4f instead of net+tax', 567 $item->get_code(), 568 $gross_discount 569 ) ); 570 } else { 571 $adjusted_total += $item->get_discount() + $item->get_discount_tax(); 572 } 573 } else { 574 $adjusted_total += $item->get_discount() + $item->get_discount_tax(); 575 } 576 } 577 578 if ( $adjusted_total != $total_discount ) { 579 error_log( sprintf( 580 'TPC Debug: Adjusted total discount from %.4f to %.4f', 581 $total_discount, 582 $adjusted_total 583 ) ); 584 } 585 586 return $adjusted_total; 587 } 588 589 /** 590 * Adjust StoreaBill invoice discount total to use gross amounts. 591 * This filter is called when StoreaBill calculates the total discount for the invoice. 592 * 593 * @param float $total Current discount total 594 * @param object $invoice The invoice document 595 * @return float Adjusted total 596 */ 597 public function storeabill_get_discount_total_gross( float $total, $invoice ): float { 598 // Get the WooCommerce order 599 if ( ! method_exists( $invoice, 'get_order' ) ) { 600 return $total; 601 } 602 603 $order = $invoice->get_order(); 604 if ( ! $order ) { 605 return $total; 606 } 607 608 $adjusted_total = 0; 609 $has_tax_proof_coupons = false; 610 611 foreach ( $order->get_coupons() as $item ) { 612 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 613 if ( $gross_discount ) { 614 $adjusted_total += floatval( $gross_discount ); 615 $has_tax_proof_coupons = true; 616 error_log( sprintf( 617 'TPC Debug: StoreaBill discount total - Using gross: %.4f for %s', 618 $gross_discount, 619 $item->get_code() 620 ) ); 621 } else { 622 $adjusted_total += $item->get_discount() + $item->get_discount_tax(); 623 } 624 } 625 626 if ( $has_tax_proof_coupons && $adjusted_total != $total ) { 627 error_log( sprintf( 628 'TPC Debug: StoreaBill total discount changed from %.4f to %.4f', 629 $total, 630 $adjusted_total 631 ) ); 632 return $adjusted_total; 633 } 634 635 return $total; 636 } 637 638 /** 639 * Adjust StoreaBill voucher total to use gross amounts. 640 * 641 * @param float $total Current voucher total 642 * @param object $order_data_store The WooCommerce order data store 643 * @return float Adjusted total 644 */ 645 public function storeabill_voucher_total_gross( float $total, $order_data_store ): float { 646 error_log( sprintf( 'TPC Debug: storeabill_voucher_total_gross called - Total: %.4f', $total ) ); 647 648 if ( ! method_exists( $order_data_store, 'get_order' ) ) { 649 error_log( 'TPC Debug: No get_order method' ); 650 return $total; 651 } 652 653 $order = $order_data_store->get_order(); 654 if ( ! $order ) { 655 error_log( 'TPC Debug: No order found' ); 656 return $total; 657 } 658 659 $adjusted_total = 0; 660 661 foreach ( $order->get_coupons() as $item ) { 662 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 663 if ( $gross_discount ) { 664 $adjusted_total += floatval( $gross_discount ); 665 error_log( sprintf( 666 'TPC Debug: StoreaBill voucher total - Using gross: %.4f for %s', 667 $gross_discount, 668 $item->get_code() 669 ) ); 670 } 671 } 672 673 if ( $adjusted_total > 0 ) { 674 error_log( sprintf( 675 'TPC Debug: StoreaBill voucher total changed from %.4f to %.4f', 676 $total, 677 $adjusted_total 678 ) ); 679 return $adjusted_total; 680 } 681 682 return $total; 683 } 684 685 /** 686 * Log all Germanized/StoreaBill hooks to find the right one. 687 * This is a temporary debugging method. 688 * 689 * @param string $hook Hook name 690 */ 691 public function log_germanized_hooks( string $hook ): void { 692 // Only log during invoice generation (check if we're in admin) 693 if ( ! is_admin() ) { 694 return; 695 } 696 697 // Only log hooks that might be related to invoice/discount 698 if ( strpos( $hook, 'gzd' ) !== false || 699 strpos( $hook, 'germanized' ) !== false || 700 strpos( $hook, 'storeabill' ) !== false || 701 strpos( $hook, 'invoice' ) !== false ) { 702 703 // Only log filter hooks (not actions) to reduce noise 704 if ( strpos( $hook, 'discount' ) !== false || 705 strpos( $hook, 'total' ) !== false || 706 strpos( $hook, 'coupon' ) !== false ) { 707 error_log( sprintf( 'TPC Debug: Hook fired: %s', $hook ) ); 708 } 709 } 710 } 711 712 /** Public wakeup to satisfy PHP's magic method requirement. */ 142 713 public function __wakeup() {} 143 714 } -
taxproof-coupons-for-woocommerce/trunk/README.md
r3343132 r3381508 5 5 **Requires at least:** 6.5 6 6 **Tested up to:** 6.8 7 **Stable tag:** 1.0. 37 **Stable tag:** 1.0.4 8 8 **License:** GPLv2 or later 9 9 **License URI:** https://www.gnu.org/licenses/gpl-2.0.html … … 14 14 15 15 - Adds **Apply coupon after tax** checkbox to coupon settings. 16 - Converts gross coupon values into precise net discounts .16 - Converts gross coupon values into precise net discounts with high accuracy. 17 17 - Guarantees the exact gross amount is deducted in cart and checkout. 18 - **StoreaBill/Germanized Pro Integration** for accurate invoice generation. 19 - **Enhanced Admin Display** showing detailed net and gross amounts. 20 - **Precision Calculations** for complex tax scenarios. 21 - **Debug Logging** for development and troubleshooting. 18 22 19 23 ## Installation … … 24 28 25 29 ## Changelog 30 31 ### 1.0.4 32 33 - **Verbesserte Präzision**: Neue präzise Berechnungsmethode für exakte Steuerumrechnungen 34 - **Erweiterte Anzeige-Funktionen**: Verbesserte Coupon-Anzeige im Warenkorb und Checkout 35 - **StoreaBill/Germanized Pro Integration**: Vollständige Kompatibilität mit Germanized Pro für Rechnungsgenerierung 36 - **Admin-Verbesserungen**: Detaillierte Anzeige von Netto- und Bruttobeträgen in der WooCommerce Admin-Oberfläche 37 - **Erweiterte Metadaten-Speicherung**: Präzise Speicherung von Coupon-Beträgen mit hoher Genauigkeit 38 - **Debug-Funktionen**: Erweiterte Logging-Funktionen für bessere Entwicklung und Fehlerbehebung 39 - **Hook-Integration**: Neue Hooks für bessere Integration mit WooCommerce und Drittanbieter-Plugins 40 - **Performance-Optimierungen**: Verbesserte Berechnungslogik für komplexe Steuerszenarien 26 41 27 42 ### 1.0.3 -
taxproof-coupons-for-woocommerce/trunk/readme.txt
r3343132 r3381508 5 5 Requires at least: 6.5 6 6 Tested up to: 6.8 7 Stable tag: 1.0. 37 Stable tag: 1.0.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 28 28 == Changelog == 29 29 30 = 1.0.4 = 31 32 Release date: January 2025 33 34 * **Verbesserte Präzision**: Neue präzise Berechnungsmethode für exakte Steuerumrechnungen 35 * **Erweiterte Anzeige-Funktionen**: Verbesserte Coupon-Anzeige im Warenkorb und Checkout 36 * **StoreaBill/Germanized Pro Integration**: Vollständige Kompatibilität mit Germanized Pro für Rechnungsgenerierung 37 * **Admin-Verbesserungen**: Detaillierte Anzeige von Netto- und Bruttobeträgen in der WooCommerce Admin-Oberfläche 38 * **Erweiterte Metadaten-Speicherung**: Präzise Speicherung von Coupon-Beträgen mit hoher Genauigkeit 39 * **Debug-Funktionen**: Erweiterte Logging-Funktionen für bessere Entwicklung und Fehlerbehebung 40 * **Hook-Integration**: Neue Hooks für bessere Integration mit WooCommerce und Drittanbieter-Plugins 41 * **Performance-Optimierungen**: Verbesserte Berechnungslogik für komplexe Steuerszenarien 42 30 43 = 1.0.3 = 31 44 -
taxproof-coupons-for-woocommerce/trunk/tax-proof-coupons-plugin.php
r3343132 r3381508 4 4 * Plugin URI: https://github.com/s-a-s-k-i-a/tax-proof-coupons 5 5 * Description: Ensure fixed-value coupons always apply after tax, regardless of VAT rate or customer location. 6 * Version: 1.0. 36 * Version: 1.0.4 7 7 * Author: Saskia Teichmann 8 8 * Author URI: https://saskialund.de … … 24 24 class Plugin { 25 25 /** Plugin version. */ 26 public const VERSION = '1.0. 2';26 public const VERSION = '1.0.4'; 27 27 28 28 /** Singleton instance. */ … … 44 44 /** Prevent direct instantiation. */ 45 45 private function __construct() {} 46 47 /** 48 * Check if Germanized (free or Pro) is active. 49 * 50 * @return bool 51 */ 52 private function is_storeabill_active(): bool { 53 return function_exists( 'WC_GZD' ); 54 } 46 55 47 56 /** Initialize WP and WooCommerce hooks. */ … … 50 59 add_action( 'woocommerce_coupon_options_save', [ $this, 'save_apply_after_tax_checkbox' ], 10, 2 ); 51 60 add_filter( 'woocommerce_coupon_get_discount_amount', [ $this, 'apply_coupon_after_tax' ], 20, 5 ); 61 add_filter( 'woocommerce_coupon_discount_amount_html', [ $this, 'adjust_coupon_display_amount' ], 20, 2 ); 62 add_filter( 'woocommerce_cart_totals_coupon_html', [ $this, 'adjust_cart_coupon_display' ], 20, 3 ); 63 64 // Store correct net amounts when creating order items 65 add_action( 'woocommerce_checkout_create_order', [ $this, 'store_coupon_net_amounts' ], 10, 2 ); 66 add_action( 'woocommerce_create_order_coupon_item', [ $this, 'set_correct_coupon_amounts' ], 10, 4 ); 67 68 // Admin display 69 add_filter( 'woocommerce_order_formatted_line_subtotal', [ $this, 'adjust_admin_coupon_display' ], 10, 3 ); 70 71 // Force correct calculation for invoices - use the correct hook 72 add_filter( 'woocommerce_order_get_total_discount', [ $this, 'adjust_total_discount_for_invoices' ], 10, 2 ); 73 74 // StoreaBill (Germanized Pro) specific hooks - only if available 75 if ( $this->is_storeabill_active() ) { 76 add_filter( 'storeabill_invoice_get_discount_total', [ $this, 'storeabill_get_discount_total_gross' ], 10, 2 ); 77 add_filter( 'storeabill_woo_order_voucher_total', [ $this, 'storeabill_voucher_total_gross' ], 10, 2 ); 78 79 // Hook sniffer to find the correct Germanized hooks (only in debug mode) 80 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 81 add_action( 'all', [ $this, 'log_germanized_hooks' ], 1 ); 82 } 83 } 52 84 } 53 85 … … 129 161 $avg_tax_rate = ( $total_gross / $total_net ) - 1; 130 162 131 // Convert the gross coupon into the correct net discount. 132 $net_discount = $gross_coupon / ( 1 + $avg_tax_rate ); 133 $net_discount = round( $net_discount, wc_get_price_decimals() ); 163 // Convert the gross coupon into the correct net discount with precision handling 164 $net_discount = $this->calculate_precise_net_discount( 165 $gross_coupon, 166 $avg_tax_rate, 167 $total_net, 168 $total_gross 169 ); 134 170 135 171 // Mark as applied so we don't double-dip. … … 139 175 } 140 176 141 /** Public wakeup to satisfy PHP’s magic method requirement. */ 177 /** 178 * Adjust the coupon display amount to show the gross value. 179 * This ensures the customer always sees the advertised coupon amount. 180 * 181 * @param string $discount_amount_html The HTML string for the discount amount 182 * @param \WC_Coupon $coupon The coupon object 183 * @return string Modified HTML 184 */ 185 public function adjust_coupon_display_amount( string $discount_amount_html, \WC_Coupon $coupon ): string { 186 // Only adjust for our tax-proof coupons 187 if ( 'fixed_cart' !== $coupon->get_discount_type() || 188 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 189 return $discount_amount_html; 190 } 191 192 // Get the original coupon amount (gross) 193 $gross_amount = floatval( $coupon->get_amount() ); 194 195 // Format it as WooCommerce would 196 $formatted_amount = wc_price( $gross_amount ); 197 198 error_log( sprintf( 'TPC Debug: Adjusting display from %s to %s', $discount_amount_html, $formatted_amount ) ); 199 200 return '-' . $formatted_amount; 201 } 202 203 /** 204 * Adjust the cart display to show the gross coupon amount. 205 * This filter specifically targets the cart and checkout displays. 206 * 207 * @param string $coupon_html Current HTML 208 * @param \WC_Coupon $coupon Coupon object 209 * @param string $discount_amount_html Discount amount HTML 210 * @return string Modified HTML 211 */ 212 public function adjust_cart_coupon_display( string $coupon_html, \WC_Coupon $coupon, string $discount_amount_html ): string { 213 // Only adjust for our tax-proof coupons 214 if ( 'fixed_cart' !== $coupon->get_discount_type() || 215 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 216 return $coupon_html; 217 } 218 219 // Get the original coupon amount (gross) 220 $gross_amount = floatval( $coupon->get_amount() ); 221 222 // Format it as WooCommerce would 223 $formatted_amount = wc_price( $gross_amount ); 224 225 // Replace the discount amount in the HTML 226 $new_html = str_replace( $discount_amount_html, '-' . $formatted_amount, $coupon_html ); 227 228 error_log( sprintf( 'TPC Debug: Cart display adjustment - Original: %s, New: %s', $coupon_html, $new_html ) ); 229 230 return $new_html; 231 } 232 233 /** 234 * Calculate precise net discount that ensures the gross amount matches exactly. 235 * This method handles rounding issues by finding the optimal net amount. 236 * 237 * @param float $gross_coupon The gross coupon amount (e.g., 100.00) 238 * @param float $avg_tax_rate The average tax rate (e.g., 0.21 for 21%) 239 * @param float $total_net Total net amount in cart 240 * @param float $total_gross Total gross amount in cart 241 * @return float The precise net discount amount 242 */ 243 private function calculate_precise_net_discount( 244 float $gross_coupon, 245 float $avg_tax_rate, 246 float $total_net, 247 float $total_gross 248 ): float { 249 $decimals = wc_get_price_decimals(); 250 $precision_decimals = 4; // Use 4 decimals for internal precision 251 252 // Debug logging 253 error_log( 'TPC Debug: Starting precise calculation' ); 254 error_log( sprintf( 'TPC Debug: Gross coupon: %.4f', $gross_coupon ) ); 255 error_log( sprintf( 'TPC Debug: Avg tax rate: %.4f (%.2f%%)', $avg_tax_rate, $avg_tax_rate * 100 ) ); 256 error_log( sprintf( 'TPC Debug: Decimals: %d', $decimals ) ); 257 258 // Initial calculation with higher precision 259 $net_discount_exact = $gross_coupon / ( 1 + $avg_tax_rate ); 260 error_log( sprintf( 'TPC Debug: Exact net discount (unrounded): %.6f', $net_discount_exact ) ); 261 262 // Try both floor and ceil to see which gives us the target gross 263 $net_floor = floor( $net_discount_exact * pow( 10, $decimals ) ) / pow( 10, $decimals ); 264 $net_ceil = ceil( $net_discount_exact * pow( 10, $decimals ) ) / pow( 10, $decimals ); 265 266 error_log( sprintf( 'TPC Debug: Net floor: %.4f, Net ceil: %.4f', $net_floor, $net_ceil ) ); 267 268 // Calculate gross for both options 269 // IMPORTANT: For Germany with 19% MwSt: 270 // 84.03 * 1.19 = 99.9957 which rounds to 99.99 (NOT 100.00!) 271 // 84.04 * 1.19 = 100.0076 which rounds to 100.01 272 $gross_floor_exact = $net_floor * ( 1 + $avg_tax_rate ); 273 $gross_ceil_exact = $net_ceil * ( 1 + $avg_tax_rate ); 274 $gross_floor = round( $gross_floor_exact, $decimals ); 275 $gross_ceil = round( $gross_ceil_exact, $decimals ); 276 277 error_log( sprintf( 'TPC Debug: Floor exact: %.6f, Ceil exact: %.6f', $gross_floor_exact, $gross_ceil_exact ) ); 278 279 error_log( sprintf( 'TPC Debug: Gross from floor: %.4f, Gross from ceil: %.4f', $gross_floor, $gross_ceil ) ); 280 281 // Choose the net amount that gives us the exact gross we want 282 // The problem: With only 2 decimals, we can't always achieve the exact gross amount 283 // Solution: Return the EXACT net amount with more precision 284 285 if ( $gross_floor == $gross_coupon ) { 286 $net_discount = $net_floor; 287 error_log( 'TPC Debug: Using floor value for exact match' ); 288 } elseif ( $gross_ceil == $gross_coupon ) { 289 $net_discount = $net_ceil; 290 error_log( 'TPC Debug: Using ceil value for exact match' ); 291 } else { 292 // For 19% MwSt: 84.03 gives 99.9957 which rounds to 99.99, NOT 100.00 293 // We need to use 84.04 which gives 100.0076 and rounds to 100.01 294 // But we can accept 100.01 and show 100.00 in display 295 error_log( sprintf( 'TPC Debug: No exact match - floor gives %.4f, ceil gives %.4f', $gross_floor, $gross_ceil ) ); 296 297 // Prefer the value that's slightly over rather than under 298 if ( $gross_floor < $gross_coupon && $gross_ceil > $gross_coupon ) { 299 // If floor is under and ceil is over, use ceil 300 $net_discount = $net_ceil; 301 error_log( 'TPC Debug: Using ceil to avoid underpayment' ); 302 } else { 303 // If neither gives exact match, we need a more sophisticated approach 304 // Calculate what net amount would give us exactly the gross we want 305 // This might require adjusting by fractions of a cent 306 307 error_log( 'TPC Debug: Neither floor nor ceil gives exact match, calculating optimal value' ); 308 309 // We'll use a binary search approach to find the optimal value 310 $lower = $net_floor; 311 $upper = $net_ceil; 312 $best_net = $net_floor; 313 $best_diff = abs( $gross_floor - $gross_coupon ); 314 315 // Try intermediate values 316 for ( $i = 1; $i < 10; $i++ ) { 317 $test_net = $lower + ( $upper - $lower ) * ( $i / 10 ); 318 $test_net = round( $test_net, $decimals + 2 ); // Use extra precision 319 $test_gross = round( $test_net * ( 1 + $avg_tax_rate ), $decimals ); 320 $diff = abs( $test_gross - $gross_coupon ); 321 322 if ( $diff < $best_diff ) { 323 $best_net = $test_net; 324 $best_diff = $diff; 325 326 if ( $diff < 0.001 ) { 327 error_log( sprintf( 'TPC Debug: Found optimal value: %.4f', $best_net ) ); 328 break; 329 } 330 } 331 } 332 333 // Round to allowed decimals 334 $net_discount = round( $best_net, $decimals ); 335 336 // If this still doesn't give us the exact gross, adjust by one cent in the right direction 337 $final_gross = round( $net_discount * ( 1 + $avg_tax_rate ), $decimals ); 338 if ( $final_gross < $gross_coupon ) { 339 $net_discount += pow( 10, -$decimals ); 340 error_log( 'TPC Debug: Adjusted up by one cent for exact match' ); 341 } elseif ( $final_gross > $gross_coupon && $net_discount > pow( 10, -$decimals ) ) { 342 $net_discount -= pow( 10, -$decimals ); 343 error_log( 'TPC Debug: Adjusted down by one cent for exact match' ); 344 } 345 } 346 } 347 348 // Final step: Always return the EXACT mathematical value 349 // Don't try to round to 2 decimals - return with full precision 350 $exact_net = $gross_coupon / ( 1 + $avg_tax_rate ); 351 352 error_log( sprintf( 'TPC Debug: Final calculation - Gross %.4f / (1 + %.4f) = %.10f', 353 $gross_coupon, $avg_tax_rate, $exact_net ) ); 354 355 // Return with high precision to ensure exact calculations 356 return $exact_net; 357 } 358 359 /** 360 * Set correct coupon amounts when creating the order item. 361 * This runs BEFORE the item is saved, allowing us to set the correct net amount. 362 * 363 * @param \WC_Order_Item_Coupon $item The coupon item being created 364 * @param string $code Coupon code 365 * @param float $discount Discount amount 366 * @param \WC_Order $order Order object 367 */ 368 public function set_correct_coupon_amounts( \WC_Order_Item_Coupon $item, string $code, float $discount, \WC_Order $order ): void { 369 error_log( sprintf( 'TPC Debug: set_correct_coupon_amounts called - Code: %s, Discount: %.4f', $code, $discount ) ); 370 371 $coupon = new \WC_Coupon( $code ); 372 373 if ( 'fixed_cart' !== $coupon->get_discount_type() || 374 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 375 error_log( 'TPC Debug: Not a tax-proof coupon, skipping' ); 376 return; 377 } 378 379 // Get the gross amount (what customer sees) 380 $gross_amount = floatval( $coupon->get_amount() ); 381 382 // Calculate tax rate from cart totals 383 $cart = WC()->cart; 384 $total_tax = $cart->get_total_tax(); 385 $subtotal = $cart->get_subtotal(); 386 $total = $cart->get_total( 'edit' ); 387 388 // Calculate average tax rate 389 $avg_tax_rate = $subtotal > 0 ? ( $total_tax / $subtotal ) : 0; 390 391 // Calculate precise net amount 392 $net_amount = $this->calculate_precise_net_discount( 393 $gross_amount, 394 $avg_tax_rate, 395 $subtotal, 396 $subtotal + $total_tax 397 ); 398 399 // Calculate tax amount 400 $tax_amount = $gross_amount - $net_amount; 401 402 // Set the correct amounts on the item BEFORE it's saved 403 // IMPORTANT: We store the EXACT net amount with more decimals for correct calculation 404 // This ensures that net * tax_rate = exactly the gross amount we want 405 $item->set_discount( $net_amount ); 406 $item->set_discount_tax( $tax_amount ); 407 408 // Store metadata for reference - with full precision 409 $item->add_meta_data( '_tpc_gross_discount', $gross_amount ); 410 $item->add_meta_data( '_tpc_net_discount', number_format( $net_amount, 10, '.', '' ) ); // Store with 10 decimals 411 $item->add_meta_data( '_tpc_tax_rate', $avg_tax_rate ); 412 $item->add_meta_data( '_tpc_applied_after_tax', 'yes' ); 413 414 error_log( sprintf( 415 'TPC Debug: Setting coupon amounts - Code: %s, Gross: %.4f, Net: %.4f, Tax: %.4f', 416 $code, 417 $gross_amount, 418 $net_amount, 419 $tax_amount 420 ) ); 421 } 422 423 /** 424 * Store precise net amounts after order is created. 425 * This ensures Germanized Pro uses the correct values for invoice generation. 426 * 427 * @param \WC_Order $order Order object 428 * @param array $data Checkout data 429 */ 430 public function store_coupon_net_amounts( \WC_Order $order, array $data ): void { 431 error_log( sprintf( 'TPC Debug: store_coupon_net_amounts called for order %s', $order->get_id() ) ); 432 433 foreach ( $order->get_coupons() as $item ) { 434 $coupon = new \WC_Coupon( $item->get_code() ); 435 436 if ( 'fixed_cart' !== $coupon->get_discount_type() || 437 'yes' !== $coupon->get_meta( 'tpc_apply_after_tax', true ) ) { 438 continue; 439 } 440 441 error_log( sprintf( 'TPC Debug: Processing tax-proof coupon %s', $item->get_code() ) ); 442 443 // Get the gross amount 444 $gross_amount = floatval( $coupon->get_amount() ); 445 446 // Calculate tax rate from order 447 // IMPORTANT: We need the subtotal BEFORE discount to get the correct tax rate 448 $order_subtotal_before_discount = 0; 449 $order_tax = 0; 450 451 // Calculate from line items (products) to get accurate rate 452 foreach ( $order->get_items() as $line_item ) { 453 $order_subtotal_before_discount += $line_item->get_subtotal(); 454 $order_tax += $line_item->get_subtotal_tax(); 455 } 456 457 $avg_tax_rate = $order_subtotal_before_discount > 0 ? ( $order_tax / $order_subtotal_before_discount ) : 0; 458 459 error_log( sprintf( 460 'TPC Debug: Order data - Subtotal (before discount): %.4f, Tax: %.4f, Calc Tax Rate: %.6f (%.2f%%)', 461 $order_subtotal_before_discount, 462 $order_tax, 463 $avg_tax_rate, 464 $avg_tax_rate * 100 465 ) ); 466 467 // Calculate exact net amount 468 $net_amount_exact = $gross_amount / ( 1 + $avg_tax_rate ); 469 $tax_amount_exact = $gross_amount - $net_amount_exact; 470 471 error_log( sprintf( 472 'TPC Debug: Calculation - Gross: %.4f / (1 + %.6f) = Net: %.10f, Tax: %.10f', 473 $gross_amount, 474 $avg_tax_rate, 475 $net_amount_exact, 476 $tax_amount_exact 477 ) ); 478 479 // Update the item with precise values 480 $item->set_discount( $net_amount_exact ); 481 $item->set_discount_tax( $tax_amount_exact ); 482 483 // Store metadata 484 $item->update_meta_data( '_tpc_gross_discount', $gross_amount ); 485 $item->update_meta_data( '_tpc_net_discount', $net_amount_exact ); 486 $item->update_meta_data( '_tpc_tax_rate', $avg_tax_rate ); 487 $item->update_meta_data( '_tpc_applied_after_tax', 'yes' ); 488 $item->save(); 489 490 error_log( sprintf( 491 'TPC Debug: Updated coupon %s - Gross: %.4f, Net: %.10f, Tax: %.10f', 492 $item->get_code(), 493 $gross_amount, 494 $net_amount_exact, 495 $tax_amount_exact 496 ) ); 497 } 498 499 // Save the order to persist all changes 500 $order->save(); 501 } 502 503 504 /** 505 * Adjust coupon display in admin order view. 506 * 507 * @param string $subtotal Formatted subtotal 508 * @param \WC_Order_Item $item Order item 509 * @param \WC_Order $order Order object 510 * @return string Modified subtotal 511 */ 512 public function adjust_admin_coupon_display( string $subtotal, \WC_Order_Item $item, \WC_Order $order ): string { 513 // Only process coupon items 514 if ( ! $item->is_type( 'coupon' ) ) { 515 return $subtotal; 516 } 517 518 $net_discount = $item->get_meta( '_tpc_net_discount' ); 519 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 520 521 error_log( sprintf( 'TPC Debug: Meta data - Net: %s, Gross: %s', 522 $net_discount ? $net_discount : 'NOT FOUND', 523 $gross_discount ? $gross_discount : 'NOT FOUND' 524 ) ); 525 526 if ( $net_discount && $gross_discount ) { 527 // Show detailed information in admin with 4 decimal places 528 $net_value = floatval( $net_discount ); 529 $net_display = number_format( $net_value, 4, ',', '' ); 530 $subtotal = sprintf( 531 '-<span class="woocommerce-Price-amount amount">%s <span class="woocommerce-Price-currencySymbol">€</span></span> <small class="woocommerce-help-tip" data-tip="Brutto: %s">(Brutto: %s)</small>', 532 $net_display, 533 wc_price( $gross_discount ), 534 wc_price( $gross_discount ) 535 ); 536 537 error_log( sprintf( 538 'TPC Debug: Admin display adjusted - Net: %.4f, Gross: %.4f', 539 $net_discount, 540 $gross_discount 541 ) ); 542 } 543 544 return $subtotal; 545 } 546 547 548 /** 549 * Adjust total discount to ensure it matches the gross coupon amount. 550 * This is critical for invoice generation. 551 * 552 * @param float $total_discount Current total discount 553 * @param \WC_Order $order Order object 554 * @return float Adjusted discount 555 */ 556 public function adjust_total_discount_for_invoices( float $total_discount, \WC_Order $order ): float { 557 $adjusted_total = 0; 558 559 foreach ( $order->get_coupons() as $item ) { 560 if ( $item->get_meta( '_tpc_applied_after_tax' ) === 'yes' ) { 561 // Use the gross discount amount for tax-proof coupons 562 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 563 if ( $gross_discount ) { 564 $adjusted_total += floatval( $gross_discount ); 565 error_log( sprintf( 566 'TPC Debug: Adjusting invoice discount for %s - Using gross: %.4f instead of net+tax', 567 $item->get_code(), 568 $gross_discount 569 ) ); 570 } else { 571 $adjusted_total += $item->get_discount() + $item->get_discount_tax(); 572 } 573 } else { 574 $adjusted_total += $item->get_discount() + $item->get_discount_tax(); 575 } 576 } 577 578 if ( $adjusted_total != $total_discount ) { 579 error_log( sprintf( 580 'TPC Debug: Adjusted total discount from %.4f to %.4f', 581 $total_discount, 582 $adjusted_total 583 ) ); 584 } 585 586 return $adjusted_total; 587 } 588 589 /** 590 * Adjust StoreaBill invoice discount total to use gross amounts. 591 * This filter is called when StoreaBill calculates the total discount for the invoice. 592 * 593 * @param float $total Current discount total 594 * @param object $invoice The invoice document 595 * @return float Adjusted total 596 */ 597 public function storeabill_get_discount_total_gross( float $total, $invoice ): float { 598 // Get the WooCommerce order 599 if ( ! method_exists( $invoice, 'get_order' ) ) { 600 return $total; 601 } 602 603 $order = $invoice->get_order(); 604 if ( ! $order ) { 605 return $total; 606 } 607 608 $adjusted_total = 0; 609 $has_tax_proof_coupons = false; 610 611 foreach ( $order->get_coupons() as $item ) { 612 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 613 if ( $gross_discount ) { 614 $adjusted_total += floatval( $gross_discount ); 615 $has_tax_proof_coupons = true; 616 error_log( sprintf( 617 'TPC Debug: StoreaBill discount total - Using gross: %.4f for %s', 618 $gross_discount, 619 $item->get_code() 620 ) ); 621 } else { 622 $adjusted_total += $item->get_discount() + $item->get_discount_tax(); 623 } 624 } 625 626 if ( $has_tax_proof_coupons && $adjusted_total != $total ) { 627 error_log( sprintf( 628 'TPC Debug: StoreaBill total discount changed from %.4f to %.4f', 629 $total, 630 $adjusted_total 631 ) ); 632 return $adjusted_total; 633 } 634 635 return $total; 636 } 637 638 /** 639 * Adjust StoreaBill voucher total to use gross amounts. 640 * 641 * @param float $total Current voucher total 642 * @param object $order_data_store The WooCommerce order data store 643 * @return float Adjusted total 644 */ 645 public function storeabill_voucher_total_gross( float $total, $order_data_store ): float { 646 error_log( sprintf( 'TPC Debug: storeabill_voucher_total_gross called - Total: %.4f', $total ) ); 647 648 if ( ! method_exists( $order_data_store, 'get_order' ) ) { 649 error_log( 'TPC Debug: No get_order method' ); 650 return $total; 651 } 652 653 $order = $order_data_store->get_order(); 654 if ( ! $order ) { 655 error_log( 'TPC Debug: No order found' ); 656 return $total; 657 } 658 659 $adjusted_total = 0; 660 661 foreach ( $order->get_coupons() as $item ) { 662 $gross_discount = $item->get_meta( '_tpc_gross_discount' ); 663 if ( $gross_discount ) { 664 $adjusted_total += floatval( $gross_discount ); 665 error_log( sprintf( 666 'TPC Debug: StoreaBill voucher total - Using gross: %.4f for %s', 667 $gross_discount, 668 $item->get_code() 669 ) ); 670 } 671 } 672 673 if ( $adjusted_total > 0 ) { 674 error_log( sprintf( 675 'TPC Debug: StoreaBill voucher total changed from %.4f to %.4f', 676 $total, 677 $adjusted_total 678 ) ); 679 return $adjusted_total; 680 } 681 682 return $total; 683 } 684 685 /** 686 * Log all Germanized/StoreaBill hooks to find the right one. 687 * This is a temporary debugging method. 688 * 689 * @param string $hook Hook name 690 */ 691 public function log_germanized_hooks( string $hook ): void { 692 // Only log during invoice generation (check if we're in admin) 693 if ( ! is_admin() ) { 694 return; 695 } 696 697 // Only log hooks that might be related to invoice/discount 698 if ( strpos( $hook, 'gzd' ) !== false || 699 strpos( $hook, 'germanized' ) !== false || 700 strpos( $hook, 'storeabill' ) !== false || 701 strpos( $hook, 'invoice' ) !== false ) { 702 703 // Only log filter hooks (not actions) to reduce noise 704 if ( strpos( $hook, 'discount' ) !== false || 705 strpos( $hook, 'total' ) !== false || 706 strpos( $hook, 'coupon' ) !== false ) { 707 error_log( sprintf( 'TPC Debug: Hook fired: %s', $hook ) ); 708 } 709 } 710 } 711 712 /** Public wakeup to satisfy PHP's magic method requirement. */ 142 713 public function __wakeup() {} 143 714 }
Note: See TracChangeset
for help on using the changeset viewer.