Skip to content

Commit

Permalink
Merge pull request #3183 from the-events-calendar/fix/sl-102-calculat…
Browse files Browse the repository at this point in the history
…e-event-availability

[SL-102] Calculate event availability accounting for Seating
  • Loading branch information
lucatume authored Aug 23, 2024
2 parents e7b74ac + 4faf2e0 commit 02b8363
Show file tree
Hide file tree
Showing 10 changed files with 577 additions and 41 deletions.
27 changes: 14 additions & 13 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@ jobs:
with:
fetch-depth: 1
submodules: recursive
- name: Set node version
run: |
. ~/.nvm/nvm.sh && nvm install $(cat .nvmrc) && nvm use
- name: Get npm cache
uses: actions/cache@v2
id: npm-cache
# ------------------------------------------------------------------------------
# Setup Node.
# ------------------------------------------------------------------------------
- name: Check for .nvmrc file
id: check-nvmrc
run: echo "::set-output name=exists::$(test -f ${{ github.workspace }}/.nvmrc && echo 'true' || echo 'false')"

- uses: actions/setup-node@v3
if: steps.check-nvmrc.outputs.exists == 'true'
with:
path: '~/.npm'
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-npm-
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install node modules
run: |
. ~/.nvm/nvm.sh && nvm use && npm ci
run: npm ci
- name: Run linting tasks
run: |
. ~/.nvm/nvm.sh && nvm use && npm run lint
run: npm run lint
27 changes: 14 additions & 13 deletions .github/workflows/tests-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@ jobs:
with:
fetch-depth: 1
submodules: recursive
- name: Set node version
run: |
. ~/.nvm/nvm.sh && nvm install $(cat .nvmrc) && nvm use
- name: Get npm cache
uses: actions/cache@v2
id: npm-cache
# ------------------------------------------------------------------------------
# Setup Node.
# ------------------------------------------------------------------------------
- name: Check for .nvmrc file
id: check-nvmrc
run: echo "::set-output name=exists::$(test -f ${{ github.workspace }}/.nvmrc && echo 'true' || echo 'false')"

- uses: actions/setup-node@v3
if: steps.check-nvmrc.outputs.exists == 'true'
with:
path: '~/.npm'
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-npm-
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install node modules
run: |
. ~/.nvm/nvm.sh && nvm use && npm ci
run: npm ci
- name: Run jest task
run: |
. ~/.nvm/nvm.sh && nvm use && npm run jest
run: npm run jest
2 changes: 1 addition & 1 deletion src/Tickets/Commerce/Flag_Actions/Decrease_Stock.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,4 @@ public function handle( Status_Interface $new_status, $old_status, \WP_Post $pos
update_post_meta( $ticket->ID, Ticket::$stock_meta_key, $ticket->stock() - $quantity );
}
}
}
}
2 changes: 1 addition & 1 deletion src/Tickets/Commerce/Flag_Actions/Increase_Stock.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ public function handle( Status_Interface $new_status, $old_status, \WP_Post $pos
update_post_meta( $ticket->ID, Ticket::$stock_meta_key, $ticket->stock() + $quantity );
}
}
}
}
124 changes: 124 additions & 0 deletions src/Tickets/Seating/Commerce/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
use TEC\Common\StellarWP\DB\DB;
use TEC\Tickets\Commerce\Cart;
use TEC\Tickets\Commerce\Module;
use TEC\Tickets\Commerce\Ticket;
use TEC\Tickets\Seating\Meta;
use Tribe__Tickets__Ticket_Object as Ticket_Object;
use Tribe__Tickets__Tickets as Tickets;
use WP_Post;

