AeliaWCEU_VAT_AssistantReportsBase_EU_VAT_By_Country_Report

Renders the report containing the EU VAT for each country in a specific period.

Defined (1)

The class is defined in the following location(s).

/src/lib/classes/reporting/reports/base/base_eu_vat_by_country_report.php  
  1. abstract class Base_EU_VAT_By_Country_Report extends \Aelia\WC\EU_VAT_Assistant\Reports\Base_Report { 
  2. // @var string Indicates which tax types should be shown (MOSS, non-MOSS or all) 
  3. protected $taxes_to_show; 
  4. // @var string Indicates which exchange rates should be used (stored with order or ECB). 
  5. protected $exchange_rates_to_use; 
  6. // @var string A list of the tax classes that are not part of MOSS 
  7. protected $non_moss_tax_classes; 
  8.  
  9. /** 
  10. * Indicates if the tax passed as a parameter should be skipped (i.e. excluded 
  11. * from the report). 
  12. * @param array tax_details An array of data describing a tax. 
  13. * @return bool True (tax should be excluded from the report) or false (tax 
  14. * should be displayed on the report). 
  15. */ 
  16. protected function should_skip(array $tax_details) { 
  17. // If all taxes should be displayed, just return "false" (i.e. don't skip) 
  18. if($this->taxes_to_show === Definitions::TAX_ALL) { 
  19. return false; 
  20.  
  21. // If the tax is a "non-MOSS" one, it should be skipped when the tax types 
  22. // to display are "MOSS only" 
  23. if($tax_details['is_moss'] == false) { 
  24. return ($this->taxes_to_show === Definitions::TAX_MOSS_ONLY); 
  25. else { 
  26. return ($this->taxes_to_show === Definitions::TAX_NON_MOSS_ONLY); 
  27.  
  28. /** 
  29. * Indicates if reports should be rendered using the exchange rates associated 
  30. * with the orders. 
  31. * @return bool 
  32. */ 
  33. protected function should_use_orders_exchange_rates() { 
  34. return ($this->exchange_rates_to_use === Definitions::FX_SAVED_WITH_ORDER); 
  35.  
  36. public function __construct() { 
  37. parent::__construct(); 
  38.  
  39. // Store which tax types should be shown 
  40. $this->taxes_to_show = get_value(Definitions::ARG_TAX_TYPE, $_REQUEST, Definitions::TAX_MOSS_ONLY); 
  41. // Store which exchange rates should be used 
  42. $this->exchange_rates_to_use = get_value(Definitions::ARG_EXCHANGE_RATES_TYPE, $_REQUEST, Definitions::FX_SAVED_WITH_ORDER); 
  43. // Keep a list of the tax classes that are not part of MOSS 
  44. $this->non_moss_tax_classes = WC_Aelia_EU_VAT_Assistant::settings()->get(Settings::FIELD_TAX_CLASSES_EXCLUDED_FROM_MOSS, array()); 
  45.  
  46. /** 
  47. * Returns the tax data for the report. 
  48. * @return array The tax data. 
  49. */ 
  50. protected function get_tax_data() { 
  51. global $wpdb; 
  52. $wpdb->show_errors(); 
  53.  
  54. $dataset = $this->get_order_report_data(array( 
  55. 'data' => array( 
  56. 'post_date' => array( 
  57. 'type' => 'post_data',  
  58. 'function' => 'DATE',  
  59. 'name' => 'order_date' 
  60. ),  
  61. '_order_currency' => array( 
  62. 'type' => 'meta',  
  63. 'order_item_type' => '',  
  64. 'function' => '',  
  65. 'name' => '_order_currency' 
  66. ),  
  67. '_eu_vat_data' => array( 
  68. 'type' => 'meta',  
  69. 'order_item_type' => '',  
  70. 'function' => '',  
  71. 'name' => '_eu_vat_data' 
  72. ),  
  73. ),  
  74. 'query_type' => 'get_results',  
  75. 'group_by' => '',  
  76. 'order_types' => array('shop_order'),  
  77. 'order_status' => $this->order_statuses_to_include(),  
  78. )); 
  79.  
  80. $tax_data = array(); 
  81. foreach($dataset as $data) { 
  82. $eu_vat_data = maybe_unserialize($data->_eu_vat_data); 
  83.  
  84. // Skip orders on which no taxes were paid 
  85. if(empty($eu_vat_data['taxes'])) { 
  86. continue; 
  87.  
  88. // Select between the the exchange rate associated with each order, or the 
  89. // ECB rate for the quarter 
  90. if($this->should_use_orders_exchange_rates()) { 
  91. $vat_currency_exchange_rate = $eu_vat_data['vat_currency_exchange_rate']; 
  92. else { 
  93. // Get exchange rates for the last day of the quarter in which the order 
  94. // was placed 
  95. $last_day_of_quarter = $this->get_last_day_of_quarter($data->order_date); 
  96.  
  97. $vat_currency_exchange_rate = $this->get_vat_currency_exchange_rate($data->_order_currency, $last_day_of_quarter); 
  98.  
  99. // If the exchange rate is not available, fall back to the one used with 
  100. // the order 
  101. if(!is_numeric($vat_currency_exchange_rate)) { 
  102. $vat_currency_exchange_rate = $eu_vat_data['vat_currency_exchange_rate']; 
  103.  
  104. $taxes_recorded = get_value('taxes', $eu_vat_data, array()); 
  105. foreach($taxes_recorded as $rate_id => $tax_details) { 
  106. // Tag the tax record to indicate if it's part of MOSS or not 
  107. $tax_details['is_moss'] = $this->is_tax_moss($tax_details['country'],  
  108. $tax_details['tax_rate_class']); 
  109.  
  110. // Skip taxes that should not appear on the report 
  111. if($this->should_skip($tax_details)) { 
  112. continue; 
  113.  
  114. if(!isset($tax_data[$rate_id])) { 
  115. $tax_data[$rate_id] = (object)array( 
  116. 'items_tax_amount' => 0,  
  117. 'shipping_tax_amount' => 0,  
  118. 'refunded_items_tax_amount' => 0,  
  119. 'refunded_shipping_tax_amount' => 0,  
  120. // Sales information 
  121. 'items_total' => 0,  
  122. 'shipping_total' => 0,  
  123. 'refunded_items_total' => 0,  
  124. 'refunded_shipping_total' => 0,  
  125.  
  126. 'tax_rate_data' => (object)array( 
  127. 'label' => $tax_details['label'],  
  128. 'tax_rate' => $tax_details['vat_rate'],  
  129. 'tax_rate_country' => $tax_details['country'],  
  130. 'tax_rate_class' => $tax_details['tax_rate_class'],  
  131. 'is_moss' => $tax_details['is_moss'],  
  132. ),  
  133. ); 
  134.  
  135. $tax_amounts = $tax_details['amounts']; 
  136. $tax_data[$rate_id]->items_tax_amount += wc_round_tax_total($tax_amounts['items_total'] * $vat_currency_exchange_rate); 
  137. $tax_data[$rate_id]->shipping_tax_amount += wc_round_tax_total($tax_amounts['shipping_total'] * $vat_currency_exchange_rate); 
  138.  
  139. $items_tax_refund = get_value('items_refund', $tax_amounts, 0); 
  140. $shipping_tax_refund = get_value('shipping_refund', $tax_amounts, 0); 
  141. $tax_data[$rate_id]->refunded_items_tax_amount += wc_round_tax_total($items_tax_refund * $vat_currency_exchange_rate); 
  142. $tax_data[$rate_id]->refunded_shipping_tax_amount += wc_round_tax_total($shipping_tax_refund * $vat_currency_exchange_rate); 
  143.  
  144. // Sales totals. The sales totals will be rounded once, at the end of 
  145. // the calculations 
  146. $vat_rate = $tax_details['vat_rate'] / 100; 
  147. $tax_data[$rate_id]->items_total += $tax_amounts['items_total'] / $vat_rate * $vat_currency_exchange_rate; 
  148. $tax_data[$rate_id]->shipping_total += $tax_amounts['shipping_total'] / $vat_rate * $vat_currency_exchange_rate; 
  149. $tax_data[$rate_id]->refunded_items_total += $items_tax_refund / $vat_rate* $vat_currency_exchange_rate; 
  150. $tax_data[$rate_id]->refunded_shipping_total += $shipping_tax_refund / $vat_rate * $vat_currency_exchange_rate; 
  151.  
  152. // Round totals, for readability. VAT totals have already been rounded at 
  153. // this stage 
  154. $decimals = wc_get_price_decimals(); 
  155. foreach($tax_data as $rate_id => $tax_details) { 
  156. $tax_data[$rate_id]->items_total = round($tax_data[$rate_id]->items_total, $decimals); 
  157. $tax_data[$rate_id]->shipping_total = round($tax_data[$rate_id]->shipping_total, $decimals); 
  158. $tax_data[$rate_id]->refunded_items_total = round($tax_data[$rate_id]->refunded_items_total, $decimals); 
  159. $tax_data[$rate_id]->refunded_shipping_total = round($tax_data[$rate_id]->refunded_shipping_total, $decimals); 
  160.  
  161. /** NOTES 
  162. * - Tax rate can be calculated by dividing the line_tax by the tax_total 
  163. * - Sale totals must be calculated in a second pass. If we add line_total and tax_total to 
  164. * the query, it will return one line per order product. However, each line will also contain 
  165. * ALL the taxes for the entire order, which would be processed multiple times. 
  166. */ 
  167. return $tax_data; 
  168.  
  169. /** 
  170. * Retrieves the refunds for the specified period and adds them to the tax 
  171. * data. 
  172. * @param array tax_data The tax data to which refund details should be added. 
  173. * @return array The tax data including the refunds applied in the specified 
  174. * period. 
  175. */ 
  176. protected function get_tax_refunds_data($tax_data) { 
  177. // This method must be implemented by descendant classes 
  178. return $tax_data; 
  179.  
  180. /** 
  181. * Reorganises the tax data, grouping and sorting it by country. 
  182. * @param array tax_data The tax data. 
  183. */ 
  184. protected function sort_taxes($tax_data) { 
  185. $result = array( 
  186. // MOSS taxes 
  187. 'moss' => array(),  
  188. // Non-MOSS taxes 
  189. 'non-moss' => array(),  
  190. ); 
  191. foreach($tax_data as $rate_id => $data) { 
  192. $target_group = &$result[$data->tax_rate_data->is_moss ? 'moss' : 'non-moss']; 
  193.  
  194. /** The tax should be paid to the country specified in "tax payable to 
  195. * country" field. If that field is empty, then take the tax country instead 
  196. */ 
  197. $country_code = get_value('tax_payable_to_country', $data->tax_rate_data, $data->tax_rate_data->tax_rate_country); 
  198. if(empty($target_group[$country_code])) { 
  199. $target_group[$country_code] = array(); 
  200. $target_group[$country_code][$rate_id] = $data; 
  201. foreach($result as $moss_group => &$taxes) { 
  202. ksort($taxes); 
  203.  
  204. return $result; 
  205.  
  206. /** 
  207. * Get the data for the report. 
  208. * @return string 
  209. */ 
  210. public function get_main_chart() { 
  211. $tax_data = $this->get_tax_data(); 
  212. // Add the refunds to the tax data 
  213. $tax_data = $this->get_tax_refunds_data($tax_data); 
  214.  
  215.  
  216. // Debug 
  217. //var_dump($tax_data); 
  218.  
  219. // Reorganise VAT information, associating it to each country and sorting it 
  220. // by country code 
  221. $taxes_by_country = $this->sort_taxes($tax_data); 
  222.  
  223. // Store the list of EU countries. It will be used to remove non-EU tax from 
  224. // the report 
  225. $eu_countries = WC_Aelia_EU_VAT_Assistant::instance()->get_eu_vat_countries(); 
  226. // Keep track of the report columns. This information will be used to adjust 
  227. // the "colspan" property 
  228. $report_columns = 13; 
  229. ?> 
  230. <div id="eu_vat_report" class="wc_aelia_eu_vat_assistant report"> 
  231. <table class="widefat"> 
  232. <thead> 
  233. <tr class="report_information"> 
  234. <th colspan="<?php echo $report_columns; ?>"> 
  235. <ul> 
  236. <li> 
  237. <span class="label"><?php 
  238. echo __('Currency for VAT returns:', $this->text_domain); 
  239. ?></span> 
  240. <span><?php echo $this->vat_currency(); ?></span> 
  241. </li> 
  242. <li> 
  243. <span class="label"><?php 
  244. echo __('Exchange rates used:', $this->text_domain); 
  245. ?></span> 
  246. <span><?php 
  247. if($this->should_use_orders_exchange_rates()) { 
  248. echo __('Rates saved with each order', $this->text_domain); 
  249. else { 
  250. echo __('ECB rates for each quarter', $this->text_domain); 
  251. ?></span> 
  252. </li> 
  253. </ul> 
  254. </th> 
  255. </tr> 
  256. <tr> 
  257. <th colspan="3" class="column_group left"></th> 
  258. <th colspan="4" class="column_group header"><?php echo __('Items', $this->text_domain); ?></th> 
  259. <th colspan="4" class="column_group header"><?php echo __('Shipping', $this->text_domain); ?></th> 
  260. <th colspan="2" class="column_group right"> </th> 
  261. </tr> 
  262. <tr class="column_headers"> 
  263. <th class="country_name"><?php echo __('Customer Country', $this->text_domain); ?></th> 
  264. <th class="country_code"><?php echo __('Country Code', $this->text_domain); ?></th> 
  265. <th class="tax_rate"><?php echo __('Tax Rate', $this->text_domain); ?></th> 
  266.  
  267. <!-- Items --> 
  268. <th class="total_row column_group left"><?php echo __('Sales', $this->text_domain); ?></th> 
  269. <th class="total_row column_group "><?php echo __('Refunds', $this->text_domain); ?></th> 
  270. <th class="total_row column_group "><?php echo __('VAT Charged', $this->text_domain); ?></th> 
  271. <th class="total_row column_group right"><?php echo __('VAT Refunded', $this->text_domain); ?></th> 
  272.  
  273. <!-- Shipping --> 
  274. <th class="total_row column_group left"><?php echo __('Shipping charged', $this->text_domain); ?></th> 
  275. <th class="total_row column_group "><?php echo __('Shipping refunded', $this->text_domain); ?></th> 
  276. <th class="total_row column_group "><?php echo __('VAT Charged', $this->text_domain); ?></th> 
  277. <th class="total_row column_group right"><?php echo __('VAT Refunded', $this->text_domain); ?></th> 
  278.  
  279. <!-- Totals --> 
  280. <th class="total_row column_group left"><?php echo __('Total charged', $this->text_domain); ?></th> 
  281. <th class="total_row column_group right"><?php echo __('Final VAT Total', $this->text_domain); ?></th> 
  282. </tr> 
  283. </thead> 
  284. <?php if(empty($taxes_by_country)) : ?> 
  285. <tbody> 
  286. <tr> 
  287. <td colspan="<?php echo $report_columns; ?>"><?php echo __('No VAT has been processed in this period', $this->text_domain); ?></td> 
  288. </tr> 
  289. </tbody> 
  290. <?php else : ?> 
  291. <tbody> 
  292. <?php 
  293. $tax_grand_totals = array( 
  294. 'items_total' => 0,  
  295. 'refunded_items_total' => 0,  
  296. 'shipping_total' => 0,  
  297. 'refunded_shipping_total' => 0,  
  298.  
  299. 'items_tax_amount' => 0,  
  300. 'refunded_items_tax_amount' => 0,  
  301. 'shipping_tax_amount' => 0,  
  302. 'refunded_shipping_tax_amount' => 0,  
  303. ); 
  304.  
  305.  
  306. // First loop - Tax groups (MOSS and non-MOSS) 
  307. foreach($taxes_by_country as $moss_group => $group_taxes) { 
  308. if(empty($group_taxes)) { 
  309. continue; 
  310. // Render a sub-header to make it easier to read the report 
  311. $this->render_group_header($moss_group, $report_columns); 
  312.  
  313. // Second loop - Taxes by country 
  314. foreach($group_taxes as $country_code => $tax_data) { 
  315. // Third loop taxes for a specific country 
  316. foreach($tax_data as $rate_id => $tax_row) { 
  317. $rate = $tax_row->tax_rate_data; 
  318. if(!empty($tax_row->tax_rate_country) && !in_array($rate->tax_rate_country, $eu_countries)) { 
  319. continue; 
  320.  
  321. $tax_payable_to_country = get_value('tax_payable_to_country', $rate, $rate->tax_rate_country) 
  322. ?> 
  323. <tr> 
  324. <th class="country_name" scope="row"><?php echo esc_html(WC()->countries->countries[$rate->tax_rate_country]); ?></th> 
  325. <th class="country_code" scope="row"><?php echo esc_html($rate->tax_rate_country); ?></th> 
  326. <td class="tax_rate"><?php echo number_format(apply_filters('woocommerce_reports_taxes_rate', $rate->tax_rate, $rate_id, $tax_row), 2); ?>%</td> 
  327.  
  328. <!-- Items --> 
  329. <td class="total_row column_group left"><?php echo $this->format_price($tax_row->items_total); ?></td> 
  330. <td class="total_row column_group "><?php echo $this->format_price($tax_row->refunded_items_total); ?></td> 
  331. <td class="total_row column_group "><?php echo $this->format_price($tax_row->items_tax_amount); ?></td> 
  332. <td class="total_row column_group right"><?php echo $this->format_price($tax_row->refunded_items_tax_amount * -1); ?></td> 
  333.  
  334. <!-- Shipping --> 
  335. <td class="total_row column_group left"><?php echo $this->format_price($tax_row->shipping_total); ?></td> 
  336. <td class="total_row column_group "><?php echo $this->format_price($tax_row->refunded_shipping_total); ?></td> 
  337. <td class="total_row column_group "><?php echo $this->format_price($tax_row->shipping_tax_amount); ?></td> 
  338. <td class="total_row column_group right"><?php echo $this->format_price($tax_row->refunded_shipping_tax_amount * -1); ?></td> 
  339.  
  340. <!-- Total --> 
  341. <td class="total_row column_group left"><?php 
  342. echo $this->format_price($tax_row->items_total 
  343. + $tax_row->shipping_total 
  344. + $tax_row->refunded_items_total 
  345. + $tax_row->refunded_shipping_total); 
  346. ?></td> 
  347. <td class="total_row column_group right"><?php 
  348. echo $this->format_price($tax_row->items_tax_amount 
  349. + $tax_row->shipping_tax_amount 
  350. + $tax_row->refunded_items_tax_amount 
  351. + $tax_row->refunded_shipping_tax_amount); 
  352. ?></td> 
  353. </tr> 
  354. <?php 
  355.  
  356. // Calculate grand totals 
  357. $tax_grand_totals['items_tax_amount'] += $tax_row->items_tax_amount; 
  358. $tax_grand_totals['refunded_items_tax_amount'] += $tax_row->refunded_items_tax_amount; 
  359. $tax_grand_totals['shipping_tax_amount'] += $tax_row->shipping_tax_amount; 
  360. $tax_grand_totals['refunded_shipping_tax_amount'] += $tax_row->refunded_shipping_tax_amount; 
  361.  
  362. // Sales data 
  363. $tax_grand_totals['items_total'] += $tax_row->items_total; 
  364. $tax_grand_totals['refunded_items_total'] += $tax_row->refunded_items_total; 
  365. $tax_grand_totals['shipping_total'] += $tax_row->shipping_total; 
  366. $tax_grand_totals['refunded_shipping_total'] += $tax_row->refunded_shipping_total; 
  367. } // Third loop - END 
  368. } // Second loop - END 
  369. } // First loop - END 
  370. ?> 
  371. </tbody> 
  372. <!--- VAT Totals ---> 
  373. <tfoot id="vat-grand-totals"> 
  374. <tr> 
  375. <th class="label" colspan="3"><?php echo __('Totals', $this->text_domain); ?></th> 
  376. <!-- Items --> 
  377. <td class="total_row column_group left"><?php echo $this->format_price($tax_grand_totals['items_total']); ?></td> 
  378. <td class="total_row column_group "><?php echo $this->format_price($tax_grand_totals['refunded_items_total']); ?></td> 
  379. <td class="total_row column_group "><?php echo $this->format_price($tax_grand_totals['items_tax_amount']); ?></td> 
  380. <td class="total_row column_group right"><?php echo $this->format_price($tax_grand_totals['refunded_items_tax_amount'] * -1); ?></td> 
  381.  
  382. <!-- Shipping --> 
  383. <td class="total_row column_group left"><?php echo $this->format_price($tax_grand_totals['shipping_total']); ?></td> 
  384. <td class="total_row column_group "><?php echo $this->format_price($tax_grand_totals['refunded_shipping_total']); ?></td> 
  385. <td class="total_row column_group "><?php echo $this->format_price($tax_grand_totals['shipping_tax_amount']); ?></td> 
  386. <td class="total_row column_group right"><?php echo $this->format_price($tax_grand_totals['refunded_shipping_tax_amount'] * -1); ?></td> 
  387.  
  388. <!-- Totals --> 
  389. <td class="total_row column_group left"><?php 
  390. echo $this->format_price($tax_grand_totals['items_total'] 
  391. + $tax_grand_totals['shipping_total'] 
  392. + $tax_grand_totals['refunded_items_total'] 
  393. + $tax_grand_totals['refunded_shipping_total']); 
  394. ?></td> 
  395. <td class="total_row column_group right"><?php 
  396. echo $this->format_price($tax_grand_totals['items_tax_amount'] 
  397. + $tax_grand_totals['shipping_tax_amount'] 
  398. + $tax_grand_totals['refunded_items_tax_amount'] 
  399. + $tax_grand_totals['refunded_shipping_tax_amount']); 
  400. ?></td> 
  401. </tr> 
  402. </tfoot> 
  403. <?php endif; ?> 
  404. </table> 
  405. </div> 
  406. <?php 
  407.  
  408. protected function render_group_header($moss_group, $report_columns) { 
  409. $group_header_content = array( 
  410. 'moss' => array( 
  411. 'title' => __('MOSS VAT Details', $this->text_domain),  
  412. 'description' => __('This section shows the data to be used to file the VAT MOSS return.', $this->text_domain),  
  413. ),  
  414. 'non-moss' => array( 
  415. 'title' => __('Domestic/non-MOSS VAT Details', $this->text_domain),  
  416. 'description' => __('This section shows the data for the domestic VAT return.', $this->text_domain),  
  417. ),  
  418. ); 
  419.  
  420. $content = get_value($moss_group, $group_header_content); 
  421. if(empty($content)) { 
  422. return; 
  423. ?> 
  424. <tr class="group_header"> 
  425. <th class="" colspan="<?php echo $report_columns; ?>"> 
  426. <div class="title"><?php 
  427. echo $content['title']; 
  428. ?></div> 
  429. <div class="description"><?php 
  430. echo $content['description']; 
  431. ?></div> 
  432. </th> 
  433. </tr> 
  434. <?php 
  435.  
  436. /** 
  437. * Renders a header on top of the standard reporting UI. 
  438. */ 
  439. protected function render_ui_header() { 
  440. include(WC_Aelia_EU_VAT_Assistant::instance()->path('views') . '/admin/reports/eu-vat-report-header.php');