PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
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/Repositories/QuoteRepository.php +75 -4 2.1.12.4.0 View file →
@@ -177,8 +177,18 @@
177 177 return apply_filters('easy_invoice_quotes_by_client', $filtered_quotes, $client_id);
178 178 }
179 179
180 180 /**
181 + * Quotes for a client — the name QuoteService calls; it did not exist.
182 + *
183 + * @param int $customer_id Client id.
184 + * @return array
185 + */
186 + public function findByCustomer($customer_id): array {
187 + return $this->findByClient((int) $customer_id);
188 + }
189 +
190 + /**
181 191 * Create new quote
182 192 *
183 193 * @since 1.0.0
184 194 * @param array $data Quote data
@@ -188,9 +198,26 @@
188 198 // Allow plugins to modify the data before creation
189 199 $data = apply_filters('easy_invoice_quote_create_data', $data);
190 200
191 201 $quote = new Quote();
202 + // Same default as invoices: discount before tax unless chosen otherwise.
203 + if (empty($data['discount_calculation_method'])) {
204 + $data['discount_calculation_method'] = 'before_tax';
205 + }
206 + // Reserve a number under the number lock. Left to the form default,
207 + // the model peeked at the next number without taking it, so quotes
208 + // created at the same moment (or by two people with the builder
209 + // open) shared one number.
210 + $requested = (string) ($data['number'] ?? $data['quote_number'] ?? '');
211 + $data['number'] = (new \EasyInvoice\Services\QuoteNumberService())->claimOrGenerate($requested);
212 + unset($data['quote_number']);
192 213 $this->setQuoteData($quote, $data);
214 +
215 + // Same as invoices: a client id without customer details is filled in
216 + // from the client record before the first save.
217 + if (($data['client_id'] ?? 0) > 0 && (empty($data['customer_email']) || empty($data['customer_name'])) && method_exists($quote, 'populateClientInfo')) {
218 + $quote->populateClientInfo();
219 + }
193 220
194 221 // Allow plugins to modify the quote before saving
195 222 do_action('easy_invoice_quote_before_create', $quote, $data);
196 223
@@ -263,8 +290,42 @@
263 290 }
264 291
265 292 return $result !== false;
266 293 }
294 +
295 + /**
296 + * Find a quote by its number (stored in post meta)
297 + *
298 + * @param string $number Quote number
299 + * @return Quote|null
300 + */
301 + public function findByNumber(string $number): ?Quote {
302 + $args = [
303 + 'post_type' => PostTypes::EASY_INVOICE_QUOTE_POST_TYPE,
304 + 'post_status' => ['publish', 'draft', 'private', 'pending'],
305 + 'posts_per_page' => 1,
306 + 'meta_query' => [
307 + [
308 + 'key' => '_easy_invoice_quote_number',
309 + 'value' => $number,
310 + 'compare' => '=',
311 + ],
312 + ],
313 + 'orderby' => 'date',
314 + 'order' => 'DESC',
315 + ];
316 +
317 + $posts = get_posts($args);
318 + if (!empty($posts)) {
319 + $post = $posts[0];
320 + if ($post && $post->post_type === PostTypes::EASY_INVOICE_QUOTE_POST_TYPE) {
321 + $quote = new Quote($post);
322 + $quote->ensureProperSlug();
323 + return apply_filters('easy_invoice_quote_found_by_number', $quote, $number);
324 + }
325 + }
326 + return null;
327 + }
267 328
268 329 /**
269 330 * Force publish quote (for URL fixes)
270 331 *
@@ -459,10 +520,11 @@
459 520 if (isset($data['notes'])) {
460 521 $quote->setNotes($data['notes']);
461 522 }
462 523
463 - if (isset($data['description'])) {
464 - $quote->setDescription($data['description']);
524 + // Always update description if it exists in data (even if empty, to allow clearing)
525 + if (array_key_exists('description', $data)) {
526 + $quote->setDescription($data['description'] ?? '');
465 527 }
466 528
467 529 if (isset($data['terms'])) {
468 530 $quote->setTerms($data['terms']);
@@ -501,8 +563,17 @@
501 563 if (isset($data['discount_value'])) {
502 564 $quote->setDiscountValue($data['discount_value']);
503 565 }
504 566
567 + // Per-quote tax switch. The invoice repository picks this up through the
568 + // form processor's pass-through; quotes set every field by hand, and this
569 + // one was missing, so a quote built with tax on lost it (and passed a
570 + // tax-free invoice on conversion) whenever the site's global tax was off.
571 + if (array_key_exists('tax_enabled', $data)) {
572 + $enabled = $data['tax_enabled'];
573 + $quote->setTaxEnabled(($enabled === true || $enabled === 1 || in_array(strtolower((string) $enabled), ['1', 'yes', 'true', 'on'], true)) ? 'yes' : 'no');
574 + }
575 +
505 576 if (isset($data['tax_rate'])) {
506 577 $quote->setTaxRate($data['tax_rate']);
507 578 }
508 579
@@ -555,10 +626,10 @@
555 626 $quote->setCustomFields($data['custom_fields']);
556 627 }
557 628
558 629 // Populate client information if we have a client_id
559 - if ($quote->getClientId() > 0) {
560 - $this->populateCustomerFromClient($quote, $quote->getClientId());
630 + if (is_numeric($quote->getClientId()) && (int) $quote->getClientId() > 0) {
631 + $this->populateCustomerFromClient($quote, (int) $quote->getClientId());
561 632 }
562 633
563 634 // Allow plugins to modify the data setting process
564 635 do_action('easy_invoice_quote_set_data_after', $quote, $data);