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
← All changes | includes/Forms/Invoice/InvoiceFieldRegistration.php +109 -6 2.2.02.4.1 View file →
@@ -90,12 +90,27 @@
90 90 */
91 91 public function registerDefaultFields(): void {
92 92 // Define all fields in a single array for easy modification by pro plugins
93 93 $all_fields = $this->getDefaultFieldsArray();
94 -
94 +
95 + // Gate the basic tax fields on the global Settings → Tax →
96 + // Enable Tax setting. When tax is globally off, the entire
97 + // tax block (override checkbox, tax rate, prices include tax)
98 + // is hidden from the invoice builder — the merchant has to
99 + // turn tax on globally before any tax controls surface here.
100 + // discount_calculation_method stays (it lives in the discount
101 + // section, controls discount timing, not tax).
102 + if (get_option('easy_invoice_tax_enabled', 'no') !== 'yes') {
103 + $tax_field_names = ['tax_enabled', 'tax_rate', 'prices_include_tax'];
104 + $all_fields = array_values(array_filter(
105 + $all_fields,
106 + static fn($f) => !in_array($f['name'] ?? '', $tax_field_names, true)
107 + ));
108 + }
109 +
95 110 // Apply filter to allow pro plugins to modify fields
96 111 $all_fields = apply_filters('easy_invoice_invoice_fields', $all_fields);
97 -
112 +
98 113 // Register all fields from the array
99 114 foreach ($all_fields as $field_config) {
100 115 $this->registerField($field_config['tab_id'], $field_config);
101 116 }
@@ -183,8 +198,18 @@
183 198 'label' => __('Issue Date', 'easy-invoice'),
184 199 'required' => true,
185 200 'grid_cols' => 'sm:col-span-3',
186 201 'order' => 4,
202 + // Both date fields are `required` but shipped with no default, so a
203 + // brand-new invoice always failed its first save with "Issue Date is
204 + // required / Due Date is required" — while the live preview beside the
205 + // form was already showing dates, which made the error look wrong.
206 + // The quote form has always defaulted its date this way.
207 + //
208 + // current_time() rather than gmdate(): gmdate() uses the server timezone,
209 + // so a site ahead of or behind UTC could get yesterday's or tomorrow's
210 + // date on a new invoice.
211 + 'default_value' => current_time('Y-m-d'),
187 212 'sanitize_callback' => 'sanitize_text_field',
188 213 'validate_callback' => function($value) {
189 214 return !empty($value) && strtotime($value) ? true : __('Please enter a valid issue date.', 'easy-invoice');
190 215 }
@@ -196,8 +221,16 @@
196 221 'label' => __('Due Date', 'easy-invoice'),
197 222 'required' => true,
198 223 'grid_cols' => 'sm:col-span-3',
199 224 'order' => 5,
225 + // Defaults to issue date + the configured payment term
226 + // (Settings → Invoice → "Due days", default 30) rather than a
227 + // hardcoded value, so the prefilled date matches the terms the site
228 + // actually bills on. See the note on issue_date above.
229 + 'default_value' => gmdate('Y-m-d', strtotime(
230 + '+' . max(0, (int) get_option('easy_invoice_invoice_due_days', 30)) . ' days',
231 + strtotime(current_time('Y-m-d'))
232 + )),
200 233 'sanitize_callback' => 'sanitize_text_field',
201 234 'validate_callback' => function($value) {
202 235 return !empty($value) && strtotime($value) ? true : __('Please enter a valid due date.', 'easy-invoice');
203 236 }
@@ -218,10 +251,36 @@
218 251 'cancelled' => __('Cancelled', 'easy-invoice')
219 252 ],
220 253 'order' => 6
221 254 ],
255 + // Customer tax identity. FormProcessor stores a field as
256 + // '_easy_invoice_' . $name, so these land on the meta keys
257 + // Services\TaxTreatment reads when deciding whether an invoice is a
258 + // cross-border reverse charge or an export.
222 259 [
223 260 'tab_id' => 'invoice-tab',
261 + 'name' => 'customer_vat_number',
262 + 'type' => 'text',
263 + 'label' => __('Customer VAT number', 'easy-invoice'),
264 + 'placeholder' => __('e.g. FR12345678901', 'easy-invoice'),
265 + 'grid_cols' => 'sm:col-span-3',
266 + 'section' => 'notes',
267 + 'order' => 4,
268 + 'description' => __('Needed to invoice a VAT-registered business in another country without charging VAT.', 'easy-invoice'),
269 + ],
270 + [
271 + 'tab_id' => 'invoice-tab',
272 + 'name' => 'customer_country',
273 + 'type' => 'text',
274 + 'label' => __('Customer country', 'easy-invoice'),
275 + 'placeholder' => __('e.g. FR', 'easy-invoice'),
276 + 'grid_cols' => 'sm:col-span-3',
277 + 'section' => 'notes',
278 + 'order' => 5,
279 + 'description' => __('Two-letter country code.', 'easy-invoice'),
280 + ],
281 + [
282 + 'tab_id' => 'invoice-tab',
224 283 'name' => 'notes',
225 284 'type' => 'textarea',
226 285 'label' => __('Notes', 'easy-invoice'),
227 286 'placeholder' => __('Additional notes for the client', 'easy-invoice'),
@@ -306,14 +365,54 @@
306 365 },
307 366 'description' => __('Enter the discount amount or percentage for this invoice.', 'easy-invoice'),
308 367 ],
309 368 [
369 + // Per-invoice "Enable Tax" override.
370 + //
371 + // Defaults to the global Settings → Tax → Enable Tax
372 + // checkbox value. The user can flip it for a specific
373 + // invoice — handy for tax-exempt customers (override
374 + // "on" globally → "off" here) or one-off taxable
375 + // invoices when tax is globally off.
376 + //
377 + // calculateTotals() in the Invoice model treats the
378 + // tax_rate as 0 when this is unchecked, regardless of
379 + // what value sits in the tax_rate field — so the rest
380 + // of this section can stay visible without applying
381 + // tax until the user opts in.
310 382 'tab_id' => 'discounts-taxes-tab',
383 + 'name' => 'tax_enabled',
384 + 'type' => 'checkbox',
385 + 'label' => __('Apply Tax to This Invoice', 'easy-invoice'),
386 + 'grid_cols' => 'sm:col-span-6',
387 + 'section' => 'tax',
388 + 'order' => 2.5,
389 + 'default_value' => function() {
390 + // Default to global setting. Saved values override.
391 + return get_option('easy_invoice_tax_enabled', 'no') === 'yes' ? 'yes' : 'no';
392 + },
393 + 'sanitize_callback' => function($value) {
394 + // Coerce any truthy input ('yes', '1', 1, true, 'on')
395 + // to 'yes'; anything else to 'no'. Stored as a
396 + // string so the field-save loop persists '' as 'no'
397 + // (PHP checkbox unchecked = field absent from POST).
398 + if ($value === 'yes' || $value === '1' || $value === 1 || $value === true || $value === 'on') return 'yes';
399 + return 'no';
400 + },
401 + 'description' => __('Override the global tax setting for this invoice. Defaults to whatever is configured in Settings → Tax.', 'easy-invoice'),
402 + ],
403 + // Discount calculation timing — belongs in the discount
404 + // section (it controls when the discount applies relative
405 + // to tax, not how tax is calculated). Pinned next to the
406 + // Discount Type / Value fields above so the grouping is
407 + // intuitive.
408 + [
409 + 'tab_id' => 'discounts-taxes-tab',
311 410 'name' => 'discount_calculation_method',
312 411 'type' => 'select',
313 - 'label' => __('Calculation Method', 'easy-invoice'),
412 + 'label' => __('Discount Timing', 'easy-invoice'),
314 413 'grid_cols' => 'sm:col-span-2',
315 - 'section' => 'tax',
414 + 'section' => 'discount',
316 415 'options' => [
317 416 'before_tax' => __('Before Tax', 'easy-invoice'),
318 417 'after_tax' => __('After Tax', 'easy-invoice')
319 418 ],
@@ -319,8 +418,12 @@
319 418 ],
320 419 'order' => 3,
321 420 'description' => __('Apply discount before tax (reduces taxable amount) or after tax (tax on full amount)', 'easy-invoice'),
322 421 ],
422 +
423 + // Tax sub-fields below sit on one row (2 + 2 + 2 = 6 cols)
424 + // so the user sees Tax Rate and Price Includes Tax
425 + // side-by-side under the Apply Tax checkbox.
323 426 [
324 427 'tab_id' => 'discounts-taxes-tab',
325 428 'name' => 'tax_rate',
326 429 'type' => 'number',
@@ -340,9 +443,9 @@
340 443 return is_numeric($value) && $value >= 0 && $value <= 100 ? true : __('Tax rate must be between 0 and 100.', 'easy-invoice');
341 444 },
342 445 'description' => __('The tax rate that will be applied to taxable items in this invoice.', 'easy-invoice'),
343 446 ],
344 -
447 +
345 448 [
346 449 'tab_id' => 'discounts-taxes-tab',
347 450 'name' => 'prices_include_tax',
348 451 'type' => 'select',
@@ -352,9 +455,9 @@
352 455 'options' => [
353 456 'no' => __('No', 'easy-invoice'),
354 457 'yes' => __('Yes', 'easy-invoice')
355 458 ],
356 - 'order' => 4,
459 + 'order' => 5,
357 460 'description' => __('Choose whether the prices in this invoice include tax or not.', 'easy-invoice'),
358 461 ],
359 462
360 463 // Payments Tab Fields