service = $service; $this->invoices = $invoices; $this->presenter = $presenter; } /** * Registers the standard routes and the action routes. * * @since 0.1.0 * * @return void */ public function register_routes() { parent::register_routes(); $permission = Permissions::require_capability( $this->capability() ); foreach ( array( 'issue' => 'issue', 'cancel' => 'cancel', 'apply-credit' => 'apply_credit', ) as $path => $method ) { register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P\d+)/' . $path, array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, $method ), 'permission_callback' => $permission, ) ); } } /** * Capability that protects every route. * * @since 0.1.0 * * @return string */ protected function capability(): string { return Capabilities::MANAGE_INVOICES; } /** * Arguments of the list route. * * @since 0.1.0 * * @return array> */ protected function collection_args(): array { return array_merge( $this->list_params( array_keys( InvoiceRepository::ORDERBY ), 'id' ), array( 'status' => array( 'type' => 'string', 'default' => '', 'enum' => array_merge( array( '', 'overdue' ), InvoiceStatus::all() ), 'validate_callback' => 'rest_validate_request_arg', ), 'customer_id' => array( 'type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint', ), 'order_id' => array( 'type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint', ), ) ); } /** * Lists invoices. * * @since 0.1.0 * * @param WP_REST_Request $request Request. * @return array{items: array>, total: int} */ protected function query_items( WP_REST_Request $request ): array { return $this->invoices->query( array( 'search' => $request['search'], 'status' => $request['status'], 'customer_id' => $request['customer_id'], 'order_id' => $request['order_id'], 'orderby' => $request['orderby'], 'order' => $request['order'], 'page' => $request['page'], 'per_page' => $request['per_page'], ) ); } /** * Lists invoices and reports the number per status in a header. * * @since 0.1.0 * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public function get_items( $request ) { $response = parent::get_items( $request ); $response->header( 'X-HostKhata-Status-Counts', wp_json_encode( $this->invoices->status_counts() ) ); return $response; } /** * Returns an invoice with its lines and payments. * * @since 0.1.0 * * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function get_item( $request ) { $invoice = $this->invoices->find( (int) $request['id'] ); if ( null === $invoice ) { return $this->error( 'hostkhata_not_found', $this->not_found_message(), 404 ); } return rest_ensure_response( $this->presenter->detail( $invoice ) ); } /** * Creates an invoice and returns it with its lines. * * With `order_id` the invoice is built from that order; otherwise from `items`. * * @since 0.1.0 * * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function create_item( $request ) { $invoice = $this->create_resource( $this->request_body( $request ) ); if ( is_wp_error( $invoice ) ) { return $invoice; } $response = rest_ensure_response( $this->presenter->detail( $invoice ) ); $response->set_status( 201 ); return $response; } /** * Edits a draft and returns it with its lines. * * @since 0.1.0 * * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function update_item( $request ) { return $this->detail_response( $this->update_resource( (int) $request['id'], $this->request_body( $request ) ) ); } /** * Issues a draft. * * @since 0.1.0 * * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function issue( $request ) { return $this->detail_response( $this->service->issue( (int) $request['id'] ) ); } /** * Cancels an invoice. * * @since 0.1.0 * * @param WP_REST_Request $request Request. * @return WP_REST_Response|WP_Error */ public function cancel( $request ) { return $this->detail_response( $this->service->cancel( (int) $request['id'] ) ); } /** * Applies the customer's credit to an invoice. * * @since 0.1.0 * * @param WP_REST_Request $request Request: optional `amount`. * @return WP_REST_Response|WP_Error */ public function apply_credit( $request ) { $body = $this->request_body( $request ); $amount = isset( $body['amount'] ) && is_numeric( $body['amount'] ) ? (int) $body['amount'] : 0; return $this->detail_response( $this->service->apply_credit( (int) $request['id'], $amount ) ); } /** * Finds one invoice. * * @since 0.1.0 * * @param int $id Item id. * @return array|null */ protected function find_item( int $id ): ?array { return $this->invoices->find( $id ); } /** * Creates an invoice. * * @since 0.1.0 * * @param array $body Request body. * @return array|WP_Error */ protected function create_resource( array $body ) { $issue = ! empty( $body['issue'] ); $order_id = Sanitizer::absint( $body['order_id'] ?? 0 ); return $order_id > 0 ? $this->service->create_for_order( $order_id, $issue ) : $this->service->create( $body, $issue ); } /** * Edits a draft. * * @since 0.1.0 * * @param int $id Item id. * @param array $body Request body. * @return array|WP_Error */ protected function update_resource( int $id, array $body ) { return $this->service->update( $id, $body ); } /** * Deletes a draft. * * @since 0.1.0 * * @param int $id Item id. * @return true|WP_Error */ protected function delete_resource( int $id ) { return $this->service->delete( $id ); } /** * Shapes an invoice for the list view. * * @since 0.1.0 * * @param array $item Item from the repository. * @return array */ protected function present( array $item ): array { return $this->presenter->summary( $item ); } /** * Message for an invoice that does not exist. * * @since 0.1.0 * * @return string */ protected function not_found_message(): string { return __( 'Invoice not found.', 'hostkhata' ); } /** * Wraps a service result as an invoice response. * * @param array|WP_Error $result Result. * @return WP_REST_Response|WP_Error */ private function detail_response( $result ) { return is_wp_error( $result ) ? $result : rest_ensure_response( $this->presenter->detail( $result ) ); } }