/**
* Class Controller.
Expand All @@ -34,6 +39,8 @@ protected function do_register(): void {
'tec_tickets_seating_timer_token_object_id_entries',
[ $this, 'filter_timer_token_object_id_entries' ],
);
add_filter( 'tribe_tickets_ticket_inventory', [ $this, 'get_seated_ticket_inventory' ], 10, 2 );
add_action( 'updated_postmeta', [ $this, 'sync_seated_tickets_stock' ], 10, 4 );
}

/**
Expand All @@ -48,6 +55,123 @@ public function unregister(): void {
'tec_tickets_seating_timer_token_object_id_entries',
[ $this, 'filter_timer_token_object_id_entries' ],
);
remove_filter( 'tribe_tickets_ticket_inventory', [ $this, 'get_seated_ticket_inventory' ] );
remove_action( 'updated_postmeta', [ $this, 'sync_seated_tickets_stock' ] );
}

/**
* Adjusts the seated ticket inventory to match the stock.
*
* @since TBD
*
* @param int $inventory The current inventory.
* @param Ticket_Object $ticket The ticket object.
*
* @return int The adjusted inventory.
*/
public function get_seated_ticket_inventory( int $inventory, Ticket_Object $ticket ): int {
$seat_type = get_post_meta( $ticket->ID, Meta::META_KEY_SEAT_TYPE, true );

if ( ! $seat_type ) {
return $inventory;
}

$event_id = $ticket->get_event_id();
$ticket_ids = [ $ticket->ID ];
$this_inventory = $inventory;
$capacity = $ticket->capacity();
// Protect from over-selling
$total_sold = max( 0, $capacity - $this_inventory );

// Remove this function from the filter to avoid infinite loops.
remove_filter( 'tribe_tickets_ticket_inventory', [ $this, 'get_seated_ticket_inventory' ] );

// Later we'll remove this specific return false filter, not one that might have been added by other code.
$return_false = static fn() => false;
add_filter( 'tribe_tickets_ticket_object_is_ticket_cache_enabled', $return_false );

// Pull the inventory from the other tickets with the same seat type.
foreach (
tribe_tickets()
->where( 'event', $event_id )
->not_in( $ticket->ID )
->where( 'meta_equals', Meta::META_KEY_SEAT_TYPE, $seat_type )
->get_ids( true ) as $ticket_id
) {
$ticket = Tickets::load_ticket_object( $ticket_id );
$sold_for_this = $capacity - $ticket->inventory();
$total_sold += $sold_for_this;
$ticket_ids[] = $ticket_id;
}

add_filter( 'tribe_tickets_ticket_inventory',
[ $this, 'get_seated_ticket_inventory' ],
10,
2
);
remove_filter( 'tribe_tickets_ticket_object_is_ticket_cache_enabled', $return_false );

$synced_inventory = $capacity - $total_sold;

return $synced_inventory;
}

/**
* Filters the stock update value for a ticket.
*
* @since TBD
*
* @param int $meta_id ID of the meta entry.
* @param int $object_id ID of the object.
* @param string $meta_key Meta key.
* @param mixed $meta_value Meta value.
*
* @return void
*/
public function sync_seated_tickets_stock( $meta_id, $object_id, $meta_key, $meta_value ): void {
if ( Ticket::$stock_meta_key !== $meta_key ) {
return;
}

if ( ! is_numeric( $meta_value ) ) {
return;
}

$seat_type = get_post_meta( $object_id, Meta::META_KEY_SEAT_TYPE, true );

// Not a seating ticket. We should not modify the stock.
if ( ! $seat_type ) {
return;
}

$ticket = Tickets::load_ticket_object( $object_id );

if ( ! $ticket instanceof Ticket_Object ) {
return;
}

$event = $ticket->get_event();

if ( ! $event instanceof WP_Post || ! $event->ID ) {
return;
}

$stock = (int) $meta_value;

// Remove the action to avoid infinite loops.
remove_action( 'tec_tickets_commerce_increase_ticket_stock', [ $this, 'sync_seated_tickets_stock' ] );

foreach (
tribe_tickets()
->where( 'event', $event->ID )
->not_in( $ticket->ID )
->where( 'meta_equals', Meta::META_KEY_SEAT_TYPE, $seat_type )
->get_ids( true ) as $ticket_id
) {
update_post_meta( $ticket_id, Ticket::$stock_meta_key, $stock );
}

add_action( 'tec_tickets_commerce_decrease_ticket_stock', [ $this, 'sync_seated_tickets_stock' ], 10, 4 );
}

