From ea2a6e91c448814cdec2cac031b42bc3fee391d9 Mon Sep 17 00:00:00 2001 From: Shazahanul Islam Shohag Date: Wed, 21 Aug 2024 18:08:52 +0600 Subject: [PATCH 01/24] feat: Dokan Data Store --- includes/DataStore/AbstractDataStore.php | 7 ++ includes/DataStore/DataStore.php | 66 +++++++++++++++++++ includes/DataStore/DataStoreInterface.php | 80 +++++++++++++++++++++++ includes/DataStore/Model.php | 67 +++++++++++++++++++ includes/DataStore/TestDataStore.php | 51 +++++++++++++++ includes/DataStore/TestModel.php | 38 +++++++++++ 6 files changed, 309 insertions(+) create mode 100644 includes/DataStore/AbstractDataStore.php create mode 100644 includes/DataStore/DataStore.php create mode 100644 includes/DataStore/DataStoreInterface.php create mode 100644 includes/DataStore/Model.php create mode 100644 includes/DataStore/TestDataStore.php create mode 100644 includes/DataStore/TestModel.php diff --git a/includes/DataStore/AbstractDataStore.php b/includes/DataStore/AbstractDataStore.php new file mode 100644 index 0000000000..8041c17816 --- /dev/null +++ b/includes/DataStore/AbstractDataStore.php @@ -0,0 +1,7 @@ + TestDataStore::class, + ]; + /** + * @var DataStoreInterface + */ + protected DataStoreInterface $instance; + /** + * @var string + */ + protected string $current_class_name; + + /** + * @throws Exception + */ + public function __construct( string $object_type ) { + $this->stores = apply_filters( 'dokan_data_stores', $this->stores ); + + // If this object type can't be found, check to see if we can load one + // level up (so if product-type isn't found, we try product). + if ( ! array_key_exists( $object_type, $this->stores ) ) { + $pieces = explode( '-', $object_type ); + $object_type = $pieces[0]; + } + + if ( array_key_exists( $object_type, $this->stores ) ) { + $store = apply_filters( 'dokan_' . $object_type . '_data_store', $this->stores[ $object_type ] ); + if ( is_object( $store ) ) { + if ( ! $store instanceof DataStoreInterface ) { + throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); + } + $this->current_class_name = get_class( $store ); + $this->instance = $store; + } else { + if ( ! class_exists( $store ) ) { + throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); + } + $this->current_class_name = $store; + $this->instance = new $store(); + } + } else { + throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); + } + } + + /** + * @throws Exception + */ + public static function load( string $object_type ): DataStore { + return new DataStore( $object_type ); + } + + /** + * @return DataStoreInterface + */ + public function get_instance(): DataStoreInterface { + return $this->instance; + } +} diff --git a/includes/DataStore/DataStoreInterface.php b/includes/DataStore/DataStoreInterface.php new file mode 100644 index 0000000000..efa8d75392 --- /dev/null +++ b/includes/DataStore/DataStoreInterface.php @@ -0,0 +1,80 @@ +id = $id; + try { + $store = DataStore::load( $this->object_type ); + $this->data_store = $store->get_instance(); + $this->data_store->get( $this ); + } catch ( Exception $e ) { + $this->id = 0; + } + } + + protected DataStoreInterface $data_store; + + /** + * Get the model. + * + * @since DOKAN_SINCE + * + * @return Model + */ + abstract public function get(): Model; + + /** + * Store a new model data. + * + * @since DOKAN_SINCE + * + * @return Model + */ + abstract public function create(): Model; + + /** + * Update the model data. + * + * @since DOKAN_SINCE + * + * @return Model + */ + abstract public function update(): Model; + + /** + * Delete the model data. + * + * @since DOKAN_SINCE + * + * @return bool + */ + abstract public function delete(): bool; +} diff --git a/includes/DataStore/TestDataStore.php b/includes/DataStore/TestDataStore.php new file mode 100644 index 0000000000..2abcb382b6 --- /dev/null +++ b/includes/DataStore/TestDataStore.php @@ -0,0 +1,51 @@ + Date: Mon, 21 Oct 2024 12:48:33 +0600 Subject: [PATCH 02/24] Add base Model --- includes/Models/BaseModel.php | 83 +++++++ .../Models/DataStore/AbstractDataStore.php | 220 ++++++++++++++++++ includes/Models/DataStore/DataStore.php | 67 ++++++ .../Models/DataStore/DataStoreInterface.php | 80 +++++++ includes/Models/DataStore/Model.php | 67 ++++++ includes/Models/DataStore/TestDataStore.php | 51 ++++ includes/Models/DataStore/TestModel.php | 38 +++ tests/php/src/DataStore/StoreTest.php | 18 ++ 8 files changed, 624 insertions(+) create mode 100644 includes/Models/BaseModel.php create mode 100644 includes/Models/DataStore/AbstractDataStore.php create mode 100644 includes/Models/DataStore/DataStore.php create mode 100644 includes/Models/DataStore/DataStoreInterface.php create mode 100644 includes/Models/DataStore/Model.php create mode 100644 includes/Models/DataStore/TestDataStore.php create mode 100644 includes/Models/DataStore/TestModel.php create mode 100644 tests/php/src/DataStore/StoreTest.php diff --git a/includes/Models/BaseModel.php b/includes/Models/BaseModel.php new file mode 100644 index 0000000000..e6f774db7b --- /dev/null +++ b/includes/Models/BaseModel.php @@ -0,0 +1,83 @@ +data_store ) { + return $this->get_id(); + } + + /** + * Trigger action before saving to the DB. Allows you to adjust object props before save. + * + * @param WC_Data $this The object being saved. + * @param WC_Data_Store_WP $data_store THe data store persisting the data. + */ + do_action( 'dokan_before_' . $this->object_type . '_object_save', $this, $this->data_store ); + + if ( $this->get_id() ) { + $this->data_store->update( $this ); + } else { + $this->data_store->create( $this ); + } + + /** + * Trigger action after saving to the DB. + * + * @param WC_Data $this The object being saved. + * @param WC_Data_Store_WP $data_store THe data store persisting the data. + */ + do_action( 'dokan_after_' . $this->object_type . '_object_save', $this, $this->data_store ); + + return $this->get_id(); + } + + /** + * Delete an object, set the ID to 0, and return result. + * + * @param bool $force_delete Should the date be deleted permanently. + * @return bool result + */ + public function delete( $force_delete = false ) { + /** + * Filters whether an object deletion should take place. Equivalent to `pre_delete_post`. + * + * @param mixed $check Whether to go ahead with deletion. + * @param Data $this The data object being deleted. + * @param bool $force_delete Whether to bypass the trash. + * + * @since 8.1.0. + */ + $check = apply_filters( "dokan_pre_delete_$this->object_type", null, $this, $force_delete ); + + if ( null !== $check ) { + return $check; + } + + if ( $this->data_store ) { + $this->data_store->delete( $this, array( 'force_delete' => $force_delete ) ); + $this->set_id( 0 ); + return true; + } + + return false; + } + + /** + * Prefix for action and filter hooks on data. + * + * @return string + */ + protected function get_hook_prefix() { + return 'dokan_' . $this->object_type . '_get_'; + } +} diff --git a/includes/Models/DataStore/AbstractDataStore.php b/includes/Models/DataStore/AbstractDataStore.php new file mode 100644 index 0000000000..b7f63791e6 --- /dev/null +++ b/includes/Models/DataStore/AbstractDataStore.php @@ -0,0 +1,220 @@ +get_fields() as $db_field_name ) { + $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); + $data[ $db_field_name ] = $value; + } + + $inserted_id = $this->insert( $data ); + + if ( $inserted_id ) { + $model->set_id( $inserted_id ); + $model->apply_changes(); + } + + do_action( $this->get_hook_prefix() . 'created', $inserted_id, $data ); + } + + /** + * Method to update a download in the database. + * + * @param BaseModel $model WC_Customer_Download object. + */ + public function update( BaseModel &$model ) { + global $wpdb; + + $data = []; + + foreach ( $this->get_fields() as $db_field_name ) { + $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); + $data[ $db_field_name ] = $value; + } + + $format = $this->get_fields_format(); + + $wpdb->update( + $this->get_table_name_with_prefix(), + $data, + array( + $this->get_id_field_name() => $model->get_id(), + ), + $format + ); + + $model->apply_changes(); + } + + /** + * Method to read a download permission from the database. + * + * @param BaseModel $model BaseModel object. + * + * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * + * @throws Exception Throw exception if invalid entity is passed. + */ + public function read( BaseModel &$model ) { + global $wpdb; + + if ( ! $model->get_id() ) { + $message = $this->get_hook_prefix() . ' : ' . __( 'Invalid entity item.', 'dokan-lite' ); + + throw new Exception( esc_html( $message ) ); + } + + $model->set_defaults(); + $table_name = $this->get_table_name_with_prefix(); + + $id_field_name = $this->get_id_field_name(); + $format = $this->get_fields_with_format()[ $id_field_name ] ?? '%d'; + + $raw_item = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$table_name} WHERE {$id_field_name} = $format", + $model->get_id() + ) + ); + + if ( ! $raw_item ) { + $message = $this->get_hook_prefix() . ' : ' . __( 'Invalid entity item.', 'dokan-lite' ); + + throw new Exception( esc_html( $message ) ); + } + + $model->set_props( $this->format_raw_data( $raw_item ) ); + $model->set_object_read( true ); + } + + /** + * Method to delete a download permission from the database. + * + * @param WC_Customer_Download $model WC_Customer_Download object. + * @param array $args Array of args to pass to the delete method. + */ + public function delete( BaseModel &$model, $args = array() ) { + $model_id = $model->get_id(); + $this->delete_by_id( $model_id ); + + $model->set_id( 0 ); + } + + + /** + * Method to delete a download permission from the database by ID. + * + * @param int $id permission_id of the download to be deleted. + * + * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + */ + public function delete_by_id( $id ) { + global $wpdb; + + $table_name = $this->get_table_name_with_prefix(); + $id_field_name = $this->get_id_field_name(); + $format = $this->get_fields_format()[ $id_field_name ] ?? '%d'; + + $result = $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$table_name} + WHERE $id_field_name = $format", + $id + ) + ); + + do_action( $this->get_hook_prefix() . 'deleted', $id, $result ); + } + + /** + * Create download permission for a user, from an array of data. + * Assumes that all the keys in the passed data are valid. + * + * @param array $data Data to create the permission for. + * @return int The database id of the created permission, or false if the permission creation failed. + */ + protected function insert( array $data ) { + global $wpdb; + + $format = $this->get_fields_format(); + + $table_name = $this->get_table_name_with_prefix(); + + $hook_prefix = $this->get_hook_prefix(); + + $result = $wpdb->insert( + $table_name, + apply_filters( $hook_prefix . 'insert_data', $data ), + apply_filters( $hook_prefix . 'insert_data_format', $format, $data ) + ); + + do_action( $hook_prefix . 'after_insert', $result, $data ); + + return $result ? $wpdb->insert_id : false; + } + + protected function format_raw_data( $raw_data ): array { + $data = array(); + + foreach ( $this->get_fields() as $db_field_name ) { + $data[ $db_field_name ] = $raw_data->{$db_field_name}; + } + + return apply_filters( $this->get_hook_prefix() . 'format_raw_data', $data, $raw_data ); + } + + protected function get_hook_prefix(): string { + global $wpdb; + + $table_name = $this->get_table_name(); + + $hook_prefix = str_replace( $wpdb->prefix, '', $table_name ); + + return "dokan_{$hook_prefix}_"; + } + + + protected function get_table_name_with_prefix(): string { + global $wpdb; + + $table_name = $this->get_table_name(); + + if ( ! str_starts_with( $table_name, $wpdb->prefix ) ) { + $table_name = $wpdb->prefix . $table_name; + } + + return $table_name; + } + + protected function get_id_field_name(): string { + return apply_filters( $this->get_hook_prefix() . 'id_field_name', 'id' ); // 'id'; + } + + /** + * Get the fields. + * + * @return array The filtered array of fields. + */ + protected function get_fields(): array { + return apply_filters( $this->get_hook_prefix() . 'fields', array_keys( $this->get_fields_with_format() ), $this->get_fields_with_format() ); + } + + /** + * Get the fields with format. + * + * @return array The filtered array of format of the fields. + */ + protected function get_fields_format(): array { + return apply_filters( $this->get_hook_prefix() . 'fields_format', array_values( $this->get_fields_with_format() ), $this->get_fields_with_format() ); + } +} diff --git a/includes/Models/DataStore/DataStore.php b/includes/Models/DataStore/DataStore.php new file mode 100644 index 0000000000..c02a2ef729 --- /dev/null +++ b/includes/Models/DataStore/DataStore.php @@ -0,0 +1,67 @@ + TestDataStore::class, + ]; + + /** + * @var DataStoreInterface + */ + protected DataStoreInterface $instance; + /** + * @var string + */ + protected string $current_class_name; + + /** + * @throws Exception + */ + public function __construct( string $object_type ) { + $this->stores = apply_filters( 'dokan_data_stores', $this->stores ); + + // If this object type can't be found, check to see if we can load one + // level up (so if product-type isn't found, we try product). + if ( ! array_key_exists( $object_type, $this->stores ) ) { + $pieces = explode( '-', $object_type ); + $object_type = $pieces[0]; + } + + if ( array_key_exists( $object_type, $this->stores ) ) { + $store = apply_filters( 'dokan_' . $object_type . '_data_store', $this->stores[ $object_type ] ); + if ( is_object( $store ) ) { + if ( ! $store instanceof DataStoreInterface ) { + throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); + } + $this->current_class_name = get_class( $store ); + $this->instance = $store; + } else { + if ( ! class_exists( $store ) ) { + throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); + } + $this->current_class_name = $store; + $this->instance = new $store(); + } + } else { + throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); + } + } + + /** + * @throws Exception + */ + public static function load( string $object_type ): DataStore { + return new DataStore( $object_type ); + } + + /** + * @return DataStoreInterface + */ + public function get_instance(): DataStoreInterface { + return $this->instance; + } +} diff --git a/includes/Models/DataStore/DataStoreInterface.php b/includes/Models/DataStore/DataStoreInterface.php new file mode 100644 index 0000000000..efa8d75392 --- /dev/null +++ b/includes/Models/DataStore/DataStoreInterface.php @@ -0,0 +1,80 @@ +id = $id; + try { + $store = DataStore::load( $this->object_type ); + $this->data_store = $store->get_instance(); + $this->data_store->get( $this ); + } catch ( Exception $e ) { + $this->id = 0; + } + } + + protected DataStoreInterface $data_store; + + /** + * Get the model. + * + * @since DOKAN_SINCE + * + * @return Model + */ + abstract public function get(): Model; + + /** + * Store a new model data. + * + * @since DOKAN_SINCE + * + * @return Model + */ + abstract public function create(): Model; + + /** + * Update the model data. + * + * @since DOKAN_SINCE + * + * @return Model + */ + abstract public function update(): Model; + + /** + * Delete the model data. + * + * @since DOKAN_SINCE + * + * @return bool + */ + abstract public function delete(): bool; +} diff --git a/includes/Models/DataStore/TestDataStore.php b/includes/Models/DataStore/TestDataStore.php new file mode 100644 index 0000000000..25389a9f61 --- /dev/null +++ b/includes/Models/DataStore/TestDataStore.php @@ -0,0 +1,51 @@ +assertTrue( true ); + } +} From 40e5ecb007797dd52e230fbd2642c0ee3eae0ea7 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Tue, 22 Oct 2024 17:32:17 +0600 Subject: [PATCH 03/24] Add vendor Model --- includes/Models/BaseModel.php | 2 +- ...bstractDataStore.php => BaseDataStore.php} | 115 +++++++++++------- .../Models/DataStore/DataStoreInterface.php | 6 +- .../Models/DataStore/VendorBalanceStore.php | 25 ++++ includes/Models/VendorBalance.php | 80 ++++++++++++ 5 files changed, 178 insertions(+), 50 deletions(-) rename includes/Models/DataStore/{AbstractDataStore.php => BaseDataStore.php} (83%) create mode 100644 includes/Models/DataStore/VendorBalanceStore.php create mode 100644 includes/Models/VendorBalance.php diff --git a/includes/Models/BaseModel.php b/includes/Models/BaseModel.php index e6f774db7b..11fa4c6710 100644 --- a/includes/Models/BaseModel.php +++ b/includes/Models/BaseModel.php @@ -1,6 +1,6 @@ get_hook_prefix() . 'created', $inserted_id, $data ); } - /** - * Method to update a download in the database. - * - * @param BaseModel $model WC_Customer_Download object. - */ - public function update( BaseModel &$model ) { - global $wpdb; - - $data = []; - - foreach ( $this->get_fields() as $db_field_name ) { - $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); - $data[ $db_field_name ] = $value; - } - - $format = $this->get_fields_format(); - - $wpdb->update( - $this->get_table_name_with_prefix(), - $data, - array( - $this->get_id_field_name() => $model->get_id(), - ), - $format - ); - - $model->apply_changes(); - } - - /** - * Method to read a download permission from the database. - * - * @param BaseModel $model BaseModel object. + /** + * Method to read a download permission from the database. + * + * @param BaseModel $model BaseModel object. * * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared - * - * @throws Exception Throw exception if invalid entity is passed. - */ + * + * @throws Exception Throw exception if invalid entity is passed. + */ public function read( BaseModel &$model ) { global $wpdb; @@ -75,16 +49,24 @@ public function read( BaseModel &$model ) { } $model->set_defaults(); - $table_name = $this->get_table_name_with_prefix(); $id_field_name = $this->get_id_field_name(); $format = $this->get_fields_with_format()[ $id_field_name ] ?? '%d'; + $this->clear_all_clauses(); + $this->add_sql_clause( 'select', $this->get_selected_columns() ); + $this->add_sql_clause( 'from', $this->get_table_name_with_prefix() ); + + $this->add_sql_clause( + 'where', + $wpdb->prepare( + "{$id_field_name} = $format", + $model->get_id() + ) + ); + $raw_item = $wpdb->get_row( - $wpdb->prepare( - "SELECT * FROM {$table_name} WHERE {$id_field_name} = $format", - $model->get_id() - ) + $this->get_query_statement() ); if ( ! $raw_item ) { @@ -97,10 +79,39 @@ public function read( BaseModel &$model ) { $model->set_object_read( true ); } + /** + * Method to update a download in the database. + * + * @param BaseModel $model WC_Customer_Download object. + */ + public function update( BaseModel &$model ) { + global $wpdb; + + $data = []; + + foreach ( $this->get_fields() as $db_field_name ) { + $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); + $data[ $db_field_name ] = $value; + } + + $format = $this->get_fields_format(); + + $wpdb->update( + $this->get_table_name_with_prefix(), + $data, + array( + $this->get_id_field_name() => $model->get_id(), + ), + $format + ); + + $model->apply_changes(); + } + /** * Method to delete a download permission from the database. * - * @param WC_Customer_Download $model WC_Customer_Download object. + * @param BaseModel $model BaseModel object. * @param array $args Array of args to pass to the delete method. */ public function delete( BaseModel &$model, $args = array() ) { @@ -110,7 +121,6 @@ public function delete( BaseModel &$model, $args = array() ) { $model->set_id( 0 ); } - /** * Method to delete a download permission from the database by ID. * @@ -163,6 +173,20 @@ protected function insert( array $data ) { return $result ? $wpdb->insert_id : false; } + /** + * Returns a list of columns selected by the query_args formatted as a comma separated string. + * + * @return string + */ + protected function get_selected_columns(): string { + $selections = apply_filters( $this->get_hook_prefix() . 'selected_columns', $this->selected_columns ); + + $selections = implode( ', ', $selections ); + + return $selections; + } + + protected function format_raw_data( $raw_data ): array { $data = array(); @@ -183,7 +207,6 @@ protected function get_hook_prefix(): string { return "dokan_{$hook_prefix}_"; } - protected function get_table_name_with_prefix(): string { global $wpdb; diff --git a/includes/Models/DataStore/DataStoreInterface.php b/includes/Models/DataStore/DataStoreInterface.php index efa8d75392..4e6d51acea 100644 --- a/includes/Models/DataStore/DataStoreInterface.php +++ b/includes/Models/DataStore/DataStoreInterface.php @@ -54,7 +54,7 @@ public function delete( Model &$model ); * @return void * @throw \Exception */ - public function get( Model &$model ); + // public function get( Model &$model ); /** * Query data. @@ -65,7 +65,7 @@ public function get( Model &$model ); * * @return array */ - public function query( array $args = [] ): array; + // public function query( array $args = [] ): array; /** * Count data. @@ -76,5 +76,5 @@ public function query( array $args = [] ): array; * * @return int */ - public function count( array $args = [] ): int; + // public function count( array $args = [] ): int; } diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php new file mode 100644 index 0000000000..3d4cd8189d --- /dev/null +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -0,0 +1,25 @@ + '%d', + 'trn_id' => '%d', + 'trn_type' => '%d', + 'perticulars' => '%s', + 'debit' => '%f', + 'credit' => '%f', + 'status' => '%s', + 'trn_date' => '%s', + 'balance_date' => '%s', + + ]; + } + + public function get_table_name(): string { + return 'dokan_vendor_balance'; + } +} diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php new file mode 100644 index 0000000000..ef3fbb5601 --- /dev/null +++ b/includes/Models/VendorBalance.php @@ -0,0 +1,80 @@ + '%d', + // 'trn_id' => '%d', + // 'trn_type' => '%d', + // 'perticulars' => '%s', + // 'debit' => '%f', + // 'credit' => '%f', + // 'status' => '%s', + // 'trn_date' => '%s', + // 'balance_date' => '%s' + + public function get_vendor_id( string $context = 'view' ) { + return $this->get_prop( 'vendor_id', $context ); + } + + public function set_vendor_id( int $id ) { + return $this->set_prop( 'vendor_id', $id ); + } + + public function get_trn_id( string $context = 'view' ) { + return $this->get_prop( 'trn_id', $context ); + } + + public function set_trn_id( int $id ) { + return $this->set_prop( 'trn_id', $id ); + } + + public function get_trn_type( string $context = 'view' ) { + return $this->get_prop( 'trn_type', $context ); + } + + public function set_trn_type( string $type ) { + return $this->set_prop( 'trn_type', $type ); + } + + public function get_particulars( string $context = 'view' ) { + return $this->get_prop( 'perticulars', $context ); + } + + public function set_particulars( string $note ) { + return $this->set_prop( 'perticulars', $note ); + } + + public function get_debit( string $context = 'view' ) { + return $this->get_prop( 'debit', $context ); + } + + public function set_debit( float $amount ) { + return $this->set_prop( 'debit', $amount ); + } + + public function get_credit( string $context = 'view' ) { + return $this->get_prop( 'credit', $context ); + } + + public function set_credit( float $amount ) { + return $this->set_prop( 'credit', $amount ); + } + + public function get_trn_date( string $context = 'view' ) { + return $this->get_prop( 'trn_date', $context ); + } + + public function set_trn_date( float $amount ) { + return $this->set_date_prop( 'trn_date', $amount ); + } + + public function get_balance_date( string $context = 'view' ) { + return $this->get_prop( 'balance_date', $context ); + } + + public function set_balance_date( float $amount ) { + return $this->set_date_prop( 'balance_date', $amount ); + } +} From a216c965138c2e9ce078c10b6156d9a2422405b6 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Wed, 23 Oct 2024 16:13:07 +0600 Subject: [PATCH 04/24] Add Test case --- includes/Models/DataStore/BaseDataStore.php | 56 ++++-- .../Models/DataStore/DataStoreInterface.php | 31 ++- .../Models/DataStore/VendorBalanceStore.php | 15 +- includes/Models/VendorBalance.php | 190 ++++++++++++++++-- tests/php/src/DataStore/StoreTest.php | 18 -- .../php/src/Models/VendorBalanceModelTest.php | 62 ++++++ 6 files changed, 309 insertions(+), 63 deletions(-) delete mode 100644 tests/php/src/DataStore/StoreTest.php create mode 100644 tests/php/src/Models/VendorBalanceModelTest.php diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index 85b539e1dc..c4607698a8 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -3,22 +3,19 @@ namespace WeDevs\Dokan\Models\DataStore; use Automattic\WooCommerce\Admin\API\Reports\SqlQuery; +use Automattic\WooCommerce\Pinterest\API\Base; use Exception; use WeDevs\Dokan\Models\BaseModel; -abstract class BaseDataStore extends SqlQuery { +abstract class BaseDataStore extends SqlQuery implements DataStoreInterface { protected $selected_columns = [ '*' ]; abstract protected function get_fields_with_format(): array; abstract public function get_table_name(): string; - public function create( BaseModel &$model ) { - $data = array(); - foreach ( $this->get_fields() as $db_field_name ) { - $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); - $data[ $db_field_name ] = $value; - } + public function create( BaseModel &$model ) { + $data = $this->get_fields_data( $model ); $inserted_id = $this->insert( $data ); @@ -30,6 +27,7 @@ public function create( BaseModel &$model ) { do_action( $this->get_hook_prefix() . 'created', $inserted_id, $data ); } + /** * Method to read a download permission from the database. * @@ -87,16 +85,13 @@ public function read( BaseModel &$model ) { public function update( BaseModel &$model ) { global $wpdb; - $data = []; + $data = $this->get_fields_data( $model ); - foreach ( $this->get_fields() as $db_field_name ) { - $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); - $data[ $db_field_name ] = $value; - } + var_dump( 'Update balance', $data ); $format = $this->get_fields_format(); - $wpdb->update( + $result = $wpdb->update( $this->get_table_name_with_prefix(), $data, array( @@ -106,6 +101,8 @@ public function update( BaseModel &$model ) { ); $model->apply_changes(); + + return $result; } /** @@ -173,6 +170,23 @@ protected function insert( array $data ) { return $result ? $wpdb->insert_id : false; } + protected function get_fields_data( BaseModel &$model ): array { + $data = array(); + + foreach ( $this->get_fields() as $db_field_name ) { + if ( method_exists( $this, 'get_' . $db_field_name ) ) { + $data[ $db_field_name ] = call_user_func( array( $this, 'get_' . $db_field_name ), $model, 'edit' ); + } else { + $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); + } + + $data[ $db_field_name ] = $value; + } + var_dump( 'get_fields_data', $data ); + + return $data; + } + /** * Returns a list of columns selected by the query_args formatted as a comma separated string. * @@ -229,7 +243,13 @@ protected function get_id_field_name(): string { * @return array The filtered array of fields. */ protected function get_fields(): array { - return apply_filters( $this->get_hook_prefix() . 'fields', array_keys( $this->get_fields_with_format() ), $this->get_fields_with_format() ); + $fields = array_keys( $this->get_fields_with_format() ); + + return apply_filters( + $this->get_hook_prefix() . 'fields', + $fields, + $this->get_fields_with_format() + ); } /** @@ -238,6 +258,12 @@ protected function get_fields(): array { * @return array The filtered array of format of the fields. */ protected function get_fields_format(): array { - return apply_filters( $this->get_hook_prefix() . 'fields_format', array_values( $this->get_fields_with_format() ), $this->get_fields_with_format() ); + $format = array_values( $this->get_fields_with_format() ); + + return apply_filters( + $this->get_hook_prefix() . 'fields_format', + $format, + $this->get_fields_with_format() + ); } } diff --git a/includes/Models/DataStore/DataStoreInterface.php b/includes/Models/DataStore/DataStoreInterface.php index 4e6d51acea..956c9c2763 100644 --- a/includes/Models/DataStore/DataStoreInterface.php +++ b/includes/Models/DataStore/DataStoreInterface.php @@ -1,6 +1,8 @@ '%s', 'trn_date' => '%s', 'balance_date' => '%s', - ]; } public function get_table_name(): string { return 'dokan_vendor_balance'; } + + /** + * Used to get perticulars through the get_perticulars method by the DataStore. + * + * @param VendorBalance $model + * @param string $context + * @return string + */ + protected function get_perticulars( VendorBalance $model, string $context = 'edit' ): string { + return $model->get_particulars( $context ); + } } diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index ef3fbb5601..f31d588461 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -2,79 +2,229 @@ namespace WeDevs\Dokan\Models; -class VendorBalance extends BaseModel { - - // 'vendor_id' => '%d', - // 'trn_id' => '%d', - // 'trn_type' => '%d', - // 'perticulars' => '%s', - // 'debit' => '%f', - // 'credit' => '%f', - // 'status' => '%s', - // 'trn_date' => '%s', - // 'balance_date' => '%s' +use WeDevs\Dokan\Models\DataStore\VendorBalanceStore; +class VendorBalance extends BaseModel { + const TRN_TYPE_DOKAN_ORDERS = 'dokan_orders'; + const TRN_TYPE_DOKAN_WITHDRAW = 'dokan_withdraw'; + const TRN_TYPE_DOKAN_REFUND = 'dokan_refund'; + + /** + * This is the name of this object type. + * + * @var string + */ + protected $object_type = 'dokan_vendor_balance'; + + /** + * Cache group. + * + * @var string + */ + protected $cache_group = 'dokan_vendor_balance'; + + /** + * The default data of the object. + * + * @var array + */ + protected $data = [ + 'vendor_id' => 0, + 'trn_id' => 0, + 'trn_type' => '', + 'perticulars' => '', + 'debit' => 0, + 'credit' => 0, + 'status' => '', + 'trn_date' => '', + 'balance_date' => '', + ]; + + public function __construct( int $id = 0 ) { + parent::__construct( $id ); + + $this->data_store = new VendorBalanceStore(); + } + + /** + * Gets the vendor ID of the vendor balance. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int The vendor ID. + */ public function get_vendor_id( string $context = 'view' ) { return $this->get_prop( 'vendor_id', $context ); } + /** + * Sets the vendor ID of the vendor balance. + * + * @param int $id The vendor ID. + * @return void + */ public function set_vendor_id( int $id ) { return $this->set_prop( 'vendor_id', $id ); } + /** + * Gets the transaction ID. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int The transaction ID. + */ public function get_trn_id( string $context = 'view' ) { return $this->get_prop( 'trn_id', $context ); } + /** + * Sets the transaction ID. + * + * @param int $id The transaction ID. + * @return void + */ public function set_trn_id( int $id ) { return $this->set_prop( 'trn_id', $id ); } + /** + * Gets the transaction type. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string The transaction type. Valid values are: + * 'dokan_orders', 'dokan_withdraw', 'dokan_refund'. + */ public function get_trn_type( string $context = 'view' ) { return $this->get_prop( 'trn_type', $context ); } + /** + * Sets the transaction type for the transaction. + * + * @param string $type The type of the transaction. Valid values are: + * 'dokan_orders, 'dokan_withdraw', 'dokan_refund'. + * @return void + */ public function set_trn_type( string $type ) { return $this->set_prop( 'trn_type', $type ); } - public function get_particulars( string $context = 'view' ) { - return $this->get_prop( 'perticulars', $context ); + /** + * Gets the particulars for the transaction. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string The particulars. + */ + public function get_particulars( string $context = 'view' ): string { + return (string) $this->get_prop( 'perticulars', $context ); } + /** + * Set the perticulars (note) for the transaction. + * + * @param string $note The note to be stored. + * @return void + */ public function set_particulars( string $note ) { return $this->set_prop( 'perticulars', $note ); } - public function get_debit( string $context = 'view' ) { - return $this->get_prop( 'debit', $context ); + /** + * Get the debit amount. + * + * @param string $context The context in which to get the debit amount. + * @return float The debit amount. + */ + public function get_debit( string $context = 'view' ): float { + return floatval( $this->get_prop( 'debit', $context ) ); } + /** + * Set the debit amount. + * + * @param float $amount The debit amount. + * @return void + */ public function set_debit( float $amount ) { return $this->set_prop( 'debit', $amount ); } + /** + * Get the credit amount. + * + * @param string $context The context in which to get the credit amount. + * @return float The credit amount. + */ public function get_credit( string $context = 'view' ) { - return $this->get_prop( 'credit', $context ); + return floatval( $this->get_prop( 'credit', $context ) ); } + /** + * Set the credit amount. + * + * @param float $amount The credit amount. + * @return void + */ public function set_credit( float $amount ) { return $this->set_prop( 'credit', $amount ); } + /** + * Get the status of the vendor balance. + * + * @param string $context The context in which to get the status. Valid values are 'view' and 'edit'. + * @return string The status of the vendor balance. + */ + public function get_status( string $context = 'view' ): string { + return $this->get_prop( 'status', $context ); + } + + /** + * Set the status of the vendor balance. + * + * @param string $status The status to be set. + * @return void + */ + public function set_status( string $status ) { + return $this->set_prop( 'status', $status ); + } + + /** + * Get the transaction date. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return WC_DateTime The transaction date. + */ public function get_trn_date( string $context = 'view' ) { return $this->get_prop( 'trn_date', $context ); } - public function set_trn_date( float $amount ) { - return $this->set_date_prop( 'trn_date', $amount ); + /** + * Set the transaction date. + * + * @param string $date The transaction date. Accepts date in `Y-m-d` or `Y-m-d H:i:s` format. + * @return void + */ + public function set_trn_date( string $date ) { + return $this->set_date_prop( 'trn_date', $date ); } + /** + * Get the balance date. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return WC_DateTime The balance date. + */ public function get_balance_date( string $context = 'view' ) { return $this->get_prop( 'balance_date', $context ); } - public function set_balance_date( float $amount ) { - return $this->set_date_prop( 'balance_date', $amount ); + /** + * Set the balance date. + * + * @param string $date The balance date. Accepts date in `Y-m-d` or `Y-m-d H:i:s` format. + * @return void + */ + public function set_balance_date( string $date ) { + return $this->set_date_prop( 'balance_date', $date ); } } diff --git a/tests/php/src/DataStore/StoreTest.php b/tests/php/src/DataStore/StoreTest.php deleted file mode 100644 index 020ad19b77..0000000000 --- a/tests/php/src/DataStore/StoreTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue( true ); - } -} diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php new file mode 100644 index 0000000000..2fd6fde2f4 --- /dev/null +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -0,0 +1,62 @@ +set_debit( 100 ); + $vendor_balance->set_trn_id( 1 ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_particulars( 'test' ); + $vendor_balance->save(); + + $this->assertNotEmpty( $vendor_balance->get_id() ); + $this->assertDatabaseCount( + 'dokan_vendor_balance', 1, [ + 'id' => $vendor_balance->get_id(), + // 'debit' => 100, + 'trn_id' => 1, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_ORDERS, + ] + ); + } + + public function test_update_method() { + $vendor_balance = new VendorBalance(); + $vendor_balance->save(); + + $this->assertEmpty( $vendor_balance->get_particulars() ); + + $vendor_balance->set_particulars( 'test123' ); + var_dump( $vendor_balance->get_changes(), $vendor_balance->get_particulars( 'edit' ) ); + $vendor_balance->save(); + + $this->assertDatabaseHas( + 'dokan_vendor_balance', [ + 'perticulars' => 'test123', + ] + ); + } + + public function test_delete_method() { + $vendor_balance = new VendorBalance(); + $vendor_balance->save(); + $id = $vendor_balance->get_id(); + $this->assertNotEmpty( $vendor_balance->get_id() ); + + $vendor_balance->delete(); + + $this->assertDatabaseCount( 'dokan_vendor_balance', 0, [ 'id' => $id ] ); + } +} From 7e681f92d4f52663536c50e58e4841dc3199119c Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Wed, 23 Oct 2024 18:28:15 +0600 Subject: [PATCH 05/24] Add test case for datastore --- includes/DataStore/AbstractDataStore.php | 7 -- includes/DataStore/DataStore.php | 66 --------------- includes/DataStore/DataStoreInterface.php | 80 ------------------- includes/DataStore/Model.php | 67 ---------------- includes/DataStore/TestDataStore.php | 51 ------------ includes/DataStore/TestModel.php | 38 --------- includes/Models/DataStore/BaseDataStore.php | 41 +++++++--- includes/Models/DataStore/DataStore.php | 67 ---------------- includes/Models/DataStore/Model.php | 67 ---------------- includes/Models/DataStore/TestDataStore.php | 51 ------------ includes/Models/DataStore/TestModel.php | 38 --------- .../Models/DataStore/VendorBalanceStore.php | 2 +- .../php/src/Models/VendorBalanceModelTest.php | 3 +- 13 files changed, 34 insertions(+), 544 deletions(-) delete mode 100644 includes/DataStore/AbstractDataStore.php delete mode 100644 includes/DataStore/DataStore.php delete mode 100644 includes/DataStore/DataStoreInterface.php delete mode 100644 includes/DataStore/Model.php delete mode 100644 includes/DataStore/TestDataStore.php delete mode 100644 includes/DataStore/TestModel.php delete mode 100644 includes/Models/DataStore/DataStore.php delete mode 100644 includes/Models/DataStore/Model.php delete mode 100644 includes/Models/DataStore/TestDataStore.php delete mode 100644 includes/Models/DataStore/TestModel.php diff --git a/includes/DataStore/AbstractDataStore.php b/includes/DataStore/AbstractDataStore.php deleted file mode 100644 index 8041c17816..0000000000 --- a/includes/DataStore/AbstractDataStore.php +++ /dev/null @@ -1,7 +0,0 @@ - TestDataStore::class, - ]; - /** - * @var DataStoreInterface - */ - protected DataStoreInterface $instance; - /** - * @var string - */ - protected string $current_class_name; - - /** - * @throws Exception - */ - public function __construct( string $object_type ) { - $this->stores = apply_filters( 'dokan_data_stores', $this->stores ); - - // If this object type can't be found, check to see if we can load one - // level up (so if product-type isn't found, we try product). - if ( ! array_key_exists( $object_type, $this->stores ) ) { - $pieces = explode( '-', $object_type ); - $object_type = $pieces[0]; - } - - if ( array_key_exists( $object_type, $this->stores ) ) { - $store = apply_filters( 'dokan_' . $object_type . '_data_store', $this->stores[ $object_type ] ); - if ( is_object( $store ) ) { - if ( ! $store instanceof DataStoreInterface ) { - throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); - } - $this->current_class_name = get_class( $store ); - $this->instance = $store; - } else { - if ( ! class_exists( $store ) ) { - throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); - } - $this->current_class_name = $store; - $this->instance = new $store(); - } - } else { - throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); - } - } - - /** - * @throws Exception - */ - public static function load( string $object_type ): DataStore { - return new DataStore( $object_type ); - } - - /** - * @return DataStoreInterface - */ - public function get_instance(): DataStoreInterface { - return $this->instance; - } -} diff --git a/includes/DataStore/DataStoreInterface.php b/includes/DataStore/DataStoreInterface.php deleted file mode 100644 index efa8d75392..0000000000 --- a/includes/DataStore/DataStoreInterface.php +++ /dev/null @@ -1,80 +0,0 @@ -id = $id; - try { - $store = DataStore::load( $this->object_type ); - $this->data_store = $store->get_instance(); - $this->data_store->get( $this ); - } catch ( Exception $e ) { - $this->id = 0; - } - } - - protected DataStoreInterface $data_store; - - /** - * Get the model. - * - * @since DOKAN_SINCE - * - * @return Model - */ - abstract public function get(): Model; - - /** - * Store a new model data. - * - * @since DOKAN_SINCE - * - * @return Model - */ - abstract public function create(): Model; - - /** - * Update the model data. - * - * @since DOKAN_SINCE - * - * @return Model - */ - abstract public function update(): Model; - - /** - * Delete the model data. - * - * @since DOKAN_SINCE - * - * @return bool - */ - abstract public function delete(): bool; -} diff --git a/includes/DataStore/TestDataStore.php b/includes/DataStore/TestDataStore.php deleted file mode 100644 index 2abcb382b6..0000000000 --- a/includes/DataStore/TestDataStore.php +++ /dev/null @@ -1,51 +0,0 @@ -get_fields_data( $model ); @@ -25,6 +40,8 @@ public function create( BaseModel &$model ) { } do_action( $this->get_hook_prefix() . 'created', $inserted_id, $data ); + + return $inserted_id; } @@ -49,7 +66,7 @@ public function read( BaseModel &$model ) { $model->set_defaults(); $id_field_name = $this->get_id_field_name(); - $format = $this->get_fields_with_format()[ $id_field_name ] ?? '%d'; + $format = $this->get_id_field_format(); $this->clear_all_clauses(); $this->add_sql_clause( 'select', $this->get_selected_columns() ); @@ -75,6 +92,8 @@ public function read( BaseModel &$model ) { $model->set_props( $this->format_raw_data( $raw_item ) ); $model->set_object_read( true ); + + return $raw_item; } /** @@ -87,8 +106,6 @@ public function update( BaseModel &$model ) { $data = $this->get_fields_data( $model ); - var_dump( 'Update balance', $data ); - $format = $this->get_fields_format(); $result = $wpdb->update( @@ -130,7 +147,7 @@ public function delete_by_id( $id ) { $table_name = $this->get_table_name_with_prefix(); $id_field_name = $this->get_id_field_name(); - $format = $this->get_fields_format()[ $id_field_name ] ?? '%d'; + $format = $this->get_id_field_format(); $result = $wpdb->query( $wpdb->prepare( @@ -141,6 +158,8 @@ public function delete_by_id( $id ) { ); do_action( $this->get_hook_prefix() . 'deleted', $id, $result ); + + return $result; } /** @@ -167,7 +186,7 @@ protected function insert( array $data ) { do_action( $hook_prefix . 'after_insert', $result, $data ); - return $result ? $wpdb->insert_id : false; + return $result ? $wpdb->insert_id : 0; } protected function get_fields_data( BaseModel &$model ): array { @@ -177,12 +196,9 @@ protected function get_fields_data( BaseModel &$model ): array { if ( method_exists( $this, 'get_' . $db_field_name ) ) { $data[ $db_field_name ] = call_user_func( array( $this, 'get_' . $db_field_name ), $model, 'edit' ); } else { - $value = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); + $data[ $db_field_name ] = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); } - - $data[ $db_field_name ] = $value; } - var_dump( 'get_fields_data', $data ); return $data; } @@ -237,6 +253,11 @@ protected function get_id_field_name(): string { return apply_filters( $this->get_hook_prefix() . 'id_field_name', 'id' ); // 'id'; } + + protected function get_id_field_format(): string { + return apply_filters( $this->get_hook_prefix() . 'id_field_format', '%d' ); // 'id'; + } + /** * Get the fields. * diff --git a/includes/Models/DataStore/DataStore.php b/includes/Models/DataStore/DataStore.php deleted file mode 100644 index c02a2ef729..0000000000 --- a/includes/Models/DataStore/DataStore.php +++ /dev/null @@ -1,67 +0,0 @@ - TestDataStore::class, - ]; - - /** - * @var DataStoreInterface - */ - protected DataStoreInterface $instance; - /** - * @var string - */ - protected string $current_class_name; - - /** - * @throws Exception - */ - public function __construct( string $object_type ) { - $this->stores = apply_filters( 'dokan_data_stores', $this->stores ); - - // If this object type can't be found, check to see if we can load one - // level up (so if product-type isn't found, we try product). - if ( ! array_key_exists( $object_type, $this->stores ) ) { - $pieces = explode( '-', $object_type ); - $object_type = $pieces[0]; - } - - if ( array_key_exists( $object_type, $this->stores ) ) { - $store = apply_filters( 'dokan_' . $object_type . '_data_store', $this->stores[ $object_type ] ); - if ( is_object( $store ) ) { - if ( ! $store instanceof DataStoreInterface ) { - throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); - } - $this->current_class_name = get_class( $store ); - $this->instance = $store; - } else { - if ( ! class_exists( $store ) ) { - throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); - } - $this->current_class_name = $store; - $this->instance = new $store(); - } - } else { - throw new Exception( esc_html__( 'Invalid data store.', 'dokan-lite' ) ); - } - } - - /** - * @throws Exception - */ - public static function load( string $object_type ): DataStore { - return new DataStore( $object_type ); - } - - /** - * @return DataStoreInterface - */ - public function get_instance(): DataStoreInterface { - return $this->instance; - } -} diff --git a/includes/Models/DataStore/Model.php b/includes/Models/DataStore/Model.php deleted file mode 100644 index cda7ca70a5..0000000000 --- a/includes/Models/DataStore/Model.php +++ /dev/null @@ -1,67 +0,0 @@ -id = $id; - try { - $store = DataStore::load( $this->object_type ); - $this->data_store = $store->get_instance(); - $this->data_store->get( $this ); - } catch ( Exception $e ) { - $this->id = 0; - } - } - - protected DataStoreInterface $data_store; - - /** - * Get the model. - * - * @since DOKAN_SINCE - * - * @return Model - */ - abstract public function get(): Model; - - /** - * Store a new model data. - * - * @since DOKAN_SINCE - * - * @return Model - */ - abstract public function create(): Model; - - /** - * Update the model data. - * - * @since DOKAN_SINCE - * - * @return Model - */ - abstract public function update(): Model; - - /** - * Delete the model data. - * - * @since DOKAN_SINCE - * - * @return bool - */ - abstract public function delete(): bool; -} diff --git a/includes/Models/DataStore/TestDataStore.php b/includes/Models/DataStore/TestDataStore.php deleted file mode 100644 index 25389a9f61..0000000000 --- a/includes/Models/DataStore/TestDataStore.php +++ /dev/null @@ -1,51 +0,0 @@ - '%d', 'trn_id' => '%d', - 'trn_type' => '%d', + 'trn_type' => '%s', 'perticulars' => '%s', 'debit' => '%f', 'credit' => '%f', diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 2fd6fde2f4..f0ff8b8901 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -38,10 +38,11 @@ public function test_update_method() { $this->assertEmpty( $vendor_balance->get_particulars() ); + // Change the particulars $vendor_balance->set_particulars( 'test123' ); - var_dump( $vendor_balance->get_changes(), $vendor_balance->get_particulars( 'edit' ) ); $vendor_balance->save(); + // Assert changes are applied to the database. $this->assertDatabaseHas( 'dokan_vendor_balance', [ 'perticulars' => 'test123', From f583f2cdbb423259e7f5c136414992d164fbc082 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Thu, 24 Oct 2024 11:57:15 +0600 Subject: [PATCH 06/24] Add test case for CRUD --- includes/Models/BaseModel.php | 10 +++++++ includes/Models/DataStore/BaseDataStore.php | 2 +- .../Models/DataStore/VendorBalanceStore.php | 28 +++++++++++++++++++ includes/Models/VendorBalance.php | 25 +++++++++++++++-- .../php/src/Models/VendorBalanceModelTest.php | 27 ++++++++++++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/includes/Models/BaseModel.php b/includes/Models/BaseModel.php index 11fa4c6710..ac4b151032 100644 --- a/includes/Models/BaseModel.php +++ b/includes/Models/BaseModel.php @@ -80,4 +80,14 @@ public function delete( $force_delete = false ) { protected function get_hook_prefix() { return 'dokan_' . $this->object_type . '_get_'; } + + /** + * Get All Meta Data. + * + * @since 2.6.0 + * @return array of objects. + */ + public function get_meta_data() { + return apply_filters( $this->get_hook_prefix() . 'meta_data', array() ); + } } diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index 2cfd2b7730..6b0a180134 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -75,7 +75,7 @@ public function read( BaseModel &$model ) { $this->add_sql_clause( 'where', $wpdb->prepare( - "{$id_field_name} = $format", + " AND {$id_field_name} = $format", $model->get_id() ) ); diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index 304b034e65..89cc8f5e92 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -35,4 +35,32 @@ public function get_table_name(): string { protected function get_perticulars( VendorBalance $model, string $context = 'edit' ): string { return $model->get_particulars( $context ); } + + /** + * Get the balance to insert into the database. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + protected function get_balance_date( VendorBalance $model, string $context = 'edit' ) { + if ( $model->get_balance_date( $context ) ) { + return $model->get_balance_date( $context )->date( 'Y-m-d H:i:s' ); + } + + return ''; + } + + /** + * Get the transaction date to insert into the database. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + protected function get_trn_date( VendorBalance $model, string $context = 'edit' ) { + if ( $model->get_trn_date( $context ) ) { + return $model->get_trn_date( $context )->date( 'Y-m-d H:i:s' ); + } + + return ''; + } } diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index f31d588461..ef10d8e4d1 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -40,10 +40,19 @@ class VendorBalance extends BaseModel { 'balance_date' => '', ]; + /** + * Initializes the vendor balance model. + * + * @param int $id The ID of the vendor balance to initialize. Default is 0. + */ public function __construct( int $id = 0 ) { parent::__construct( $id ); + $this->set_id( $id ); + $this->data_store = apply_filters( $this->get_hook_prefix() . 'data_store', new VendorBalanceStore() ); - $this->data_store = new VendorBalanceStore(); + if ( $this->get_id() > 0 ) { + $this->data_store->read( $this ); + } } /** @@ -128,6 +137,16 @@ public function set_particulars( string $note ) { return $this->set_prop( 'perticulars', $note ); } + /** + * Set the perticulars (note) for the transaction. + * + * @param string $note The note to be stored. + * @return void + */ + protected function set_perticulars( string $note ) { + return $this->set_particulars( $note ); + } + /** * Get the debit amount. * @@ -192,7 +211,7 @@ public function set_status( string $status ) { * Get the transaction date. * * @param string $context What the value is for. Valid values are 'view' and 'edit'. - * @return WC_DateTime The transaction date. + * @return \WC_DateTime The transaction date. */ public function get_trn_date( string $context = 'view' ) { return $this->get_prop( 'trn_date', $context ); @@ -212,7 +231,7 @@ public function set_trn_date( string $date ) { * Get the balance date. * * @param string $context What the value is for. Valid values are 'view' and 'edit'. - * @return WC_DateTime The balance date. + * @return \WC_DateTime The balance date. */ public function get_balance_date( string $context = 'view' ) { return $this->get_prop( 'balance_date', $context ); diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index f0ff8b8901..73abfabde2 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -60,4 +60,31 @@ public function test_delete_method() { $this->assertDatabaseCount( 'dokan_vendor_balance', 0, [ 'id' => $id ] ); } + + public function test_read_method() { + $vendor_balance = new VendorBalance(); + $vendor_balance->set_particulars( 'test' ); + $vendor_balance->set_debit( 100 ); + $vendor_balance->set_trn_id( 1 ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_vendor_id( 1 ); + $vendor_balance->set_trn_date( '2020-01-01' ); + $vendor_balance->set_status( 'wc-pending' ); + $vendor_balance->set_balance_date( '2020-01-01' ); + + $vendor_balance->save(); + + $this->assertNotEmpty( $vendor_balance->get_id() ); + + $existing_vendor_balance = new VendorBalance( $vendor_balance->get_id() ); + + $this->assertEquals( $vendor_balance->get_particulars(), $existing_vendor_balance->get_particulars() ); + $this->assertEquals( $vendor_balance->get_debit(), $existing_vendor_balance->get_debit() ); + $this->assertEquals( $vendor_balance->get_trn_id(), $existing_vendor_balance->get_trn_id() ); + $this->assertEquals( $vendor_balance->get_trn_type(), $existing_vendor_balance->get_trn_type() ); + $this->assertEquals( $vendor_balance->get_vendor_id(), $existing_vendor_balance->get_vendor_id() ); + $this->assertEquals( $vendor_balance->get_trn_date()->date_i18n(), $existing_vendor_balance->get_trn_date()->date_i18n() ); + $this->assertEquals( $vendor_balance->get_status(), $existing_vendor_balance->get_status() ); + $this->assertEquals( $vendor_balance->get_balance_date()->date_i18n(), $existing_vendor_balance->get_balance_date()->date_i18n() ); + } } From 73b289d2a67c1e7620c4f1fd3ec8fa50030fc646 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Thu, 24 Oct 2024 12:49:50 +0600 Subject: [PATCH 07/24] Cast Date object to mysql date formate --- includes/Models/DataStore/BaseDataStore.php | 20 +++++++++++-- .../Models/DataStore/VendorBalanceStore.php | 28 ------------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index 6b0a180134..3ab5909157 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -194,15 +194,31 @@ protected function get_fields_data( BaseModel &$model ): array { foreach ( $this->get_fields() as $db_field_name ) { if ( method_exists( $this, 'get_' . $db_field_name ) ) { - $data[ $db_field_name ] = call_user_func( array( $this, 'get_' . $db_field_name ), $model, 'edit' ); + $val = call_user_func( array( $this, 'get_' . $db_field_name ), $model, 'edit' ); } else { - $data[ $db_field_name ] = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); + $val = call_user_func( array( $model, 'get_' . $db_field_name ), 'edit' ); } + + if ( is_a( $val, 'WC_DateTime' ) ) { + $val = $val->date( $this->get_date_format_for_field( $db_field_name ) ); + } + + $data[ $db_field_name ] = $val; } return $data; } + /** + * Get the date format for a specific database field. + * + * @param string $db_field_name The name of the database field. + * @return string The format in which the date is returned. + */ + protected function get_date_format_for_field( string $db_field_name ): string { + return 'Y-m-d H:i:s'; + } + /** * Returns a list of columns selected by the query_args formatted as a comma separated string. * diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index 89cc8f5e92..304b034e65 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -35,32 +35,4 @@ public function get_table_name(): string { protected function get_perticulars( VendorBalance $model, string $context = 'edit' ): string { return $model->get_particulars( $context ); } - - /** - * Get the balance to insert into the database. - * - * @param string $context What the value is for. Valid values are 'view' and 'edit'. - * @return string - */ - protected function get_balance_date( VendorBalance $model, string $context = 'edit' ) { - if ( $model->get_balance_date( $context ) ) { - return $model->get_balance_date( $context )->date( 'Y-m-d H:i:s' ); - } - - return ''; - } - - /** - * Get the transaction date to insert into the database. - * - * @param string $context What the value is for. Valid values are 'view' and 'edit'. - * @return string - */ - protected function get_trn_date( VendorBalance $model, string $context = 'edit' ) { - if ( $model->get_trn_date( $context ) ) { - return $model->get_trn_date( $context )->date( 'Y-m-d H:i:s' ); - } - - return ''; - } } From c8b207cdbdf4dddeb624bbb25d5b088da86f2c7f Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Fri, 25 Oct 2024 10:01:08 +0600 Subject: [PATCH 08/24] Add developer docs --- docs/data-store.md | 192 ++++++++++++++++++++ includes/Models/DataStore/BaseDataStore.php | 63 +++++-- 2 files changed, 238 insertions(+), 17 deletions(-) create mode 100644 docs/data-store.md diff --git a/docs/data-store.md b/docs/data-store.md new file mode 100644 index 0000000000..5148775126 --- /dev/null +++ b/docs/data-store.md @@ -0,0 +1,192 @@ +## Dokan Model & Data Store Documentation + +- [Data Model](#data-model) +- [Data Store](#data-store) +- [Uses of Models](#uses-of-models) + +## Data Model + +### Overview +The Dokan Data Model class should follow a structure similar to the `WC_Product` class, where the data layer is abstracted through the **Data Store**. This separation ensures that the model class is used throughout the Dokan plugin without interacting directly with the database or Data Store. + +### Goal +The goal is to minimize the use of raw queries and enhance performance across various parts of the plugin. The data model should efficiently manage storing and retrieving data from the database. + +### Implementation +The model class should extend the [\WeDevs\Dokan\Models\BaseModel](../includes/Models/BaseModel.php) class. It includes the following key properties and methods: + +#### Properties +- `protected $object_type`: The type of object, such as `product`. +- `protected $data`: Holds the default data for the object. + +#### Methods +- The `protected $data_store` property is initialized in the constructor. +- Getter and Setter methods use the `get_{key}` and `set_{key}` conventions, leveraging the `get_prop( $prop_name, $context )` and `set_prop( $prop_name, $prop_value )` methods for interacting with data properties. + +Below is a sample class implementation: + +```php +class Department extends \WeDevs\Dokan\Models\BaseModel { + protected $object_type = 'department'; + + protected $data = array( + 'name' => '', + 'date_created' => null, + 'date_updated' => null, + ); + + /** + * Initialize the data store + */ + public function __construct( int $item_id = 0 ) { + parent::__construct( $item_id ); + $this->data_store = new \WeDevs\Dokan\Models\DataStore\DepartmentStore(); + + if ( $this->get_id() > 0 ) { + $this->data = $this->data_store->read( $this ); + } + } + + public function get_name( $context = 'view' ) { + return $this->get_prop('name', $context); + } + + public function set_name( $name ) { + return $this->set_prop( 'name', $name ); + } + + /** + * @return \WC_DateTime|NULL Since the value was set by `set_date_prop` method + */ + public function get_date_created( $context = 'view' ) { + return $this->get_prop('date_created', $context); + } + + /** + * Set the date type value using the `set_date_prop` method. + */ + public function set_date_created( $date_created ) { + return $this->set_date_prop( 'date_created', $date_created ); + } + + /** + * @return \WC_DateTime|NULL Since the value was set by `set_date_prop` method + */ + public function get_date_updated( $context = 'view' ) { + return $this->get_prop('date_updated', $context); + } + + public function set_date_updated( $date_updated ) { + return $this->set_date_prop( 'date_updated', $date_updated ); + } + + /** + * Set the updated date for the entity. + * + * This method is protected to ensure that only internal code like `set_props` method + * can set the updated date dynamically. External clients should use + * the `set_date_created` and `set_date_updated` methods to manage dates semantically. + * + * @param string|int|DateTime $date_created + */ + protected function set_updated_at( $date_updated ) { + $this->set_date_updated( $date_updated ); + } +} +``` + +## Data Store + +- [Override DB Date Format](#override-db-date-format) +- [Customizing the ID Field](#customizing-the-id-field) + +Your data store class should extend the [\WeDevs\Dokan\Models\DataStore\BaseDataStore](../includes/Models/DataStore/BaseDataStore.php) class and must implement the following methods: + +- `get_table_name`: Defines the table name in the database. +- `get_fields_with_format`: Returns the fields with format as an array where key is the db field name and value is the format.. + +Sample implementation: + +```php +class DepartmentStore extends \WeDevs\Dokan\Models\DataStore\BaseDataStore { + public function get_table_name(): string { + return 'dokan_department'; + } + + public function get_fields_with_format(): array { + return [ + 'name' => '%s', + 'date_created' => '%s', + 'updated_at' => '%s', + ]; + } + + public function get_date_updated( $context = 'view' ) { + return $this->get_prop('date_updated', $context); + } +} +``` + +### Override DB Date Format + +The Data Store first checks for the existence of the `get_{field_name}` method to map the data during insert or update operations. There are two ways to override the DB date format: + +**Option 1:** +Override the `get_date_format_for_field` method to specify a custom format. +```php +protected function get_date_format_for_field( string $db_field_name ): string { + return 'Y-m-d'; +} +``` + +**Option 2:** +Create a custom method like `get_{db_field}` for more control over date formatting: +```php +protected function get_updated_at( Department $model, $context = 'edit' ): string { + return $model->get_date_updated( $context )->date('Y-m-d'); +} +``` +> **Option 2** could be used to map the data during insert or update operations when Model's data key and Store's data key are different. + +### Customizing the ID Field + +To customize the name and format of the ID field in the database, override the `get_id_field_name` and `get_id_field_format` methods. By default, the ID field is set to `id` and format is `%d`. + + + + + + + + + +## Uses of Models + +### Create a New Record +```php +$department = new Department(); +$department->set_name('Department 1'); +$department->save(); +``` + +### Read a Record +```php +$department = new Department( $department_id ); +echo $department->get_name(); +``` + +### Update a Record +```php +$department = new Department( $department_id ); +$department->set_name('Department 2'); +$department->save(); +``` + +### Case Study +Pls check the example [get_particulars](../includes/Models/VendorBalance.php#L16) & [set_perticulars](../includes/Models/VendorBalance.php#L146) methods of Model and [get_perticulars](../includes/Models/DataStore/VendorBalanceStore.php#L35) method of Data Store. + +`perticulars` field name in database is *typo* but I don't want to expose public method `get_perticulars` in Model class instead of `get_particulars`. + +We could do the same thing by overriding the following methods in Data Store. +- `protected function map_model_to_db_data( BaseModel &$model ): array`: Prepare data for saving a BaseModel to the database. +- `map_db_raw_to_model_data`: Maps database raw data to model data. \ No newline at end of file diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index 3ab5909157..d98a3ed3da 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -30,7 +30,7 @@ abstract public function get_table_name(): string; * @param BaseModel $model The model object containing the data to be inserted. */ public function create( BaseModel &$model ) { - $data = $this->get_fields_data( $model ); + $data = $this->map_model_to_db_data( $model ); $inserted_id = $this->insert( $data ); @@ -44,7 +44,6 @@ public function create( BaseModel &$model ) { return $inserted_id; } - /** * Method to read a download permission from the database. * @@ -90,7 +89,7 @@ public function read( BaseModel &$model ) { throw new Exception( esc_html( $message ) ); } - $model->set_props( $this->format_raw_data( $raw_item ) ); + $model->set_props( $this->map_db_raw_to_model_data( $raw_item ) ); $model->set_object_read( true ); return $raw_item; @@ -104,7 +103,7 @@ public function read( BaseModel &$model ) { public function update( BaseModel &$model ) { global $wpdb; - $data = $this->get_fields_data( $model ); + $data = $this->map_model_to_db_data( $model ); $format = $this->get_fields_format(); @@ -189,7 +188,13 @@ protected function insert( array $data ) { return $result ? $wpdb->insert_id : 0; } - protected function get_fields_data( BaseModel &$model ): array { + /** + * Prepare data for saving a BaseModel to the database. + * + * @param BaseModel $model The model to prepare. + * @return array Array of data to save. + */ + protected function map_model_to_db_data( BaseModel &$model ): array { $data = array(); foreach ( $this->get_fields() as $db_field_name ) { @@ -209,6 +214,22 @@ protected function get_fields_data( BaseModel &$model ): array { return $data; } + /** + * Maps database raw data to model data. + * + * @param object $raw_data The raw data object retrieved from the database. + * @return array An array of model data mapped from the database fields. + */ + protected function map_db_raw_to_model_data( $raw_data ): array { + $data = array(); + + foreach ( $this->get_fields() as $db_field_name ) { + $data[ $db_field_name ] = $raw_data->{$db_field_name}; + } + + return apply_filters( $this->get_hook_prefix() . 'map_db_raw_to_model_data', $data, $raw_data ); + } + /** * Get the date format for a specific database field. * @@ -232,17 +253,11 @@ protected function get_selected_columns(): string { return $selections; } - - protected function format_raw_data( $raw_data ): array { - $data = array(); - - foreach ( $this->get_fields() as $db_field_name ) { - $data[ $db_field_name ] = $raw_data->{$db_field_name}; - } - - return apply_filters( $this->get_hook_prefix() . 'format_raw_data', $data, $raw_data ); - } - + /** + * Generates a hook prefix. + * + * @return string The hook prefix. + */ protected function get_hook_prefix(): string { global $wpdb; @@ -253,6 +268,11 @@ protected function get_hook_prefix(): string { return "dokan_{$hook_prefix}_"; } + /** + * Gets the table name with the WordPress table prefix. + * + * @return string The table name with the WordPress table prefix. + */ protected function get_table_name_with_prefix(): string { global $wpdb; @@ -265,11 +285,20 @@ protected function get_table_name_with_prefix(): string { return $table_name; } + /** + * Get the name of the id field. + * + * @return string The name of the id field. + */ protected function get_id_field_name(): string { return apply_filters( $this->get_hook_prefix() . 'id_field_name', 'id' ); // 'id'; } - + /** + * Gets the format of the id field. + * + * @return string The format of the id field. + */ protected function get_id_field_format(): string { return apply_filters( $this->get_hook_prefix() . 'id_field_format', '%d' ); // 'id'; } From aae536f03c891d2506b15943538a13e7ee6f2e00 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Fri, 25 Oct 2024 11:55:40 +0600 Subject: [PATCH 09/24] Add delete_by method --- includes/Commission.php | 15 +++---- includes/Models/BaseModel.php | 11 +++++ includes/Models/DataStore/BaseDataStore.php | 40 +++++++++++++++++++ .../Models/DataStore/VendorBalanceStore.php | 24 +++++++++++ includes/Models/VendorBalance.php | 17 ++++++++ includes/Order/Hooks.php | 14 +++---- .../php/src/Models/VendorBalanceModelTest.php | 22 ++++++++++ 7 files changed, 125 insertions(+), 18 deletions(-) diff --git a/includes/Commission.php b/includes/Commission.php index f0040530b4..26893b301d 100644 --- a/includes/Commission.php +++ b/includes/Commission.php @@ -4,7 +4,9 @@ use WC_Order; use WC_Product; +use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\ProductCategory\Helper; +use WeDevs\DokanPro\Modules\DeliveryTime\StorePickup\Vendor; use WP_Error; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -97,15 +99,10 @@ public function calculate_gateway_fee( $order_id ) { [ '%d' ] ); - $wpdb->update( - $wpdb->dokan_vendor_balance, - [ 'debit' => (float) $net_amount ], - [ - 'trn_id' => $tmp_order->get_id(), - 'trn_type' => 'dokan_orders', - ], - [ '%f' ], - [ '%d', '%s' ] + VendorBalance::update_by_transaction( + $tmp_order->get_id(), + 'dokan_orders', + [ 'debit' => (float) $net_amount ] ); $tmp_order->update_meta_data( 'dokan_gateway_fee', $gateway_fee ); diff --git a/includes/Models/BaseModel.php b/includes/Models/BaseModel.php index ac4b151032..fb0fade3c8 100644 --- a/includes/Models/BaseModel.php +++ b/includes/Models/BaseModel.php @@ -72,6 +72,17 @@ public function delete( $force_delete = false ) { return false; } + /** + * Delete raws from the database. + * + * @param array $data Array of args to delete an object, e.g. `array( 'id' => 1, status => ['draft', 'cancelled'] )` or `array( 'id' => 1, 'status' => 'publish' )`. + * @return bool result + */ + public static function delete_by( array $data ) { + $object = new static(); + return $object->data_store->delete_by( $data ); + } + /** * Prefix for action and filter hooks on data. * diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index d98a3ed3da..55a5d43c35 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -161,6 +161,46 @@ public function delete_by_id( $id ) { return $result; } + /** + * Method to delete a download permission from the database by ID. + * + * @param int $id permission_id of the download to be deleted. + * + * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + */ + public function delete_by( array $data ) { + global $wpdb; + + $table_name = $this->get_table_name_with_prefix(); + $field_format = $this->get_fields_with_format(); + $field_format[ $this->get_id_field_name() ] = $this->get_id_field_format(); + + $where = []; + + foreach ( $data as $key => $value ) { + if ( is_array( $value ) ) { + // Fill format array based on the number of values and specified format for the key. + $placeholders = implode( ',', array_fill( 0, count( $value ), '%s' ) ); + + // Generate the WHERE clause securely using $wpdb->prepare() with placeholders. + $where[] = $wpdb->prepare( "{$key} IN ({$placeholders})", ...$value ); + } else { + $format = $field_format[ $key ]; + $where[] = $wpdb->prepare( "{$key} = {$format}", $value ); + } + } + $where_clause = implode( ' AND ', $where ); + + $result = $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$table_name} + WHERE {$where_clause}" + ) + ); + + return $result; + } + /** * Create download permission for a user, from an array of data. * Assumes that all the keys in the passed data are valid. diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index 304b034e65..e6b52d6c83 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -21,6 +21,30 @@ protected function get_fields_with_format(): array { ]; } + public function update_by_transaction( int $trn_id, string $trn_type, array $data ) { + global $wpdb; + + $fields_format = $this->get_fields_with_format(); + $data_format = []; + + foreach ( $data as $key => $value ) { + $data_format[] = $fields_format[ $key ]; + } + + $result = $wpdb->update( + $this->get_table_name_with_prefix(), + $data, + [ + 'trn_id' => $trn_id, + 'trn_type' => $trn_type, + ], + $data_format, + [ '%d', '%s' ] + ); + + return $result; + } + public function get_table_name(): string { return 'dokan_vendor_balance'; } diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index ef10d8e4d1..9f3c1fea06 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -55,6 +55,23 @@ public function __construct( int $id = 0 ) { } } + /** + * Updates the vendor balance based on a transaction. + * + * @param int $trn_id The transaction ID. + * @param string $trn_type The type of transaction. Valid values are + * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_ORDERS}, + * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_WITHDRAW}, + * and {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_REFUND}. + * @param array $data The data to update. + * + * @return bool True if updated successfully. False otherwise. + */ + public static function update_by_transaction( int $trn_id, string $trn_type, array $data ) { + $model = new static(); + return $model->get_data_store()->update_by_transaction( $trn_id, $trn_type, $data ); + } + /** * Gets the vendor ID of the vendor balance. * diff --git a/includes/Order/Hooks.php b/includes/Order/Hooks.php index 6bbc834613..d9cc85cbbc 100644 --- a/includes/Order/Hooks.php +++ b/includes/Order/Hooks.php @@ -4,6 +4,7 @@ use Exception; use WC_Order; +use WeDevs\Dokan\Models\VendorBalance; // don't call the file directly if ( ! defined( 'ABSPATH' ) ) { @@ -123,15 +124,10 @@ public function on_order_status_change( $order_id, $old_status, $new_status, $or } // update on vendor-balance table - $wpdb->update( - $wpdb->dokan_vendor_balance, - [ 'status' => $new_status ], - [ - 'trn_id' => $order_id, - 'trn_type' => 'dokan_orders', - ], - [ '%s' ], - [ '%d', '%s' ] + VendorBalance::update_by_transaction( + $order_id, + VendorBalance::TRN_TYPE_DOKAN_ORDERS, + [ 'status' => $new_status ] ); } diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 73abfabde2..b958582b49 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -87,4 +87,26 @@ public function test_read_method() { $this->assertEquals( $vendor_balance->get_status(), $existing_vendor_balance->get_status() ); $this->assertEquals( $vendor_balance->get_balance_date()->date_i18n(), $existing_vendor_balance->get_balance_date()->date_i18n() ); } + + public function test_update_transaction_method() { + $vendor_balance = new VendorBalance(); + $vendor_balance->set_particulars( 'test' ); + $vendor_balance->set_debit( 100 ); + $vendor_balance->set_trn_id( 1 ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_vendor_id( 1 ); + $vendor_balance->set_trn_date( '2020-01-01' ); + $vendor_balance->set_status( 'wc-pending' ); + $vendor_balance->set_balance_date( '2020-01-01' ); + $vendor_balance->save(); + + $result = VendorBalance::update_by_transaction( + 1, VendorBalance::TRN_TYPE_DOKAN_ORDERS, [ + 'debit' => 200, + ] + ); + + $updated_vendor_balance = new VendorBalance( $vendor_balance->get_id() ); + $this->assertEquals( 200, $updated_vendor_balance->get_debit() ); + } } From 9b5aa1c05df23e801d1f7f22746eeee09e040d60 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Tue, 29 Oct 2024 15:38:27 +0600 Subject: [PATCH 10/24] Add update_by to base store --- includes/Models/DataStore/BaseDataStore.php | 122 +++++++++++++++--- .../Models/DataStore/VendorBalanceStore.php | 33 ++++- includes/Models/VendorBalance.php | 16 ++- .../php/src/Models/VendorBalanceModelTest.php | 3 + 4 files changed, 150 insertions(+), 24 deletions(-) diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index 55a5d43c35..f65e64d7ed 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -7,6 +7,11 @@ use Exception; use WeDevs\Dokan\Models\BaseModel; +/** + * Base data store class. + * + * @since DOKAN_SINCE + */ abstract class BaseDataStore extends SqlQuery implements DataStoreInterface { protected $selected_columns = [ '*' ]; @@ -49,7 +54,7 @@ public function create( BaseModel &$model ) { * * @param BaseModel $model BaseModel object. * - * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * @phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared * * @throws Exception Throw exception if invalid entity is passed. */ @@ -139,9 +144,11 @@ public function delete( BaseModel &$model, $args = array() ) { * * @param int $id permission_id of the download to be deleted. * - * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * @phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * + * @return int Number of affected rows. */ - public function delete_by_id( $id ) { + public function delete_by_id( $id ): int { global $wpdb; $table_name = $this->get_table_name_with_prefix(); @@ -162,20 +169,107 @@ public function delete_by_id( $id ) { } /** - * Method to delete a download permission from the database by ID. + * Delete raws from the database. * - * @param int $id permission_id of the download to be deleted. + * @param array $data Array of args to delete an object, e.g. `array( 'id' => 1, status => ['draft', 'cancelled'] )` or `array( 'id' => 1, 'status' => 'publish' )`. * - * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * @phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * + * @return int Number of affected rows. */ - public function delete_by( array $data ) { + public function delete_by( array $data ): int { global $wpdb; $table_name = $this->get_table_name_with_prefix(); - $field_format = $this->get_fields_with_format(); + + $where_clause = $this->prepare_where_clause( $data ); + + $result = $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$table_name} + WHERE {$where_clause}" + ) + ); + + if ( $result === false ) { + throw new Exception( esc_html__( 'Failed to delete.', 'dokan-lite' ) ); + } + + return (int) $result; + } + + /** + * Updates rows in the database. + * + * @param array $where Array of args to identify the object to be updated, e.g. `array( 'id' => 1, status => ['draft', 'cancelled'] )` or `array( 'id' => 1, 'status' => 'publish' )`. + * @param array $data_to_update Array of args to update the object, e.g. `array( 'status' => 'publish' )`. + * + * @return int Number of affected rows. + */ + public function update_by( array $where, array $data_to_update ) { + global $wpdb; + + $fields_format = $this->get_fields_with_format(); $field_format[ $this->get_id_field_name() ] = $this->get_id_field_format(); - $where = []; + $data_format = []; + + var_dump( 'Test' ); + + foreach ( $data_to_update as $key => $value ) { + $data_format[] = $fields_format[ $key ]; + } + + $where_format[] = []; + + foreach ( $where as $key => $value ) { + $where_format[] = $fields_format[ $key ]; + } + + var_dump( + $this->get_table_name_with_prefix(), + $data_to_update, + $where, + $data_format, + $where_format + ); + $result = $wpdb->update( + $this->get_table_name_with_prefix(), + $data_to_update, + $where, + $data_format, + $where_format + ); + + if ( $result === false ) { + throw new Exception( esc_html__( 'Failed to update.', 'dokan-lite' ) ); + } + var_dump( $result ); + return (int) $result; + } + + /** + * Prepares a SQL WHERE clause from an associative array of data. + * + * This method takes an array of data where keys are column names and values + * are the values to filter by. It generates a secure SQL WHERE clause using + * prepared statements to protect against SQL injection. + * + * If a value in the data array is an array itself, it generates an IN clause + * with multiple placeholders. Otherwise, it generates a simple equality check. + * + * @param array $data Associative array of column names and values to filter by. + * @return string The generated WHERE clause. + */ + protected function prepare_where_clause( array $data ): string { + global $wpdb; + + $where = [ '1=1' ]; + + $field_format = $this->get_fields_with_format(); + + // Add ID field format for the WHERE clause. + $field_format[ $this->get_id_field_name() ] = $this->get_id_field_format(); foreach ( $data as $key => $value ) { if ( is_array( $value ) ) { @@ -189,16 +283,8 @@ public function delete_by( array $data ) { $where[] = $wpdb->prepare( "{$key} = {$format}", $value ); } } - $where_clause = implode( ' AND ', $where ); - - $result = $wpdb->query( - $wpdb->prepare( - "DELETE FROM {$table_name} - WHERE {$where_clause}" - ) - ); - return $result; + return implode( ' AND ', $where ); } /** diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index e6b52d6c83..6f670122c5 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -5,8 +5,16 @@ use WeDevs\Dokan\Models\BaseModel; use WeDevs\Dokan\Models\VendorBalance; +/** + * Class VendorBalanceStore + * + * @since DOKAN_SINCE + */ class VendorBalanceStore extends BaseDataStore { + /** + * @inheritDoc + */ protected function get_fields_with_format(): array { return [ 'vendor_id' => '%d', @@ -21,6 +29,28 @@ protected function get_fields_with_format(): array { ]; } + + /** + * @inheritDoc + */ + public function get_table_name(): string { + return 'dokan_vendor_balance'; + } + + /** + * Update vendor balance by transaction. + * + * @since DOKAN_SINCE + * + * @param int $trn_id The transaction ID. + * @param string $trn_type The type of transaction. Valid values are + * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_ORDERS}, + * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_WITHDRAW}, + * and {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_REFUND}. + * @param array $data The data to update. + * + * @return bool True if updated successfully. False otherwise. + */ public function update_by_transaction( int $trn_id, string $trn_type, array $data ) { global $wpdb; @@ -45,9 +75,6 @@ public function update_by_transaction( int $trn_id, string $trn_type, array $dat return $result; } - public function get_table_name(): string { - return 'dokan_vendor_balance'; - } /** * Used to get perticulars through the get_perticulars method by the DataStore. diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index 9f3c1fea06..54f8a4be82 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -65,11 +65,21 @@ public function __construct( int $id = 0 ) { * and {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_REFUND}. * @param array $data The data to update. * - * @return bool True if updated successfully. False otherwise. + * @return int Number of affected rows. */ - public static function update_by_transaction( int $trn_id, string $trn_type, array $data ) { + public static function update_by_transaction( int $trn_id, string $trn_type, array $data ): int { $model = new static(); - return $model->get_data_store()->update_by_transaction( $trn_id, $trn_type, $data ); + $model = new self(); + + // $model->get_data_store()->update_by( + // [ + // 'trn_id' => $trn_id, + // 'trn_type' => $trn_type, + // ], + // $data + // ); + + return $model->data_store->update_by_transaction( $trn_id, $trn_type, $data ); } /** diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index b958582b49..29b4fd59c8 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -5,6 +5,9 @@ use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\Test\DokanTestCase; +/** + * @group data-store + */ class VendorBalanceModelTest extends DokanTestCase { /** * Indicates if the test is a unit test. From 80b6bee6ea8adc244351a0bff75711e0d4f71543 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Wed, 6 Nov 2024 10:33:44 +0600 Subject: [PATCH 11/24] Add general update query method in Data Sotre --- includes/Models/DataStore/BaseDataStore.php | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index f65e64d7ed..3a3ada9997 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -201,7 +201,7 @@ public function delete_by( array $data ): int { /** * Updates rows in the database. * - * @param array $where Array of args to identify the object to be updated, e.g. `array( 'id' => 1, status => ['draft', 'cancelled'] )` or `array( 'id' => 1, 'status' => 'publish' )`. + * @param array $where Array of args to identify the object to be updated, e.g. `array( 'id' => 1, 'status' => 'draft' )`. * @param array $data_to_update Array of args to update the object, e.g. `array( 'status' => 'publish' )`. * * @return int Number of affected rows. @@ -214,25 +214,16 @@ public function update_by( array $where, array $data_to_update ) { $data_format = []; - var_dump( 'Test' ); - foreach ( $data_to_update as $key => $value ) { $data_format[] = $fields_format[ $key ]; } - $where_format[] = []; + $where_format = []; foreach ( $where as $key => $value ) { $where_format[] = $fields_format[ $key ]; } - var_dump( - $this->get_table_name_with_prefix(), - $data_to_update, - $where, - $data_format, - $where_format - ); $result = $wpdb->update( $this->get_table_name_with_prefix(), $data_to_update, @@ -244,7 +235,7 @@ public function update_by( array $where, array $data_to_update ) { if ( $result === false ) { throw new Exception( esc_html__( 'Failed to update.', 'dokan-lite' ) ); } - var_dump( $result ); + return (int) $result; } From fc33dc8ddb102ae171fa5ea90d3519063cd48176 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Wed, 6 Nov 2024 10:34:58 +0600 Subject: [PATCH 12/24] Update vendor balance Model and Store to perform update operations --- .../Models/DataStore/VendorBalanceStore.php | 39 ------------------- includes/Models/VendorBalance.php | 17 ++++---- .../php/src/Models/VendorBalanceModelTest.php | 1 + 3 files changed, 8 insertions(+), 49 deletions(-) diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index 6f670122c5..0f9ebf6551 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -37,45 +37,6 @@ public function get_table_name(): string { return 'dokan_vendor_balance'; } - /** - * Update vendor balance by transaction. - * - * @since DOKAN_SINCE - * - * @param int $trn_id The transaction ID. - * @param string $trn_type The type of transaction. Valid values are - * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_ORDERS}, - * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_WITHDRAW}, - * and {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_REFUND}. - * @param array $data The data to update. - * - * @return bool True if updated successfully. False otherwise. - */ - public function update_by_transaction( int $trn_id, string $trn_type, array $data ) { - global $wpdb; - - $fields_format = $this->get_fields_with_format(); - $data_format = []; - - foreach ( $data as $key => $value ) { - $data_format[] = $fields_format[ $key ]; - } - - $result = $wpdb->update( - $this->get_table_name_with_prefix(), - $data, - [ - 'trn_id' => $trn_id, - 'trn_type' => $trn_type, - ], - $data_format, - [ '%d', '%s' ] - ); - - return $result; - } - - /** * Used to get perticulars through the get_perticulars method by the DataStore. * diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index 54f8a4be82..93a845a3e4 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -69,17 +69,14 @@ public function __construct( int $id = 0 ) { */ public static function update_by_transaction( int $trn_id, string $trn_type, array $data ): int { $model = new static(); - $model = new self(); - // $model->get_data_store()->update_by( - // [ - // 'trn_id' => $trn_id, - // 'trn_type' => $trn_type, - // ], - // $data - // ); - - return $model->data_store->update_by_transaction( $trn_id, $trn_type, $data ); + return $model->get_data_store()->update_by( + [ + 'trn_id' => $trn_id, + 'trn_type' => $trn_type, + ], + $data + ); } /** diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 29b4fd59c8..94c871cb61 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -5,6 +5,7 @@ use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\Test\DokanTestCase; + /** * @group data-store */ From a07efbf368f314136792f72a2b60b88045be0abd Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Wed, 6 Nov 2024 19:58:15 +0600 Subject: [PATCH 13/24] Refactor dokan order sync for vendor balance --- includes/Order/functions.php | 35 ++++++------------- .../php/src/Models/VendorBalanceModelTest.php | 18 ++++++---- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/includes/Order/functions.php b/includes/Order/functions.php index 6ddde73040..3c018191ab 100644 --- a/includes/Order/functions.php +++ b/includes/Order/functions.php @@ -1,6 +1,7 @@ insert( - $wpdb->prefix . 'dokan_vendor_balance', - [ - 'vendor_id' => $seller_id, - 'trn_id' => $order_id, - 'trn_type' => 'dokan_orders', - 'perticulars' => 'New order', - 'debit' => $net_amount, - 'credit' => 0, - 'status' => $order_status, - 'trn_date' => dokan_current_datetime()->format( 'Y-m-d H:i:s' ), - 'balance_date' => dokan_current_datetime()->modify( "+ $threshold_day days" )->format( 'Y-m-d H:i:s' ), - ], - [ - '%d', - '%d', - '%s', - '%s', - '%f', - '%f', - '%s', - '%s', - '%s', - ] - ); + $vendor_balance = new VendorBalance(); + $vendor_balance->set_vendor_id( $seller_id ); + $vendor_balance->set_trn_id( $order_id ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_particulars( 'New order' ); + $vendor_balance->set_debit( $net_amount ); + $vendor_balance->set_trn_date( dokan_current_datetime()->format( 'Y-m-d H:i:s' ) ); + $vendor_balance->set_balance_date( dokan_current_datetime()->modify( "+ $threshold_day days" )->format( 'Y-m-d H:i:s' ) ); + $vendor_balance->save(); } /** diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 94c871cb61..2c0ca3a238 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -10,12 +10,6 @@ * @group data-store */ class VendorBalanceModelTest extends DokanTestCase { - /** - * Indicates if the test is a unit test. - * - * @var bool - */ - protected $is_unit_test = true; public function test_save_method() { $vendor_balance = new VendorBalance(); @@ -113,4 +107,16 @@ public function test_update_transaction_method() { $updated_vendor_balance = new VendorBalance( $vendor_balance->get_id() ); $this->assertEquals( 200, $updated_vendor_balance->get_debit() ); } + + public function test_dokan_sync_insert_order_function() { + $order_id = $this->create_single_vendor_order( $this->seller_id1 ); + + $this->assertDatabaseHas( + 'dokan_vendor_balance', [ + 'trn_id' => $order_id, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_ORDERS, + 'vendor_id' => $this->seller_id1, + ] + ); + } } From a47f411999be063978200b15984e9824198b1a52 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Thu, 7 Nov 2024 11:34:42 +0600 Subject: [PATCH 14/24] Add database mission assertion method for test case --- tests/php/src/CustomAssertion/DBAssertionTrait.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/php/src/CustomAssertion/DBAssertionTrait.php b/tests/php/src/CustomAssertion/DBAssertionTrait.php index dd015b240a..89e951d3bc 100644 --- a/tests/php/src/CustomAssertion/DBAssertionTrait.php +++ b/tests/php/src/CustomAssertion/DBAssertionTrait.php @@ -74,4 +74,10 @@ public function assertDatabaseCount( string $table, int $count, array $data = [] $this->assertEquals( $count, $rows_count, "No rows found in `$table` for given data " . json_encode( $data ) ); } + + public function assertDatabaseMissing( string $table, array $data = [] ): void { + $rows_count = $this->getDatabaseCount( $table, $data ); + + $this->assertGreaterThanOrEqual( 0, $rows_count, "{$rows_count} rows found in `$table` for given data " . json_encode( $data ) ); + } } From f57f2faae9c95cc1b400461b0c0c81c578dc5622 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Thu, 7 Nov 2024 14:42:11 +0600 Subject: [PATCH 15/24] Add read_meta method to BaseStore --- includes/Models/DataStore/BaseDataStore.php | 28 +++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index 3a3ada9997..1052e94508 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -149,19 +149,7 @@ public function delete( BaseModel &$model, $args = array() ) { * @return int Number of affected rows. */ public function delete_by_id( $id ): int { - global $wpdb; - - $table_name = $this->get_table_name_with_prefix(); - $id_field_name = $this->get_id_field_name(); - $format = $this->get_id_field_format(); - - $result = $wpdb->query( - $wpdb->prepare( - "DELETE FROM {$table_name} - WHERE $id_field_name = $format", - $id - ) - ); + $result = $this->delete_by( [ $this->get_id_field_name() => $id ] ); do_action( $this->get_hook_prefix() . 'deleted', $id, $result ); @@ -185,10 +173,8 @@ public function delete_by( array $data ): int { $where_clause = $this->prepare_where_clause( $data ); $result = $wpdb->query( - $wpdb->prepare( - "DELETE FROM {$table_name} + "DELETE FROM {$table_name} WHERE {$where_clause}" - ) ); if ( $result === false ) { @@ -331,6 +317,16 @@ protected function map_model_to_db_data( BaseModel &$model ): array { return $data; } + + + /** + * Read meta data for the given model. Generally, We may not need this method. + * + * @param BaseModel $model The model for which to read meta data. + */ + public function read_meta( BaseModel &$model ): void { + } + /** * Maps database raw data to model data. * From 7d4b345855a11906855ce97d97e96788b7f6e363 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Thu, 7 Nov 2024 14:44:38 +0600 Subject: [PATCH 16/24] Add method to get vendor total balance --- .../Models/DataStore/VendorBalanceStore.php | 44 +++++++++++++ includes/Models/VendorBalance.php | 43 ++++++++++--- .../php/src/Models/VendorBalanceModelTest.php | 64 ++++++++++++++++++- 3 files changed, 139 insertions(+), 12 deletions(-) diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index 0f9ebf6551..840d560403 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -2,6 +2,7 @@ namespace WeDevs\Dokan\Models\DataStore; +use DateTimeImmutable; use WeDevs\Dokan\Models\BaseModel; use WeDevs\Dokan\Models\VendorBalance; @@ -47,4 +48,47 @@ public function get_table_name(): string { protected function get_perticulars( VendorBalance $model, string $context = 'edit' ): string { return $model->get_particulars( $context ); } + + /** + * Retrieve the total balance for a given vendor. + * + * @param int $vendor_id + * @param DateTimeImmutable $balance_date + * @return float + */ + public function get_total_balance( $vendor_id, DateTimeImmutable $balance_date ): float { + global $wpdb; + + $on_date = $balance_date->format( 'Y-m-d' ); + + $earning_status = dokan_withdraw_get_active_order_status(); + $earning_status[] = 'approved'; + $earning_status = apply_filters( 'dokan_vendor_total_balance_order_status', $earning_status, $vendor_id, $balance_date ); + $statuses_str = "'" . implode( "', '", esc_sql( $earning_status ) ) . "'"; + + $trn_types = apply_filters( + 'dokan_vendor_total_balance_trn_types', [ + VendorBalance::TRN_TYPE_DOKAN_ORDERS, + VendorBalance::TRN_TYPE_DOKAN_REFUND, + ], $vendor_id, $balance_date + ); + + $trn_types_str = "'" . implode( "', '", esc_sql( $trn_types ) ) . "'"; + + $this->add_sql_clause( 'select', 'SUM(debit) - SUM(credit) AS earnings' ); + $this->add_sql_clause( 'from', $this->get_table_name_with_prefix() ); + $this->add_sql_clause( 'where', $wpdb->prepare( ' AND vendor_id = %d', $vendor_id ) ); + $this->add_sql_clause( 'where', $wpdb->prepare( ' AND DATE(balance_date) <= %s ', $on_date ) ); + $this->add_sql_clause( 'where', " AND trn_type IN ($trn_types_str)" ); + $this->add_sql_clause( 'where', " AND status IN ($statuses_str)" ); + + $query_statement = $this->get_query_statement(); + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $earnings = $wpdb->get_var( + $query_statement + ); + + return floatval( $earnings ); + } } diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index 93a845a3e4..53e0bc9fca 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -3,6 +3,7 @@ namespace WeDevs\Dokan\Models; use WeDevs\Dokan\Models\DataStore\VendorBalanceStore; +use WeDevs\Dokan\Cache; class VendorBalance extends BaseModel { const TRN_TYPE_DOKAN_ORDERS = 'dokan_orders'; @@ -96,7 +97,7 @@ public function get_vendor_id( string $context = 'view' ) { * @return void */ public function set_vendor_id( int $id ) { - return $this->set_prop( 'vendor_id', $id ); + $this->set_prop( 'vendor_id', $id ); } /** @@ -116,7 +117,7 @@ public function get_trn_id( string $context = 'view' ) { * @return void */ public function set_trn_id( int $id ) { - return $this->set_prop( 'trn_id', $id ); + $this->set_prop( 'trn_id', $id ); } /** @@ -138,7 +139,7 @@ public function get_trn_type( string $context = 'view' ) { * @return void */ public function set_trn_type( string $type ) { - return $this->set_prop( 'trn_type', $type ); + $this->set_prop( 'trn_type', $type ); } /** @@ -158,7 +159,7 @@ public function get_particulars( string $context = 'view' ): string { * @return void */ public function set_particulars( string $note ) { - return $this->set_prop( 'perticulars', $note ); + $this->set_prop( 'perticulars', $note ); } /** @@ -168,7 +169,7 @@ public function set_particulars( string $note ) { * @return void */ protected function set_perticulars( string $note ) { - return $this->set_particulars( $note ); + $this->set_particulars( $note ); } /** @@ -188,7 +189,7 @@ public function get_debit( string $context = 'view' ): float { * @return void */ public function set_debit( float $amount ) { - return $this->set_prop( 'debit', $amount ); + $this->set_prop( 'debit', $amount ); } /** @@ -208,7 +209,7 @@ public function get_credit( string $context = 'view' ) { * @return void */ public function set_credit( float $amount ) { - return $this->set_prop( 'credit', $amount ); + $this->set_prop( 'credit', $amount ); } /** @@ -228,7 +229,7 @@ public function get_status( string $context = 'view' ): string { * @return void */ public function set_status( string $status ) { - return $this->set_prop( 'status', $status ); + $this->set_prop( 'status', $status ); } /** @@ -248,7 +249,7 @@ public function get_trn_date( string $context = 'view' ) { * @return void */ public function set_trn_date( string $date ) { - return $this->set_date_prop( 'trn_date', $date ); + $this->set_date_prop( 'trn_date', $date ); } /** @@ -268,6 +269,28 @@ public function get_balance_date( string $context = 'view' ) { * @return void */ public function set_balance_date( string $date ) { - return $this->set_date_prop( 'balance_date', $date ); + $this->set_date_prop( 'balance_date', $date ); + } + + /** + * Get the total balance of a vendor at a given date. + * + * @param int $vendor_id The vendor id. + * @param string $on_date Date in Y-m-d format. If not provided, current date is used. + * + * @return float The total balance of the vendor at the given date. + */ + public static function get_total_balance_by_vendor( $vendor_id, $on_date = null ) { + $on_date = $on_date && strtotime( $on_date ) ? dokan_current_datetime()->modify( $on_date ) : dokan_current_datetime(); + $cache_group = "seller_order_data_{$vendor_id}"; + $cache_key = "seller_earnings_{$vendor_id}_{$on_date->format('Y_m_d')}"; + $earning = Cache::get( $cache_key, $cache_group ); + + if ( false === $earning ) { + $earning = ( new static() )->get_data_store()->get_total_balance( $vendor_id, $on_date ); + Cache::set( $cache_key, $earning, $cache_group ); + } + + return $earning; } } diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 2c0ca3a238..760d03df83 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -2,9 +2,10 @@ namespace WeDevs\Dokan\Test\Models; +use Mockery; use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\Test\DokanTestCase; - +use WeDevs\DokanPro\Modules\DeliveryTime\StorePickup\Vendor; /** * @group data-store @@ -110,13 +111,72 @@ public function test_update_transaction_method() { public function test_dokan_sync_insert_order_function() { $order_id = $this->create_single_vendor_order( $this->seller_id1 ); + // Clear data that were created by create_single_vendor_order + dokan()->order->delete_seller_order( $order_id, $this->seller_id1 ); - $this->assertDatabaseHas( + $this->assertDatabaseMissing( 'dokan_vendor_balance', [ 'trn_id' => $order_id, + ] + ); + + $order_service = Mockery::mock( '\WeDevs\Dokan\Order\Manager[is_order_already_synced]' ); + $order_service->shouldReceive( 'is_order_already_synced' )->andReturn( false ); + dokan()->get_container()->extend( 'order' )->setConcrete( $order_service ); + // Resync the order. + dokan_sync_insert_order( $order_id ); + + // Check if balance is created for the order + $this->assertDatabaseCount( + 'dokan_vendor_balance', 1, [ + 'trn_id' => $order_id, 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_ORDERS, 'vendor_id' => $this->seller_id1, ] ); } + + public function test_get_total_balance_by_vendor_method() { + + $trn_id = 1; + + $vendor_balance = new VendorBalance(); + $vendor_balance->set_particulars( 'test' ); + $vendor_balance->set_debit( 100 ); + $vendor_balance->set_status( 'wc-completed' ); + $vendor_balance->set_trn_id( $trn_id++ ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_vendor_id( $this->seller_id1 ); + $vendor_balance->set_trn_date( '2020-01-01' ); + $vendor_balance->set_balance_date( '2020-01-01' ); + $vendor_balance->save(); + + $vendor_balance->set_id( 0 ); + $vendor_balance->set_debit( 0 ); + + $vendor_balance_2 = clone $vendor_balance; + $vendor_balance_2->set_trn_id( $trn_id++ ); + $vendor_balance_2->set_debit( 60 ); + $vendor_balance_2->save(); + + $vendor_balance_refund = clone $vendor_balance; + $vendor_balance_refund->set_trn_id( $trn_id++ ); + $vendor_balance_refund->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_REFUND ); + $vendor_balance_refund->set_status( 'approved' ); + $vendor_balance_refund->set_credit( 20 ); + $vendor_balance_refund->save(); + + $vendor_balance_withdraw = clone $vendor_balance; + $vendor_balance_withdraw->set_trn_id( $trn_id++ ); + $vendor_balance_withdraw->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_WITHDRAW ); + $vendor_balance_withdraw->set_status( 'approved' ); + $vendor_balance_withdraw->set_credit( 30 ); + $vendor_balance_withdraw->save(); + + $total_balance = VendorBalance::get_total_balance_by_vendor( + $this->seller_id1 + ); + // 100 + 60 - 20 - 30 = 110 + $this->assertGreaterThan( 110, $total_balance ); + } } From d348e0c2ef3f3b7d65f3c7ec00f69394b1c8ef48 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Thu, 7 Nov 2024 14:49:58 +0600 Subject: [PATCH 17/24] Fetch vendor balance with the method of VendorBalance Method --- includes/Vendor/Vendor.php | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/includes/Vendor/Vendor.php b/includes/Vendor/Vendor.php index 3449228979..6f4da3fbc4 100644 --- a/includes/Vendor/Vendor.php +++ b/includes/Vendor/Vendor.php @@ -5,6 +5,7 @@ use Automattic\WooCommerce\Utilities\NumberUtil; use WC_Order; use WeDevs\Dokan\Cache; +use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\Product\ProductCache; use WP_Error; use WP_Query; @@ -786,40 +787,7 @@ public function get_product_views() { * @return float|string float if formatted is false, string otherwise */ public function get_earnings( $formatted = true, $on_date = '' ) { - global $wpdb; - - $on_date = $on_date && strtotime( $on_date ) ? dokan_current_datetime()->modify( $on_date ) : dokan_current_datetime(); - $cache_group = "seller_order_data_{$this->get_id()}"; - $cache_key = "seller_earnings_{$this->get_id()}_{$on_date->format('Y_m_d')}"; - $earning = Cache::get( $cache_key, $cache_group ); - $on_date = $on_date->format( 'Y-m-d H:i:s' ); - - if ( false === $earning ) { - $status = dokan_withdraw_get_active_order_status_in_comma(); - $debit_balance = $wpdb->get_var( - $wpdb->prepare( - "SELECT SUM(debit) AS earnings - FROM {$wpdb->prefix}dokan_vendor_balance - WHERE - vendor_id = %d AND DATE(balance_date) <= %s AND status IN ($status) AND trn_type = 'dokan_orders'", - $this->id, $on_date - ) - ); - - $credit_balance = $wpdb->get_var( - $wpdb->prepare( - "SELECT SUM(credit) AS earnings - FROM {$wpdb->prefix}dokan_vendor_balance - WHERE - vendor_id = %d AND DATE(balance_date) <= %s AND trn_type = %s AND status = %s", - $this->id, $on_date, 'dokan_refund', 'approved' - ) - ); - - $earning = floatval( $debit_balance - $credit_balance ); - - Cache::set( $cache_key, $earning, $cache_group ); - } + $earning = VendorBalance::get_total_balance_by_vendor( $this->id, $on_date ); if ( $formatted ) { return apply_filters( 'dokan_get_formatted_seller_earnings', wc_price( $earning ), $this->id ); From 45965a3a3f6204605978ecf46d53012abfa34b29 Mon Sep 17 00:00:00 2001 From: Mahbub Rabbani Date: Fri, 8 Nov 2024 09:31:21 +0600 Subject: [PATCH 18/24] Refactor/vendor balance raw query (#2430) * Add delete_by method * Add update_by to base store * Add general update query method in Data Sotre * Update vendor balance Model and Store to perform update operations * Refactor dokan order sync for vendor balance * Add database mission assertion method for test case * Add read_meta method to BaseStore * Add method to get vendor total balance * Fetch vendor balance with the method of VendorBalance Method --- includes/Commission.php | 15 +- includes/Models/BaseModel.php | 11 ++ includes/Models/DataStore/BaseDataStore.php | 137 ++++++++++++++++-- .../Models/DataStore/VendorBalanceStore.php | 56 +++++++ includes/Models/VendorBalance.php | 67 +++++++-- includes/Order/Hooks.php | 14 +- includes/Order/functions.php | 36 ++--- includes/Vendor/Vendor.php | 36 +---- .../src/CustomAssertion/DBAssertionTrait.php | 6 + .../php/src/Models/VendorBalanceModelTest.php | 104 ++++++++++++- 10 files changed, 376 insertions(+), 106 deletions(-) diff --git a/includes/Commission.php b/includes/Commission.php index f0040530b4..26893b301d 100644 --- a/includes/Commission.php +++ b/includes/Commission.php @@ -4,7 +4,9 @@ use WC_Order; use WC_Product; +use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\ProductCategory\Helper; +use WeDevs\DokanPro\Modules\DeliveryTime\StorePickup\Vendor; use WP_Error; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -97,15 +99,10 @@ public function calculate_gateway_fee( $order_id ) { [ '%d' ] ); - $wpdb->update( - $wpdb->dokan_vendor_balance, - [ 'debit' => (float) $net_amount ], - [ - 'trn_id' => $tmp_order->get_id(), - 'trn_type' => 'dokan_orders', - ], - [ '%f' ], - [ '%d', '%s' ] + VendorBalance::update_by_transaction( + $tmp_order->get_id(), + 'dokan_orders', + [ 'debit' => (float) $net_amount ] ); $tmp_order->update_meta_data( 'dokan_gateway_fee', $gateway_fee ); diff --git a/includes/Models/BaseModel.php b/includes/Models/BaseModel.php index ac4b151032..fb0fade3c8 100644 --- a/includes/Models/BaseModel.php +++ b/includes/Models/BaseModel.php @@ -72,6 +72,17 @@ public function delete( $force_delete = false ) { return false; } + /** + * Delete raws from the database. + * + * @param array $data Array of args to delete an object, e.g. `array( 'id' => 1, status => ['draft', 'cancelled'] )` or `array( 'id' => 1, 'status' => 'publish' )`. + * @return bool result + */ + public static function delete_by( array $data ) { + $object = new static(); + return $object->data_store->delete_by( $data ); + } + /** * Prefix for action and filter hooks on data. * diff --git a/includes/Models/DataStore/BaseDataStore.php b/includes/Models/DataStore/BaseDataStore.php index d98a3ed3da..1052e94508 100644 --- a/includes/Models/DataStore/BaseDataStore.php +++ b/includes/Models/DataStore/BaseDataStore.php @@ -7,6 +7,11 @@ use Exception; use WeDevs\Dokan\Models\BaseModel; +/** + * Base data store class. + * + * @since DOKAN_SINCE + */ abstract class BaseDataStore extends SqlQuery implements DataStoreInterface { protected $selected_columns = [ '*' ]; @@ -49,7 +54,7 @@ public function create( BaseModel &$model ) { * * @param BaseModel $model BaseModel object. * - * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * @phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared * * @throws Exception Throw exception if invalid entity is passed. */ @@ -139,26 +144,124 @@ public function delete( BaseModel &$model, $args = array() ) { * * @param int $id permission_id of the download to be deleted. * - * @phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * @phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * + * @return int Number of affected rows. + */ + public function delete_by_id( $id ): int { + $result = $this->delete_by( [ $this->get_id_field_name() => $id ] ); + + do_action( $this->get_hook_prefix() . 'deleted', $id, $result ); + + return $result; + } + + /** + * Delete raws from the database. + * + * @param array $data Array of args to delete an object, e.g. `array( 'id' => 1, status => ['draft', 'cancelled'] )` or `array( 'id' => 1, 'status' => 'publish' )`. + * + * @phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + * + * @return int Number of affected rows. */ - public function delete_by_id( $id ) { + public function delete_by( array $data ): int { global $wpdb; $table_name = $this->get_table_name_with_prefix(); - $id_field_name = $this->get_id_field_name(); - $format = $this->get_id_field_format(); + + $where_clause = $this->prepare_where_clause( $data ); $result = $wpdb->query( - $wpdb->prepare( - "DELETE FROM {$table_name} - WHERE $id_field_name = $format", - $id - ) + "DELETE FROM {$table_name} + WHERE {$where_clause}" ); - do_action( $this->get_hook_prefix() . 'deleted', $id, $result ); + if ( $result === false ) { + throw new Exception( esc_html__( 'Failed to delete.', 'dokan-lite' ) ); + } - return $result; + return (int) $result; + } + + /** + * Updates rows in the database. + * + * @param array $where Array of args to identify the object to be updated, e.g. `array( 'id' => 1, 'status' => 'draft' )`. + * @param array $data_to_update Array of args to update the object, e.g. `array( 'status' => 'publish' )`. + * + * @return int Number of affected rows. + */ + public function update_by( array $where, array $data_to_update ) { + global $wpdb; + + $fields_format = $this->get_fields_with_format(); + $field_format[ $this->get_id_field_name() ] = $this->get_id_field_format(); + + $data_format = []; + + foreach ( $data_to_update as $key => $value ) { + $data_format[] = $fields_format[ $key ]; + } + + $where_format = []; + + foreach ( $where as $key => $value ) { + $where_format[] = $fields_format[ $key ]; + } + + $result = $wpdb->update( + $this->get_table_name_with_prefix(), + $data_to_update, + $where, + $data_format, + $where_format + ); + + if ( $result === false ) { + throw new Exception( esc_html__( 'Failed to update.', 'dokan-lite' ) ); + } + + return (int) $result; + } + + /** + * Prepares a SQL WHERE clause from an associative array of data. + * + * This method takes an array of data where keys are column names and values + * are the values to filter by. It generates a secure SQL WHERE clause using + * prepared statements to protect against SQL injection. + * + * If a value in the data array is an array itself, it generates an IN clause + * with multiple placeholders. Otherwise, it generates a simple equality check. + * + * @param array $data Associative array of column names and values to filter by. + * @return string The generated WHERE clause. + */ + protected function prepare_where_clause( array $data ): string { + global $wpdb; + + $where = [ '1=1' ]; + + $field_format = $this->get_fields_with_format(); + + // Add ID field format for the WHERE clause. + $field_format[ $this->get_id_field_name() ] = $this->get_id_field_format(); + + foreach ( $data as $key => $value ) { + if ( is_array( $value ) ) { + // Fill format array based on the number of values and specified format for the key. + $placeholders = implode( ',', array_fill( 0, count( $value ), '%s' ) ); + + // Generate the WHERE clause securely using $wpdb->prepare() with placeholders. + $where[] = $wpdb->prepare( "{$key} IN ({$placeholders})", ...$value ); + } else { + $format = $field_format[ $key ]; + $where[] = $wpdb->prepare( "{$key} = {$format}", $value ); + } + } + + return implode( ' AND ', $where ); } /** @@ -214,6 +317,16 @@ protected function map_model_to_db_data( BaseModel &$model ): array { return $data; } + + + /** + * Read meta data for the given model. Generally, We may not need this method. + * + * @param BaseModel $model The model for which to read meta data. + */ + public function read_meta( BaseModel &$model ): void { + } + /** * Maps database raw data to model data. * diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index 304b034e65..840d560403 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -2,11 +2,20 @@ namespace WeDevs\Dokan\Models\DataStore; +use DateTimeImmutable; use WeDevs\Dokan\Models\BaseModel; use WeDevs\Dokan\Models\VendorBalance; +/** + * Class VendorBalanceStore + * + * @since DOKAN_SINCE + */ class VendorBalanceStore extends BaseDataStore { + /** + * @inheritDoc + */ protected function get_fields_with_format(): array { return [ 'vendor_id' => '%d', @@ -21,6 +30,10 @@ protected function get_fields_with_format(): array { ]; } + + /** + * @inheritDoc + */ public function get_table_name(): string { return 'dokan_vendor_balance'; } @@ -35,4 +48,47 @@ public function get_table_name(): string { protected function get_perticulars( VendorBalance $model, string $context = 'edit' ): string { return $model->get_particulars( $context ); } + + /** + * Retrieve the total balance for a given vendor. + * + * @param int $vendor_id + * @param DateTimeImmutable $balance_date + * @return float + */ + public function get_total_balance( $vendor_id, DateTimeImmutable $balance_date ): float { + global $wpdb; + + $on_date = $balance_date->format( 'Y-m-d' ); + + $earning_status = dokan_withdraw_get_active_order_status(); + $earning_status[] = 'approved'; + $earning_status = apply_filters( 'dokan_vendor_total_balance_order_status', $earning_status, $vendor_id, $balance_date ); + $statuses_str = "'" . implode( "', '", esc_sql( $earning_status ) ) . "'"; + + $trn_types = apply_filters( + 'dokan_vendor_total_balance_trn_types', [ + VendorBalance::TRN_TYPE_DOKAN_ORDERS, + VendorBalance::TRN_TYPE_DOKAN_REFUND, + ], $vendor_id, $balance_date + ); + + $trn_types_str = "'" . implode( "', '", esc_sql( $trn_types ) ) . "'"; + + $this->add_sql_clause( 'select', 'SUM(debit) - SUM(credit) AS earnings' ); + $this->add_sql_clause( 'from', $this->get_table_name_with_prefix() ); + $this->add_sql_clause( 'where', $wpdb->prepare( ' AND vendor_id = %d', $vendor_id ) ); + $this->add_sql_clause( 'where', $wpdb->prepare( ' AND DATE(balance_date) <= %s ', $on_date ) ); + $this->add_sql_clause( 'where', " AND trn_type IN ($trn_types_str)" ); + $this->add_sql_clause( 'where', " AND status IN ($statuses_str)" ); + + $query_statement = $this->get_query_statement(); + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $earnings = $wpdb->get_var( + $query_statement + ); + + return floatval( $earnings ); + } } diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index ef10d8e4d1..53e0bc9fca 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -3,6 +3,7 @@ namespace WeDevs\Dokan\Models; use WeDevs\Dokan\Models\DataStore\VendorBalanceStore; +use WeDevs\Dokan\Cache; class VendorBalance extends BaseModel { const TRN_TYPE_DOKAN_ORDERS = 'dokan_orders'; @@ -55,6 +56,30 @@ public function __construct( int $id = 0 ) { } } + /** + * Updates the vendor balance based on a transaction. + * + * @param int $trn_id The transaction ID. + * @param string $trn_type The type of transaction. Valid values are + * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_ORDERS}, + * {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_WITHDRAW}, + * and {@see WeDevs\Dokan\Models\VendorBalance::TRN_TYPE_DOKAN_REFUND}. + * @param array $data The data to update. + * + * @return int Number of affected rows. + */ + public static function update_by_transaction( int $trn_id, string $trn_type, array $data ): int { + $model = new static(); + + return $model->get_data_store()->update_by( + [ + 'trn_id' => $trn_id, + 'trn_type' => $trn_type, + ], + $data + ); + } + /** * Gets the vendor ID of the vendor balance. * @@ -72,7 +97,7 @@ public function get_vendor_id( string $context = 'view' ) { * @return void */ public function set_vendor_id( int $id ) { - return $this->set_prop( 'vendor_id', $id ); + $this->set_prop( 'vendor_id', $id ); } /** @@ -92,7 +117,7 @@ public function get_trn_id( string $context = 'view' ) { * @return void */ public function set_trn_id( int $id ) { - return $this->set_prop( 'trn_id', $id ); + $this->set_prop( 'trn_id', $id ); } /** @@ -114,7 +139,7 @@ public function get_trn_type( string $context = 'view' ) { * @return void */ public function set_trn_type( string $type ) { - return $this->set_prop( 'trn_type', $type ); + $this->set_prop( 'trn_type', $type ); } /** @@ -134,7 +159,7 @@ public function get_particulars( string $context = 'view' ): string { * @return void */ public function set_particulars( string $note ) { - return $this->set_prop( 'perticulars', $note ); + $this->set_prop( 'perticulars', $note ); } /** @@ -144,7 +169,7 @@ public function set_particulars( string $note ) { * @return void */ protected function set_perticulars( string $note ) { - return $this->set_particulars( $note ); + $this->set_particulars( $note ); } /** @@ -164,7 +189,7 @@ public function get_debit( string $context = 'view' ): float { * @return void */ public function set_debit( float $amount ) { - return $this->set_prop( 'debit', $amount ); + $this->set_prop( 'debit', $amount ); } /** @@ -184,7 +209,7 @@ public function get_credit( string $context = 'view' ) { * @return void */ public function set_credit( float $amount ) { - return $this->set_prop( 'credit', $amount ); + $this->set_prop( 'credit', $amount ); } /** @@ -204,7 +229,7 @@ public function get_status( string $context = 'view' ): string { * @return void */ public function set_status( string $status ) { - return $this->set_prop( 'status', $status ); + $this->set_prop( 'status', $status ); } /** @@ -224,7 +249,7 @@ public function get_trn_date( string $context = 'view' ) { * @return void */ public function set_trn_date( string $date ) { - return $this->set_date_prop( 'trn_date', $date ); + $this->set_date_prop( 'trn_date', $date ); } /** @@ -244,6 +269,28 @@ public function get_balance_date( string $context = 'view' ) { * @return void */ public function set_balance_date( string $date ) { - return $this->set_date_prop( 'balance_date', $date ); + $this->set_date_prop( 'balance_date', $date ); + } + + /** + * Get the total balance of a vendor at a given date. + * + * @param int $vendor_id The vendor id. + * @param string $on_date Date in Y-m-d format. If not provided, current date is used. + * + * @return float The total balance of the vendor at the given date. + */ + public static function get_total_balance_by_vendor( $vendor_id, $on_date = null ) { + $on_date = $on_date && strtotime( $on_date ) ? dokan_current_datetime()->modify( $on_date ) : dokan_current_datetime(); + $cache_group = "seller_order_data_{$vendor_id}"; + $cache_key = "seller_earnings_{$vendor_id}_{$on_date->format('Y_m_d')}"; + $earning = Cache::get( $cache_key, $cache_group ); + + if ( false === $earning ) { + $earning = ( new static() )->get_data_store()->get_total_balance( $vendor_id, $on_date ); + Cache::set( $cache_key, $earning, $cache_group ); + } + + return $earning; } } diff --git a/includes/Order/Hooks.php b/includes/Order/Hooks.php index 3bef0c4f5d..5197a87054 100644 --- a/includes/Order/Hooks.php +++ b/includes/Order/Hooks.php @@ -4,6 +4,7 @@ use Exception; use WC_Order; +use WeDevs\Dokan\Models\VendorBalance; // don't call the file directly if ( ! defined( 'ABSPATH' ) ) { @@ -125,15 +126,10 @@ public function on_order_status_change( $order_id, $old_status, $new_status, $or } // update on vendor-balance table - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $wpdb->dokan_vendor_balance, - [ 'status' => $new_status ], - [ - 'trn_id' => $order_id, - 'trn_type' => 'dokan_orders', - ], - [ '%s' ], - [ '%d', '%s' ] + VendorBalance::update_by_transaction( + $order_id, + VendorBalance::TRN_TYPE_DOKAN_ORDERS, + [ 'status' => $new_status ] ); } diff --git a/includes/Order/functions.php b/includes/Order/functions.php index d831572676..6a92ad3da3 100644 --- a/includes/Order/functions.php +++ b/includes/Order/functions.php @@ -1,6 +1,7 @@ insert( - $wpdb->prefix . 'dokan_vendor_balance', - [ - 'vendor_id' => $seller_id, - 'trn_id' => $order_id, - 'trn_type' => 'dokan_orders', - 'perticulars' => 'New order', - 'debit' => $net_amount, - 'credit' => 0, - 'status' => $order_status, - 'trn_date' => dokan_current_datetime()->format( 'Y-m-d H:i:s' ), - 'balance_date' => dokan_current_datetime()->modify( "+ $threshold_day days" )->format( 'Y-m-d H:i:s' ), - ], - [ - '%d', - '%d', - '%s', - '%s', - '%f', - '%f', - '%s', - '%s', - '%s', - ] - ); + $vendor_balance = new VendorBalance(); + $vendor_balance->set_vendor_id( $seller_id ); + $vendor_balance->set_trn_id( $order_id ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_particulars( 'New order' ); + $vendor_balance->set_debit( $net_amount ); + $vendor_balance->set_trn_date( dokan_current_datetime()->format( 'Y-m-d H:i:s' ) ); + $vendor_balance->set_balance_date( dokan_current_datetime()->modify( "+ $threshold_day days" )->format( 'Y-m-d H:i:s' ) ); + $vendor_balance->save(); } /** diff --git a/includes/Vendor/Vendor.php b/includes/Vendor/Vendor.php index 3449228979..6f4da3fbc4 100644 --- a/includes/Vendor/Vendor.php +++ b/includes/Vendor/Vendor.php @@ -5,6 +5,7 @@ use Automattic\WooCommerce\Utilities\NumberUtil; use WC_Order; use WeDevs\Dokan\Cache; +use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\Product\ProductCache; use WP_Error; use WP_Query; @@ -786,40 +787,7 @@ public function get_product_views() { * @return float|string float if formatted is false, string otherwise */ public function get_earnings( $formatted = true, $on_date = '' ) { - global $wpdb; - - $on_date = $on_date && strtotime( $on_date ) ? dokan_current_datetime()->modify( $on_date ) : dokan_current_datetime(); - $cache_group = "seller_order_data_{$this->get_id()}"; - $cache_key = "seller_earnings_{$this->get_id()}_{$on_date->format('Y_m_d')}"; - $earning = Cache::get( $cache_key, $cache_group ); - $on_date = $on_date->format( 'Y-m-d H:i:s' ); - - if ( false === $earning ) { - $status = dokan_withdraw_get_active_order_status_in_comma(); - $debit_balance = $wpdb->get_var( - $wpdb->prepare( - "SELECT SUM(debit) AS earnings - FROM {$wpdb->prefix}dokan_vendor_balance - WHERE - vendor_id = %d AND DATE(balance_date) <= %s AND status IN ($status) AND trn_type = 'dokan_orders'", - $this->id, $on_date - ) - ); - - $credit_balance = $wpdb->get_var( - $wpdb->prepare( - "SELECT SUM(credit) AS earnings - FROM {$wpdb->prefix}dokan_vendor_balance - WHERE - vendor_id = %d AND DATE(balance_date) <= %s AND trn_type = %s AND status = %s", - $this->id, $on_date, 'dokan_refund', 'approved' - ) - ); - - $earning = floatval( $debit_balance - $credit_balance ); - - Cache::set( $cache_key, $earning, $cache_group ); - } + $earning = VendorBalance::get_total_balance_by_vendor( $this->id, $on_date ); if ( $formatted ) { return apply_filters( 'dokan_get_formatted_seller_earnings', wc_price( $earning ), $this->id ); diff --git a/tests/php/src/CustomAssertion/DBAssertionTrait.php b/tests/php/src/CustomAssertion/DBAssertionTrait.php index dd015b240a..89e951d3bc 100644 --- a/tests/php/src/CustomAssertion/DBAssertionTrait.php +++ b/tests/php/src/CustomAssertion/DBAssertionTrait.php @@ -74,4 +74,10 @@ public function assertDatabaseCount( string $table, int $count, array $data = [] $this->assertEquals( $count, $rows_count, "No rows found in `$table` for given data " . json_encode( $data ) ); } + + public function assertDatabaseMissing( string $table, array $data = [] ): void { + $rows_count = $this->getDatabaseCount( $table, $data ); + + $this->assertGreaterThanOrEqual( 0, $rows_count, "{$rows_count} rows found in `$table` for given data " . json_encode( $data ) ); + } } diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 73abfabde2..760d03df83 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -2,16 +2,15 @@ namespace WeDevs\Dokan\Test\Models; +use Mockery; use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\Test\DokanTestCase; +use WeDevs\DokanPro\Modules\DeliveryTime\StorePickup\Vendor; +/** + * @group data-store + */ class VendorBalanceModelTest extends DokanTestCase { - /** - * Indicates if the test is a unit test. - * - * @var bool - */ - protected $is_unit_test = true; public function test_save_method() { $vendor_balance = new VendorBalance(); @@ -87,4 +86,97 @@ public function test_read_method() { $this->assertEquals( $vendor_balance->get_status(), $existing_vendor_balance->get_status() ); $this->assertEquals( $vendor_balance->get_balance_date()->date_i18n(), $existing_vendor_balance->get_balance_date()->date_i18n() ); } + + public function test_update_transaction_method() { + $vendor_balance = new VendorBalance(); + $vendor_balance->set_particulars( 'test' ); + $vendor_balance->set_debit( 100 ); + $vendor_balance->set_trn_id( 1 ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_vendor_id( 1 ); + $vendor_balance->set_trn_date( '2020-01-01' ); + $vendor_balance->set_status( 'wc-pending' ); + $vendor_balance->set_balance_date( '2020-01-01' ); + $vendor_balance->save(); + + $result = VendorBalance::update_by_transaction( + 1, VendorBalance::TRN_TYPE_DOKAN_ORDERS, [ + 'debit' => 200, + ] + ); + + $updated_vendor_balance = new VendorBalance( $vendor_balance->get_id() ); + $this->assertEquals( 200, $updated_vendor_balance->get_debit() ); + } + + public function test_dokan_sync_insert_order_function() { + $order_id = $this->create_single_vendor_order( $this->seller_id1 ); + // Clear data that were created by create_single_vendor_order + dokan()->order->delete_seller_order( $order_id, $this->seller_id1 ); + + $this->assertDatabaseMissing( + 'dokan_vendor_balance', [ + 'trn_id' => $order_id, + ] + ); + + $order_service = Mockery::mock( '\WeDevs\Dokan\Order\Manager[is_order_already_synced]' ); + $order_service->shouldReceive( 'is_order_already_synced' )->andReturn( false ); + dokan()->get_container()->extend( 'order' )->setConcrete( $order_service ); + // Resync the order. + dokan_sync_insert_order( $order_id ); + + // Check if balance is created for the order + $this->assertDatabaseCount( + 'dokan_vendor_balance', 1, [ + 'trn_id' => $order_id, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_ORDERS, + 'vendor_id' => $this->seller_id1, + ] + ); + } + + public function test_get_total_balance_by_vendor_method() { + + $trn_id = 1; + + $vendor_balance = new VendorBalance(); + $vendor_balance->set_particulars( 'test' ); + $vendor_balance->set_debit( 100 ); + $vendor_balance->set_status( 'wc-completed' ); + $vendor_balance->set_trn_id( $trn_id++ ); + $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_vendor_id( $this->seller_id1 ); + $vendor_balance->set_trn_date( '2020-01-01' ); + $vendor_balance->set_balance_date( '2020-01-01' ); + $vendor_balance->save(); + + $vendor_balance->set_id( 0 ); + $vendor_balance->set_debit( 0 ); + + $vendor_balance_2 = clone $vendor_balance; + $vendor_balance_2->set_trn_id( $trn_id++ ); + $vendor_balance_2->set_debit( 60 ); + $vendor_balance_2->save(); + + $vendor_balance_refund = clone $vendor_balance; + $vendor_balance_refund->set_trn_id( $trn_id++ ); + $vendor_balance_refund->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_REFUND ); + $vendor_balance_refund->set_status( 'approved' ); + $vendor_balance_refund->set_credit( 20 ); + $vendor_balance_refund->save(); + + $vendor_balance_withdraw = clone $vendor_balance; + $vendor_balance_withdraw->set_trn_id( $trn_id++ ); + $vendor_balance_withdraw->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_WITHDRAW ); + $vendor_balance_withdraw->set_status( 'approved' ); + $vendor_balance_withdraw->set_credit( 30 ); + $vendor_balance_withdraw->save(); + + $total_balance = VendorBalance::get_total_balance_by_vendor( + $this->seller_id1 + ); + // 100 + 60 - 20 - 30 = 110 + $this->assertGreaterThan( 110, $total_balance ); + } } From a8ce67dc3bffee4d6d106b27406ac7fce44bf0c5 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Fri, 8 Nov 2024 15:36:42 +0600 Subject: [PATCH 19/24] Rename method get_total_balance_by_vendor to test_get_total_earning_by_vendor_method --- includes/Models/DataStore/VendorBalanceStore.php | 2 +- includes/Models/VendorBalance.php | 4 ++-- includes/Vendor/Vendor.php | 2 +- tests/php/src/Models/VendorBalanceModelTest.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index 840d560403..ba83784950 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -56,7 +56,7 @@ protected function get_perticulars( VendorBalance $model, string $context = 'edi * @param DateTimeImmutable $balance_date * @return float */ - public function get_total_balance( $vendor_id, DateTimeImmutable $balance_date ): float { + public function get_earning_balance( $vendor_id, DateTimeImmutable $balance_date ): float { global $wpdb; $on_date = $balance_date->format( 'Y-m-d' ); diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index 53e0bc9fca..e53941393a 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -280,14 +280,14 @@ public function set_balance_date( string $date ) { * * @return float The total balance of the vendor at the given date. */ - public static function get_total_balance_by_vendor( $vendor_id, $on_date = null ) { + public static function get_total_earning_by_vendor( $vendor_id, $on_date = null ) { $on_date = $on_date && strtotime( $on_date ) ? dokan_current_datetime()->modify( $on_date ) : dokan_current_datetime(); $cache_group = "seller_order_data_{$vendor_id}"; $cache_key = "seller_earnings_{$vendor_id}_{$on_date->format('Y_m_d')}"; $earning = Cache::get( $cache_key, $cache_group ); if ( false === $earning ) { - $earning = ( new static() )->get_data_store()->get_total_balance( $vendor_id, $on_date ); + $earning = ( new static() )->get_data_store()->get_earning_balance( $vendor_id, $on_date ); Cache::set( $cache_key, $earning, $cache_group ); } diff --git a/includes/Vendor/Vendor.php b/includes/Vendor/Vendor.php index 6f4da3fbc4..4d986145a3 100644 --- a/includes/Vendor/Vendor.php +++ b/includes/Vendor/Vendor.php @@ -787,7 +787,7 @@ public function get_product_views() { * @return float|string float if formatted is false, string otherwise */ public function get_earnings( $formatted = true, $on_date = '' ) { - $earning = VendorBalance::get_total_balance_by_vendor( $this->id, $on_date ); + $earning = VendorBalance::get_total_earning_by_vendor( $this->id, $on_date ); if ( $formatted ) { return apply_filters( 'dokan_get_formatted_seller_earnings', wc_price( $earning ), $this->id ); diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 760d03df83..25b21de903 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -136,7 +136,7 @@ public function test_dokan_sync_insert_order_function() { ); } - public function test_get_total_balance_by_vendor_method() { + public function test_get_total_earning_by_vendor_method() { $trn_id = 1; @@ -173,7 +173,7 @@ public function test_get_total_balance_by_vendor_method() { $vendor_balance_withdraw->set_credit( 30 ); $vendor_balance_withdraw->save(); - $total_balance = VendorBalance::get_total_balance_by_vendor( + $total_balance = VendorBalance::get_total_earning_by_vendor( $this->seller_id1 ); // 100 + 60 - 20 - 30 = 110 From 59ea07f1f5d03b29da9ccb452ac65069c7aee359 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Fri, 8 Nov 2024 15:53:04 +0600 Subject: [PATCH 20/24] Update docs blocks --- includes/Models/DataStore/VendorBalanceStore.php | 2 +- includes/Models/VendorBalance.php | 15 ++++++++++----- tests/php/src/Models/VendorBalanceModelTest.php | 1 - 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/includes/Models/DataStore/VendorBalanceStore.php b/includes/Models/DataStore/VendorBalanceStore.php index ba83784950..af05b95212 100644 --- a/includes/Models/DataStore/VendorBalanceStore.php +++ b/includes/Models/DataStore/VendorBalanceStore.php @@ -56,7 +56,7 @@ protected function get_perticulars( VendorBalance $model, string $context = 'edi * @param DateTimeImmutable $balance_date * @return float */ - public function get_earning_balance( $vendor_id, DateTimeImmutable $balance_date ): float { + public function get_total_earning_by_vendor( $vendor_id, DateTimeImmutable $balance_date ): float { global $wpdb; $on_date = $balance_date->format( 'Y-m-d' ); diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index e53941393a..a47db9d602 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -273,12 +273,17 @@ public function set_balance_date( string $date ) { } /** - * Get the total balance of a vendor at a given date. + * Calculates and retrieves the total earnings of a vendor up to a specific date. * - * @param int $vendor_id The vendor id. - * @param string $on_date Date in Y-m-d format. If not provided, current date is used. + * This calculation includes: + * - The sum of completed order amounts + * - Minus the sum of refunded amounts + * - Ignores/Excludes any withdrawals from the total earnings * - * @return float The total balance of the vendor at the given date. + * @param int $vendor_id The unique identifier for the vendor. + * @param string $on_date Optional date in 'Y-m-d' format to calculate earnings up to. Defaults to the current date if not provided. + * + * @return float The vendor's total earnings up to the specified date. */ public static function get_total_earning_by_vendor( $vendor_id, $on_date = null ) { $on_date = $on_date && strtotime( $on_date ) ? dokan_current_datetime()->modify( $on_date ) : dokan_current_datetime(); @@ -287,7 +292,7 @@ public static function get_total_earning_by_vendor( $vendor_id, $on_date = null $earning = Cache::get( $cache_key, $cache_group ); if ( false === $earning ) { - $earning = ( new static() )->get_data_store()->get_earning_balance( $vendor_id, $on_date ); + $earning = ( new static() )->get_data_store()->get_total_earning_by_vendor( $vendor_id, $on_date ); Cache::set( $cache_key, $earning, $cache_group ); } diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 25b21de903..984cfe32c8 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -137,7 +137,6 @@ public function test_dokan_sync_insert_order_function() { } public function test_get_total_earning_by_vendor_method() { - $trn_id = 1; $vendor_balance = new VendorBalance(); From b950b9cd96e3014e0b7712085fa8d2d0d32a0c89 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Tue, 12 Nov 2024 15:02:55 +0600 Subject: [PATCH 21/24] Bind model to Service provider --- .../Providers/ModelServiceProvider.php | 38 +++++++++++++++++++ .../Providers/ServiceProvider.php | 1 + includes/Models/VendorBalance.php | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 includes/DependencyManagement/Providers/ModelServiceProvider.php diff --git a/includes/DependencyManagement/Providers/ModelServiceProvider.php b/includes/DependencyManagement/Providers/ModelServiceProvider.php new file mode 100644 index 0000000000..48b68f0e7e --- /dev/null +++ b/includes/DependencyManagement/Providers/ModelServiceProvider.php @@ -0,0 +1,38 @@ +services, true ); + } + + /** + * Register the classes. + */ + public function register(): void { + foreach ( $this->services as $service ) { + $this->getContainer()->add( $service, $service ); + } + } +} diff --git a/includes/DependencyManagement/Providers/ServiceProvider.php b/includes/DependencyManagement/Providers/ServiceProvider.php index 19c5e1696d..b8385f74c9 100644 --- a/includes/DependencyManagement/Providers/ServiceProvider.php +++ b/includes/DependencyManagement/Providers/ServiceProvider.php @@ -61,6 +61,7 @@ public function boot(): void { $this->getContainer()->addServiceProvider( new FrontendServiceProvider() ); $this->getContainer()->addServiceProvider( new AjaxServiceProvider() ); $this->getContainer()->addServiceProvider( new AnalyticsServiceProvider() ); + $this->getContainer()->addServiceProvider( new ModelServiceProvider() ); } /** diff --git a/includes/Models/VendorBalance.php b/includes/Models/VendorBalance.php index a47db9d602..58a9f59ebe 100644 --- a/includes/Models/VendorBalance.php +++ b/includes/Models/VendorBalance.php @@ -49,7 +49,7 @@ class VendorBalance extends BaseModel { public function __construct( int $id = 0 ) { parent::__construct( $id ); $this->set_id( $id ); - $this->data_store = apply_filters( $this->get_hook_prefix() . 'data_store', new VendorBalanceStore() ); + $this->data_store = apply_filters( $this->get_hook_prefix() . 'data_store', dokan()->get_container()->get( VendorBalanceStore::class ) ); if ( $this->get_id() > 0 ) { $this->data_store->read( $this ); From 774843df34917d339f59bbe3d431a91e9c7da9d5 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Tue, 12 Nov 2024 15:05:18 +0600 Subject: [PATCH 22/24] Get model from the container to perform operation --- includes/Commission.php | 4 +++- includes/Order/Hooks.php | 7 ++++--- includes/Order/functions.php | 5 +++-- includes/Vendor/Vendor.php | 4 +++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/includes/Commission.php b/includes/Commission.php index 26893b301d..1eb44860ba 100644 --- a/includes/Commission.php +++ b/includes/Commission.php @@ -99,7 +99,9 @@ public function calculate_gateway_fee( $order_id ) { [ '%d' ] ); - VendorBalance::update_by_transaction( + $vendor_balance_model = dokan()->get_container()->get( VendorBalance::class ); + + $vendor_balance_model->update_by_transaction( $tmp_order->get_id(), 'dokan_orders', [ 'debit' => (float) $net_amount ] diff --git a/includes/Order/Hooks.php b/includes/Order/Hooks.php index 5197a87054..c7b90d5356 100644 --- a/includes/Order/Hooks.php +++ b/includes/Order/Hooks.php @@ -125,10 +125,12 @@ public function on_order_status_change( $order_id, $old_status, $new_status, $or return; } + $vendor_balance_model = dokan()->get_container()->get( VendorBalance::class ); + // update on vendor-balance table - VendorBalance::update_by_transaction( + $vendor_balance_model->update_by_transaction( $order_id, - VendorBalance::TRN_TYPE_DOKAN_ORDERS, + $vendor_balance_model::TRN_TYPE_DOKAN_ORDERS, [ 'status' => $new_status ] ); } @@ -150,7 +152,6 @@ private function is_status_change_allowed( string $current_status, string $new_s // Ensure both statuses have 'wc-' prefix $current_status = $this->maybe_add_wc_prefix( $current_status ); $new_status = $this->maybe_add_wc_prefix( $new_status ); - // Define the default whitelist of allowed status transitions $default_whitelist = [ 'wc-pending' => [ 'any' ], diff --git a/includes/Order/functions.php b/includes/Order/functions.php index 6a92ad3da3..e34dc3a715 100644 --- a/includes/Order/functions.php +++ b/includes/Order/functions.php @@ -270,10 +270,11 @@ function dokan_sync_insert_order( $order_id ) { ] ); - $vendor_balance = new VendorBalance(); + $vendor_balance = dokan()->get_container()->get( VendorBalance::class ); + $vendor_balance->set_vendor_id( $seller_id ); $vendor_balance->set_trn_id( $order_id ); - $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); + $vendor_balance->set_trn_type( $vendor_balance::TRN_TYPE_DOKAN_ORDERS ); $vendor_balance->set_particulars( 'New order' ); $vendor_balance->set_debit( $net_amount ); $vendor_balance->set_trn_date( dokan_current_datetime()->format( 'Y-m-d H:i:s' ) ); diff --git a/includes/Vendor/Vendor.php b/includes/Vendor/Vendor.php index 19d3600d80..ab85cb4dcb 100644 --- a/includes/Vendor/Vendor.php +++ b/includes/Vendor/Vendor.php @@ -790,7 +790,9 @@ public function get_product_views() { * @return float|string float if formatted is false, string otherwise */ public function get_earnings( $formatted = true, $on_date = '' ) { - $earning = VendorBalance::get_total_earning_by_vendor( $this->id, $on_date ); + $vendor_balance_model = dokan()->get_container()->get( VendorBalance::class ); + + $earning = $vendor_balance_model->get_total_earning_by_vendor( $this->id, $on_date ); if ( $formatted ) { return apply_filters( 'dokan_get_formatted_seller_earnings', wc_price( $earning ), $this->id ); From 8417e496d9b53edf18cac1e80a39e711ab5d7fd0 Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Wed, 13 Nov 2024 10:01:56 +0600 Subject: [PATCH 23/24] Remove empty lines --- docs/data-store.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/data-store.md b/docs/data-store.md index 5148775126..312302962c 100644 --- a/docs/data-store.md +++ b/docs/data-store.md @@ -152,14 +152,6 @@ protected function get_updated_at( Department $model, $context = 'edit' ): strin To customize the name and format of the ID field in the database, override the `get_id_field_name` and `get_id_field_format` methods. By default, the ID field is set to `id` and format is `%d`. - - - - - - - - ## Uses of Models ### Create a New Record From cb6b3645d8313a49ba69d7d76a6fe6d280e67cbc Mon Sep 17 00:00:00 2001 From: Md Mahbub Rabbani Date: Wed, 13 Nov 2024 11:20:42 +0600 Subject: [PATCH 24/24] Add data provider for test case --- .../php/src/Models/VendorBalanceModelTest.php | 133 ++++++++++++------ 1 file changed, 93 insertions(+), 40 deletions(-) diff --git a/tests/php/src/Models/VendorBalanceModelTest.php b/tests/php/src/Models/VendorBalanceModelTest.php index 984cfe32c8..d44d467af1 100644 --- a/tests/php/src/Models/VendorBalanceModelTest.php +++ b/tests/php/src/Models/VendorBalanceModelTest.php @@ -5,7 +5,6 @@ use Mockery; use WeDevs\Dokan\Models\VendorBalance; use WeDevs\Dokan\Test\DokanTestCase; -use WeDevs\DokanPro\Modules\DeliveryTime\StorePickup\Vendor; /** * @group data-store @@ -136,46 +135,100 @@ public function test_dokan_sync_insert_order_function() { ); } - public function test_get_total_earning_by_vendor_method() { + /** + * @dataProvider get_data_for_total_earning_by_vendor_method + * + * @return void + */ + public function test_get_total_earning_by_vendor_method( $earings_data, $vendor_wise_earnings ) { $trn_id = 1; + // var_dump( $earings_data ); + + foreach ( $earings_data as $earning_data ) { + $vendor_balance = new VendorBalance(); + $vendor_balance->set_particulars( 'test' ); + $vendor_balance->set_debit( $earning_data['debit'] ?? 0 ); + $vendor_balance->set_credit( $earning_data['credit'] ?? 0 ); + $vendor_balance->set_status( $earning_data['status'] ); + $vendor_balance->set_trn_id( $earning_data['trn_id'] ); + $vendor_balance->set_trn_type( $earning_data['trn_type'] ); + $vendor_balance->set_vendor_id( $earning_data['vendor_id'] ?? $this->seller_id1 ); + $vendor_balance->set_trn_date( '2020-01-01' ); + $vendor_balance->set_balance_date( '2020-01-01' ); + $vendor_balance->save(); + } + + foreach ( $vendor_wise_earnings as $vendor_id => $vendor_earnings ) { + $total_balance = VendorBalance::get_total_earning_by_vendor( + $vendor_id + ); + + $this->assertEquals( $vendor_earnings, $total_balance ); + } + } - $vendor_balance = new VendorBalance(); - $vendor_balance->set_particulars( 'test' ); - $vendor_balance->set_debit( 100 ); - $vendor_balance->set_status( 'wc-completed' ); - $vendor_balance->set_trn_id( $trn_id++ ); - $vendor_balance->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_ORDERS ); - $vendor_balance->set_vendor_id( $this->seller_id1 ); - $vendor_balance->set_trn_date( '2020-01-01' ); - $vendor_balance->set_balance_date( '2020-01-01' ); - $vendor_balance->save(); - - $vendor_balance->set_id( 0 ); - $vendor_balance->set_debit( 0 ); - - $vendor_balance_2 = clone $vendor_balance; - $vendor_balance_2->set_trn_id( $trn_id++ ); - $vendor_balance_2->set_debit( 60 ); - $vendor_balance_2->save(); - - $vendor_balance_refund = clone $vendor_balance; - $vendor_balance_refund->set_trn_id( $trn_id++ ); - $vendor_balance_refund->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_REFUND ); - $vendor_balance_refund->set_status( 'approved' ); - $vendor_balance_refund->set_credit( 20 ); - $vendor_balance_refund->save(); - - $vendor_balance_withdraw = clone $vendor_balance; - $vendor_balance_withdraw->set_trn_id( $trn_id++ ); - $vendor_balance_withdraw->set_trn_type( VendorBalance::TRN_TYPE_DOKAN_WITHDRAW ); - $vendor_balance_withdraw->set_status( 'approved' ); - $vendor_balance_withdraw->set_credit( 30 ); - $vendor_balance_withdraw->save(); - - $total_balance = VendorBalance::get_total_earning_by_vendor( - $this->seller_id1 - ); - // 100 + 60 - 20 - 30 = 110 - $this->assertGreaterThan( 110, $total_balance ); + public function get_data_for_total_earning_by_vendor_method() { + $seller_id1 = 3; + $seller_id2 = 4; + + return [ + [ + [ + [ + 'trn_id' => 1, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_ORDERS, + 'vendor_id' => $seller_id1, + 'debit' => 100, + 'status' => 'wc-completed', + 'trn_date' => '2020-01-01', + 'balance_date' => '2020-01-01', + ], + [ + 'trn_id' => 2, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_ORDERS, + 'vendor_id' => $seller_id1, + 'debit' => 60, + 'status' => 'wc-completed', + 'trn_date' => '2020-01-01', + 'balance_date' => '2020-01-01', + ], + [ + 'trn_id' => 3, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_REFUND, + 'vendor_id' => $seller_id1, + 'credit' => 20, + 'status' => 'approved', + 'trn_date' => '2020-01-01', + 'balance_date' => '2020-01-01', + ], + [ + 'trn_id' => 4, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_WITHDRAW, + 'vendor_id' => $seller_id1, + 'credit' => 30, + 'status' => 'approved', + 'trn_date' => '2020-01-01', + 'balance_date' => '2020-01-01', + ], + + // Another Sellers + [ + 'trn_id' => 5, + 'trn_type' => VendorBalance::TRN_TYPE_DOKAN_ORDERS, + 'vendor_id' => $seller_id2, + 'debit' => 100, + 'status' => 'wc-completed', + 'trn_date' => '2020-01-01', + 'balance_date' => '2020-01-01', + ], + ], + + [ + // Earing = debit of completed orders - refund amount. + $seller_id1 => 140, // 100 + 60 - 20 = 140 + $seller_id2 => 100, + ], + ], + ]; } }