PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.1
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.1
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Helpers / TaxCategory.php

TaxCategory.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.1, at includes/Helpers/TaxCategory.php

165 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * EN 16931 tax category vocabulary.
4 *
5 * @package Easy_Invoice
6 * @subpackage Helpers
7 */
8
9 namespace EasyInvoice\Helpers;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * The tax categories a compliant invoice can carry.
17 *
18 * Why this exists
19 * ---------------
20 * The plugin's tax model has been a single rate plus a per-line "taxable"
21 * boolean. That is enough to print a number on a page and nothing more. Every
22 * structured e-invoicing standard in the markets this product sells into —
23 * Factur-X and UBL in France, XRechnung in Germany, Peppol BIS in Belgium —
24 * derives from EN 16931, which requires each line to carry a *category code*
25 * from UNCL5305, not a yes/no flag. "Zero-rated", "exempt" and "reverse charge"
26 * are three different things that all produce 0.00 in the tax column and mean
27 * entirely different things to a tax authority.
28 *
29 * Codes are deliberately the standard's own, so they can be written straight
30 * into an XML document later without a translation table.
31 */
32 class TaxCategory {
33
34 /** Standard rate. */
35 const STANDARD = 'S';
36
37 /** Zero rated goods. */
38 const ZERO = 'Z';
39
40 /** Exempt from tax. */
41 const EXEMPT = 'E';
42
43 /** VAT reverse charge — the customer accounts for the tax. */
44 const REVERSE_CHARGE = 'AE';
45
46 /** Intra-community supply within the EEA. */
47 const INTRA_COMMUNITY = 'K';
48
49 /** Free export item, tax not charged. */
50 const EXPORT = 'G';
51
52 /** Services outside the scope of tax. */
53 const OUT_OF_SCOPE = 'O';
54
55 /**
56 * Every supported code with a human label.
57 *
58 * @return array<string,string>
59 */
60 public static function all(): array {
61 return [
62 self::STANDARD => __( 'Standard rate', 'easy-invoice' ),
63 self::ZERO => __( 'Zero rated', 'easy-invoice' ),
64 self::EXEMPT => __( 'Exempt from tax', 'easy-invoice' ),
65 self::REVERSE_CHARGE => __( 'Reverse charge', 'easy-invoice' ),
66 self::INTRA_COMMUNITY => __( 'Intra-community supply', 'easy-invoice' ),
67 self::EXPORT => __( 'Export, tax not charged', 'easy-invoice' ),
68 self::OUT_OF_SCOPE => __( 'Outside the scope of tax', 'easy-invoice' ),
69 ];
70 }
71
72 /**
73 * Is this a code we recognise?
74 *
75 * @param string $code Category code.
76 * @return bool
77 */
78 public static function isValid( string $code ): bool {
79 return array_key_exists( $code, self::all() );
80 }
81
82 /**
83 * Human label for a code, falling back to the code itself.
84 *
85 * @param string $code Category code.
86 * @return string
87 */
88 public static function label( string $code ): string {
89 $all = self::all();
90 return $all[ $code ] ?? $code;
91 }
92
93 /**
94 * Does this category charge tax at all?
95 *
96 * Everything except the standard rate resolves to a zero tax amount, but for
97 * different reasons — which is precisely why the reason has to be recorded
98 * separately from the number.
99 *
100 * @param string $code Category code.
101 * @return bool
102 */
103 public static function isTaxable( string $code ): bool {
104 return self::STANDARD === $code;
105 }
106
107 /**
108 * Does the standard require a stated reason alongside this category?
109 *
110 * EN 16931 rule BR-E-10 and its siblings: an exempt, reverse-charge,
111 * intra-community, export or out-of-scope line must carry an exemption
112 * reason. An invoice that zero-rates tax without saying why is rejected.
113 *
114 * @param string $code Category code.
115 * @return bool
116 */
117 public static function requiresReason( string $code ): bool {
118 return in_array(
119 $code,
120 [ self::EXEMPT, self::REVERSE_CHARGE, self::INTRA_COMMUNITY, self::EXPORT, self::OUT_OF_SCOPE ],
121 true
122 );
123 }
124
125 /**
126 * The wording that has to appear on the document for a given category.
127 *
128 * These are the customary legal phrases. A merchant can override any of them
129 * through `easy_invoice_tax_exemption_reason`, because the exact wording is
130 * sometimes prescribed locally.
131 *
132 * @param string $code Category code.
133 * @return string Empty when the category needs no statement.
134 */
135 public static function defaultReason( string $code ): string {
136 switch ( $code ) {
137 case self::REVERSE_CHARGE:
138 $reason = __( 'Reverse charge — VAT to be accounted for by the recipient.', 'easy-invoice' );
139 break;
140 case self::INTRA_COMMUNITY:
141 $reason = __( 'Intra-community supply — exempt under Article 138 of Directive 2006/112/EC.', 'easy-invoice' );
142 break;
143 case self::EXPORT:
144 $reason = __( 'Export outside the EU — zero rated.', 'easy-invoice' );
145 break;
146 case self::EXEMPT:
147 $reason = __( 'Exempt from VAT.', 'easy-invoice' );
148 break;
149 case self::OUT_OF_SCOPE:
150 $reason = __( 'Outside the scope of VAT.', 'easy-invoice' );
151 break;
152 default:
153 $reason = '';
154 }
155
156 /**
157 * Filter the statement printed for a tax category.
158 *
159 * @param string $reason Default wording.
160 * @param string $code Category code.
161 */
162 return (string) apply_filters( 'easy_invoice_tax_exemption_reason', $reason, $code );
163 }
164 }
165