[Back] <?php
/*
* Copyright (c) 2024 LatePoint LLC. All rights reserved.
*/
class OsInvoicesHelper {
public static function get_invoices_for_select() : array {
$invoices = new OsInvoiceModel();
$invoices = $invoices->order_by( 'id desc' )->set_limit( 100 )->get_results_as_models();
$invoice_options = [];
foreach ( $invoices as $invoice ) {
$name = sprintf(esc_html__('%s To %s', 'latepoint'), OsMoneyHelper::format_price($invoice->charge_amount, true, false), $invoice->get_customer()->full_name) . ' [#' . $invoice->get_invoice_number() . ' : ID: ' . $invoice->id . ']';
$invoice_options[] = [ 'value' => $invoice->id, 'label' => esc_html( $name ) ];
}
return $invoice_options;
}
public static function replace_invoice_vars_in_template(string $text, array $vars, string $original_text) : string{
if ( isset( $vars['invoice'] ) ) {
$invoice = $vars['invoice'];
$needles = [
'{{invoice_status}}',
'{{invoice_due_date}}',
'{{invoice_amount}}',
'{{invoice_number}}',
'{{invoice_access_url}}',
'{{invoice_pay_url}}',
'{{invoice_receipt_url}}',
];
$replacements = [
OsInvoicesHelper::readable_status($invoice->status),
$invoice->get_readable_due_at(),
OsMoneyHelper::format_price($invoice->charge_amount, true, false),
$invoice->get_invoice_number(),
$invoice->get_access_url(),
$invoice->get_pay_url(),
$invoice->get_receipt_url()
];
$text = str_replace( $needles, $replacements, $text );
}
return $text;
}
public static function handle_invoice_created(OsInvoiceModel $invoice){
$objects = [];
$objects[] = ['model' => 'invoice', 'id' => $invoice->id, 'model_ready' => $invoice];
OsProcessJobsHelper::create_jobs_for_event('invoice_created', $objects);
}
public static function handle_invoice_updated(OsInvoiceModel $new_invoice, OsInvoiceModel $old_invoice){
// remove previously scheduled jobs for this invoice because it's changed and might not need them anymore
// remove only those that are in "scheduled" status, those that were already sent or errored should stay
$jobs = new OsProcessJobModel();
$jobs->delete_where(['status' => LATEPOINT_JOB_STATUS_SCHEDULED, 'object_id' => $new_invoice->id, 'object_model_type' => 'invoice']);
$objects = [];
$objects[] = ['model' => 'invoice', 'id' => $new_invoice->id, 'model_ready' => $new_invoice];
$objects[] = ['model' => 'old_invoice', 'id' => $old_invoice->id, 'model_ready' => $old_invoice];
OsProcessJobsHelper::create_jobs_for_event('invoice_updated', $objects);
}
public static function log_invoice_created(OsInvoiceModel $invoice) {
$data = [];
$data['invoice_id'] = $invoice->id;
$data['code'] = 'invoice_created';
$data['description'] = wp_json_encode(['invoice_data_vars' => $invoice->get_data_vars()]);
OsActivitiesHelper::create_activity($data);
}
public static function log_invoice_updated(OsInvoiceModel $invoice, OsInvoiceModel $old_invoice) {
$data = [];
$data['invoice_id'] = $invoice->id;
$data['code'] = 'invoice_updated';
$data['description'] = wp_json_encode(['invoice_data_vars' => ['new' => $invoice->get_data_vars(), 'old' => $old_invoice->get_data_vars()]]);
OsActivitiesHelper::create_activity($data);
}
public static function readable_status( string $status ): string {
$statuses = self::list_of_statuses_for_select();
return $statuses[ $status ] ?? __( 'n/a', 'latepoint' );
}
public static function get_invoice_by_key( string $key ): OsInvoiceModel {
if ( empty( $key ) ) {
return new OsInvoiceModel();
}
$invoice = new OsInvoiceModel();
return $invoice->where( [ 'access_key' => $key ] )->set_limit( 1 )->get_results_as_models();
}
public static function get_invoice_logo_url(): string {
$default_logo_url = LATEPOINT_IMAGES_URL . 'logo.svg';
return OsImageHelper::get_image_url_by_id(OsSettingsHelper::get_settings_value('invoices_company_logo'), 'thumbnail', $default_logo_url);
}
public static function invoice_document_html( OsInvoiceModel $invoice, bool $show_controls, ?OsTransactionModel $transaction = null ) {
$invoice_data = json_decode( $invoice->data, true );
?>
<div class="invoice-document status-<?php echo esc_attr( $invoice->status ); ?>" data-invoice-id="<?php echo esc_attr($invoice->id); ?>" data-route="<?php echo OsRouterHelper::build_route_name('invoices', 'change_status'); ?>">
<?php if ( $show_controls ) { ?>
<div class="invoice-controls">
<div class="ic-block">
<?php echo OsFormHelper::select_field( 'invoice[status]', __( 'Status', 'latepoint' ), OsInvoicesHelper::list_of_statuses_for_select(), $invoice->status, ['class' => 'invoice-change-status-selector'] ); ?>
</div>
<div class="ic-block">
<a target="_blank" href="<?php echo $invoice->get_access_url(); ?>" class="ic-external-link">
<span><?php esc_html_e( 'Open', 'latepoint' ); ?></span>
<i class="latepoint-icon latepoint-icon-external-link"></i>
</a>
</div>
<div class="ic-block make-last">
<button type="button" class="latepoint-btn latepoint-btn-sm latepoint-btn-outline"
data-os-params="<?php echo esc_attr(http_build_query( [ 'invoice_id' => $invoice->id ] )); ?>"
data-os-action="<?php echo esc_attr(OsRouterHelper::build_route_name( 'invoices', 'edit_data' )); ?>"
data-os-output-target="lightbox"
data-os-after-call="latepointInvoicesAdminFeature.init_invoice_data_form"
data-os-lightbox-classes="width-500">
<i class="latepoint-icon latepoint-icon-edit-2"></i>
<span><?php esc_html_e( 'Edit Data', 'latepoint' ); ?></span>
</button>
<button type="button" class="latepoint-btn latepoint-btn-sm latepoint-btn-outline"
data-os-params="<?php echo esc_attr(http_build_query( [ 'invoice_id' => $invoice->id ] )); ?>"
data-os-action="<?php echo esc_attr(OsRouterHelper::build_route_name( 'invoices', 'email_form' )); ?>"
data-os-output-target="lightbox"
data-os-after-call="latepointInvoicesAdminFeature.init_email_invoice_form"
data-os-lightbox-classes="width-500">
<i class="latepoint-icon latepoint-icon-mail"></i>
<span><?php esc_html_e( 'Email Invoice', 'latepoint' ); ?></span>
</button>
</div>
</div>
<?php } ?>
<div class="invoice-document-i">
<?php
if ( empty( $transaction ) ) {
switch ( $invoice->status ) {
case LATEPOINT_INVOICE_STATUS_PAID:
echo '<div class="invoice-status-paid-label">' . esc_html( self::readable_status( $invoice->status ) ) . '</div>';
break;
case LATEPOINT_INVOICE_STATUS_DRAFT:
echo '<div class="invoice-status-draft-label">' . esc_html( self::readable_status( $invoice->status ) ) . '</div>';
break;
case LATEPOINT_INVOICE_STATUS_VOID:
echo '<div class="invoice-status-voided-label">' . esc_html( self::readable_status( $invoice->status ) ) . '</div>';
break;
}
}
?>
<div class="invoice-heading">
<div class="invoice-info">
<div class="invoice-title"><?php echo $transaction ? esc_html__( 'Receipt', 'latepoint' ) : esc_html__( 'Invoice', 'latepoint' ); ?></div>
<div class="invoice-data">
<div class="invoice-row">
<div class="id-label"><?php esc_html_e( 'Invoice number', 'latepoint' ); ?></div>
<div class="id-value"><?php echo esc_html( $invoice->get_invoice_number() ); ?></div>
</div>
<?php if ( $transaction ) { ?>
<div class="invoice-row">
<div class="id-label"><?php esc_html_e( 'Receipt number', 'latepoint' ); ?></div>
<div class="id-value"><?php echo esc_html( $transaction->receipt_number ); ?></div>
</div>
<div class="invoice-row">
<div class="id-label"><?php esc_html_e( 'Date paid', 'latepoint' ); ?></div>
<div class="id-value"><?php echo esc_html( OsTimeHelper::get_readable_date( new OsWpDateTime( $transaction->created_at, new DateTimeZone('UTC') ) ) ); ?></div>
</div>
<?php } else { ?>
<div class="invoice-row">
<div class="id-label"><?php esc_html_e( 'Date of issue', 'latepoint' ); ?></div>
<div class="id-value"><?php echo esc_html( OsTimeHelper::get_readable_date( new OsWpDateTime( $invoice->created_at, new DateTimeZone('UTC') ) ) ); ?></div>
</div>
<div class="invoice-row">
<div class="id-label"><?php esc_html_e( 'Date due', 'latepoint' ); ?></div>
<div class="id-value"><?php echo esc_html( OsTimeHelper::get_readable_date( new OsWpDateTime( $invoice->due_at, new DateTimeZone('UTC') ) ) ); ?></div>
</div>
<?php } ?>
<?php if(!empty($invoice_data['tax_id'])){ ?>
<div class="invoice-row">
<div class="id-label"><?php esc_html_e( 'VAT Number', 'latepoint' ); ?></div>
<div class="id-value"><?php echo esc_html( $invoice_data['tax_id'] ); ?></div>
</div>
<?php } ?>
</div>
</div>
<div class="invoice-logo">
<img src="<?php echo esc_attr( self::get_invoice_logo_url() ); ?>" width="50" height="50" alt="<?php esc_attr_e('LatePoint Dashboard', 'latepoint'); ?>">
</div>
</div>
<div class="invoice-to-from">
<div class="invoice-from">
<?php if(!empty($invoice_data['company'])){ ?>
<div class="if-heading"><?php echo esc_html( $invoice_data['company'] ); ?></div>
<?php } ?>
<div class="if-data-block">
<?php echo wp_kses($invoice_data['from'], ['br' => []]); ?>
</div>
</div>
<div class="invoice-from">
<div class="if-heading"><?php echo esc_html( __('Bill to', 'latepoint') ); ?></div>
<div class="if-data-block">
<?php echo wp_kses($invoice_data['to'], ['br' => []]); ?>
</div>
</div>
</div>
<?php do_action( 'latepoint_invoice_before_amount_info', $invoice, $transaction ); ?>
<?php if ( empty( $transaction ) ) { ?>
<div class="invoice-due-info">
<div class="invoice-due-amount">
<?php echo esc_html( sprintf( __( '%s due %s', 'latepoint' ), OsMoneyHelper::format_price( $invoice->charge_amount, true, false ), OsTimeHelper::get_readable_date_from_string( $invoice->due_at ) ) ); ?>
</div>
<?php if ( $invoice->status == LATEPOINT_INVOICE_STATUS_OPEN ) { ?>
<div class="invoice-due-pay-link-w">
<a href="<?php echo $invoice->get_pay_url(); ?>" target="_blank"><?php esc_html_e( 'Pay Online', 'latepoint' ); ?></a>
</div>
<?php } ?>
<?php if ( $invoice->status == LATEPOINT_INVOICE_STATUS_PAID || $invoice->get_successful_payments() ) { ?>
<div class="invoice-due-pay-link-w">
<a target="_blank"
href="<?php echo OsOrdersHelper::generate_direct_manage_order_url( $invoice->get_order(), 'customer', 'list_payments' ) ?>"><?php esc_html_e( 'View Payments', 'latepoint' ); ?></a>
</div>
<?php } ?>
</div>
<?php } else { ?>
<div class="invoice-due-info">
<div class="invoice-due-amount">
<?php echo esc_html( sprintf( __( '%s paid on %s', 'latepoint' ), OsMoneyHelper::format_price( $transaction->amount, true, false ), OsTimeHelper::get_readable_date_from_string( $transaction->created_at ) ) ); ?>
</div>
</div>
<?php } ?>
<div class="invoice-items">
<div class="invoice-items-table-heading">
<div class="it-column"><?php esc_html_e( 'Description', 'latepoint' ); ?></div>
<div class="it-column"><?php esc_html_e( 'Amount', 'latepoint' ); ?></div>
</div>
<?php OsPriceBreakdownHelper::output_price_breakdown( $invoice_data['price_breakdown'] ); ?>
</div>
<div class="invoice-totals">
<div class="it-row">
<div class="it-column"><?php esc_html_e( 'Subtotal', 'latepoint' ); ?></div>
<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $invoice_data['totals']['subtotal'], true, false ) ); ?></div>
</div>
<div class="it-row">
<div class="it-column"><?php esc_html_e( 'Total', 'latepoint' ); ?></div>
<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $invoice_data['totals']['total'], true, false ) ); ?></div>
</div>
<?php if ( empty( $transaction ) ) { ?>
<?php if ( ! empty( $invoice_data['totals']['payments'] ) ) { ?>
<div class="it-row it-row-positive">
<div class="it-column"><?php esc_html_e( 'Payments & Credits', 'latepoint' ); ?></div>
<div class="it-column"><?php echo '-' . esc_html( OsMoneyHelper::format_price( $invoice_data['totals']['payments'], true, false ) ); ?></div>
</div>
<?php } ?>
<div class="it-row it-row-bold">
<div class="it-column"><?php esc_html_e( 'Amount Due', 'latepoint' ); ?></div>
<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $invoice->charge_amount, true, false ) ); ?></div>
</div>
<?php } else { ?>
<div class="it-row it-row-bold">
<div class="it-column"><?php esc_html_e( 'Amount Paid', 'latepoint' ); ?></div>
<div class="it-column"><?php echo esc_html( OsMoneyHelper::format_price( $transaction->amount, true, false ) ); ?></div>
</div>
<?php } ?>
</div>
<?php if ( OsSettingsHelper::get_settings_value( 'invoice_terms', '' ) ) { ?>
<div class="invoice-terms">
<div class="terms-heading"><?php esc_html_e( 'Terms & Conditions', 'latepoint' ); ?></div>
<div class="terms-content"><?php echo esc_html( OsSettingsHelper::get_settings_value( 'invoice_terms', '' ) ); ?></div>
</div>
<?php } ?>
</div>
</div>
<?php
}
public static function list_of_statuses_for_select(): array {
$statuses = [
LATEPOINT_INVOICE_STATUS_OPEN => __( 'Open', 'latepoint' ),
LATEPOINT_INVOICE_STATUS_PAID => __( 'Paid', 'latepoint' ),
LATEPOINT_INVOICE_STATUS_PARTIALLY_PAID => __( 'Partially Paid', 'latepoint' ),
LATEPOINT_INVOICE_STATUS_DRAFT => __( 'Draft', 'latepoint' ),
LATEPOINT_INVOICE_STATUS_VOID => __( 'Void', 'latepoint' ),
LATEPOINT_INVOICE_STATUS_UNCOLLECTIBLE => __( 'Uncollectible', 'latepoint' ),
];
/**
* Get the list of invoice statuses
*
* @param {array} $statuses Array of invoice statuses
*
* @returns {array} Filtered array of invoice statuses
* @since 5.1.0
* @hook latepoint_invoices_statuses_for_select
*
*/
return apply_filters( 'latepoint_invoices_statuses_for_select', $statuses );
}
public static function list_of_payment_portions_for_select(): array {
$payment_portions = [
LATEPOINT_PAYMENT_PORTION_FULL => __('Full', 'latepoint'),
LATEPOINT_PAYMENT_PORTION_REMAINING => __('Remaining', 'latepoint'),
LATEPOINT_PAYMENT_PORTION_DEPOSIT => __('Deposit', 'latepoint'),
LATEPOINT_PAYMENT_PORTION_CUSTOM => __('Custom', 'latepoint'),
];
/**
* Get the list of invoice payment_portions
*
* @param {array} $payment_portions Array of invoice payment_portions
*
* @returns {array} Filtered array of invoice payment_portions
* @since 5.1.0
* @hook latepoint_invoices_payment_portions_for_select
*
*/
return apply_filters( 'latepoint_invoices_payment_portions_for_select', $payment_portions );
}
/**
* @param OsOrderModel $order
*
* @return void
* @throws Exception
*/
public static function list_invoices_for_order( OsOrderModel $order ) {
if ( $order->is_new_record() ) {
return;
}
$invoices = new OsInvoiceModel();
$invoices = $invoices->where( [ 'order_id' => $order->id ] )->get_results_as_models();
if ( OsRolesHelper::can_user( 'invoice__view' ) ) { ?>
<div class="invoices-info-w">
<div class="os-form-sub-header">
<h3><?php esc_html_e( 'Invoices', 'latepoint' ); ?></h3>
</div>
<div class="list-of-invoices">
<?php if ( $invoices ) {
foreach ( $invoices as $invoice ) {
echo OsInvoicesHelper::generate_invoice_tile_on_order_edit_form($invoice);
}
}
?>
</div>
<?php if ( OsRolesHelper::can_user( 'invoice__create' ) ) { ?>
<div class="quick-add-item-button"
data-os-params="<?php echo OsUtilHelper::build_os_params(['order_id' => $order->id]); ?>"
data-os-after-call="latepointInvoicesAdminFeature.init_quick_invoice_settings_form"
data-os-output-target=".quick-invoice-settings-form-wrapper"
data-os-before-after="before"
data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'invoices', 'new_form' ) ); ?>">
<i class="latepoint-icon latepoint-icon-plus2"></i>
<span><?php esc_html_e( 'New Invoice', 'latepoint' ); ?></span>
</div>
<div class="quick-invoice-settings-form-wrapper"></div>
<?php } ?>
</div>
<?php
}
}
/**
* @param OsOrderModel $order
*
* @return array
*/
public static function generate_invoice_data_from_order( OsOrderModel $order ): array {
$data = [
'company' => OsReplacerHelper::replace_business_vars(OsSettingsHelper::get_settings_value( 'invoices_company_name', '' )),
'from' => OsReplacerHelper::replace_all_vars(OsInvoicesHelper::get_invoice_data_bill_from(), ['order' => $order, 'customer' => $order->get_customer()]),
'to' => OsReplacerHelper::replace_all_vars(OsInvoicesHelper::get_invoice_data_bill_to(), ['order' => $order, 'customer' => $order->get_customer()]),
'price_breakdown' => json_decode( $order->price_breakdown ),
'tax_id' => OsSettingsHelper::get_settings_value('invoices_tax_id', ''),
'totals' => [
'subtotal' => $order->get_subtotal(),
'total' => $order->get_total(),
]
];
/**
* Invoice data generated from an order
*
* @since 5.1.0
* @hook latepoint_invoices_data_from_order
*
* @param {array} $data array of values for invoice
* @param {OsOrderModel} $order order model
* @returns {array} The filtered array of invoice data
*/
$data = apply_filters('latepoint_invoices_data_from_order', $data, $order);
return $data;
}
/**
* @param OsOrderModel $order
* @param OsPaymentRequestModel|null $payment_request
*
* @return void
*/
public static function create_invoices_for_new_order( OsOrderModel $order, ?OsPaymentRequestModel $payment_request = null) {
$existing_invoices = new OsInvoiceModel();
$existing_invoices = $existing_invoices->where(['order_id' => $order->id])->get_results_as_models();
$total_invoiced_amount = 0;
if($existing_invoices){
foreach($existing_invoices as $invoice) {
$total_invoiced_amount+= $invoice->charge_amount;
}
}
$order_total = $order->get_total();
$total_invoiced_amount = OsMoneyHelper::pad_to_db_format( $total_invoiced_amount );
if($total_invoiced_amount > 0 && $total_invoiced_amount == $order_total){
return;
}
$invoice = new OsInvoiceModel();
$invoice->order_id = $order->id;
$invoice->data = json_encode( self::generate_invoice_data_from_order( $order ) );
if ( $order->get_initial_payment_data_value( 'time' ) == LATEPOINT_PAYMENT_TIME_NOW ) {
// need to pay now, portion will depend on what customer/booker selected
$invoice->payment_portion = $order->get_initial_payment_data_value( 'portion' );
$invoice->charge_amount = $order->get_initial_payment_data_value( 'charge_amount' );
if ( $order->get_initial_payment_data_value( 'portion' ) != LATEPOINT_PAYMENT_PORTION_FULL ) {
// since we are not paying full balance, create invoice for the remaining amount
$invoice_for_remaining_balance = clone $invoice;
$invoice_for_remaining_balance->charge_amount = $order->get_total() - $invoice->charge_amount;
$invoice_for_remaining_balance->payment_portion = LATEPOINT_PAYMENT_PORTION_REMAINING;
}
if ( ! empty( $payment_request ) ) {
$invoice->due_at = $payment_request->due_at;
}
} else {
// will pay later, need to generate full price invoice
$invoice->charge_amount = $order->get_total();
$invoice->payment_portion = LATEPOINT_PAYMENT_PORTION_FULL;
}
if ( $order->get_total_amount_paid_from_transactions() == $invoice->charge_amount ) {
// since the order has just been created - any transactions that were made - are part of the time of creation, so should be on the invoice
$invoice->status = LATEPOINT_INVOICE_STATUS_PAID;
}
if ( $invoice->save() ) {
/**
* Invoice was created
*
* @param {OsInvoiceModel} $invoice instance of invoice model that was created
*
* @since 5.1.0
* @hook latepoint_invoice_created
*
*/
do_action( 'latepoint_invoice_created', $invoice );
if ( ! empty( $payment_request ) ) {
// if we have payment request, update it with created invoice
$payment_request->invoice_id = $invoice->id;
$payment_request->order_id = $order->id;
if($payment_request->save()){
/**
* Invoice was created
*
* @param {OsInvoiceModel} $payment_request instance of payment request model that was created
*
* @since 5.1.0
* @hook latepoint_payment_request_created
*
*/
do_action( 'latepoint_payment_request_created', $payment_request );
}
}
if ( isset( $invoice_for_remaining_balance ) && $invoice_for_remaining_balance->charge_amount > 0 ) {
$order_items = $order->get_items();
foreach ( $order_items as $item ) {
if ( $item->is_booking() ) {
$booking = $item->build_original_object_from_item_data();
$invoice_for_remaining_balance->due_at = $booking->start_datetime_utc;
break;
}
}
$invoice_for_remaining_balance->status = LATEPOINT_INVOICE_STATUS_DRAFT;
$invoice_for_remaining_balance->save();
/**
* Invoice was created
*
* @param {OsInvoiceModel} $invoice instance of invoice model that was created
*
* @since 5.1.0
* @hook latepoint_invoice_created
*
*/
do_action( 'latepoint_invoice_created', $invoice_for_remaining_balance );
}
}
}
/**
*
* Tries to get a matching invoice for a transaction, this is only useful when a new order is created and a transaction needs to find an invoice to attach to
*
* @param OsTransactionModel $transaction
*
* @return OsInvoiceModel
*/
public static function get_matching_invoice_for_transaction( OsTransactionModel $transaction ): OsInvoiceModel {
$invoice = new OsInvoiceModel();
$invoice->where( [ 'order_id' => $transaction->order_id ] );
$invoice->where( [ 'payment_portion' => $transaction->payment_portion ] );
$invoice->where( [ 'charge_amount' => $transaction->amount ] );
$invoice->where( [ 'status' => LATEPOINT_INVOICE_STATUS_OPEN ] );
$invoice = $invoice->set_limit( 1 )->get_results_as_models();
if ( empty( $invoice ) ) {
$invoice = new OsInvoiceModel();
}
/**
* Try to get a matching invoice for a transaction
*
* @param {OsTransactionModel} $transaction transaction to match invoice to
*
* @returns {OsInvoiceModel} Filtered invoice model
* @since 5.1.0
* @hook latepoint_invoice_get_matching_invoice_for_transaction
*
*/
return apply_filters( 'latepoint_invoice_get_matching_invoice_for_transaction', $invoice, $transaction );
}
public static function create_invoice_from_transaction( OsTransactionModel $transaction_to_create_invoice ): bool {
$order = new OsOrderModel( $transaction_to_create_invoice->order_id );
if ( $order->is_new_record() ) {
return false;
}
$invoice = new OsInvoiceModel();
$invoice->charge_amount = $transaction_to_create_invoice->amount;
$invoice->data = self::generate_invoice_data_from_order( $order );
$invoice->order_id = $order->id;
$invoice->status = LATEPOINT_INVOICE_STATUS_PAID;
$invoice->payment_portion = $transaction_to_create_invoice->payment_portion;
$invoice->due_at = $transaction_to_create_invoice->created_at;
if ( $invoice->save() ) {
return $transaction_to_create_invoice->update_attributes( [ 'invoice_id' => $invoice->id ] );
}
return false;
}
public static function generate_invoice_tile_on_order_edit_form( $invoice ) : string {
$html = '';
$html.= '<div class="os-invoice-wrapper" data-reload-tile-route="'.esc_attr(OsRouterHelper::build_route_name('invoices', 'reload_invoice_tile')).'" data-route="' . esc_attr( OsRouterHelper::build_route_name( 'invoices', 'view' ) ) . '" data-invoice-id="' . esc_attr( $invoice->id ) . '">';
$html.= '<div class="quick-invoice-head">
<div class="quick-invoice-icon"><i class="latepoint-icon latepoint-icon-file-text"></i></div>
<div class="quick-invoice-amount">' . OsMoneyHelper::format_price( $invoice->charge_amount, true, false ) . '</div>
<div class="lp-invoice-status lp-invoice-status-' . $invoice->status . '">' . self::readable_status( $invoice->status ) . '</div>
</div>
<div class="quick-invoice-sub">
<div class="lp-invoice-number"><span>' . esc_html__( 'Invoice Number:', 'latepoint' ) . '</span> <strong>' . esc_html( $invoice->get_invoice_number() ) . '</strong></div>
<div class="lp-invoice-date">' . sprintf( esc_html__( 'Due: %s', 'latepoint' ), OsTimeHelper::get_readable_date( new OsWpDateTime( $invoice->due_at, new DateTimeZone('UTC') ) ) ) . '</div>
</div>';
$html.= '</div>';
return $html;
}
public static function update_activity_after_invoice_job_run( OsActivityModel $activity, OsProcessJobModel $process_job, string $event_type ): OsActivityModel {
switch ( $event_type ) {
case 'invoice_created':
case 'invoice_updated':
$invoice = new OsInvoiceModel( $process_job->object_id );
$activity->customer_id = $invoice->get_customer()->id;
$activity->order_id = $invoice->order_id;
break;
}
return $activity;
}
public static function add_activity_view_vars_for_invoice( array $vars, OsActivityModel $activity ): array {
$data = json_decode( $activity->description, true );
switch ( $activity->code ) {
case 'invoice_created':
$link_to_invoice = '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', 'latepoint' ) . '</a>';
$vars['name'] = __('Invoice Created', 'latepoint');
$vars['meta_html'] = '<div class="activity-preview-to"><span class="os-value">' . $link_to_invoice . '</span><span class="os-label">' . __( 'Created On:', 'latepoint' ) . '</span><span class="os-value">' . $activity->nice_created_at . '</div>';
$vars['content_html'] = '<pre class="format-json">' . wp_json_encode( $data['invoice_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
break;
case 'invoice_updated':
$link_to_invoice = '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', 'latepoint' ) . '</a>';
$vars['name'] = __('Invoice Updated', 'latepoint');
$vars['meta_html'] = '<div class="activity-preview-to"><span class="os-value">' . $link_to_invoice . '</span><span class="os-label">' . __( 'Updated On:', 'latepoint' ) . '</span><span class="os-value">' . $activity->nice_created_at . '</div>';
$changes = OsUtilHelper::compare_model_data_vars( $data['invoice_data_vars']['new'], $data['invoice_data_vars']['old'] );
$vars['content_html'] = '<pre class="format-json">' . wp_json_encode( $changes, JSON_PRETTY_PRINT ) . '</pre>';
break;
}
return $vars;
}
public static function add_invoice_activity_code( array $codes ): array {
$codes['invoice_created'] = __( 'Invoice Created', 'latepoint' );
$codes['invoice_updated'] = __( 'Invoice Updated', 'latepoint' );
return $codes;
}
public static function add_invoice_templates_for_event_actions( array $templates, string $action_type, $wp_filesystem ) {
switch ( $action_type ) {
case 'send_email':
$templates[] = [
'id' => 'invoice__created__to_customer',
'to_user_type' => 'customer',
'name' => "New Invoice",
'to_email' => '{{customer_full_name}} <{{customer_email}}>',
'subject' => "New Invoice {{invoice_number}}",
'content' => OsEmailHelper::get_email_layout( $wp_filesystem->get_contents( LATEPOINT_ADDON_PRO_VIEWS_ABSPATH . 'mailers/customer/invoice_created.html' ) )
];
$templates[] = [
'id' => 'invoice__paid__to_customer',
'to_user_type' => 'customer',
'name' => "Invoice Paid",
'to_email' => '{{customer_full_name}} <{{customer_email}}>',
'subject' => "Invoice #{{invoice_number}} Paid",
'content' => OsEmailHelper::get_email_layout( $wp_filesystem->get_contents( LATEPOINT_ADDON_PRO_VIEWS_ABSPATH . 'mailers/customer/invoice_paid.html' ) )
];
}
return $templates;
}
public static function get_invoice_object_for_process( array $object_data, string $source, string $value, bool $include_model ): array {
switch ( $source ) {
case 'invoice_id':
$object_data = [ 'model' => 'invoice', 'id' => $value ];
if ( $include_model ) {
$model = new OsInvoiceModel( $value );
$object_data['model_ready'] = $model;
}
break;
}
return $object_data;
}
public static function prepare_replacement_vars_for_invoice( array $vars, array $data_objects, array $other_vars ): array {
foreach ( $data_objects as $data_object ) {
switch ( $data_object['model'] ) {
case 'old_invoice':
$old_invoice = $data_object['model_ready'] ?? new OsInvoiceModel( $data_object['id'] );
$temp_vars = self::generate_replacement_vars_from_invoice( $old_invoice );
foreach ( $temp_vars as $key => $data ) {
$vars[ 'old_' . $key ] = $data;
}
break;
case 'invoice':
$invoice = $data_object['model_ready'] ?? new OsInvoiceModel( $data_object['id'] );
$vars = array_merge( $vars, self::generate_replacement_vars_from_invoice( $invoice ) );
break;
}
}
return $vars;
}
/**
*
* Prepares an array of variables to be used in replacer method from a order object
*
* @param OsInvoiceModel $invoice
* @param array $other_vars
*
* @return array
*/
public static function generate_replacement_vars_from_invoice( OsInvoiceModel $invoice, array $other_vars = [] ): array {
$vars = [];
$vars['invoice'] = $invoice;
$vars['customer'] = $invoice->get_customer();
$vars['order'] = $invoice->get_order();
if ( ! empty( $other_vars ) ) {
$vars = array_merge( $vars, $other_vars );
}
/**
* Returns an array of replacement variables, based on supplied <code>OsInvoiceModel</code> instance
*
* @param {array} $vars Current array of replacement variables
* @param {OsInvoiceModel} $invoice Instance of <code>OsInvoiceModel</code> to generate replacement variables for
* @param {array} $other_vars Array of additional (pre-prepared) replacement variables
*
* @returns {array} Filtered array of replacement variables
* @since 1.1.0
* @hook latepoint_prepare_replacement_vars_from_invoice
*
*/
return apply_filters( 'latepoint_prepare_replacement_vars_from_invoice', $vars, $invoice, $other_vars );
}
public static function add_data_source_for_invoice_events( array $data_sources, \LatePoint\Misc\ProcessEvent $event ): array {
switch ( $event->type ) {
case 'invoice_created':
$invoices_for_select = \OsInvoicesHelper::get_invoices_for_select();
$data_sources[] = [
'name' => 'invoice_id',
'values' => $invoices_for_select,
'label' => __( 'Choose an invoice for this test run:', 'latepoint' ),
'model' => 'invoice'
];
break;
case 'invoice_updated':
$invoices_for_select = \OsInvoicesHelper::get_invoices_for_select();
$data_sources[] = [
'name' => 'new_invoice_id',
'values' => $invoices_for_select,
'label' => __( 'Choose old invoice to be used for this test run:', 'latepoint' ),
'model' => 'invoice'
];
$data_sources[] = [
'name' => 'old_invoice_id',
'values' => $invoices_for_select,
'label' => __( 'Choose new invoice to be used for this test run:', 'latepoint' ),
'model' => 'invoice'
];
break;
}
return $data_sources;
}
public static function prepare_data_for_run( \LatePoint\Misc\ProcessAction $action ): \LatePoint\Misc\ProcessAction {
foreach ( $action->selected_data_objects as $data_object ) {
switch ( $data_object['model'] ) {
case 'invoice':
$action->prepared_data_for_run['activity_data']['invoice_id'] = $data_object['id'];
if ( ! empty( $data_object['model_ready'] ) ) {
$action->prepared_data_for_run['activity_data']['order_id'] = $data_object['model_ready']->order_id;
}
break;
}
}
return $action;
}
public static function set_object_model_type_for_invoice_processes( ?string $object_model_type, OsProcessModel $process, array $objects ): ?string {
if ( in_array( $process->event_type, [ 'invoice_created', 'invoice_updated' ] ) ) {
$object_model_type = 'invoice';
}
return $object_model_type;
}
public static function set_event_time_utc_for_invoice_processes( ?OsWpDateTime $event_time_utc, OsProcessModel $process, array $objects ): ?OsWpDateTime {
if ( in_array( $process->event_type, [ 'invoice_created', 'invoice_updated' ] ) ) {
try {
switch ( $process->event_type ) {
case 'invoice_created':
$event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->created_at, new DateTimeZone( 'UTC' ) );
break;
case 'invoice_updated':
$event_time_utc = new OsWpDateTime( $objects[0]['model_ready']->updated_at, new DateTimeZone( 'UTC' ) );
break;
}
} catch ( Exception $e ) {
OsDebugHelper::log( 'Error creating jobs for workflow', 'process_jobs_error', print_r( $process->id, true ) . ' ' . print_r( $objects, true ) . ' ' . $e->getMessage() );
return $event_time_utc;
}
}
return $event_time_utc;
}
public static function add_invoice_to_process_event_models( OsModel $model, string $property_object_name ): OsModel {
switch ( $property_object_name ) {
case 'old_invoice':
case 'invoice':
$model = new OsInvoiceModel();
break;
}
return $model;
}
public static function add_values_for_process_event_condition( array $values, string $property, string $property_object, string $property_attribute ): array {
switch ( $property_object ) {
case 'invoice':
case 'old_invoice':
switch ( $property_attribute ) {
case 'status':
$statuses = OsInvoicesHelper::list_of_statuses_for_select();
foreach ( $statuses as $status_key => $status ) {
$values[] = [ 'value' => $status_key, 'label' => $status ];
}
break;
case 'payment_portion':
$statuses = OsInvoicesHelper::list_of_payment_portions_for_select();
foreach ( $statuses as $status_key => $status ) {
$values[] = [ 'value' => $status_key, 'label' => $status ];
}
break;
}
break;
}
return $values;
}
public static function add_operators_to_conditions_for_invoice_events( array $operators, string $property, string $object_code, string $object_property ): array {
switch ( $object_code ) {
case 'old_invoice':
// TODO time range operators instead of removing these opearators completely
if ( $object_property != 'due_at' ) {
$operators['equal'] = __( 'was equal to', 'latepoint' );
$operators['not_equal'] = __( 'was not equal to', 'latepoint' );
}
$operators['changed'] = __( 'has changed', 'latepoint' );
$operators['not_changed'] = __( 'has not changed', 'latepoint' );
break;
case 'invoice':
// TODO time range operators instead of removing these opearators completely
if ( $object_property != 'due_at' ) {
$operators['equal'] = __( 'is equal to', 'latepoint' );
$operators['not_equal'] = __( 'is not equal to', 'latepoint' );
}
break;
}
return $operators;
}
public static function add_conditions_for_invoice_events( array $objects, string $event_type ): array {
switch ( $event_type ) {
case 'invoice_created':
$objects[] = [ 'code' => 'invoice', 'model' => 'OsInvoiceModel', 'label' => __( 'Invoice', 'latepoint' ), 'properties' => [] ];
break;
case 'invoice_updated':
$objects[] = [ 'code' => 'old_invoice', 'model' => 'OsInvoiceModel', 'label' => __( 'Old Invoice', 'latepoint' ), 'properties' => [] ];
$objects[] = [ 'code' => 'invoice', 'model' => 'OsInvoiceModel', 'label' => __( 'New Invoice', 'latepoint' ), 'properties' => [] ];
break;
}
return $objects;
}
public static function add_process_events_for_invoices( array $process_events ): array {
$process_events[] = 'invoice_created';
$process_events[] = 'invoice_updated';
return $process_events;
}
public static function add_names_for_process_events_for_invoices( array $process_event_names ): array {
$process_event_names['invoice_created'] = __( 'Invoice Created', 'latepoint' );
$process_event_names['invoice_updated'] = __( 'Invoice Updated', 'latepoint' );
return $process_event_names;
}
public static function get_subject_for_invoice_email(): string {
$default_subject = 'Your Invoice for Order #{{order_confirmation_code}}';
return OsSettingsHelper::get_settings_value( 'invoices_email_subject', $default_subject );
}
public static function get_invoice_data_bill_from(): string {
$default = '{{business_address}}<br>{{business_phone}}';
return OsSettingsHelper::get_settings_value( 'invoices_data_from', $default );
}
public static function get_invoice_data_bill_to(): string {
$default = '{{customer_full_name}}<br>{{customer_email}}<br>{{customer_phone}}';
return OsSettingsHelper::get_settings_value( 'invoices_data_to', $default );
}
public static function get_content_for_invoice_email(): string {
$default_content = 'Hi {{customer_full_name}},<br><br>Thank you for choosing {{business_name}}.<br>You can view your invoice using <a href="{{invoice_access_url}}">this link</a><br><br>If you have any questions, feel free to contact us.';
return OsSettingsHelper::get_settings_value( 'invoices_email_content', $default_content );
}
public static function output_invoice_vars() {
?>
<div class="available-vars-block">
<h4><?php _e( 'Invoices', 'latepoint' ); ?></h4>
<ul>
<li><span class="var-label"><?php esc_html_e( 'Invoice Status', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_status}}</span></li>
<li><span class="var-label"><?php esc_html_e( 'Invoice Due Date', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_due_date}}</span></li>
<li><span class="var-label"><?php esc_html_e( 'Invoice Amount', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_amount}}</span></li>
<li><span class="var-label"><?php esc_html_e( 'Invoice Number', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_number}}</span></li>
<li><span class="var-label"><?php esc_html_e( 'Invoice Access URL', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_access_url}}</span></li>
<li><span class="var-label"><?php esc_html_e( 'Invoice Pay URL', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_pay_url}}</span></li>
<li><span class="var-label"><?php esc_html_e( 'Invoice Receipt URL', 'latepoint' ); ?></span> <span class="var-code os-click-to-copy">{{invoice_receipt_url}}</span></li>
</ul>
</div>
<?php
}
public static function add_settings_sticky_menu_item(array $items) : array{
$items[] = ['href' => 'stickySectionInvoice', 'label' => __( 'Invoices', 'latepoint' )];
return $items;
}
public static function output_invoice_settings() {
?>
<div class="white-box section-anchor" id="stickySectionInvoice">
<div class="white-box-header">
<div class="os-form-sub-header"><h3><?php esc_html_e( 'Invoices', 'latepoint' ); ?></h3></div>
</div>
<div class="white-box-content no-padding">
<?php
echo '<div class="sub-section-row">
<div class="sub-section-label">
<h3>' . __( 'Invoice Data', 'latepoint' ) . '</h3>
</div>
<div class="sub-section-content">';
echo '<div class="os-row">
<div class="os-col-lg-12">'.OsFormHelper::media_uploader_field( 'settings[invoices_company_logo]', 0, __( 'Company Logo', 'latepoint' ), __( 'Remove Image', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_company_logo' ) ).'</div>
</div>';
echo '<div class="os-row os-mb-2">';
echo '<div class="os-col-4">';
echo OsFormHelper::text_field( 'settings[invoices_company_name]', __( 'Company Name', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_company_name', '' ), [ 'theme' => 'simple' ] );
echo '</div>';
echo '<div class="os-col-4">';
echo OsFormHelper::text_field( 'settings[invoices_tax_id]', __( 'VAT Number/Tax ID', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_tax_id', '' ), [ 'theme' => 'simple' ] );
echo '</div>';
echo '<div class="os-col-4">';
echo OsFormHelper::text_field( 'settings[invoices_number_prefix]', __( 'Number Prefix', 'latepoint' ), OsSettingsHelper::get_settings_value( 'invoices_number_prefix', 'INV-' ), [ 'theme' => 'simple' ] );
echo '</div>';
echo '</div>';
echo '<div class="os-mb-2">';
echo OsFormHelper::textarea_field( 'settings[invoices_data_from]', __( 'Bill From', 'latepoint' ), self::get_invoice_data_bill_from(), [ 'theme' => 'simple' ] );
echo '</div>';
echo '<div>';
echo OsFormHelper::textarea_field( 'settings[invoices_data_to]', __( 'Bill To', 'latepoint' ), self::get_invoice_data_bill_to(), [ 'theme' => 'simple' ] );
echo '</div>';
echo '</div>
</div>
<div class="sub-section-row">
<div class="sub-section-label">
<h3>' . __( 'Email Invoice', 'latepoint' ) . '</h3>
</div>
<div class="sub-section-content">
<div class="latepoint-message latepoint-message-subtle">' . __( 'This subject and content will be used when invoice is being emailed. ', 'latepoint' ) . OsUtilHelper::template_variables_link_html() . '</div>';
echo OsFormHelper::text_field( 'settings[invoices_email_subject]', __( 'Subject', 'latepoint' ), self::get_subject_for_invoice_email(), [ 'theme' => 'simple' ] );
OsFormHelper::wp_editor_field( 'settings[invoices_email_content]', 'settingsInvoiceEmailContent', __( 'Email Content', 'latepoint' ), self::get_content_for_invoice_email() );
echo '</div>
</div>';
?>
</div>
</div>
<?php
}
/**
* Get Invoice by transaction intent key
* @param string $intent_key
* @return OsInvoiceModel | false
*/
public static function get_invoice_by_transaction_intent_key( string $intent_key ) {
$transaction_intent = OsTransactionIntentHelper::get_transaction_intent_by_intent_key($intent_key);
if (!$transaction_intent->is_new_record()) {
return new OsInvoiceModel($transaction_intent->invoice_id);
}
return false;
}
}