repository = $repository; } /** * Create a new invoice * * @since 1.0.0 * @param array $data The invoice data * @return Invoice|false The created invoice or false on failure */ public function createInvoice(array $data) { // Allow plugins to modify data before creation $data = apply_filters('easy_invoice_service_invoice_create_data', $data); // Validate required fields $required_fields = ['title']; $errors = $this->validateRequiredFields($data, $required_fields); if (!empty($errors)) { $this->log('Invoice creation failed: ' . implode(', ', $errors), 'error'); return false; } // Sanitize data $sanitized_data = $this->sanitizeInvoiceData($data); // Allow plugins to perform actions before creation do_action('easy_invoice_service_invoice_before_create', $sanitized_data); // Create the invoice $invoice = $this->repository->create($sanitized_data); if ($invoice) { $this->log('Invoice created successfully: ' . $invoice->getId()); // Allow plugins to perform actions after creation do_action('easy_invoice_service_invoice_created', $invoice, $sanitized_data); return $invoice; } $this->log('Invoice creation failed', 'error'); return false; } /** * Update an existing invoice * * @since 1.0.0 * @param int $id The invoice ID * @param array $data The invoice data * @return Invoice|null The updated invoice or null on failure */ public function updateInvoice(int $id, array $data) { // Allow plugins to modify data before update $data = apply_filters('easy_invoice_service_invoice_update_data', $data, $id); // Get the existing invoice $invoice = $this->repository->find($id); if (!$invoice) { $this->log('Invoice not found for update: ' . $id, 'error'); return null; } // Sanitize data $sanitized_data = $this->sanitizeInvoiceData($data); // Allow plugins to perform actions before update do_action('easy_invoice_service_invoice_before_update', $invoice, $sanitized_data); // Update the invoice $updated_invoice = $this->repository->update($id, $sanitized_data); if ($updated_invoice) { $this->log('Invoice updated successfully: ' . $id); // Allow plugins to perform actions after update do_action('easy_invoice_service_invoice_updated', $updated_invoice, $sanitized_data); return $updated_invoice; } $this->log('Invoice update failed: ' . $id, 'error'); return null; } /** * Delete an invoice * * @since 1.0.0 * @param int $id The invoice ID * @return bool True if deleted successfully */ public function deleteInvoice(int $id): bool { // Get the invoice before deletion $invoice = $this->repository->find($id); if (!$invoice) { $this->log('Invoice not found for deletion: ' . $id, 'error'); return false; } // Allow plugins to perform actions before deletion do_action('easy_invoice_service_invoice_before_delete', $invoice); // Delete the invoice $deleted = $this->repository->delete($id); if ($deleted) { $this->log('Invoice deleted successfully: ' . $id); // Allow plugins to perform actions after deletion do_action('easy_invoice_service_invoice_deleted', $id); return true; } $this->log('Invoice deletion failed: ' . $id, 'error'); return false; } /** * Get invoice by ID * * @since 1.0.0 * @param int $id The invoice ID * @return Invoice|null The invoice or null if not found */ public function getInvoice(int $id) { $invoice = $this->repository->find($id); // Allow plugins to modify the found invoice return apply_filters('easy_invoice_service_invoice_found', $invoice, $id); } /** * Get all invoices * * @since 1.0.0 * @param array $args Optional arguments to filter the results * @return array Array of Invoice models */ public function getAllInvoices(array $args = []): array { $invoices = $this->repository->all($args); // Allow plugins to modify the invoices list return apply_filters('easy_invoice_service_invoices_found', $invoices, $args); } /** * Get invoices by customer * * @since 1.0.0 * @param int $customer_id The customer ID * @return array Array of Invoice models */ public function getInvoicesByCustomer(int $customer_id): array { $invoices = $this->repository->findByCustomer($customer_id); // Allow plugins to modify the filtered results return apply_filters('easy_invoice_service_invoices_by_customer', $invoices, $customer_id); } /** * Get invoices by status * * @since 1.0.0 * @param string $status The invoice status * @return array Array of Invoice models */ public function getInvoicesByStatus(string $status): array { $invoices = $this->repository->findByStatus($status); // Allow plugins to modify the filtered results return apply_filters('easy_invoice_service_invoices_by_status', $invoices, $status); } /** * Get invoices due within a date range * * @since 1.0.0 * @param string $start_date The start date in 'Y-m-d' format * @param string $end_date The end date in 'Y-m-d' format * @return array Array of Invoice models */ public function getInvoicesByDueDate(string $start_date, ?string $end_date = null): array { $invoices = $this->repository->findByDueDate($start_date, $end_date); // Allow plugins to modify the filtered results return apply_filters('easy_invoice_service_invoices_by_due_date', $invoices, $start_date, $end_date); } /** * Count invoices * * @since 1.0.0 * @param array $args Optional arguments to filter the results * @return int Number of invoices */ public function countInvoices(array $args = []): int { $count = $this->repository->count($args); // Allow plugins to modify the count return apply_filters('easy_invoice_service_invoice_count', $count, $args); } /** * Calculate invoice total * * @since 1.0.0 * @param Invoice $invoice The invoice * @return float The calculated total */ public function calculateInvoiceTotal(Invoice $invoice): float { $items = $invoice->getItems(); $subtotal = 0; // Calculate subtotal from items foreach ($items as $item) { $quantity = floatval($item['quantity'] ?? 0); $price = floatval($item['price'] ?? 0); $subtotal += $quantity * $price; } // Apply discount $discount_type = $invoice->getDiscountType(); $discount_value = floatval($invoice->getDiscountValue() ?? 0); if ($discount_type === 'percentage' && $discount_value > 0) { $discount_amount = $subtotal * ($discount_value / 100); $subtotal -= $discount_amount; } elseif ($discount_type === 'fixed' && $discount_value > 0) { $subtotal -= $discount_value; } // Apply tax $tax_rate = floatval($invoice->getTaxRate() ?? 0); $prices_include_tax = $invoice->getPricesIncludeTax(); if ($tax_rate > 0) { if ($prices_include_tax) { // Tax is already included in prices $total = $subtotal; } else { // Add tax to subtotal $tax_amount = $subtotal * ($tax_rate / 100); $total = $subtotal + $tax_amount; } } else { $total = $subtotal; } // Allow plugins to modify the calculated total return apply_filters('easy_invoice_service_invoice_total', $total, $invoice, $subtotal); } /** * Send invoice to customer * * @since 1.0.0 * @param Invoice $invoice The invoice * @param string $email The customer email * @return bool True if email was sent successfully */ public function sendInvoiceToCustomer(Invoice $invoice, string $email): bool { // Allow plugins to modify email data $email_data = apply_filters('easy_invoice_service_invoice_email_data', [ 'to' => $email, 'subject' => sprintf(__('Invoice #%s from %s', 'easy-invoice'), $invoice->getNumber(), get_bloginfo('name')), 'message' => $this->generateInvoiceEmailMessage($invoice), 'headers' => ['Content-Type: text/html; charset=UTF-8'] ], $invoice); // Allow plugins to handle email sending $sent = apply_filters('easy_invoice_service_invoice_email_send', null, $email_data, $invoice); if ($sent === null) { $sent = $this->sendEmail( $email_data['to'], $email_data['subject'], $email_data['message'], $email_data['headers'] ); } if ($sent) { // Allow plugins to perform actions after email sent do_action('easy_invoice_service_invoice_email_sent', $invoice, $email, $sent); } return $sent; } /** * Generate invoice email message * * @since 1.0.0 * @param Invoice $invoice The invoice * @return string The email message */ protected function generateInvoiceEmailMessage(Invoice $invoice): string { $message = sprintf( '

%s

', __('Please find attached your invoice.', 'easy-invoice') ); $message .= sprintf( '

%s: %s

', __('Invoice Number', 'easy-invoice'), $invoice->getNumber() ); $message .= sprintf( '

%s: %s

', __('Amount', 'easy-invoice'), $this->formatCurrency($this->calculateInvoiceTotal($invoice), $invoice->getCurrencyCode(), $invoice->getCurrencyPosition()) ); $message .= sprintf( '

%s: %s

', __('Due Date', 'easy-invoice'), $invoice->getDueDate() ); // Allow plugins to modify the email message return apply_filters('easy_invoice_service_invoice_email_message', $message, $invoice); } /** * Sanitize invoice data * * @since 1.0.0 * @param array $data The invoice data * @return array The sanitized data */ protected function sanitizeInvoiceData(array $data): array { $sanitization_rules = [ 'title' => 'text_field', 'description' => 'textarea', 'number' => 'text_field', 'issue_date' => 'text_field', 'due_date' => 'text_field', 'status' => 'text_field', 'notes' => 'textarea', 'terms' => 'html', 'internal_notes' => 'textarea', 'payment_instructions' => 'textarea', 'payment_gateways' => 'array', 'invoice_template' => 'text_field', 'customer_name' => 'text_field', 'customer_address' => 'textarea', 'customer_email' => 'email', 'shipping_name' => 'text_field', 'shipping_address' => 'textarea', 'discount_type' => 'text_field', 'discount_value' => 'float', 'tax_rate' => 'float', 'calculation_method' => 'text_field', 'prices_include_tax' => 'int', 'currency_code' => 'text_field', 'currency_position' => 'text_field', 'footer_text' => 'textarea', 'items' => 'array', 'custom_fields' => 'array' ]; return $this->sanitizeData($data, $sanitization_rules); } }