inventory_id ); } if ( ! empty( $settings->lendable ) ) { return TRUE; } $cats = (array) WPIMLend_Settings::get( 'lendable_categories' ); return $cats && in_array( (int) $core_item->category_id, $cats, TRUE ); } /** * Peak number in use on any single day of [from, to], per item (tech spec §9). * * @return array inventory_id => int */ public static function peak_usage( array $ids, $from, $to, $exclude_booking = 0 ) { $events = []; foreach ( WPIMLend_Repo::occupancy( $ids, $from, $to, self::today(), $exclude_booking ) as $row ) { $s = max( $row->s, $from ); $e = min( $row->e, $to ); if ( $s > $e || (int) $row->q < 1 ) { continue; } $id = (int) $row->inventory_id; $events[ $id ][] = [ $s, 1, (int) $row->q ]; // starts on $s $events[ $id ][] = [ self::next_day( $e ), 0, - (int) $row->q ]; // free again the day after $e } $peak = []; foreach ( $events as $id => $list ) { // Same date: releases (0) before starts (1), so back-to-back ranges do not overlap. usort( $list, function ( $a, $b ) { return strcmp( $a[0], $b[0] ) ?: ( $a[1] - $b[1] ); } ); $running = 0; $max = 0; foreach ( $list as $ev ) { $running += $ev[2]; $max = max( $max, $running ); } $peak[ $id ] = $max; } return $peak; } /** * Number in use on each day of [from, to], per item, from one occupancy query (calendar). * * @return array inventory_id => [ 'Y-m-d' => int ] */ public static function daily_usage( array $ids, $from, $to ) { $days = []; for ( $d = $from; $d <= $to; $d = self::next_day( $d ) ) { $days[] = $d; } $out = []; foreach ( $ids as $id ) { $out[ (int) $id ] = array_fill_keys( $days, 0 ); } foreach ( WPIMLend_Repo::occupancy( $ids, $from, $to, self::today() ) as $row ) { $id = (int) $row->inventory_id; for ( $d = max( $row->s, $from ), $end = min( $row->e, $to ); $d <= $end; $d = self::next_day( $d ) ) { $out[ $id ][ $d ] += (int) $row->q; } } return $out; } public static function next_day( $ymd ) { return gmdate( 'Y-m-d', strtotime( $ymd . ' 00:00:00 UTC' ) + DAY_IN_SECONDS ); } /** * Availability for many items. Without dates this is "right now" (today..today), which is * exactly the M1 rule when there are no bookings. * * @param string|null $from Site-local Y-m-d. * @param string|null $to Site-local Y-m-d. * * @return array inventory_id => [ owned, out_of_service, on_loan, available, track_units, lendable ] */ public static function for_items( array $ids, $from = NULL, $to = NULL, $exclude_booking = 0 ) { $ids = array_values( array_unique( array_filter( array_map( 'absint', $ids ) ) ) ); if ( ! $ids ) { return []; } $from = $from ? $from : self::today(); $to = $to ? $to : $from; if ( $to < $from ) { list( $from, $to ) = [ $to, $from ]; } $core = WPIMLend_Repo::get_core_items( $ids ); $settings = WPIMLend_Repo::get_item_settings_map( $ids ); $units = WPIMLend_Repo::unit_counts( $ids ); $open = WPIMLend_Repo::open_quantities( $ids ); $peak = self::peak_usage( $ids, $from, $to, $exclude_booking ); $out = []; foreach ( $ids as $id ) { if ( ! isset( $core[ $id ] ) ) { continue; } $s = isset( $settings[ $id ] ) ? $settings[ $id ] : NULL; $track_units = $s && ! empty( $s->track_units ); $counts = isset( $units[ $id ] ) ? $units[ $id ] : []; if ( $track_units ) { $owned = array_sum( $counts ) - ( isset( $counts['retired'] ) ? $counts['retired'] : 0 ); $oos = isset( $counts['out_of_service'] ) ? $counts['out_of_service'] : 0; } else { $owned = max( 0, (int) $core[ $id ]->inventory_quantity ); $oos = 0; } $on_loan = isset( $open[ $id ] ) ? $open[ $id ] : 0; $in_use = isset( $peak[ $id ] ) ? $peak[ $id ] : 0; $available = (int) apply_filters( 'wpim_lending_available_now', $owned - $oos - $in_use, $id, $from, $to ); $out[ $id ] = [ 'owned' => $owned, 'out_of_service' => $oos, 'on_loan' => $on_loan, 'available' => $available, 'track_units' => $track_units, 'lendable' => self::is_lendable( $core[ $id ], $s ? $s : (object) [ 'lendable' => 0 ] ), ]; } return $out; } public static function for_item( $inventory_id, $from = NULL, $to = NULL, $exclude_booking = 0 ) { $all = self::for_items( [ $inventory_id ], $from, $to, $exclude_booking ); return isset( $all[ $inventory_id ] ) ? $all[ $inventory_id ] : NULL; } /** * Site-local "today" as Y-m-d. */ public static function today() { return wp_date( 'Y-m-d' ); } /** * Default due date for an item, site-local Y-m-d. */ public static function default_due_date( $inventory_id = 0 ) { $days = (int) WPIMLend_Settings::get( 'default_days' ); if ( $inventory_id ) { $s = WPIMLend_Repo::get_item_settings( $inventory_id ); if ( ! empty( $s->default_days ) ) { $days = (int) $s->default_days; } } return wp_date( 'Y-m-d', time() + max( 1, $days ) * DAY_IN_SECONDS ); } }