count( 'sliced_invoice' ) + $this->count( 'sliced_quote' ) > 0; } public function preview(): array { $invoices = $this->ids( 'sliced_invoice' ); $quotes = $this->ids( 'sliced_quote' ); $already = 0; foreach ( $invoices as $id ) { if ( $this->alreadyImported( \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE, (string) $id ) ) { $already++; } } foreach ( $quotes as $id ) { if ( $this->alreadyImported( \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE, (string) $id ) ) { $already++; } } $clients = []; $payments = 0; foreach ( array_merge( $invoices, $quotes ) as $id ) { $c = (int) get_post_meta( $id, '_sliced_client', true ); if ( $c > 0 ) { $clients[ $c ] = true; } } foreach ( $invoices as $id ) { $payments += count( $this->payments( $id ) ); } return [ 'clients' => count( $clients ), 'invoices' => count( $invoices ), 'quotes' => count( $quotes ), 'payments' => $payments, 'already' => $already, ]; } public function run(): array { foreach ( $this->ids( 'sliced_quote' ) as $id ) { $this->importQuote( $id ); } foreach ( $this->ids( 'sliced_invoice' ) as $id ) { $this->importInvoice( $id ); } return $this->report(); } /** * One Sliced invoice. * * @param int $id Sliced post id. * @return void */ private function importInvoice( int $id ): void { $d = $this->document( $id, 'invoice' ); $d['status'] = $this->invoiceStatus( $id ); $d['due_date'] = $this->toDate( get_post_meta( $id, '_sliced_invoice_due', true ) ); $invoice_id = $this->createInvoice( $d ); if ( $invoice_id <= 0 ) { return; } foreach ( $this->payments( $id ) as $i => $p ) { $status = (string) ( $p['status'] ?? '' ); $this->createPayment( $invoice_id, [ 'source_id' => $id . ':' . $i, 'amount' => $this->toNumber( $p['amount'] ?? 0 ), 'date' => $this->toDateTime( $p['date'] ?? '' ) ?: get_post_field( 'post_date', $id ), 'method' => (string) ( $p['gateway'] ?? 'imported' ), 'transaction_id' => (string) ( $p['payment_id'] ?? '' ), 'status' => 'success' === $status ? 'completed' : ( 'refunded' === $status ? 'refunded' : 'pending' ), 'notes' => (string) ( $p['memo'] ?? '' ), ] ); } } /** * One Sliced quote. * * @param int $id Sliced post id. * @return void */ private function importQuote( int $id ): void { $d = $this->document( $id, 'quote' ); $d['status'] = $this->quoteStatus( $id ); $d['expiry_date'] = $this->toDate( get_post_meta( $id, '_sliced_quote_valid_until', true ) ); $this->createQuote( $d ); } /** * The fields both types share, normalised. * * @param int $id Sliced post id. * @param string $kind 'invoice' or 'quote'. * @return array */ private function document( int $id, string $kind ): array { $post = get_post( $id ); $src_user = get_userdata( (int) get_post_meta( $id, '_sliced_client', true ) ) ?: null; $client_id = $this->client( (int) get_post_meta( $id, '_sliced_client', true ) ); $created = $this->toDateTime( get_post_meta( $id, '_sliced_' . $kind . '_created', true ) ) ?: $post->post_date; $items = []; $raw = get_post_meta( $id, '_sliced_items', true ); // Normally [ [ item, item, … ] ]; older sites stored the list directly. $rows = []; if ( is_array( $raw ) ) { $rows = ( isset( $raw[0] ) && is_array( $raw[0] ) && ! isset( $raw[0]['title'] ) && ! isset( $raw[0]['amount'] ) ) ? $raw[0] : $raw; } foreach ( $rows as $row ) { if ( ! is_array( $row ) ) { continue; } $items[] = [ 'name' => (string) ( $row['title'] ?? '' ), 'description' => wp_strip_all_tags( (string) ( $row['description'] ?? '' ) ), 'quantity' => $this->toNumber( $row['qty'] ?? 1 ), 'price' => $this->toNumber( $row['amount'] ?? 0 ), 'adjust_percentage' => $this->toNumber( $row['tax'] ?? 0 ), 'taxable' => ( $row['taxable'] ?? '' ) === 'on', ]; } $tax = get_post_meta( $id, '_sliced_tax', true ); $tax = is_array( $tax ) ? ( $tax[0] ?? 0 ) : $tax; return [ 'source_id' => (string) $id, 'title' => (string) $post->post_title, 'number' => (string) get_post_meta( $id, '_sliced_number', true ), 'issue_date' => $this->toDate( get_post_meta( $id, '_sliced_' . $kind . '_created', true ) ) ?: substr( $post->post_date, 0, 10 ), 'created' => $created, 'client_id' => $client_id, 'customer_name' => $src_user ? ( (string) get_user_meta( $src_user->ID, '_sliced_client_business', true ) ?: $src_user->display_name ) : '', 'customer_email' => $src_user ? $src_user->user_email : '', 'customer_address' => $src_user ? (string) get_user_meta( $src_user->ID, '_sliced_client_address', true ) : '', 'items' => $items, 'tax_rate' => $this->toNumber( $tax ), 'discount_type' => 'percentage' === get_post_meta( $id, '_sliced_discount_type', true ) ? 'percentage' : 'fixed', 'discount_value' => $this->toNumber( get_post_meta( $id, '_sliced_discount', true ) ), 'currency_code' => strtoupper( (string) get_post_meta( $id, '_sliced_currency', true ) ), 'notes' => wp_strip_all_tags( (string) get_post_meta( $id, '_sliced_description', true ) ), 'terms' => wp_strip_all_tags( (string) get_post_meta( $id, '_sliced_' . $kind . '_terms', true ) ), ]; } /** * Map a Sliced client user to ours. * * @param int $user_id Sliced client user id. * @return int */ private function client( int $user_id ): int { if ( $user_id <= 0 ) { return 0; } $user = get_userdata( $user_id ); if ( ! $user ) { return 0; } list( $first, $last ) = $this->splitName( $user->display_name ); return $this->findOrCreateClient( [ 'source_id' => (string) $user_id, 'email' => $user->user_email, 'first_name' => $user->first_name ?: $first, 'last_name' => $user->last_name ?: $last, 'business' => (string) get_user_meta( $user_id, '_sliced_client_business', true ), 'address' => (string) get_user_meta( $user_id, '_sliced_client_address', true ), 'extra_info' => (string) get_user_meta( $user_id, '_sliced_client_extra_info', true ), ] ); } /** * Sliced status term → ours. * * @param int $id Sliced invoice. * @return string */ private function invoiceStatus( int $id ): string { $terms = wp_get_object_terms( $id, 'invoice_status', [ 'fields' => 'slugs' ] ); $slug = is_array( $terms ) && ! empty( $terms ) ? $terms[0] : 'draft'; $map = [ 'draft' => 'draft', 'unpaid' => 'available', 'paid' => 'paid', 'overdue' => 'available', 'cancelled' => 'cancelled' ]; return $map[ $slug ] ?? 'available'; } /** * Sliced quote status term → ours. * * @param int $id Sliced quote. * @return string */ private function quoteStatus( int $id ): string { $terms = wp_get_object_terms( $id, 'quote_status', [ 'fields' => 'slugs' ] ); $slug = is_array( $terms ) && ! empty( $terms ) ? $terms[0] : 'draft'; $map = [ 'draft' => 'draft', 'sent' => 'sent', 'accepted' => 'accepted', 'declined' => 'declined', 'cancelled' => 'declined' ]; return $map[ $slug ] ?? 'available'; } /** * Payment rows of a Sliced invoice. * * @param int $id Sliced invoice. * @return array */ private function payments( int $id ): array { $raw = get_post_meta( $id, '_sliced_payment', true ); if ( is_array( $raw ) && isset( $raw[0] ) && is_array( $raw[0] ) && ! isset( $raw[0]['amount'] ) ) { $raw = $raw[0]; } return is_array( $raw ) ? array_values( array_filter( $raw, 'is_array' ) ) : []; } /** * Ids of a Sliced post type. * * @param string $type Post type. * @return int[] */ private function ids( string $type ): array { return array_map( 'intval', get_posts( [ 'post_type' => $type, 'post_status' => [ 'publish', 'draft', 'pending', 'private', 'future' ], 'fields' => 'ids', 'posts_per_page' => -1, 'orderby' => 'ID', 'order' => 'ASC', ] ) ); } /** * How many of a Sliced post type exist. * * @param string $type Post type. * @return int */ private function count( string $type ): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN ('trash','auto-draft')", $type ) ); } }