/**
Expand Down
64 changes: 55 additions & 9 deletions src/Tickets/Seating/Frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
use Tribe__Template as Base_Template;
use Tribe__Tickets__Main as ET;
use Tribe__Tickets__Tickets as Tickets;
use Tribe__Tickets__Tickets_Handler as Tickets_Handler;
use WP_Error;

/**
Expand Down Expand Up @@ -115,8 +114,14 @@ public function print_tickets_block( $html, $file, $name, $template, $context ):
return $html;
}

$prices = [];
$provider = Tickets::get_event_ticket_provider_object( $post_id );

if ( ! $provider ) {
return $html;
}

$prices = [];

foreach ( tribe_tickets()->where( 'event', $post_id )->get_ids( true ) as $ticket_id ) {
$ticket = $provider->get_ticket( $post_id, $ticket_id );
if ( ! $ticket ) {
Expand All @@ -130,17 +135,11 @@ public function print_tickets_block( $html, $file, $name, $template, $context ):
return $html;
}

$inventory = $this->get_events_ticket_capacity_for_seating( $post_id );
$cost_range = tribe_format_currency( min( $prices ), $post_id )
. ' - '
. tribe_format_currency( max( $prices ), $post_id );

/**
* @var Tickets_Handler $tickets_handler
*/
$tickets_handler = tribe( 'tickets.handler' );
$capacity_meta_key = $tickets_handler->key_capacity;
$inventory = get_post_meta( $post_id, $capacity_meta_key, true );

$timeout = $this->container->get( Timer::class )->get_timeout( $post_id );

$html = $this->template->template(
Expand All @@ -167,6 +166,53 @@ public function print_tickets_block( $html, $file, $name, $template, $context ):
return $html;
}

/**
* Adjusts the event's ticket capacity to consider seating.
*
* @since TBD
*
* @param int $event_id The event ID.
*
* @return int
*/
public function get_events_ticket_capacity_for_seating( int $event_id ): int {
if ( ! tec_tickets_seating_enabled( $event_id ) ) {
return 0;
}

$provider = Tickets::get_event_ticket_provider_object( $event_id );

if ( ! $provider ) {
return 0;
}

$available = [];

foreach ( tribe_tickets()->where( 'event', $event_id )->get_ids( true ) as $ticket_id ) {
$ticket = $provider->get_ticket( $event_id, $ticket_id );

if ( ! $ticket ) {
continue;
}

$seat_type = get_post_meta( $ticket->ID, Meta::META_KEY_SEAT_TYPE, true );

if ( empty( $available[ $seat_type ] ) ) {
// The array's keys are the seating types. In order for us to calculate the stock per type and NOT per ticket.
$available[ $seat_type ] = $ticket->stock();
continue;
}

$available[ $seat_type ] = $available[ $seat_type ] < $ticket->stock() ? $available[ $seat_type ] : $ticket->stock();
}

if ( empty( $available ) ) {
return 0;
}

return array_sum( $available );
}

/**
* Returns the HTML content of the seat selection modal.
*
Expand Down
22 changes: 20 additions & 2 deletions src/Tribe/Ticket_Object.php
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,15 @@ public function inventory() {

// If we don't have the provider, get the result from inventory.
if ( empty( $provider ) ) {
$inventory = $capacity - $this->qty_sold() - $this->qty_pending();
/**
* Filters the inventory of the ticket.
*
* @since TBD
*
* @param int $inventory The inventory of the ticket.
* @param Tribe__Tickets__Ticket_Object $ticket The ticket object.
*/
$inventory = apply_filters( 'tribe_tickets_ticket_inventory', $capacity - $this->qty_sold() - $this->qty_pending(), $this );

if ( $is_ticket_cache_enabled ) {
$cache->set( $cache_key, $inventory, 0, Cache::TRIGGER_SAVE_POST );
Expand Down Expand Up @@ -669,7 +677,7 @@ public function inventory() {

// Do the math!
$inventory[] = $capacity - $attendees_count;

// Calculate and verify the Event Inventory
if (
Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE === $this->global_stock_mode()
Expand Down Expand Up @@ -709,6 +717,16 @@ public function inventory() {
// Prevents Negative
$inventory = max( $inventory, 0 );

/**
* Filters the inventory of the ticket.
*
* @since TBD
*
* @param int $inventory The inventory of the ticket.
* @param Tribe__Tickets__Ticket_Object $ticket The ticket object.
*/
$inventory = apply_filters( 'tribe_tickets_ticket_inventory', $inventory, $this );

if ( $is_ticket_cache_enabled ) {
$cache->set( $cache_key, $inventory, 0, Cache::TRIGGER_SAVE_POST );
}
Expand Down
4 changes: 2 additions & 2 deletions src/resources/postcss/my-tickets/_orders.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@
}

span.tec-tickets__ticket-information__seat-label:after {
background-color: #d6d6d6;
content: "";
display: inline-block;
width: 1px;
height: 11px;
background-color: #d6d6d6;
margin-left: 5px;
width: 1px;
}
}
Loading

0 comments on commit 02b8363

Please sign in to comment.