Changeset 3790901
- Timestamp:
- 11/08/2026 11:23:25 AM (less than one hour ago)
- Location:
- rentbaik
- Files:
-
- 58 added
- 22 edited
- 1 copied
-
assets/banner-1544x500.png (modified) ( previous)
-
assets/banner-772x250.png (modified) ( previous)
-
tags/1.0.1 (copied) (copied from rentbaik/trunk )
-
tags/1.0.1/assets/admin/admin.css (modified) (2 diffs)
-
tags/1.0.1/assets/admin/admin.js (modified) (2 diffs)
-
tags/1.0.1/assets/sample (added)
-
tags/1.0.1/assets/sample/bike-city-green.png (added)
-
tags/1.0.1/assets/sample/car-compact-silver.png (added)
-
tags/1.0.1/assets/sample/car-compact-white.png (added)
-
tags/1.0.1/assets/sample/car-family-black.png (added)
-
tags/1.0.1/assets/sample/customer-1.png (added)
-
tags/1.0.1/assets/sample/customer-2.png (added)
-
tags/1.0.1/assets/sample/customer-3.png (added)
-
tags/1.0.1/assets/sample/customer-4.png (added)
-
tags/1.0.1/assets/sample/customer-5.png (added)
-
tags/1.0.1/assets/sample/customer-6.png (added)
-
tags/1.0.1/assets/sample/customer-7.png (added)
-
tags/1.0.1/assets/sample/customer-8.png (added)
-
tags/1.0.1/assets/sample/damage-sample.png (added)
-
tags/1.0.1/assets/sample/ebike-teal.png (added)
-
tags/1.0.1/assets/sample/gear-bike.png (added)
-
tags/1.0.1/assets/sample/gear-car.png (added)
-
tags/1.0.1/assets/sample/gear-scooter.png (added)
-
tags/1.0.1/assets/sample/licence-sample.png (added)
-
tags/1.0.1/assets/sample/passport-sample.png (added)
-
tags/1.0.1/assets/sample/receipt-sample.png (added)
-
tags/1.0.1/assets/sample/registration-sample.png (added)
-
tags/1.0.1/assets/sample/scooter-125-black.png (added)
-
tags/1.0.1/assets/sample/scooter-125-red.png (added)
-
tags/1.0.1/assets/sample/scooter-125-white.png (added)
-
tags/1.0.1/assets/sample/scooter-160-blue.png (added)
-
tags/1.0.1/assets/sample/scooter-160-grey.png (added)
-
tags/1.0.1/assets/sample/signature-sample.png (added)
-
tags/1.0.1/includes/Admin/CalendarView.php (modified) (7 diffs)
-
tags/1.0.1/includes/Admin/DashboardPage.php (modified) (1 diff)
-
tags/1.0.1/includes/Admin/DayAgenda.php (modified) (1 diff)
-
tags/1.0.1/includes/Admin/SitePages.php (added)
-
tags/1.0.1/includes/Customers/Photos.php (modified) (2 diffs)
-
tags/1.0.1/includes/Setup/SampleData.php (modified) (22 diffs)
-
tags/1.0.1/languages/rentbaik.pot (modified) (48 diffs)
-
tags/1.0.1/readme.txt (modified) (4 diffs)
-
tags/1.0.1/rentbaik.php (modified) (2 diffs)
-
trunk/assets/admin/admin.css (modified) (2 diffs)
-
trunk/assets/admin/admin.js (modified) (2 diffs)
-
trunk/assets/sample (added)
-
trunk/assets/sample/bike-city-green.png (added)
-
trunk/assets/sample/car-compact-silver.png (added)
-
trunk/assets/sample/car-compact-white.png (added)
-
trunk/assets/sample/car-family-black.png (added)
-
trunk/assets/sample/customer-1.png (added)
-
trunk/assets/sample/customer-2.png (added)
-
trunk/assets/sample/customer-3.png (added)
-
trunk/assets/sample/customer-4.png (added)
-
trunk/assets/sample/customer-5.png (added)
-
trunk/assets/sample/customer-6.png (added)
-
trunk/assets/sample/customer-7.png (added)
-
trunk/assets/sample/customer-8.png (added)
-
trunk/assets/sample/damage-sample.png (added)
-
trunk/assets/sample/ebike-teal.png (added)
-
trunk/assets/sample/gear-bike.png (added)
-
trunk/assets/sample/gear-car.png (added)
-
trunk/assets/sample/gear-scooter.png (added)
-
trunk/assets/sample/licence-sample.png (added)
-
trunk/assets/sample/passport-sample.png (added)
-
trunk/assets/sample/receipt-sample.png (added)
-
trunk/assets/sample/registration-sample.png (added)
-
trunk/assets/sample/scooter-125-black.png (added)
-
trunk/assets/sample/scooter-125-red.png (added)
-
trunk/assets/sample/scooter-125-white.png (added)
-
trunk/assets/sample/scooter-160-blue.png (added)
-
trunk/assets/sample/scooter-160-grey.png (added)
-
trunk/assets/sample/signature-sample.png (added)
-
trunk/includes/Admin/CalendarView.php (modified) (7 diffs)
-
trunk/includes/Admin/DashboardPage.php (modified) (1 diff)
-
trunk/includes/Admin/DayAgenda.php (modified) (1 diff)
-
trunk/includes/Admin/SitePages.php (added)
-
trunk/includes/Customers/Photos.php (modified) (2 diffs)
-
trunk/includes/Setup/SampleData.php (modified) (22 diffs)
-
trunk/languages/rentbaik.pot (modified) (48 diffs)
-
trunk/readme.txt (modified) (4 diffs)
-
trunk/rentbaik.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
rentbaik/tags/1.0.1/assets/admin/admin.css
r3790685 r3790901 158 158 } 159 159 160 /* Customer pages card */ 161 .rentbaik-sitepages__list { 162 list-style: none; 163 margin: 8px 0 0; 164 padding: 0; 165 } 166 167 .rentbaik-sitepages__item { 168 display: grid; 169 grid-template-columns: minmax(0, 1fr) auto; 170 align-items: center; 171 gap: 10px; 172 margin: 0; 173 padding-block: 10px; 174 border-block-start: 1px solid var(--rentbaik-line); 175 } 176 177 .rentbaik-sitepages__item:first-child { 178 border-block-start: 0; 179 } 180 181 .rentbaik-sitepages__text span:not(.rentbaik-pill) { 182 display: block; 183 color: var(--rentbaik-muted); 184 overflow-wrap: anywhere; 185 } 186 187 .rentbaik-sitepages__text code { 188 display: inline-block; 189 margin-block-start: 4px; 190 font-size: 12px; 191 overflow-wrap: anywhere; 192 } 193 194 .rentbaik-sitepages__actions { 195 display: flex; 196 align-items: center; 197 gap: 10px; 198 white-space: nowrap; 199 } 200 201 .rentbaik-sitepages__try { 202 display: flex; 203 align-items: center; 204 gap: 6px; 205 margin: 10px 0 0; 206 padding-block-start: 10px; 207 border-block-start: 1px solid var(--rentbaik-line); 208 } 209 210 @media (max-width: 600px) { 211 .rentbaik-sitepages__item { 212 grid-template-columns: minmax(0, 1fr); 213 } 214 } 215 160 216 /* Status list */ 161 217 .rentbaik-status { … … 1185 1241 .rentbaik-cal__legend .rentbaik-cal__unpaid { 1186 1242 margin: 0; 1243 } 1244 1245 /* Short labels on a booking bar: late, today, not fully paid, deposit. */ 1246 .rentbaik-cal__text { 1247 flex: 1 1 auto; 1248 } 1249 1250 .rentbaik-cal__tags { 1251 flex: none; 1252 display: inline-flex; 1253 gap: 3px; 1254 margin-inline-start: auto; 1255 } 1256 1257 .rentbaik-cal__tag { 1258 display: inline-block; 1259 padding-inline: 6px; 1260 border-radius: 8px; 1261 font-size: 10px; 1262 font-weight: 600; 1263 line-height: 16px; 1264 white-space: nowrap; 1265 } 1266 1267 .rentbaik-cal__tag--alert { 1268 background: #b32d2e; 1269 color: #fff; 1270 } 1271 1272 .rentbaik-cal__tag--warn { 1273 background: #f5e6ab; 1274 color: #614a00; 1275 } 1276 1277 .rentbaik-cal__tag--info { 1278 background: #135e96; 1279 color: #fff; 1280 } 1281 1282 .rentbaik-cal__tag--muted { 1283 background: rgba(0, 0, 0, 0.07); 1284 color: #3c434a; 1285 } 1286 1287 .rentbaik-cal__legend .rentbaik-cal__tag { 1288 margin-inline-end: 2px; 1289 } 1290 1291 .rentbaik-agenda__tags .rentbaik-cal__tag { 1292 vertical-align: middle; 1187 1293 } 1188 1294 -
rentbaik/tags/1.0.1/assets/admin/admin.js
r3790685 r3790901 148 148 } ); 149 149 }; 150 // rest_url() already holds "?rest_route=" when permalinks are plain, so add parameters with "&" then. 151 var sep = function ( url ) { 152 return url.indexOf( '?' ) === -1 ? '?' : '&'; 153 }; 150 154 var open = function ( id, from, day ) { 151 155 var back = window.location.href.split( '#' )[ 0 ]; 152 156 mark( id, '' ); 153 load( 'booking-' + id, cfg.panelUrl + id + '?back=' + encodeURIComponent( back ), from, 'panelError', day ? function () {157 load( 'booking-' + id, cfg.panelUrl + id + sep( cfg.panelUrl ) + 'back=' + encodeURIComponent( back ), from, 'panelError', day ? function () { 154 158 // Opened from the day agenda: offer a way back to it. 155 159 var button = document.createElement( 'button' ); … … 167 171 var vehicle = new URLSearchParams( window.location.search ).get( 'vehicle' ) || '0'; 168 172 mark( 0, day ); 169 load( 'day-' + day, cfg.dayUrl + day + '?vehicle=' + encodeURIComponent( vehicle ), from, 'dayError' );173 load( 'day-' + day, cfg.dayUrl + day + sep( cfg.dayUrl ) + 'vehicle=' + encodeURIComponent( vehicle ), from, 'dayError' ); 170 174 }; 171 175 -
rentbaik/tags/1.0.1/includes/Admin/CalendarView.php
r3790685 r3790901 145 145 $photos = Photos::for_bookings( array_map( 'intval', wp_list_pluck( $rows, 'id' ) ) ); 146 146 $now = Time::to_mysql( Time::now() ); 147 $today = Time::local( Time::now() )->format( 'Y-m-d' ); 147 148 $labels = BookingService::status_labels(); 148 149 $pay = BookingService::payment_state_labels(); … … 180 181 $plate = $unit ? (string) $unit['plate_number'] : ''; 181 182 $state = BookingService::payment_state( $row ); 183 $late_hours = 'overdue' === $tone ? max( 1, (int) floor( ( time() - $return->getTimestamp() ) / HOUR_IN_SECONDS ) ) : 0; 184 $flags = self::flags( $row, $status, $state, $start_day, Time::local( $return )->format( 'Y-m-d' ), $today, $late_hours ); 182 185 183 186 $title = implode( … … 192 195 sprintf( __( 'Return: %s', 'rentbaik' ), Format::datetime( (string) $row['return_at'] ) ), 193 196 ( $labels[ $status ] ?? $status ) . ' · ' . ( $pay[ $state ] ?? $state ), 197 implode( ' · ', wp_list_pluck( $flags, 'title' ) ), 194 198 $customer ? (string) $customer['phone'] : '', 195 199 ) … … 214 218 'vehicle' => $vehicle_label, 215 219 'plate' => $plate, 216 'unpaid' => 'paid' !== $state && ! in_array( $status, array( 'completed' ), true ), 220 'unpaid' => isset( $flags['unpaid'] ), 221 'flags' => $flags, 217 222 'photo' => isset( $photos[ (int) $row['id'] ] ) ? Photos::url( $photos[ (int) $row['id'] ], true ) : '', 218 223 'title' => $title, … … 225 230 } 226 231 return $list; 232 } 233 234 /** 235 * Short labels on a booking bar: late, return or pick-up today, not fully paid, deposit not given back. 236 * 237 * @param array<string, mixed> $row Booking row. 238 * @param string $status Booking status. 239 * @param string $state Payment state (unpaid, partial, paid). 240 * @param string $start_day Local pick-up day (Y-m-d). 241 * @param string $return_day Local return day (Y-m-d). 242 * @param string $today Local today (Y-m-d). 243 * @param int $late_hours Hours past the return time (0 = not late). 244 * @return array<string, array{label: string, title: string, tone: string, at: string}> Key => label, full text, 245 * tone (alert, warn, info, muted) 246 * and end of the bar (start, end). 247 */ 248 public static function flags( array $row, string $status, string $state, string $start_day, string $return_day, string $today, int $late_hours ): array { 249 $flags = array(); 250 if ( $late_hours > 0 ) { 251 $flags['late'] = array( 252 'label' => __( 'Late', 'rentbaik' ), 253 /* translators: %d: number of hours. */ 254 'title' => sprintf( _n( 'Late by %d hour', 'Late by %d hours', $late_hours, 'rentbaik' ), $late_hours ), 255 'tone' => 'alert', 256 'at' => 'end', 257 ); 258 } elseif ( 'active' === $status && $return_day === $today ) { 259 $flags['return_today'] = array( 260 'label' => '↓ ' . __( 'Today', 'rentbaik' ), 261 'title' => __( 'Comes back today', 'rentbaik' ), 262 'tone' => 'info', 263 'at' => 'end', 264 ); 265 } 266 if ( in_array( $status, array( 'pending', 'confirmed' ), true ) && $start_day === $today ) { 267 $flags['pickup_today'] = array( 268 'label' => '↑ ' . __( 'Today', 'rentbaik' ), 269 'title' => __( 'Picked up today', 'rentbaik' ), 270 'tone' => 'info', 271 'at' => 'start', 272 ); 273 } 274 if ( (int) $row['total'] > 0 && 'paid' !== $state && ! in_array( $status, array( 'completed', 'cancelled', 'no_show' ), true ) ) { 275 $flags['unpaid'] = array( 276 'label' => 'partial' === $state ? __( 'Part paid', 'rentbaik' ) : __( 'Unpaid', 'rentbaik' ), 277 'title' => __( 'Not fully paid', 'rentbaik' ), 278 // Money to collect now is stronger than a future booking that is paid at pick-up. 279 'tone' => in_array( $status, array( 'active', 'returned' ), true ) ? 'warn' : 'muted', 280 'at' => 'end', 281 ); 282 } 283 if ( in_array( $status, array( 'returned', 'completed' ), true ) && 'held' === (string) $row['deposit_status'] ) { 284 $flags['deposit'] = array( 285 'label' => __( 'Deposit', 'rentbaik' ), 286 'title' => __( 'Deposit not given back yet', 'rentbaik' ), 287 'tone' => 'warn', 288 'at' => 'end', 289 ); 290 } 291 return $flags; 227 292 } 228 293 … … 474 539 echo '<span class="rentbaik-cal__nounit">' . esc_html__( 'No unit', 'rentbaik' ) . '</span>'; 475 540 } 476 if ( $segment['unpaid'] ) { 477 echo '<i class="rentbaik-cal__unpaid" title="' . esc_attr__( 'Not fully paid', 'rentbaik' ) . '"></i>'; 541 $tags = array(); 542 foreach ( (array) ( $segment['flags'] ?? array() ) as $key => $flag ) { 543 // Pick-up labels sit on the first piece of a bar that crosses weeks, the others on the last piece. 544 if ( ( 'start' === $flag['at'] && ! $segment['cont_in'] ) || ( 'end' === $flag['at'] && ! $segment['cont_out'] ) ) { 545 $tags[ $key ] = $flag; 546 } 547 } 548 if ( ! empty( $tags ) ) { 549 echo '<span class="rentbaik-cal__tags">'; 550 foreach ( $tags as $key => $flag ) { 551 echo '<span class="rentbaik-cal__tag rentbaik-cal__tag--' . esc_attr( $flag['tone'] ) . ' rentbaik-cal__tag--' . esc_attr( $key ) . '" title="' . esc_attr( $flag['title'] ) . '">' . esc_html( $flag['label'] ) . '</span>'; 552 } 553 echo '</span>'; 478 554 } 479 555 if ( $grip_b ) { … … 708 784 echo '<li><i class="rentbaik-cal__swatch rentbaik-cal__swatch--' . esc_attr( $key ) . '" aria-hidden="true"></i>' . esc_html( $label ) . '</li>'; 709 785 } 710 echo '<li><i class="rentbaik-cal__unpaid" aria-hidden="true"></i>' . esc_html__( 'Not fully paid', 'rentbaik' ) . '</li>'; 786 foreach ( array( 787 array( 'alert', __( 'Late', 'rentbaik' ), __( 'Late return', 'rentbaik' ) ), 788 array( 'info', '↑↓ ' . __( 'Today', 'rentbaik' ), __( 'Picked up or comes back today', 'rentbaik' ) ), 789 array( 'warn', __( 'Unpaid', 'rentbaik' ), __( 'Not fully paid', 'rentbaik' ) ), 790 array( 'warn', __( 'Deposit', 'rentbaik' ), __( 'Deposit not given back yet', 'rentbaik' ) ), 791 ) as $tag ) { 792 echo '<li><span class="rentbaik-cal__tag rentbaik-cal__tag--' . esc_attr( $tag[0] ) . '" aria-hidden="true">' . esc_html( $tag[1] ) . '</span>' . esc_html( $tag[2] ) . '</li>'; 793 } 711 794 if ( $fleet > 0 ) { 712 795 echo '<li><span class="rentbaik-cal__occ">' . esc_html( '1/' . $fleet ) . '</span> ' . esc_html__( 'vehicles out / fleet', 'rentbaik' ) . '</li>'; -
rentbaik/tags/1.0.1/includes/Admin/DashboardPage.php
r3790685 r3790901 215 215 216 216 echo '<div class="rentbaik-grid">'; 217 SitePages::render_card(); 217 218 if ( $done < count( $steps ) ) { 218 219 self::render_setup( $steps, $done ); -
rentbaik/tags/1.0.1/includes/Admin/DayAgenda.php
r3790685 r3790901 229 229 echo '<span class="rentbaik-agenda__tags">'; 230 230 View::print_status( $status, $labels[ $status ] ?? $status ); 231 if ( $row['unpaid']) {232 echo ' < i class="rentbaik-cal__unpaid" title="' . esc_attr__( 'Not fully paid', 'rentbaik' ) . '"></i><span class="screen-reader-text">' . esc_html__( 'Not fully paid', 'rentbaik') . '</span>';231 foreach ( (array) ( $row['flags'] ?? array() ) as $key => $flag ) { 232 echo ' <span class="rentbaik-cal__tag rentbaik-cal__tag--' . esc_attr( $flag['tone'] ) . ' rentbaik-cal__tag--' . esc_attr( $key ) . '" title="' . esc_attr( $flag['title'] ) . '">' . esc_html( $flag['label'] ) . '</span>'; 233 233 } 234 234 echo '</span></button></li>'; -
rentbaik/tags/1.0.1/includes/Customers/Photos.php
r3790685 r3790901 263 263 * @return void 264 264 */ 265 p rivatestatic function make_thumb( string $path ): void {265 public static function make_thumb( string $path ): void { 266 266 $editor = wp_get_image_editor( $path ); 267 267 if ( is_wp_error( $editor ) ) { … … 278 278 * @return string 279 279 */ 280 p rivatestatic function thumb_path( string $path ): string {280 public static function thumb_path( string $path ): string { 281 281 $info = pathinfo( $path ); 282 282 return $info['dirname'] . '/' . $info['filename'] . '-thumb.' . ( $info['extension'] ?? 'jpg' ); -
rentbaik/tags/1.0.1/includes/Setup/SampleData.php
r3790685 r3790901 1 1 <?php 2 2 /** 3 * Sample data: example vehicles, units, extras and bookings for a first look, removable in one click.3 * Sample data: example vehicles, units, extras and a month of bookings for a first look, removable in one click. 4 4 * 5 5 * @package Rentbaik … … 8 8 namespace Rentbaik\Setup; 9 9 10 use DateTimeImmutable; 10 11 use Rentbaik\Availability\BlockRepository; 11 12 use Rentbaik\Booking\BookingRepository; 12 13 use Rentbaik\Booking\BookingService; 13 14 use Rentbaik\Customers\CustomerRepository; 15 use Rentbaik\Customers\Photos; 14 16 use Rentbaik\Database\Repository; 15 17 use Rentbaik\Fleet\UnitRepository; … … 36 38 37 39 /** 38 * Post meta that marks a sample vehicle .40 * Post meta that marks a sample vehicle (and sample images in the Media Library). 39 41 */ 40 42 public const META = '_rentbaik_sample'; … … 88 90 89 91 /** 92 * Payment methods paid before the pick-up (the others are paid at the counter). 93 */ 94 private const PREPAID = array( 'bank_transfer', 'woocommerce', 'qris' ); 95 96 /** 97 * Media Library images made in this request: asset name => attachment ID. 98 * 99 * @var array<string, int> 100 */ 101 private static $media = array(); 102 103 /** 90 104 * Saved record of the sample data. 91 105 * … … 101 115 'bookings' => array(), 102 116 'customers' => array(), 117 'media' => array(), 118 'blocks' => array(), 103 119 ); 104 120 foreach ( $defaults as $key => $empty ) { … … 144 160 $types = array_keys( Vehicle::enabled_types() ); 145 161 $plate = 100; 146 $first = array(); 162 $models = array(); 163 $extras = array(); 147 164 148 165 Emails::mute( true ); … … 157 174 } 158 175 $data['vehicles'][] = $vehicle_id; 159 if ( ! isset( $first[ $definition['type'] ] ) ) { 160 $first[ $definition['type'] ] = $vehicle_id; 161 } 176 self::add_vehicle_images( $vehicle_id, $definition ); 177 $models[ $definition['art'] ] = $vehicle_id; 162 178 for ( $i = 0; $i < $definition['units']; $i++ ) { 163 179 $unit_id = self::create_unit( $vehicle_id, $location, $plate, $definition['colors'][ $i % count( $definition['colors'] ) ] ); … … 165 181 if ( $unit_id > 0 ) { 166 182 $data['units'][] = $unit_id; 183 // A photo of this unit in its own colour. 184 $art_color = $definition['art_colors'][ $i % count( $definition['art_colors'] ) ]; 185 $photo = self::media( $definition['art'] . '-' . $art_color, (string) $definition['title'] ); 186 if ( $photo > 0 ) { 187 UnitRepository::save( $unit_id, array( 'photo_id' => $photo ) ); 188 } 167 189 } 168 190 } 169 191 } 170 192 171 foreach ( self::extra_definitions() as $ extra ) {193 foreach ( self::extra_definitions() as $key => $extra ) { 172 194 if ( empty( array_intersect( $extra['vehicle_types'], $types ) ) ) { 173 195 continue; … … 190 212 if ( $extra_id > 0 ) { 191 213 $data['extras'][] = $extra_id; 214 $extras[ $key ] = $extra_id; 192 215 } 193 216 } 194 217 195 218 // Save what exists so far, so a failure below still leaves everything removable. 219 $data['media'] = array_values( array_unique( array_merge( $data['media'], array_values( self::$media ) ) ) ); 196 220 update_option( self::OPTION, $data, false ); 197 221 198 $vehicles = array_values( $first ); 199 if ( ! empty( $vehicles ) ) { 200 $data = self::create_bookings( $data, $vehicles, $location ); 222 if ( ! empty( $models ) ) { 223 $data = self::create_bookings( $data, $models, $extras, $location ); 224 update_option( self::OPTION, $data, false ); 225 $data = self::add_service_block( $data, $models ); 201 226 } 202 227 } finally { … … 204 229 } 205 230 231 $data['media'] = array_values( array_unique( array_merge( $data['media'], array_values( self::$media ) ) ) ); 206 232 update_option( self::OPTION, $data, false ); 207 233 Repository::bump_data_version(); … … 222 248 public static function remove(): void { 223 249 $data = self::data(); 250 251 foreach ( $data['blocks'] as $block_id ) { 252 BlockRepository::delete( $block_id ); 253 } 224 254 225 255 foreach ( $data['bookings'] as $booking_id ) { … … 233 263 foreach ( $data['customers'] as $customer_id ) { 234 264 if ( empty( BookingRepository::for_customer( $customer_id ) ) ) { 265 // Sample photos and payment proofs in the private folder. 266 Photos::remove_for_customer( $customer_id ); 235 267 CustomerRepository::delete( $customer_id ); 236 268 } … … 270 302 } 271 303 304 foreach ( $data['media'] as $attachment_id ) { 305 if ( '1' === (string) get_post_meta( $attachment_id, self::META, true ) ) { 306 wp_delete_attachment( $attachment_id, true ); 307 } 308 } 309 272 310 delete_option( self::OPTION ); 273 311 Repository::bump_data_version(); 312 } 313 314 /** 315 * Path of a sample illustration shipped with the plugin. 316 * 317 * @param string $name Asset name without extension. 318 * @return string '' when missing. 319 */ 320 private static function asset( string $name ): string { 321 $path = RENTBAIK_PATH . 'assets/sample/' . sanitize_file_name( $name ) . '.png'; 322 return file_exists( $path ) ? $path : ''; 323 } 324 325 /** 326 * Media Library image from a sample illustration (made once per request and reused). 327 * 328 * @param string $name Asset name. 329 * @param string $title Image title. 330 * @return int Attachment ID (0 when it could not be made). 331 */ 332 private static function media( string $name, string $title ): int { 333 if ( isset( self::$media[ $name ] ) ) { 334 return self::$media[ $name ]; 335 } 336 $source = self::asset( $name ); 337 if ( '' === $source ) { 338 return 0; 339 } 340 $upload = wp_upload_bits( 'rentbaik-sample-' . $name . '.png', null, (string) Photos::filesystem()->get_contents( $source ) ); 341 if ( ! empty( $upload['error'] ) ) { 342 return 0; 343 } 344 $attachment_id = wp_insert_attachment( 345 array( 346 'post_mime_type' => 'image/png', 347 'post_title' => $title, 348 'post_status' => 'inherit', 349 ), 350 $upload['file'] 351 ); 352 if ( ! $attachment_id || is_wp_error( $attachment_id ) ) { 353 return 0; 354 } 355 require_once ABSPATH . 'wp-admin/includes/image.php'; 356 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) ); 357 update_post_meta( $attachment_id, self::META, '1' ); 358 update_post_meta( $attachment_id, '_wp_attachment_image_alt', $title ); 359 self::$media[ $name ] = (int) $attachment_id; 360 return (int) $attachment_id; 361 } 362 363 /** 364 * Featured image and gallery of a sample vehicle: its colours and the gear that comes with it. 365 * 366 * @param int $vehicle_id Vehicle post ID. 367 * @param array<string, mixed> $definition Vehicle definition. 368 * @return void 369 */ 370 private static function add_vehicle_images( int $vehicle_id, array $definition ): void { 371 $title = (string) $definition['title']; 372 $featured = self::media( $definition['art'] . '-' . $definition['art_colors'][0], $title ); 373 if ( $featured > 0 ) { 374 set_post_thumbnail( $vehicle_id, $featured ); 375 } 376 $gallery = array(); 377 foreach ( array_slice( $definition['art_colors'], 1 ) as $color ) { 378 $gallery[] = self::media( $definition['art'] . '-' . $color, $title ); 379 } 380 $gear = array( 381 'motorcycle' => 'gear-scooter', 382 'car' => 'gear-car', 383 ); 384 $gallery[] = self::media( $gear[ $definition['type'] ] ?? 'gear-bike', __( 'Included gear (sample)', 'rentbaik' ) ); 385 $gallery = array_filter( $gallery ); 386 if ( ! empty( $gallery ) ) { 387 update_post_meta( $vehicle_id, '_rentbaik_gallery', implode( ',', $gallery ) ); 388 } 389 } 390 391 /** 392 * Copy a sample illustration into the private folder. 393 * 394 * @param string $name Asset name. 395 * @param string $prefix File name prefix (photo, proof). 396 * @return string File name in the private folder ('' when missing). 397 */ 398 private static function private_copy( string $name, string $prefix ): string { 399 $source = self::asset( $name ); 400 if ( '' === $source ) { 401 return ''; 402 } 403 $file = $prefix . '-' . strtolower( wp_generate_password( 24, false ) ) . '.png'; 404 $path = Photos::dir() . '/' . $file; 405 if ( ! Photos::filesystem()->put_contents( $path, (string) Photos::filesystem()->get_contents( $source ), FS_CHMOD_FILE ) ) { 406 return ''; 407 } 408 Photos::make_thumb( $path ); 409 return $file; 410 } 411 412 /** 413 * Customer photo taken at the hand-over and the transfer receipt a customer uploaded, as on a real booking. 414 * 415 * @param int $booking_id Booking ID. 416 * @param array<string, mixed> $plan Plan. 417 * @return void 418 */ 419 private static function add_booking_files( int $booking_id, array $plan ): void { 420 $booking = BookingRepository::find( $booking_id ); 421 if ( ! $booking ) { 422 return; 423 } 424 $add = static function ( string $asset, string $prefix, string $type, string $status, string $when ) use ( $booking_id, $booking ): void { 425 $file = self::private_copy( $asset, $prefix ); 426 if ( '' !== $file ) { 427 Photos::insert( 428 array( 429 'customer_id' => (int) $booking['customer_id'], 430 'booking_id' => $booking_id, 431 'doc_type' => $type, 432 'file_name' => $file, 433 'mime_type' => 'image/png', 434 'status' => $status, 435 'created_at' => $when, 436 ), 437 array( '%d', '%d', '%s', '%s', '%s', '%s', '%s' ) 438 ); 439 } 440 }; 441 if ( ! empty( $plan['proof'] ) ) { 442 $add( 'receipt-sample', 'proof', 'payment_proof', (string) $plan['proof'], self::created_at( $booking ) ); 443 } 444 if ( ! empty( $plan['photo'] ) && in_array( $plan['status'], array( 'active', 'returned', 'completed' ), true ) ) { 445 $add( 'customer-' . (int) $plan['photo'], 'photo', 'photo', 'valid', (string) $booking['pickup_at'] ); 446 } 274 447 } 275 448 … … 283 456 return array( 284 457 array( 285 'type' => 'motorcycle', 286 'title' => __( 'Automatic scooter 125 cc (sample)', 'rentbaik' ), 287 'content' => __( 'Easy automatic scooter for the city. Two helmets included.', 'rentbaik' ) . "\n\n" . $note, 288 'specs' => array( 458 'type' => 'motorcycle', 459 'art' => 'scooter-125', 460 'art_colors' => array( 'white', 'red', 'black' ), 461 'title' => __( 'Automatic scooter 125 cc (sample)', 'rentbaik' ), 462 'content' => __( 'Easy automatic scooter for the city. Two helmets included.', 'rentbaik' ) . "\n\n" . $note, 463 'specs' => array( 289 464 'engine_cc' => 125, 290 465 'transmission' => 'automatic', 291 466 'helmets' => 2, 292 467 ), 293 'rates' => array(468 'rates' => array( 294 469 'hour' => 4, 295 470 'day' => 15, 296 471 'week' => 90, 297 472 ), 298 'deposit' => 50,299 'units' => 3,300 'colors' => array( __( 'White', 'rentbaik' ), __( 'Red', 'rentbaik' ), __( 'Black', 'rentbaik' ) ),473 'deposit' => 50, 474 'units' => 3, 475 'colors' => array( __( 'White', 'rentbaik' ), __( 'Red', 'rentbaik' ), __( 'Black', 'rentbaik' ) ), 301 476 ), 302 477 array( 303 'type' => 'motorcycle', 304 'title' => __( 'Comfort scooter 160 cc (sample)', 'rentbaik' ), 305 'content' => __( 'Bigger scooter with a large seat and storage, good for longer trips.', 'rentbaik' ) . "\n\n" . $note, 306 'specs' => array( 478 'type' => 'motorcycle', 479 'art' => 'scooter-160', 480 'art_colors' => array( 'grey', 'blue' ), 481 'title' => __( 'Comfort scooter 160 cc (sample)', 'rentbaik' ), 482 'content' => __( 'Bigger scooter with a large seat and storage, good for longer trips.', 'rentbaik' ) . "\n\n" . $note, 483 'specs' => array( 307 484 'engine_cc' => 160, 308 485 'transmission' => 'automatic', 309 486 'helmets' => 2, 310 487 ), 311 'rates' => array(488 'rates' => array( 312 489 'day' => 22, 313 490 'week' => 130, 314 491 ), 315 'deposit' => 80,316 'units' => 2,317 'colors' => array( __( 'Grey', 'rentbaik' ), __( 'Blue', 'rentbaik' ) ),492 'deposit' => 80, 493 'units' => 2, 494 'colors' => array( __( 'Grey', 'rentbaik' ), __( 'Blue', 'rentbaik' ) ), 318 495 ), 319 496 array( 320 'type' => 'car', 321 'title' => __( 'Compact car, automatic (sample)', 'rentbaik' ), 322 'content' => __( 'Small and easy to park. Air conditioning, five seats.', 'rentbaik' ) . "\n\n" . $note, 323 'specs' => array( 497 'type' => 'car', 498 'art' => 'car-compact', 499 'art_colors' => array( 'silver', 'white' ), 500 'title' => __( 'Compact car, automatic (sample)', 'rentbaik' ), 501 'content' => __( 'Small and easy to park. Air conditioning, five seats.', 'rentbaik' ) . "\n\n" . $note, 502 'specs' => array( 324 503 'seats' => 5, 325 504 'doors' => 5, … … 329 508 'luggage' => 2, 330 509 ), 331 'rates' => array(510 'rates' => array( 332 511 'day' => 45, 333 512 'week' => 270, 334 513 ), 335 'deposit' => 200,336 'units' => 2,337 'colors' => array( __( 'Silver', 'rentbaik' ), __( 'White', 'rentbaik' ) ),514 'deposit' => 200, 515 'units' => 2, 516 'colors' => array( __( 'Silver', 'rentbaik' ), __( 'White', 'rentbaik' ) ), 338 517 ), 339 518 array( 340 'type' => 'car', 341 'title' => __( 'Family car, 7 seats (sample)', 'rentbaik' ), 342 'content' => __( 'Room for the whole family and their bags.', 'rentbaik' ) . "\n\n" . $note, 343 'specs' => array( 519 'type' => 'car', 520 'art' => 'car-family', 521 'art_colors' => array( 'black' ), 522 'title' => __( 'Family car, 7 seats (sample)', 'rentbaik' ), 523 'content' => __( 'Room for the whole family and their bags.', 'rentbaik' ) . "\n\n" . $note, 524 'specs' => array( 344 525 'seats' => 7, 345 526 'doors' => 5, … … 349 530 'luggage' => 3, 350 531 ), 351 'rates' => array(532 'rates' => array( 352 533 'day' => 65, 353 534 'week' => 390, 354 535 ), 355 'deposit' => 300,356 'units' => 1,357 'colors' => array( __( 'Black', 'rentbaik' ) ),536 'deposit' => 300, 537 'units' => 1, 538 'colors' => array( __( 'Black', 'rentbaik' ) ), 358 539 ), 359 540 array( 360 'type' => 'bicycle', 361 'title' => __( 'City bike (sample)', 'rentbaik' ), 362 'content' => __( 'Comfortable bike with a basket and a lock.', 'rentbaik' ) . "\n\n" . $note, 363 'specs' => array( 541 'type' => 'bicycle', 542 'art' => 'bike-city', 543 'art_colors' => array( 'green' ), 544 'title' => __( 'City bike (sample)', 'rentbaik' ), 545 'content' => __( 'Comfortable bike with a basket and a lock.', 'rentbaik' ) . "\n\n" . $note, 546 'specs' => array( 364 547 'frame_size' => 'M', 365 548 'gears' => 7, 366 549 ), 367 'rates' => array(550 'rates' => array( 368 551 'hour' => 3, 369 552 'day' => 10, 370 553 ), 371 'deposit' => 0,372 'units' => 2,373 'colors' => array( __( 'Green', 'rentbaik' ) ),554 'deposit' => 0, 555 'units' => 2, 556 'colors' => array( __( 'Green', 'rentbaik' ) ), 374 557 ), 375 558 array( 376 'type' => 'ebike', 377 'title' => __( 'Electric bike (sample)', 'rentbaik' ), 378 'content' => __( 'Pedal assist up to 60 km per charge.', 'rentbaik' ) . "\n\n" . $note, 379 'specs' => array( 559 'type' => 'ebike', 560 'art' => 'ebike', 561 'art_colors' => array( 'teal' ), 562 'title' => __( 'Electric bike (sample)', 'rentbaik' ), 563 'content' => __( 'Pedal assist up to 60 km per charge.', 'rentbaik' ) . "\n\n" . $note, 564 'specs' => array( 380 565 'frame_size' => 'M', 381 566 'range_km' => 60, 382 567 ), 383 'rates' => array(568 'rates' => array( 384 569 'hour' => 6, 385 570 'day' => 25, 386 571 ), 387 'deposit' => 100,388 'units' => 2,389 'colors' => array( __( 'White', 'rentbaik' ) ),390 ), 391 ); 392 } 393 394 /** 395 * Sample extras .396 * 397 * @return array< int, array<string, mixed>>572 'deposit' => 100, 573 'units' => 2, 574 'colors' => array( __( 'White', 'rentbaik' ) ), 575 ), 576 ); 577 } 578 579 /** 580 * Sample extras, by key. 581 * 582 * @return array<string, array<string, mixed>> 398 583 */ 399 584 private static function extra_definitions(): array { 400 585 return array( 401 array(586 'helmet' => array( 402 587 'name' => __( 'Extra helmet (sample)', 'rentbaik' ), 403 588 'description' => __( 'Clean helmet for a passenger.', 'rentbaik' ), … … 407 592 'vehicle_types' => array( 'motorcycle', 'bicycle', 'ebike' ), 408 593 ), 409 array(594 'poncho' => array( 410 595 'name' => __( 'Rain poncho (sample)', 'rentbaik' ), 411 596 'description' => __( 'Yours to keep.', 'rentbaik' ), … … 415 600 'vehicle_types' => array( 'motorcycle', 'bicycle', 'ebike' ), 416 601 ), 417 array(602 'child_seat' => array( 418 603 'name' => __( 'Child seat (sample)', 'rentbaik' ), 419 604 'description' => __( 'For children from 9 to 18 kg.', 'rentbaik' ), … … 490 675 491 676 /** 492 * Three sample bookings: one picked up today, one returning today and one waiting for confirmation. 677 * A month of sample bookings in every common situation, around today. 678 * 679 * Keys: model (vehicle art), offset (days from today), hour, days, status, source, method (payment), paid 680 * (none, part, full, refunded), part (share paid), deposit (none, held, returned), proof (pending, valid), 681 * photo (customer portrait 1-8), extra (extra key), no_unit (taken although no unit is free). 682 * 683 * @return array<int, array<string, mixed>> 684 */ 685 private static function plans(): array { 686 $keys = array( 'first_name', 'country', 'phone', 'model', 'offset', 'hour', 'days', 'status', 'source', 'method', 'paid', 'part', 'deposit', 'proof', 'photo', 'extra', 'no_unit' ); 687 $rows = array( 688 // Finished last week: paid in cash, deposit given back. 689 array( 'Emma', 'GB', '15550106', 'scooter-125', -7, 10, 2, 'completed', 'whatsapp', 'cash', 'full', 0, 'returned', '', 1, 'helmet', false ), 690 // Finished: paid by card at the counter. 691 array( 'Olivia', 'US', '15550108', 'car-compact', -6, 9, 2, 'completed', 'web', 'card', 'full', 0, 'returned', '', 4, 'child_seat', false ), 692 // Returned two days ago, the deposit is still to be given back. 693 array( 'Lucas', 'FR', '15550103', 'scooter-160', -4, 9, 2, 'returned', 'admin', 'cash', 'full', 0, 'held', '', 3, '', false ), 694 // Cancelled by the customer: the transfer was refunded. 695 array( 'Liam', 'NZ', '15550107', 'bike-city', -3, 10, 1, 'cancelled', 'web', 'bank_transfer', 'refunded', 0.5, 'none', '', 0, '', false ), 696 // Did not come. 697 array( 'Wei', 'CN', '15550109', 'ebike', -2, 11, 1, 'no_show', 'web', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 698 // Should have come back yesterday: late, deposit held, rent not paid yet. 699 array( 'Noah', 'US', '15550105', 'car-compact', -3, 9, 2, 'active', 'admin', 'pay_at_pickup', 'none', 0, 'held', '', 7, '', false ), 700 // On rent, comes back today; half paid with a QR code. 701 array( 'Maria', 'DE', '15550101', 'scooter-125', -2, 16, 2, 'active', 'whatsapp', 'qris', 'part', 0.5, 'held', '', 2, 'poncho', false ), 702 // Long rental under way, paid in advance by bank transfer. 703 array( 'Sofia', 'IT', '15550104', 'car-family', -5, 10, 12, 'active', 'web', 'bank_transfer', 'full', 0, 'held', 'valid', 5, '', false ), 704 // Picks up today, pays at the counter. 705 array( 'Alex', 'AU', '15550100', 'scooter-125', 0, 10, 3, 'confirmed', 'web', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 706 // Picks up this afternoon, already paid online. 707 array( 'Aisha', 'AE', '15550110', 'scooter-160', 0, 15, 2, 'confirmed', 'web', 'woocommerce', 'full', 0, 'none', '', 0, '', false ), 708 // Taken by phone although the only family car is out: needs a unit before pick-up. 709 array( 'Omar', 'EG', '15550111', 'car-family', 2, 10, 3, 'confirmed', 'admin', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', true ), 710 // Waiting for confirmation, transfer receipt uploaded but not checked yet. 711 array( 'Kenji', 'JP', '15550102', 'ebike', 3, 11, 4, 'pending', 'web', 'bank_transfer', 'none', 0, 'none', 'pending', 0, '', false ), 712 // Confirmed with a part payment by transfer. 713 array( 'Priya', 'IN', '15550112', 'car-compact', 6, 10, 5, 'confirmed', 'web', 'bank_transfer', 'part', 0.3, 'none', 'valid', 0, 'child_seat', false ), 714 // A week by WhatsApp, pays at pick-up. 715 array( 'Mateo', 'MX', '15550113', 'scooter-125', 9, 9, 7, 'confirmed', 'whatsapp', 'pay_at_pickup', 'none', 0, 'none', '', 0, 'helmet', false ), 716 // Waiting for confirmation from WhatsApp. 717 array( 'Putri', 'ID', '15550114', 'bike-city', 12, 8, 1, 'pending', 'whatsapp', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 718 // Paid online for later this month. 719 array( 'Lena', 'SE', '15550115', 'car-compact', 16, 10, 4, 'confirmed', 'web', 'woocommerce', 'full', 0, 'none', '', 0, '', false ), 720 // A returning customer books again. 721 array( 'Emma', 'GB', '15550106', 'scooter-160', 19, 10, 3, 'pending', 'whatsapp', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 722 ); 723 return array_map( 724 static function ( array $row ) use ( $keys ): array { 725 return array_combine( $keys, $row ); 726 }, 727 $rows 728 ); 729 } 730 731 /** 732 * The planned model, or a similar one when its vehicle type is switched off (a shop with only cars still gets 733 * every situation). 734 * 735 * @param string $model Vehicle art. 736 * @param array<string, int> $models Created models: art => vehicle ID. 737 * @return string '' when no model exists. 738 */ 739 private static function model_for( string $model, array $models ): string { 740 $similar = array( 741 'scooter-125' => array( 'scooter-160', 'bike-city', 'ebike' ), 742 'scooter-160' => array( 'scooter-125', 'ebike', 'bike-city' ), 743 'car-compact' => array( 'car-family', 'scooter-160', 'scooter-125' ), 744 'car-family' => array( 'car-compact', 'scooter-160', 'scooter-125' ), 745 'bike-city' => array( 'ebike', 'scooter-125', 'scooter-160' ), 746 'ebike' => array( 'bike-city', 'scooter-160', 'scooter-125' ), 747 ); 748 foreach ( array_merge( array( $model ), $similar[ $model ] ?? array(), array_keys( $models ) ) as $candidate ) { 749 if ( isset( $models[ $candidate ] ) ) { 750 return $candidate; 751 } 752 } 753 return ''; 754 } 755 756 /** 757 * Days from today for a plan, kept inside the current month where its situation allows it. 758 * 759 * @param array<string, mixed> $plan Plan. 760 * @return int 761 */ 762 private static function offset_in_month( array $plan ): int { 763 $today = new DateTimeImmutable( 'today', wp_timezone() ); 764 $offset = (int) $plan['offset']; 765 $days = (int) $plan['days']; 766 if ( $offset > 0 ) { 767 // Later this month: keep the whole rental before the end of the month when there is room. 768 $left = (int) $today->format( 't' ) - (int) $today->format( 'j' ); 769 return max( 1, min( $offset, $left - $days ) ); 770 } 771 if ( $offset + $days <= 0 ) { 772 // Already over: as early as needed, but not before the first day of the month when it fits. 773 $first = 1 - (int) $today->format( 'j' ); 774 return max( $offset, $first ) + $days <= 0 ? max( $offset, $first ) : $offset; 775 } 776 return $offset; 777 } 778 779 /** 780 * Create the sample bookings and bring each to the situation of its plan. 493 781 * 494 782 * @param array<string, int[]> $data Sample data record. 495 * @param int[] $vehicles Sample vehicle IDs to book. 783 * @param array<string, int> $models Vehicle art => vehicle ID. 784 * @param array<string, int> $extras Extra key => extra ID. 496 785 * @param int $location_id Location ID. 497 786 * @return array<string, int[]> 498 787 */ 499 private static function create_bookings( array $data, array $vehicles, int $location_id ): array { 500 $plans = array( 501 array( 502 'first_name' => 'Alex', 503 'country' => 'AU', 504 'phone' => '15550100', 505 'offset' => 0, 506 'hour' => 10, 507 'days' => 3, 508 'status' => 'confirmed', 509 'source' => 'web', 510 ), 511 array( 512 'first_name' => 'Maria', 513 'country' => 'DE', 514 'phone' => '15550101', 515 'offset' => -2, 516 'hour' => 16, 517 'days' => 2, 518 'status' => 'active', 519 'source' => 'whatsapp', 520 ), 521 array( 522 'first_name' => 'Kenji', 523 'country' => 'JP', 524 'phone' => '15550102', 525 'offset' => 3, 526 'hour' => 11, 527 'days' => 4, 528 'status' => 'pending', 529 'source' => 'web', 530 ), 531 ); 532 533 foreach ( $plans as $index => $plan ) { 534 $vehicle_id = $vehicles[ $index % count( $vehicles ) ]; 788 private static function create_bookings( array $data, array $models, array $extras, int $location_id ): array { 789 foreach ( self::plans() as $plan ) { 790 $plan['model'] = self::model_for( (string) $plan['model'], $models ); 791 if ( '' === $plan['model'] ) { 792 continue; 793 } 794 $offset = self::offset_in_month( $plan ); 535 795 $booking_id = 0; 536 // Move a day later when the location is closed on the planned day .796 // Move a day later when the location is closed on the planned day or no unit is free. 537 797 for ( $shift = 0; $shift < 7 && $booking_id <= 0; $shift++ ) { 538 $pickup = Time::local_midnight( $plan['offset'] + $shift )->modify( '+' . $plan['hour'] . ' hours' ); 539 $result = BookingService::create( 540 array( 541 'vehicle_id' => $vehicle_id, 542 'pickup' => $pickup, 543 'return' => $pickup->modify( '+' . $plan['days'] . ' days' ), 544 'location_id' => $location_id, 545 'customer' => array( 546 'first_name' => $plan['first_name'], 547 'last_name' => __( 'Sample', 'rentbaik' ), 548 'phone' => $plan['phone'], 549 'country' => $plan['country'], 550 'notes' => __( 'Sample data', 'rentbaik' ), 551 ), 552 'status' => 'pending' === $plan['status'] ? 'pending' : 'confirmed', 553 'source' => $plan['source'], 554 'payment_method' => 'pay_at_pickup', 555 'internal_note' => __( 'Sample booking. It is removed together with the sample data.', 'rentbaik' ), 556 'allow_past' => true, 557 ) 798 $pickup = Time::local_midnight( $offset + $shift )->modify( '+' . $plan['hour'] . ' hours' ); 799 $input = array( 800 'vehicle_id' => $models[ $plan['model'] ], 801 'pickup' => $pickup, 802 'return' => $pickup->modify( '+' . $plan['days'] . ' days' ), 803 'location_id' => $location_id, 804 'customer' => array( 805 'first_name' => $plan['first_name'], 806 'last_name' => __( 'Sample', 'rentbaik' ), 807 'phone' => $plan['phone'], 808 'country' => $plan['country'], 809 'notes' => __( 'Sample data', 'rentbaik' ), 810 ), 811 'status' => 'pending' === $plan['status'] ? 'pending' : 'confirmed', 812 'source' => $plan['source'], 813 'payment_method' => $plan['method'], 814 'internal_note' => __( 'Sample booking. It is removed together with the sample data.', 'rentbaik' ), 815 'allow_past' => true, 816 'allow_unassigned' => ! empty( $plan['no_unit'] ), 558 817 ); 818 if ( ! empty( $plan['extra'] ) && isset( $extras[ $plan['extra'] ] ) ) { 819 $input['extras'] = array( $extras[ $plan['extra'] ] => 1 ); 820 } 821 $result = BookingService::create( $input ); 822 // An extra the vehicle type does not allow: try again without it. 823 if ( ! is_int( $result ) && isset( $input['extras'] ) ) { 824 unset( $input['extras'] ); 825 $result = BookingService::create( $input ); 826 } 559 827 $booking_id = is_int( $result ) ? $result : 0; 560 828 } … … 567 835 $data['customers'][] = (int) $booking['customer_id']; 568 836 } 569 if ( 'active' === $plan['status'] ) { 570 BookingService::set_status( $booking_id, 'active' ); 837 self::play_out( $booking_id, $plan ); 838 self::add_booking_files( $booking_id, $plan ); 839 } 840 return $data; 841 } 842 843 /** 844 * When the sample booking was made: three days before the pick-up, never later than an hour ago. 845 * 846 * @param array<string, mixed> $booking Booking row. 847 * @return string UTC (Y-m-d H:i:s). 848 */ 849 private static function created_at( array $booking ): string { 850 $pickup = Time::from_utc( (string) $booking['pickup_at'] ); 851 $latest = Time::now()->modify( '-1 hour' ); 852 $made = $pickup ? $pickup->modify( '-3 days' ) : $latest; 853 return Time::to_mysql( $made < $latest ? $made : $latest ); 854 } 855 856 /** 857 * Bring a sample booking to the situation of its plan: handed over, paid, returned, cancelled or not shown. 858 * Dates are set to when it would have happened, so the reports and the calendar look like a real month. 859 * 860 * @param int $booking_id Booking ID. 861 * @param array<string, mixed> $plan Plan. 862 * @return void 863 */ 864 private static function play_out( int $booking_id, array $plan ): void { 865 $booking = BookingRepository::find( $booking_id ); 866 if ( ! $booking ) { 867 return; 868 } 869 $now = Time::to_mysql( Time::now() ); 870 $made = self::created_at( $booking ); 871 $pickup = min( (string) $booking['pickup_at'], $now ); 872 $return = min( (string) $booking['return_at'], $now ); 873 $method = (string) $plan['method']; 874 $paid = (string) ( $plan['paid'] ?? 'none' ); 875 $deposit = (string) ( $plan['deposit'] ?? 'none' ); 876 // Money paid in advance arrives two hours after booking; the rest at the counter. 877 $prepaid = Time::to_mysql( min( Time::now(), Time::from_utc( $made )->modify( '+2 hours' ) ) ); 878 $pay = static function ( string $type, string $how, int $amount, string $when, string $note = '' ) use ( $booking_id ): void { 879 if ( $amount > 0 ) { 880 BookingService::add_payment( 881 $booking_id, 882 array( 883 'payment_type' => $type, 884 'method' => $how, 885 'amount' => $amount, 886 'paid_at' => $when, 887 'reference' => 'cash' === $how ? '' : 'SAMPLE-' . $booking_id, 888 'note' => '' === $note ? __( 'Sample payment', 'rentbaik' ) : $note, 889 ) 890 ); 891 } 892 }; 893 894 BookingRepository::patch( $booking_id, array( 'created_at' => $made ) ); 895 896 $total = (int) $booking['total']; 897 $share = 'full' === $paid ? $total : (int) round( $total * (float) ( $plan['part'] ?? 0 ) ); 898 if ( in_array( $paid, array( 'full', 'part', 'refunded' ), true ) && in_array( $method, self::PREPAID, true ) ) { 899 $pay( 'payment', $method, $share, $prepaid ); 900 } 901 902 if ( in_array( $plan['status'], array( 'active', 'returned', 'completed' ), true ) ) { 903 BookingService::set_status( $booking_id, 'active' ); 904 BookingRepository::patch( $booking_id, array( 'actual_pickup_at' => $pickup ) ); 905 if ( in_array( $paid, array( 'full', 'part' ), true ) && ! in_array( $method, self::PREPAID, true ) ) { 906 $pay( 'payment', $method, $share, $pickup ); 907 } 908 if ( 'none' !== $deposit ) { 909 $pay( 'deposit', 'cash', (int) $booking['deposit_amount'], $pickup, __( 'Deposit at pick-up', 'rentbaik' ) ); 910 } 911 } 912 913 if ( in_array( $plan['status'], array( 'returned', 'completed' ), true ) ) { 914 BookingService::set_status( $booking_id, 'returned' ); 915 BookingRepository::patch( $booking_id, array( 'actual_return_at' => $return ) ); 916 if ( 'returned' === $deposit ) { 917 $pay( 'deposit_refund', 'cash', (int) $booking['deposit_amount'], $return, __( 'Deposit given back', 'rentbaik' ) ); 918 } 919 if ( 'completed' === $plan['status'] ) { 920 BookingService::set_status( $booking_id, 'completed' ); 921 } 922 } 923 924 if ( 'cancelled' === $plan['status'] ) { 925 BookingService::set_status( $booking_id, 'cancelled' ); 926 if ( 'refunded' === $paid ) { 927 $pay( 'refund', $method, $share, max( $prepaid, Time::to_mysql( Time::now()->modify( '-1 day' ) ) ), __( 'Refund after cancelling', 'rentbaik' ) ); 928 } 929 } 930 if ( 'no_show' === $plan['status'] ) { 931 BookingService::set_status( $booking_id, 'no_show' ); 932 } 933 } 934 935 /** 936 * Service for a scooter over the next two days, so the calendar shows a unit that cannot be rented. 937 * 938 * @param array<string, int[]> $data Sample data record. 939 * @param array<string, int> $models Vehicle art => vehicle ID. 940 * @return array<string, int[]> 941 */ 942 private static function add_service_block( array $data, array $models ): array { 943 $vehicle_id = $models['scooter-160'] ?? ( $models['scooter-125'] ?? 0 ); 944 if ( $vehicle_id <= 0 ) { 945 return $data; 946 } 947 $start = Time::to_mysql( Time::local_midnight( 1 )->modify( '+8 hours' ) ); 948 $end = Time::to_mysql( Time::local_midnight( 3 )->modify( '+8 hours' ) ); 949 $units = array(); 950 foreach ( $data['units'] as $unit_id ) { 951 $unit = UnitRepository::find( $unit_id ); 952 if ( $unit && (int) $unit['vehicle_id'] === $vehicle_id ) { 953 $units[] = $unit_id; 954 } 955 } 956 $busy = BlockRepository::busy_units( $units, $start, $end ); 957 foreach ( $units as $unit_id ) { 958 if ( ! in_array( $unit_id, $busy, true ) ) { 959 $block_id = BlockRepository::add( $unit_id, $start, $end, 'maintenance', 0, null, __( 'Oil change (sample)', 'rentbaik' ) ); 960 if ( $block_id > 0 ) { 961 $data['blocks'][] = $block_id; 962 } 963 break; 571 964 } 572 965 } … … 580 973 * @return int 581 974 */ 582 p rivatestatic function price( float $usd ): int {975 public static function price( float $usd ): int { 583 976 if ( $usd <= 0 ) { 584 977 return 0; … … 586 979 $currency = Format::currency(); 587 980 $amount = $usd * (float) ( self::USD_RATES[ $currency ] ?? 1 ); 588 $step = $amount >= 100 ? 10 ** ( (int) floor( log10( $amount ) ) - 1 ) : 1; 589 $amount = max( $step, round( $amount / $step ) * $step ); 981 // Small amounts keep halves (5.50), larger ones are rounded to two significant digits (88,000). 982 $step = $amount >= 100 ? 10 ** ( (int) floor( log10( $amount ) ) - 1 ) : ( $amount < 10 ? 0.5 : 1 ); 983 $amount = max( $step, round( $amount / $step ) * $step ); 590 984 return (int) round( $amount * ( 10 ** Format::decimals() ) ); 591 985 } -
rentbaik/tags/1.0.1/languages/rentbaik.pot
r3790685 r3790901 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Rentbaik 1.0. 0\n"5 "Project-Id-Version: Rentbaik 1.0.1\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/rentbaik\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026-11-0 3T05:38:00+00:00\n"12 "POT-Creation-Date: 2026-11-08T10:09:24+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.12.0\n" … … 18 18 #. Author of the plugin 19 19 #: rentbaik.php 20 #: includes/Admin/Menu.php:4 621 #: includes/Admin/Menu.php:4 720 #: includes/Admin/Menu.php:44 21 #: includes/Admin/Menu.php:45 22 22 #: includes/Admin/View.php:31 23 23 #: includes/Privacy/Privacy.php:170 … … 114 114 msgstr "" 115 115 116 #: includes/Admin/Admin.php:4 7117 #: includes/Admin/Menu.php:1 22116 #: includes/Admin/Admin.php:48 117 #: includes/Admin/Menu.php:132 118 118 #: includes/Admin/SettingsPage.php:480 119 119 msgid "Settings" 120 120 msgstr "" 121 121 122 #: includes/Admin/Admin.php:5 3122 #: includes/Admin/Admin.php:54 123 123 #: includes/Admin/DashboardPage.php:186 124 #: includes/Admin/Menu.php:5 8124 #: includes/Admin/Menu.php:56 125 125 msgid "Dashboard" 126 126 msgstr "" … … 177 177 #: includes/Admin/BookingsPage.php:331 178 178 #: includes/Admin/BookingsPage.php:732 179 #: includes/Admin/CalendarView.php:285 180 #: includes/Admin/CalendarView.php:790 179 181 msgid "Deposit" 180 182 msgstr "" … … 305 307 #: includes/Admin/ExtrasPage.php:59 306 308 #: includes/Admin/ExtrasPage.php:111 307 #: includes/Fleet/VehiclePostType.php:39 3309 #: includes/Fleet/VehiclePostType.php:395 308 310 msgid "Price" 309 311 msgstr "" … … 311 313 #: includes/Admin/BookingMove.php:191 312 314 #: includes/Admin/BookingsPage.php:162 313 #: includes/Admin/CalendarView.php:5 21315 #: includes/Admin/CalendarView.php:597 314 316 msgid "Unit" 315 317 msgstr "" … … 518 520 #: includes/Admin/BookingPanel.php:201 519 521 #: includes/Admin/ExtrasPage.php:52 520 #: includes/Admin/Menu.php: 86521 #: includes/Admin/Menu.php: 87522 #: includes/Admin/Menu.php:96 523 #: includes/Admin/Menu.php:97 522 524 #: templates/booking-form.php:96 523 525 msgid "Extras" … … 688 690 #: includes/Admin/CustomersPage.php:63 689 691 #: includes/Admin/CustomersPage.php:117 690 #: includes/Admin/Menu.php:6 6691 #: includes/Admin/Menu.php:6 7692 #: includes/Admin/Menu.php:64 693 #: includes/Admin/Menu.php:65 692 694 msgid "Bookings" 693 695 msgstr "" … … 782 784 783 785 #: includes/Admin/BookingsPage.php:190 784 #: includes/Admin/CalendarView.php:7 03786 #: includes/Admin/CalendarView.php:779 785 787 #: includes/Booking/BookingService.php:44 786 788 #: includes/Frontend/BookingForm.php:311 … … 825 827 826 828 #: includes/Admin/BookingsPage.php:286 827 #: includes/Admin/DashboardPage.php:39 0829 #: includes/Admin/DashboardPage.php:391 828 830 #: includes/Admin/ExtrasPage.php:79 831 #: includes/Admin/SitePages.php:162 829 832 #: includes/Admin/UnitsTable.php:368 830 833 msgid "Edit" … … 925 928 #: includes/Admin/SetupWizard.php:455 926 929 #: includes/Admin/UnitsTable.php:54 927 #: includes/Fleet/VehiclePostType.php:39 2930 #: includes/Fleet/VehiclePostType.php:394 928 931 #: includes/Frontend/VehicleContent.php:49 929 932 msgid "Type" … … 1082 1085 1083 1086 #: includes/Admin/CalendarView.php:117 1084 #: includes/Admin/Menu.php: 751085 #: includes/Admin/Menu.php: 761087 #: includes/Admin/Menu.php:85 1088 #: includes/Admin/Menu.php:86 1086 1089 #: includes/Admin/UnitsPage.php:59 1087 #: includes/Fleet/VehiclePostType.php:39 41090 #: includes/Fleet/VehiclePostType.php:396 1088 1091 msgid "Units" 1089 1092 msgstr "" … … 1094 1097 1095 1098 #: includes/Admin/CalendarView.php:124 1096 #: includes/Admin/DashboardPage.php:312 1099 #: includes/Admin/CalendarView.php:260 1100 #: includes/Admin/CalendarView.php:268 1101 #: includes/Admin/CalendarView.php:788 1102 #: includes/Admin/DashboardPage.php:313 1097 1103 msgid "Today" 1098 1104 msgstr "" … … 1110 1116 1111 1117 #. translators: %s: date and time. 1112 #: includes/Admin/CalendarView.php:19 01118 #: includes/Admin/CalendarView.php:193 1113 1119 #: includes/Notifications/WhatsApp.php:74 1114 1120 #, php-format … … 1117 1123 1118 1124 #. translators: %s: date and time. 1119 #: includes/Admin/CalendarView.php:19 21125 #: includes/Admin/CalendarView.php:195 1120 1126 #: includes/Notifications/WhatsApp.php:79 1121 1127 #, php-format … … 1123 1129 msgstr "" 1124 1130 1131 #: includes/Admin/CalendarView.php:252 1132 #: includes/Admin/CalendarView.php:787 1133 msgid "Late" 1134 msgstr "" 1135 1136 #. translators: %d: number of hours. 1137 #: includes/Admin/CalendarView.php:254 1138 #, php-format 1139 msgid "Late by %d hour" 1140 msgid_plural "Late by %d hours" 1141 msgstr[0] "" 1142 msgstr[1] "" 1143 1144 #: includes/Admin/CalendarView.php:261 1145 msgid "Comes back today" 1146 msgstr "" 1147 1148 #: includes/Admin/CalendarView.php:269 1149 msgid "Picked up today" 1150 msgstr "" 1151 1152 #: includes/Admin/CalendarView.php:276 1153 #: includes/Booking/BookingService.php:600 1154 msgid "Part paid" 1155 msgstr "" 1156 1157 #: includes/Admin/CalendarView.php:276 1158 #: includes/Admin/CalendarView.php:789 1159 #: includes/Booking/BookingService.php:599 1160 msgid "Unpaid" 1161 msgstr "" 1162 1163 #: includes/Admin/CalendarView.php:277 1164 #: includes/Admin/CalendarView.php:789 1165 msgid "Not fully paid" 1166 msgstr "" 1167 1168 #: includes/Admin/CalendarView.php:286 1169 #: includes/Admin/CalendarView.php:790 1170 msgid "Deposit not given back yet" 1171 msgstr "" 1172 1125 1173 #. translators: %s: date. 1126 #: includes/Admin/CalendarView.php: 3661127 #: includes/Admin/CalendarView.php: 5351174 #: includes/Admin/CalendarView.php:431 1175 #: includes/Admin/CalendarView.php:611 1128 1176 #, php-format 1129 1177 msgid "Show the agenda of %s" … … 1131 1179 1132 1180 #. translators: 1: vehicles out, 2: fleet size. 1133 #: includes/Admin/CalendarView.php: 3711181 #: includes/Admin/CalendarView.php:436 1134 1182 #, php-format 1135 1183 msgid "%1$d of %2$d vehicles out" 1136 1184 msgstr "" 1137 1185 1138 #: includes/Admin/CalendarView.php: 3741186 #: includes/Admin/CalendarView.php:439 1139 1187 msgid "Closed" 1140 1188 msgstr "" 1141 1189 1142 1190 #. translators: %s: date. 1143 #: includes/Admin/CalendarView.php: 3851191 #: includes/Admin/CalendarView.php:450 1144 1192 #, php-format 1145 1193 msgid "Add booking on %s" … … 1147 1195 1148 1196 #. translators: %d: number of bookings not shown. 1149 #: includes/Admin/CalendarView.php: 3971197 #: includes/Admin/CalendarView.php:462 1150 1198 #, php-format 1151 1199 msgid "+%d more" 1152 1200 msgstr "" 1153 1201 1154 #: includes/Admin/CalendarView.php: 4571202 #: includes/Admin/CalendarView.php:522 1155 1203 msgid "Drag to change the pick-up date" 1156 1204 msgstr "" 1157 1205 1158 #: includes/Admin/CalendarView.php: 4741206 #: includes/Admin/CalendarView.php:539 1159 1207 msgid "No unit" 1160 1208 msgstr "" 1161 1209 1162 #: includes/Admin/CalendarView.php:477 1163 #: includes/Admin/CalendarView.php:710 1164 #: includes/Admin/DayAgenda.php:232 1165 msgid "Not fully paid" 1166 msgstr "" 1167 1168 #: includes/Admin/CalendarView.php:480 1210 #: includes/Admin/CalendarView.php:556 1169 1211 msgid "Drag to change the return date" 1170 1212 msgstr "" 1171 1213 1172 1214 #. translators: %d: number of units. 1173 #: includes/Admin/CalendarView.php: 5541215 #: includes/Admin/CalendarView.php:630 1174 1216 #, php-format 1175 1217 msgid "%d unit" … … 1178 1220 msgstr[1] "" 1179 1221 1180 #: includes/Admin/CalendarView.php: 5701222 #: includes/Admin/CalendarView.php:646 1181 1223 msgid "No unit yet" 1182 1224 msgstr "" 1183 1225 1184 #: includes/Admin/CalendarView.php: 5711226 #: includes/Admin/CalendarView.php:647 1185 1227 msgid "drag to a unit" 1186 1228 msgstr "" 1187 1229 1188 #: includes/Admin/CalendarView.php: 6701230 #: includes/Admin/CalendarView.php:746 1189 1231 #: includes/Admin/UnitsPage.php:166 1190 1232 #: includes/Admin/UnitsPage.php:222 1191 #: includes/Admin/UnitsPage.php:38 81233 #: includes/Admin/UnitsPage.php:389 1192 1234 msgid "Service" 1193 1235 msgstr "" 1194 1236 1195 #: includes/Admin/CalendarView.php: 6701237 #: includes/Admin/CalendarView.php:746 1196 1238 #: includes/Admin/UnitsPage.php:167 1197 #: includes/Admin/UnitsPage.php:38 81239 #: includes/Admin/UnitsPage.php:389 1198 1240 msgid "Blocked" 1199 1241 msgstr "" 1200 1242 1201 #: includes/Admin/CalendarView.php: 6881243 #: includes/Admin/CalendarView.php:764 1202 1244 msgid "No units yet. Add the units (plate numbers) of your vehicles first." 1203 1245 msgstr "" 1204 1246 1205 #: includes/Admin/CalendarView.php:7 021247 #: includes/Admin/CalendarView.php:778 1206 1248 #: includes/Booking/BookingService.php:43 1207 1249 msgid "Waiting" 1208 1250 msgstr "" 1209 1251 1210 #: includes/Admin/CalendarView.php:7 041252 #: includes/Admin/CalendarView.php:780 1211 1253 #: includes/Booking/BookingService.php:45 1212 1254 #: includes/Fleet/UnitRepository.php:41 … … 1215 1257 msgstr "" 1216 1258 1217 #: includes/Admin/CalendarView.php:705 1259 #: includes/Admin/CalendarView.php:781 1260 #: includes/Admin/CalendarView.php:787 1218 1261 #: includes/Admin/UnitsTable.php:312 1219 1262 msgid "Late return" 1220 1263 msgstr "" 1221 1264 1222 #: includes/Admin/CalendarView.php:7 061265 #: includes/Admin/CalendarView.php:782 1223 1266 msgid "Returned / completed" 1224 1267 msgstr "" 1225 1268 1226 #: includes/Admin/CalendarView.php:712 1269 #: includes/Admin/CalendarView.php:788 1270 msgid "Picked up or comes back today" 1271 msgstr "" 1272 1273 #: includes/Admin/CalendarView.php:795 1227 1274 msgid "vehicles out / fleet" 1228 1275 msgstr "" 1229 1276 1230 1277 #. translators: %d: number of pick-ups. 1231 #: includes/Admin/CalendarView.php: 7361278 #: includes/Admin/CalendarView.php:819 1232 1279 #, php-format 1233 1280 msgid "%d pick-up" … … 1237 1284 1238 1285 #. translators: %d: number of returns. 1239 #: includes/Admin/CalendarView.php: 7381286 #: includes/Admin/CalendarView.php:821 1240 1287 #, php-format 1241 1288 msgid "%d return" … … 1245 1292 1246 1293 #. translators: %d: number of bookings waiting for confirmation. 1247 #: includes/Admin/CalendarView.php: 7401294 #: includes/Admin/CalendarView.php:823 1248 1295 #, php-format 1249 1296 msgid "%d waiting" … … 1253 1300 1254 1301 #: includes/Admin/CustomersPage.php:58 1255 #: includes/Admin/Menu.php: 951256 #: includes/Admin/Menu.php: 961302 #: includes/Admin/Menu.php:105 1303 #: includes/Admin/Menu.php:106 1257 1304 msgid "Customers" 1258 1305 msgstr "" … … 1358 1405 1359 1406 #: includes/Admin/DashboardPage.php:65 1360 #: includes/Fleet/VehiclePostType.php:14 61407 #: includes/Fleet/VehiclePostType.php:148 1361 1408 msgid "Units (plate numbers)" 1362 1409 msgstr "" … … 1505 1552 msgstr "" 1506 1553 1507 #: includes/Admin/DashboardPage.php:23 51554 #: includes/Admin/DashboardPage.php:236 1508 1555 msgid "Pick-ups today" 1509 1556 msgstr "" 1510 1557 1511 #: includes/Admin/DashboardPage.php:23 61558 #: includes/Admin/DashboardPage.php:237 1512 1559 msgid "Returns today" 1513 1560 msgstr "" 1514 1561 1515 #: includes/Admin/DashboardPage.php:23 71562 #: includes/Admin/DashboardPage.php:238 1516 1563 msgid "Late returns" 1517 1564 msgstr "" 1518 1565 1519 #: includes/Admin/DashboardPage.php:23 81566 #: includes/Admin/DashboardPage.php:239 1520 1567 msgid "Waiting for confirmation" 1521 1568 msgstr "" 1522 1569 1523 1570 #. translators: %d: number of more bookings. 1524 #: includes/Admin/DashboardPage.php:26 11571 #: includes/Admin/DashboardPage.php:262 1525 1572 #, php-format 1526 1573 msgid "+ %d more" 1527 1574 msgstr "" 1528 1575 1529 #: includes/Admin/DashboardPage.php:26 51576 #: includes/Admin/DashboardPage.php:266 1530 1577 msgid "Review and confirm" 1531 1578 msgstr "" 1532 1579 1533 #: includes/Admin/DashboardPage.php:26 71580 #: includes/Admin/DashboardPage.php:268 1534 1581 msgid "Everything is back on time." 1535 1582 msgstr "" 1536 1583 1537 #: includes/Admin/DashboardPage.php:26 71584 #: includes/Admin/DashboardPage.php:268 1538 1585 msgid "Nothing planned." 1539 1586 msgstr "" 1540 1587 1541 #: includes/Admin/DashboardPage.php:28 31588 #: includes/Admin/DashboardPage.php:284 1542 1589 msgid "Fleet right now" 1543 1590 msgstr "" 1544 1591 1545 #: includes/Admin/DashboardPage.php:28 31592 #: includes/Admin/DashboardPage.php:284 1546 1593 #: includes/Admin/UnitsPage.php:94 1547 1594 msgid "All units" 1548 1595 msgstr "" 1549 1596 1550 #: includes/Admin/DashboardPage.php:28 51597 #: includes/Admin/DashboardPage.php:286 1551 1598 msgid "No units yet." 1552 1599 msgstr "" 1553 1600 1554 1601 #. translators: %d: percentage of the fleet on rent. 1555 #: includes/Admin/DashboardPage.php:29 01602 #: includes/Admin/DashboardPage.php:291 1556 1603 #, php-format 1557 1604 msgid "%d%% on rent" 1558 1605 msgstr "" 1559 1606 1560 #: includes/Admin/DashboardPage.php:29 11607 #: includes/Admin/DashboardPage.php:292 1561 1608 msgid "Fleet status" 1562 1609 msgstr "" 1563 1610 1564 #: includes/Admin/DashboardPage.php:31 21611 #: includes/Admin/DashboardPage.php:313 1565 1612 msgid "vs yesterday" 1566 1613 msgstr "" 1567 1614 1568 #: includes/Admin/DashboardPage.php:31 31615 #: includes/Admin/DashboardPage.php:314 1569 1616 msgid "Last 7 days" 1570 1617 msgstr "" 1571 1618 1572 #: includes/Admin/DashboardPage.php:31 31619 #: includes/Admin/DashboardPage.php:314 1573 1620 msgid "vs the 7 days before" 1574 1621 msgstr "" 1575 1622 1576 #: includes/Admin/DashboardPage.php:31 41623 #: includes/Admin/DashboardPage.php:315 1577 1624 msgid "This month" 1578 1625 msgstr "" 1579 1626 1580 #: includes/Admin/DashboardPage.php:31 41627 #: includes/Admin/DashboardPage.php:315 1581 1628 msgid "vs the same days last month" 1582 1629 msgstr "" 1583 1630 1584 #: includes/Admin/DashboardPage.php:31 61631 #: includes/Admin/DashboardPage.php:317 1585 1632 msgid "Income received" 1586 1633 msgstr "" 1587 1634 1588 #: includes/Admin/DashboardPage.php:32 61635 #: includes/Admin/DashboardPage.php:327 1589 1636 msgid "Rental payments recorded in Rentbaik, without deposits." 1590 1637 msgstr "" 1591 1638 1592 #: includes/Admin/DashboardPage.php:34 71639 #: includes/Admin/DashboardPage.php:348 1593 1640 msgid "Welcome to Rentbaik" 1594 1641 msgstr "" 1595 1642 1596 #: includes/Admin/DashboardPage.php:34 81643 #: includes/Admin/DashboardPage.php:349 1597 1644 msgid "Answer a few simple questions and your booking page is ready. Not sure yet? Try it with sample data first." 1598 1645 msgstr "" 1599 1646 1600 #: includes/Admin/DashboardPage.php:3 491647 #: includes/Admin/DashboardPage.php:350 1601 1648 msgid "About 5 minutes" 1602 1649 msgstr "" 1603 1650 1604 #: includes/Admin/DashboardPage.php:3 491651 #: includes/Admin/DashboardPage.php:350 1605 1652 msgid "Every step can be skipped" 1606 1653 msgstr "" 1607 1654 1608 #: includes/Admin/DashboardPage.php:3 491655 #: includes/Admin/DashboardPage.php:350 1609 1656 msgid "Change everything later" 1610 1657 msgstr "" 1611 1658 1612 #: includes/Admin/DashboardPage.php:35 11659 #: includes/Admin/DashboardPage.php:352 1613 1660 msgid "Start the setup wizard" 1614 1661 msgstr "" 1615 1662 1616 #: includes/Admin/DashboardPage.php:35 61663 #: includes/Admin/DashboardPage.php:357 1617 1664 msgid "Hide this" 1618 1665 msgstr "" 1619 1666 1620 #: includes/Admin/DashboardPage.php:37 01667 #: includes/Admin/DashboardPage.php:371 1621 1668 msgid "Get ready to take bookings" 1622 1669 msgstr "" 1623 1670 1624 1671 #. translators: 1: completed steps, 2: total steps. 1625 #: includes/Admin/DashboardPage.php:37 21672 #: includes/Admin/DashboardPage.php:373 1626 1673 #, php-format 1627 1674 msgid "%1$d of %2$d done" 1628 1675 msgstr "" 1629 1676 1630 #: includes/Admin/DashboardPage.php:37 41677 #: includes/Admin/DashboardPage.php:375 1631 1678 msgid "Prefer step by step? Open the setup wizard." 1632 1679 msgstr "" 1633 1680 1634 #: includes/Admin/DashboardPage.php:39 01681 #: includes/Admin/DashboardPage.php:391 1635 1682 msgid "Set up" 1636 1683 msgstr "" 1637 1684 1638 #: includes/Admin/DashboardPage.php:40 41685 #: includes/Admin/DashboardPage.php:405 1639 1686 msgid "System status" 1640 1687 msgstr "" 1641 1688 1642 #: includes/Admin/DashboardPage.php:40 71689 #: includes/Admin/DashboardPage.php:408 1643 1690 msgid "OK" 1644 1691 msgstr "" 1645 1692 1646 #: includes/Admin/DashboardPage.php:40 71693 #: includes/Admin/DashboardPage.php:408 1647 1694 msgid "Check" 1648 1695 msgstr "" 1649 1696 1650 #: includes/Admin/DashboardPage.php:40 71697 #: includes/Admin/DashboardPage.php:408 1651 1698 msgid "Optional" 1652 1699 msgstr "" … … 1699 1746 #: includes/Admin/ExtrasPage.php:187 1700 1747 #: includes/Admin/UnitsPage.php:51 1701 #: includes/Admin/UnitsPage.php:4 291748 #: includes/Admin/UnitsPage.php:430 1702 1749 msgid "You are not allowed to manage the fleet." 1703 1750 msgstr "" … … 1763 1810 #: includes/Admin/ExtrasPage.php:112 1764 1811 #: includes/Admin/SettingsPage.php:110 1765 #: includes/Fleet/VehiclePostType.php:21 31812 #: includes/Fleet/VehiclePostType.php:215 1766 1813 #: includes/Frontend/VehicleContent.php:74 1767 1814 msgid "Per day" … … 1810 1857 msgstr "" 1811 1858 1812 #: includes/Admin/Menu.php:5 71859 #: includes/Admin/Menu.php:55 1813 1860 msgid "Rentbaik Dashboard" 1814 1861 msgstr "" 1815 1862 1816 #: includes/Admin/Menu.php:104 1817 #: includes/Fleet/VehiclePostType.php:115 1863 #: includes/Admin/Menu.php:76 1864 #: includes/Admin/Menu.php:77 1865 #: includes/Admin/SetupWizard.php:78 1866 #: includes/Fleet/VehiclePostType.php:80 1867 #: includes/Fleet/VehiclePostType.php:90 1868 #: includes/Fleet/VehiclePostType.php:91 1869 msgid "Vehicles" 1870 msgstr "" 1871 1872 #: includes/Admin/Menu.php:114 1873 #: includes/Fleet/VehiclePostType.php:117 1818 1874 msgid "Vehicle categories" 1819 1875 msgstr "" 1820 1876 1821 #: includes/Admin/Menu.php:1 051822 #: includes/Fleet/VehiclePostType.php:11 71877 #: includes/Admin/Menu.php:115 1878 #: includes/Fleet/VehiclePostType.php:119 1823 1879 msgid "Categories" 1824 1880 msgstr "" 1825 1881 1826 #: includes/Admin/Menu.php:1 121882 #: includes/Admin/Menu.php:122 1827 1883 #: includes/Admin/SetupWizard.php:140 1828 1884 #: includes/Admin/SetupWizard.php:162 … … 1830 1886 msgstr "" 1831 1887 1832 #: includes/Admin/Menu.php:1 131888 #: includes/Admin/Menu.php:123 1833 1889 #: includes/Admin/SettingsPage.php:480 1834 1890 msgid "Setup wizard" 1835 1891 msgstr "" 1836 1892 1837 #: includes/Admin/Menu.php:1 211893 #: includes/Admin/Menu.php:131 1838 1894 msgid "Rentbaik Settings" 1895 msgstr "" 1896 1897 #: includes/Admin/PrivateCheck.php:111 1898 msgid "Customer files are not protected yet." 1899 msgstr "" 1900 1901 #: includes/Admin/PrivateCheck.php:112 1902 msgid "Your web server opens files in the Rentbaik private folder directly. Customer photos and payment receipts have random names that cannot be guessed, but to block the folder completely add this rule to your nginx configuration (or ask your host):" 1903 msgstr "" 1904 1905 #: includes/Admin/PrivateCheck.php:114 1906 msgid "Check again" 1907 msgstr "" 1908 1909 #: includes/Admin/PrivateCheck.php:124 1910 msgid "You are not allowed to do this." 1911 msgstr "" 1912 1913 #: includes/Admin/PrivateCheck.php:129 1914 msgid "The private folder is protected." 1915 msgstr "" 1916 1917 #: includes/Admin/PrivateCheck.php:131 1918 msgid "The check could not reach your site. Please try again later." 1839 1919 msgstr "" 1840 1920 … … 1920 2000 1921 2001 #: includes/Admin/SettingsPage.php:111 1922 #: includes/Fleet/VehiclePostType.php:21 22002 #: includes/Fleet/VehiclePostType.php:214 1923 2003 #: includes/Frontend/VehicleContent.php:73 1924 2004 msgid "Per hour" … … 2026 2106 2027 2107 #: includes/Admin/SettingsPage.php:175 2028 #: includes/Fleet/VehiclePostType.php:2 282108 #: includes/Fleet/VehiclePostType.php:230 2029 2109 msgid "Minimum rental (hours)" 2030 2110 msgstr "" 2031 2111 2032 2112 #: includes/Admin/SettingsPage.php:177 2033 #: includes/Fleet/VehiclePostType.php:2 292113 #: includes/Fleet/VehiclePostType.php:231 2034 2114 msgid "Maximum rental (days)" 2035 2115 msgstr "" … … 2064 2144 2065 2145 #: includes/Admin/SettingsPage.php:194 2066 #: includes/Fleet/VehiclePostType.php:22 72146 #: includes/Fleet/VehiclePostType.php:229 2067 2147 msgid "Security deposit" 2068 2148 msgstr "" … … 2336 2416 2337 2417 #: includes/Admin/SettingsPage.php:587 2418 #: includes/Admin/SitePages.php:159 2338 2419 msgid "View" 2339 2420 msgstr "" … … 2482 2563 #: includes/Admin/SetupWizard.php:76 2483 2564 msgid "Pick-up place" 2484 msgstr ""2485 2486 #: includes/Admin/SetupWizard.php:782487 #: includes/Fleet/VehiclePostType.php:802488 #: includes/Fleet/VehiclePostType.php:902489 #: includes/Fleet/VehiclePostType.php:912490 msgid "Vehicles"2491 2565 msgstr "" 2492 2566 … … 2717 2791 2718 2792 #: includes/Admin/SetupWizard.php:521 2793 #: includes/Admin/SitePages.php:159 2794 #: includes/Admin/SitePages.php:177 2719 2795 #: templates/booking-form.php:69 2720 2796 msgid "(opens in a new tab)" … … 2798 2874 msgstr "" 2799 2875 2876 #: includes/Admin/SitePages.php:32 2877 msgid "Customers choose dates and see the free vehicles with the price." 2878 msgstr "" 2879 2880 #: includes/Admin/SitePages.php:33 2881 msgid "Booking form in three steps; the confirmation and the booking details open here too." 2882 msgstr "" 2883 2884 #: includes/Admin/SitePages.php:34 2885 msgid "Customers look up their booking with its number and their email or phone." 2886 msgstr "" 2887 2888 #: includes/Admin/SitePages.php:56 2889 msgid "Vehicle pages" 2890 msgstr "" 2891 2892 #. translators: %s: vehicle name. 2893 #: includes/Admin/SitePages.php:58 2894 #, php-format 2895 msgid "Every vehicle has its own page with photos, prices, availability and a booking button. Example: %s" 2896 msgstr "" 2897 2898 #: includes/Admin/SitePages.php:126 2899 msgid "Your pages for customers" 2900 msgstr "" 2901 2902 #: includes/Admin/SitePages.php:128 2903 msgid "Change pages" 2904 msgstr "" 2905 2906 #: includes/Admin/SitePages.php:131 2907 msgid "Rentbaik made these pages when it was activated. Add them to your site menu, or put the shortcodes on any page or in any page builder." 2908 msgstr "" 2909 2910 #. translators: %d: number of pages. 2911 #: includes/Admin/SitePages.php:136 2912 #, php-format 2913 msgid "%d page is missing." 2914 msgid_plural "%d pages are missing." 2915 msgstr[0] "" 2916 msgstr[1] "" 2917 2918 #: includes/Admin/SitePages.php:139 2919 msgid "Create them in Settings → Display" 2920 msgstr "" 2921 2922 #: includes/Admin/SitePages.php:148 2923 msgid "Not published" 2924 msgstr "" 2925 2926 #: includes/Admin/SitePages.php:151 2927 msgid "Missing" 2928 msgstr "" 2929 2930 #. translators: %s: booking number. 2931 #: includes/Admin/SitePages.php:174 2932 #, php-format 2933 msgid "See sample booking %s as the customer sees it" 2934 msgstr "" 2935 2800 2936 #: includes/Admin/UnitsPage.php:59 2801 2937 msgid "Every real vehicle with its plate number, kilometres and status." … … 2805 2941 #: includes/Admin/UnitsPage.php:93 2806 2942 #: includes/Admin/UnitsPage.php:142 2807 #: includes/Fleet/VehiclePostType.php:29 62943 #: includes/Fleet/VehiclePostType.php:298 2808 2944 msgid "Add unit" 2809 2945 msgstr "" … … 2951 3087 2952 3088 #. translators: %s: unit status. 2953 #: includes/Admin/UnitsPage.php:34 43089 #: includes/Admin/UnitsPage.php:345 2954 3090 #, php-format 2955 3091 msgid "Status set to %s (bulk action)." … … 2957 3093 2958 3094 #. translators: %d: number of units. 2959 #: includes/Admin/UnitsPage.php:34 73095 #: includes/Admin/UnitsPage.php:348 2960 3096 #, php-format 2961 3097 msgid "%d unit updated." … … 2964 3100 msgstr[1] "" 2965 3101 2966 #: includes/Admin/UnitsPage.php:37 13102 #: includes/Admin/UnitsPage.php:372 2967 3103 msgid "Choose a start and an end; the end must be after the start." 2968 3104 msgstr "" 2969 3105 2970 #: includes/Admin/UnitsPage.php:37 63106 #: includes/Admin/UnitsPage.php:377 2971 3107 msgid "Dates blocked. Note: this unit already has a booking or block in that period." 2972 3108 msgstr "" 2973 3109 2974 #: includes/Admin/UnitsPage.php:37 83110 #: includes/Admin/UnitsPage.php:379 2975 3111 msgid "Dates blocked." 2976 3112 msgstr "" 2977 3113 2978 3114 #. translators: 1: block type, 2: start, 3: end. 2979 #: includes/Admin/UnitsPage.php:38 73115 #: includes/Admin/UnitsPage.php:388 2980 3116 #, php-format 2981 3117 msgid "Blocked (%1$s): %2$s – %3$s." … … 2983 3119 2984 3120 #. translators: 1: start, 2: end. 2985 #: includes/Admin/UnitsPage.php:41 03121 #: includes/Admin/UnitsPage.php:411 2986 3122 #, php-format 2987 3123 msgid "Block removed: %1$s – %2$s." 2988 3124 msgstr "" 2989 3125 2990 #: includes/Admin/UnitsPage.php:41 13126 #: includes/Admin/UnitsPage.php:412 2991 3127 msgid "Block removed." 2992 3128 msgstr "" … … 3077 3213 3078 3214 #: includes/Admin/UnitsTable.php:330 3079 #: includes/Fleet/VehiclePostType.php:26 73215 #: includes/Fleet/VehiclePostType.php:269 3080 3216 msgid "No units yet. A unit is one real vehicle with its own plate number." 3081 3217 msgstr "" … … 3218 3354 msgstr "" 3219 3355 3220 #: includes/Booking/BookingService.php:5993221 msgid "Unpaid"3222 msgstr ""3223 3224 #: includes/Booking/BookingService.php:6003225 msgid "Part paid"3226 msgstr ""3227 3228 3356 #: includes/Capabilities.php:70 3229 3357 msgid "Rental Manager" … … 3395 3523 msgstr "" 3396 3524 3397 #: includes/Fleet/VehiclePostType.php:11 63525 #: includes/Fleet/VehiclePostType.php:118 3398 3526 msgid "Vehicle category" 3399 3527 msgstr "" 3400 3528 3401 #: includes/Fleet/VehiclePostType.php:1 183529 #: includes/Fleet/VehiclePostType.php:120 3402 3530 msgid "Add category" 3403 3531 msgstr "" 3404 3532 3405 #: includes/Fleet/VehiclePostType.php:1 193533 #: includes/Fleet/VehiclePostType.php:121 3406 3534 msgid "Edit category" 3407 3535 msgstr "" 3408 3536 3409 #: includes/Fleet/VehiclePostType.php:12 03537 #: includes/Fleet/VehiclePostType.php:122 3410 3538 msgid "Search categories" 3411 3539 msgstr "" 3412 3540 3413 #: includes/Fleet/VehiclePostType.php:14 33541 #: includes/Fleet/VehiclePostType.php:145 3414 3542 msgid "Vehicle details" 3415 3543 msgstr "" 3416 3544 3417 #: includes/Fleet/VehiclePostType.php:14 43545 #: includes/Fleet/VehiclePostType.php:146 3418 3546 #: templates/vehicle-details.php:35 3419 3547 msgid "Prices" 3420 3548 msgstr "" 3421 3549 3422 #: includes/Fleet/VehiclePostType.php:14 53550 #: includes/Fleet/VehiclePostType.php:147 3423 3551 msgid "Photo gallery" 3424 3552 msgstr "" 3425 3553 3426 #: includes/Fleet/VehiclePostType.php:16 23554 #: includes/Fleet/VehiclePostType.php:164 3427 3555 msgid "Vehicle type" 3428 3556 msgstr "" 3429 3557 3430 #: includes/Fleet/VehiclePostType.php:16 73558 #: includes/Fleet/VehiclePostType.php:169 3431 3559 #: includes/Frontend/VehicleContent.php:53 3432 3560 msgid "Brand" 3433 3561 msgstr "" 3434 3562 3435 #: includes/Fleet/VehiclePostType.php:16 73563 #: includes/Fleet/VehiclePostType.php:169 3436 3564 msgid "e.g. Honda" 3437 3565 msgstr "" 3438 3566 3439 #: includes/Fleet/VehiclePostType.php:1 683567 #: includes/Fleet/VehiclePostType.php:170 3440 3568 #: includes/Frontend/VehicleContent.php:56 3441 3569 msgid "Model year" … … 3443 3571 3444 3572 #. translators: %s: vehicle type, e.g. Car. 3445 #: includes/Fleet/VehiclePostType.php:1 783573 #: includes/Fleet/VehiclePostType.php:180 3446 3574 #, php-format 3447 3575 msgid "%s specifications" 3448 3576 msgstr "" 3449 3577 3450 #: includes/Fleet/VehiclePostType.php:21 43578 #: includes/Fleet/VehiclePostType.php:216 3451 3579 #: includes/Frontend/VehicleContent.php:75 3452 3580 msgid "Per week (7 days)" 3453 3581 msgstr "" 3454 3582 3455 #: includes/Fleet/VehiclePostType.php:21 53583 #: includes/Fleet/VehiclePostType.php:217 3456 3584 #: includes/Frontend/VehicleContent.php:76 3457 3585 msgid "Per month (30 days)" 3458 3586 msgstr "" 3459 3587 3460 #: includes/Fleet/VehiclePostType.php:22 03588 #: includes/Fleet/VehiclePostType.php:222 3461 3589 msgid "Leave a price empty if you do not offer that period. Longer rentals automatically use the cheaper weekly or monthly price when it is set." 3462 3590 msgstr "" 3463 3591 3464 #: includes/Fleet/VehiclePostType.php:23 13592 #: includes/Fleet/VehiclePostType.php:233 3465 3593 msgid "Empty deposit, minimum and maximum use the defaults from Settings → Rental rules. Maximum 0 means no limit." 3466 3594 msgstr "" 3467 3595 3468 #: includes/Fleet/VehiclePostType.php:2 493596 #: includes/Fleet/VehiclePostType.php:251 3469 3597 msgid "Choose photos" 3470 3598 msgstr "" 3471 3599 3472 #: includes/Fleet/VehiclePostType.php:25 03600 #: includes/Fleet/VehiclePostType.php:252 3473 3601 msgid "Remove all" 3474 3602 msgstr "" 3475 3603 3476 #: includes/Fleet/VehiclePostType.php:25 13604 #: includes/Fleet/VehiclePostType.php:253 3477 3605 msgid "The featured image is the main photo. Add more photos here for the vehicle page." 3478 3606 msgstr "" 3479 3607 3480 #: includes/Fleet/VehiclePostType.php:28 53608 #: includes/Fleet/VehiclePostType.php:287 3481 3609 msgid "Save the vehicle first, then add its units." 3482 3610 msgstr "" 3483 3611 3484 3612 #. translators: %s: price. 3485 #: includes/Fleet/VehiclePostType.php:41 63613 #: includes/Fleet/VehiclePostType.php:418 3486 3614 #, php-format 3487 3615 msgid "%s / day" … … 3489 3617 3490 3618 #. translators: %s: price. 3491 #: includes/Fleet/VehiclePostType.php:4 193619 #: includes/Fleet/VehiclePostType.php:421 3492 3620 #, php-format 3493 3621 msgid "%s / hour" 3494 3622 msgstr "" 3495 3623 3496 #: includes/Fleet/VehiclePostType.php:42 13624 #: includes/Fleet/VehiclePostType.php:423 3497 3625 msgid "No price yet" 3498 3626 msgstr "" 3499 3627 3500 3628 #. translators: 1: ready units, 2: all units. 3501 #: includes/Fleet/VehiclePostType.php:4 283629 #: includes/Fleet/VehiclePostType.php:430 3502 3630 #, php-format 3503 3631 msgid "%1$d ready of %2$d" … … 3952 4080 msgstr "" 3953 4081 3954 #: includes/Setup/SampleData.php:282 4082 #: includes/Setup/SampleData.php:384 4083 msgid "Included gear (sample)" 4084 msgstr "" 4085 4086 #: includes/Setup/SampleData.php:455 3955 4087 msgid "This is sample data from Rentbaik. Change it, or remove all sample data from the Rentbaik dashboard." 3956 4088 msgstr "" 3957 4089 3958 #: includes/Setup/SampleData.php: 2864090 #: includes/Setup/SampleData.php:461 3959 4091 msgid "Automatic scooter 125 cc (sample)" 3960 4092 msgstr "" 3961 4093 3962 #: includes/Setup/SampleData.php: 2874094 #: includes/Setup/SampleData.php:462 3963 4095 msgid "Easy automatic scooter for the city. Two helmets included." 3964 4096 msgstr "" 3965 4097 3966 #: includes/Setup/SampleData.php: 3003967 #: includes/Setup/SampleData.php: 3373968 #: includes/Setup/SampleData.php: 3894098 #: includes/Setup/SampleData.php:475 4099 #: includes/Setup/SampleData.php:516 4100 #: includes/Setup/SampleData.php:574 3969 4101 msgid "White" 3970 4102 msgstr "" 3971 4103 3972 #: includes/Setup/SampleData.php: 3004104 #: includes/Setup/SampleData.php:475 3973 4105 msgid "Red" 3974 4106 msgstr "" 3975 4107 3976 #: includes/Setup/SampleData.php: 3003977 #: includes/Setup/SampleData.php: 3574108 #: includes/Setup/SampleData.php:475 4109 #: includes/Setup/SampleData.php:538 3978 4110 msgid "Black" 3979 4111 msgstr "" 3980 4112 3981 #: includes/Setup/SampleData.php: 3044113 #: includes/Setup/SampleData.php:481 3982 4114 msgid "Comfort scooter 160 cc (sample)" 3983 4115 msgstr "" 3984 4116 3985 #: includes/Setup/SampleData.php: 3054117 #: includes/Setup/SampleData.php:482 3986 4118 msgid "Bigger scooter with a large seat and storage, good for longer trips." 3987 4119 msgstr "" 3988 4120 3989 #: includes/Setup/SampleData.php: 3174121 #: includes/Setup/SampleData.php:494 3990 4122 msgid "Grey" 3991 4123 msgstr "" 3992 4124 3993 #: includes/Setup/SampleData.php: 3174125 #: includes/Setup/SampleData.php:494 3994 4126 msgid "Blue" 3995 4127 msgstr "" 3996 4128 3997 #: includes/Setup/SampleData.php: 3214129 #: includes/Setup/SampleData.php:500 3998 4130 msgid "Compact car, automatic (sample)" 3999 4131 msgstr "" 4000 4132 4001 #: includes/Setup/SampleData.php: 3224133 #: includes/Setup/SampleData.php:501 4002 4134 msgid "Small and easy to park. Air conditioning, five seats." 4003 4135 msgstr "" 4004 4136 4005 #: includes/Setup/SampleData.php: 3374137 #: includes/Setup/SampleData.php:516 4006 4138 msgid "Silver" 4007 4139 msgstr "" 4008 4140 4009 #: includes/Setup/SampleData.php: 3414141 #: includes/Setup/SampleData.php:522 4010 4142 msgid "Family car, 7 seats (sample)" 4011 4143 msgstr "" 4012 4144 4013 #: includes/Setup/SampleData.php: 3424145 #: includes/Setup/SampleData.php:523 4014 4146 msgid "Room for the whole family and their bags." 4015 4147 msgstr "" 4016 4148 4017 #: includes/Setup/SampleData.php: 3614149 #: includes/Setup/SampleData.php:544 4018 4150 msgid "City bike (sample)" 4019 4151 msgstr "" 4020 4152 4021 #: includes/Setup/SampleData.php: 3624153 #: includes/Setup/SampleData.php:545 4022 4154 msgid "Comfortable bike with a basket and a lock." 4023 4155 msgstr "" 4024 4156 4025 #: includes/Setup/SampleData.php: 3734157 #: includes/Setup/SampleData.php:556 4026 4158 msgid "Green" 4027 4159 msgstr "" 4028 4160 4029 #: includes/Setup/SampleData.php: 3774161 #: includes/Setup/SampleData.php:562 4030 4162 msgid "Electric bike (sample)" 4031 4163 msgstr "" 4032 4164 4033 #: includes/Setup/SampleData.php: 3784165 #: includes/Setup/SampleData.php:563 4034 4166 msgid "Pedal assist up to 60 km per charge." 4035 4167 msgstr "" 4036 4168 4037 #: includes/Setup/SampleData.php: 4024169 #: includes/Setup/SampleData.php:587 4038 4170 msgid "Extra helmet (sample)" 4039 4171 msgstr "" 4040 4172 4041 #: includes/Setup/SampleData.php: 4034173 #: includes/Setup/SampleData.php:588 4042 4174 msgid "Clean helmet for a passenger." 4043 4175 msgstr "" 4044 4176 4045 #: includes/Setup/SampleData.php: 4104177 #: includes/Setup/SampleData.php:595 4046 4178 msgid "Rain poncho (sample)" 4047 4179 msgstr "" 4048 4180 4049 #: includes/Setup/SampleData.php: 4114181 #: includes/Setup/SampleData.php:596 4050 4182 msgid "Yours to keep." 4051 4183 msgstr "" 4052 4184 4053 #: includes/Setup/SampleData.php: 4184185 #: includes/Setup/SampleData.php:603 4054 4186 msgid "Child seat (sample)" 4055 4187 msgstr "" 4056 4188 4057 #: includes/Setup/SampleData.php: 4194189 #: includes/Setup/SampleData.php:604 4058 4190 msgid "For children from 9 to 18 kg." 4059 4191 msgstr "" 4060 4192 4061 #: includes/Setup/SampleData.php: 4864062 #: includes/Setup/SampleData.php: 5504193 #: includes/Setup/SampleData.php:671 4194 #: includes/Setup/SampleData.php:809 4063 4195 msgid "Sample data" 4064 4196 msgstr "" 4065 4197 4066 #: includes/Setup/SampleData.php: 5474198 #: includes/Setup/SampleData.php:806 4067 4199 msgid "Sample" 4068 4200 msgstr "" 4069 4201 4070 #: includes/Setup/SampleData.php: 5554202 #: includes/Setup/SampleData.php:814 4071 4203 msgid "Sample booking. It is removed together with the sample data." 4204 msgstr "" 4205 4206 #: includes/Setup/SampleData.php:888 4207 msgid "Sample payment" 4208 msgstr "" 4209 4210 #: includes/Setup/SampleData.php:909 4211 msgid "Deposit at pick-up" 4212 msgstr "" 4213 4214 #: includes/Setup/SampleData.php:917 4215 msgid "Deposit given back" 4216 msgstr "" 4217 4218 #: includes/Setup/SampleData.php:927 4219 msgid "Refund after cancelling" 4220 msgstr "" 4221 4222 #: includes/Setup/SampleData.php:959 4223 msgid "Oil change (sample)" 4072 4224 msgstr "" 4073 4225 -
rentbaik/tags/1.0.1/readme.txt
r3790685 r3790901 1 1 === Rentbaik === 2 2 Contributors: rentbaik 3 Tags: rental, booking, car rental, bike rental, scooter rental3 Tags: rental, booking, car rental, motorbike rental, scooter rental 4 4 Requires at least: 6.5 5 5 Tested up to: 7.1 6 6 Requires PHP: 8.3 7 Stable tag: 1.0. 07 Stable tag: 1.0.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 68 68 = Can I try Rentbaik before adding my own vehicles? = 69 69 70 Yes. Click **Add sample data** in the setup wizard or on the Rentbaik dashboard. Rentbaik adds example vehicles marked "(sample)", units, extras and three bookings, without sending any email. **Remove sample data** deletes all of it again; your own vehicles and bookings are not touched. 70 Yes. Click **Add sample data** in the setup wizard or on the Rentbaik dashboard. Rentbaik adds example vehicles marked "(sample)" with pictures, units with their own photo, extras, a service day and a month of bookings in every common situation: picked up today, coming back today, late, part paid, deposit still to give back, waiting for a unit, waiting for confirmation with a transfer receipt, cancelled with a refund and not shown. Customer photos and receipts are drawings marked "SAMPLE", not real people or documents. No email is sent. **Remove sample data** deletes all of it again; your own vehicles and bookings are not touched. 71 72 = Where are the pages for my customers? = 73 74 Rentbaik creates the pages Rentals, Book a rental and My booking when it is activated. The Rentbaik dashboard lists them with their shortcodes and links to view or edit them, plus an example vehicle page. Add them to your site menu, or place the shortcodes on any page. 71 75 72 76 = Do I need WooCommerce or Elementor? = … … 150 154 == Changelog == 151 155 156 = 1.0.1 = 157 * Fix: the booking details panel and the day agenda on the Rentbaik dashboard did not open on sites with "Plain" permalinks ("The booking could not be loaded"). 158 * New: "Your pages for customers" card on the Rentbaik dashboard. It lists the Rentals, Book a rental and My booking pages with their shortcodes and View and Edit links, an example vehicle page, and a link to see a sample booking as the customer sees it. 159 * New: labels on the calendar bars and in the day agenda: Late, Today (pick-up or return), Unpaid or Part paid, and Deposit (not given back yet). 160 * Improved: the sample data now fills the current month with 17 bookings in every common situation: completed, returned with the deposit still held, cancelled with a refund, no-show, late, coming back today, picked up today, without a unit, waiting with a transfer receipt, part paid and paid online. 161 * Improved: sample vehicles and units have pictures, and sample customers have photos and transfer receipts. All are drawings marked "SAMPLE", not real people or documents. Remove sample data deletes them too. 162 * Improved: when a vehicle type is switched off, the sample bookings use a similar vehicle. 163 * Translation: complete Indonesian translation. 164 * Tip: if you added sample data with 1.0.0, remove it and add it again to see the new example month. 165 152 166 = 1.0.0 = 153 167 * First release. … … 155 169 == Upgrade Notice == 156 170 171 = 1.0.1 = 172 Fixes the booking panel on sites with Plain permalinks. Adds a card with your customer pages, labels on the calendar and a full month of sample data with pictures. 173 157 174 = 1.0.0 = 158 175 First release. -
rentbaik/tags/1.0.1/rentbaik.php
r3790685 r3790901 3 3 * Plugin Name: Rentbaik 4 4 * Description: Run a bike, scooter or car rental business in WordPress: fleet by plate number, rates, availability, bookings and local payment options. Works with any theme. 5 * Version: 1.0. 05 * Version: 1.0.1 6 6 * Requires at least: 6.5 7 7 * Requires PHP: 8.3 … … 17 17 defined( 'ABSPATH' ) || exit; 18 18 19 define( 'RENTBAIK_VERSION', '1.0. 0' );19 define( 'RENTBAIK_VERSION', '1.0.1' ); 20 20 define( 'RENTBAIK_FILE', __FILE__ ); 21 21 define( 'RENTBAIK_PATH', plugin_dir_path( __FILE__ ) ); -
rentbaik/trunk/assets/admin/admin.css
r3790685 r3790901 158 158 } 159 159 160 /* Customer pages card */ 161 .rentbaik-sitepages__list { 162 list-style: none; 163 margin: 8px 0 0; 164 padding: 0; 165 } 166 167 .rentbaik-sitepages__item { 168 display: grid; 169 grid-template-columns: minmax(0, 1fr) auto; 170 align-items: center; 171 gap: 10px; 172 margin: 0; 173 padding-block: 10px; 174 border-block-start: 1px solid var(--rentbaik-line); 175 } 176 177 .rentbaik-sitepages__item:first-child { 178 border-block-start: 0; 179 } 180 181 .rentbaik-sitepages__text span:not(.rentbaik-pill) { 182 display: block; 183 color: var(--rentbaik-muted); 184 overflow-wrap: anywhere; 185 } 186 187 .rentbaik-sitepages__text code { 188 display: inline-block; 189 margin-block-start: 4px; 190 font-size: 12px; 191 overflow-wrap: anywhere; 192 } 193 194 .rentbaik-sitepages__actions { 195 display: flex; 196 align-items: center; 197 gap: 10px; 198 white-space: nowrap; 199 } 200 201 .rentbaik-sitepages__try { 202 display: flex; 203 align-items: center; 204 gap: 6px; 205 margin: 10px 0 0; 206 padding-block-start: 10px; 207 border-block-start: 1px solid var(--rentbaik-line); 208 } 209 210 @media (max-width: 600px) { 211 .rentbaik-sitepages__item { 212 grid-template-columns: minmax(0, 1fr); 213 } 214 } 215 160 216 /* Status list */ 161 217 .rentbaik-status { … … 1185 1241 .rentbaik-cal__legend .rentbaik-cal__unpaid { 1186 1242 margin: 0; 1243 } 1244 1245 /* Short labels on a booking bar: late, today, not fully paid, deposit. */ 1246 .rentbaik-cal__text { 1247 flex: 1 1 auto; 1248 } 1249 1250 .rentbaik-cal__tags { 1251 flex: none; 1252 display: inline-flex; 1253 gap: 3px; 1254 margin-inline-start: auto; 1255 } 1256 1257 .rentbaik-cal__tag { 1258 display: inline-block; 1259 padding-inline: 6px; 1260 border-radius: 8px; 1261 font-size: 10px; 1262 font-weight: 600; 1263 line-height: 16px; 1264 white-space: nowrap; 1265 } 1266 1267 .rentbaik-cal__tag--alert { 1268 background: #b32d2e; 1269 color: #fff; 1270 } 1271 1272 .rentbaik-cal__tag--warn { 1273 background: #f5e6ab; 1274 color: #614a00; 1275 } 1276 1277 .rentbaik-cal__tag--info { 1278 background: #135e96; 1279 color: #fff; 1280 } 1281 1282 .rentbaik-cal__tag--muted { 1283 background: rgba(0, 0, 0, 0.07); 1284 color: #3c434a; 1285 } 1286 1287 .rentbaik-cal__legend .rentbaik-cal__tag { 1288 margin-inline-end: 2px; 1289 } 1290 1291 .rentbaik-agenda__tags .rentbaik-cal__tag { 1292 vertical-align: middle; 1187 1293 } 1188 1294 -
rentbaik/trunk/assets/admin/admin.js
r3790685 r3790901 148 148 } ); 149 149 }; 150 // rest_url() already holds "?rest_route=" when permalinks are plain, so add parameters with "&" then. 151 var sep = function ( url ) { 152 return url.indexOf( '?' ) === -1 ? '?' : '&'; 153 }; 150 154 var open = function ( id, from, day ) { 151 155 var back = window.location.href.split( '#' )[ 0 ]; 152 156 mark( id, '' ); 153 load( 'booking-' + id, cfg.panelUrl + id + '?back=' + encodeURIComponent( back ), from, 'panelError', day ? function () {157 load( 'booking-' + id, cfg.panelUrl + id + sep( cfg.panelUrl ) + 'back=' + encodeURIComponent( back ), from, 'panelError', day ? function () { 154 158 // Opened from the day agenda: offer a way back to it. 155 159 var button = document.createElement( 'button' ); … … 167 171 var vehicle = new URLSearchParams( window.location.search ).get( 'vehicle' ) || '0'; 168 172 mark( 0, day ); 169 load( 'day-' + day, cfg.dayUrl + day + '?vehicle=' + encodeURIComponent( vehicle ), from, 'dayError' );173 load( 'day-' + day, cfg.dayUrl + day + sep( cfg.dayUrl ) + 'vehicle=' + encodeURIComponent( vehicle ), from, 'dayError' ); 170 174 }; 171 175 -
rentbaik/trunk/includes/Admin/CalendarView.php
r3790685 r3790901 145 145 $photos = Photos::for_bookings( array_map( 'intval', wp_list_pluck( $rows, 'id' ) ) ); 146 146 $now = Time::to_mysql( Time::now() ); 147 $today = Time::local( Time::now() )->format( 'Y-m-d' ); 147 148 $labels = BookingService::status_labels(); 148 149 $pay = BookingService::payment_state_labels(); … … 180 181 $plate = $unit ? (string) $unit['plate_number'] : ''; 181 182 $state = BookingService::payment_state( $row ); 183 $late_hours = 'overdue' === $tone ? max( 1, (int) floor( ( time() - $return->getTimestamp() ) / HOUR_IN_SECONDS ) ) : 0; 184 $flags = self::flags( $row, $status, $state, $start_day, Time::local( $return )->format( 'Y-m-d' ), $today, $late_hours ); 182 185 183 186 $title = implode( … … 192 195 sprintf( __( 'Return: %s', 'rentbaik' ), Format::datetime( (string) $row['return_at'] ) ), 193 196 ( $labels[ $status ] ?? $status ) . ' · ' . ( $pay[ $state ] ?? $state ), 197 implode( ' · ', wp_list_pluck( $flags, 'title' ) ), 194 198 $customer ? (string) $customer['phone'] : '', 195 199 ) … … 214 218 'vehicle' => $vehicle_label, 215 219 'plate' => $plate, 216 'unpaid' => 'paid' !== $state && ! in_array( $status, array( 'completed' ), true ), 220 'unpaid' => isset( $flags['unpaid'] ), 221 'flags' => $flags, 217 222 'photo' => isset( $photos[ (int) $row['id'] ] ) ? Photos::url( $photos[ (int) $row['id'] ], true ) : '', 218 223 'title' => $title, … … 225 230 } 226 231 return $list; 232 } 233 234 /** 235 * Short labels on a booking bar: late, return or pick-up today, not fully paid, deposit not given back. 236 * 237 * @param array<string, mixed> $row Booking row. 238 * @param string $status Booking status. 239 * @param string $state Payment state (unpaid, partial, paid). 240 * @param string $start_day Local pick-up day (Y-m-d). 241 * @param string $return_day Local return day (Y-m-d). 242 * @param string $today Local today (Y-m-d). 243 * @param int $late_hours Hours past the return time (0 = not late). 244 * @return array<string, array{label: string, title: string, tone: string, at: string}> Key => label, full text, 245 * tone (alert, warn, info, muted) 246 * and end of the bar (start, end). 247 */ 248 public static function flags( array $row, string $status, string $state, string $start_day, string $return_day, string $today, int $late_hours ): array { 249 $flags = array(); 250 if ( $late_hours > 0 ) { 251 $flags['late'] = array( 252 'label' => __( 'Late', 'rentbaik' ), 253 /* translators: %d: number of hours. */ 254 'title' => sprintf( _n( 'Late by %d hour', 'Late by %d hours', $late_hours, 'rentbaik' ), $late_hours ), 255 'tone' => 'alert', 256 'at' => 'end', 257 ); 258 } elseif ( 'active' === $status && $return_day === $today ) { 259 $flags['return_today'] = array( 260 'label' => '↓ ' . __( 'Today', 'rentbaik' ), 261 'title' => __( 'Comes back today', 'rentbaik' ), 262 'tone' => 'info', 263 'at' => 'end', 264 ); 265 } 266 if ( in_array( $status, array( 'pending', 'confirmed' ), true ) && $start_day === $today ) { 267 $flags['pickup_today'] = array( 268 'label' => '↑ ' . __( 'Today', 'rentbaik' ), 269 'title' => __( 'Picked up today', 'rentbaik' ), 270 'tone' => 'info', 271 'at' => 'start', 272 ); 273 } 274 if ( (int) $row['total'] > 0 && 'paid' !== $state && ! in_array( $status, array( 'completed', 'cancelled', 'no_show' ), true ) ) { 275 $flags['unpaid'] = array( 276 'label' => 'partial' === $state ? __( 'Part paid', 'rentbaik' ) : __( 'Unpaid', 'rentbaik' ), 277 'title' => __( 'Not fully paid', 'rentbaik' ), 278 // Money to collect now is stronger than a future booking that is paid at pick-up. 279 'tone' => in_array( $status, array( 'active', 'returned' ), true ) ? 'warn' : 'muted', 280 'at' => 'end', 281 ); 282 } 283 if ( in_array( $status, array( 'returned', 'completed' ), true ) && 'held' === (string) $row['deposit_status'] ) { 284 $flags['deposit'] = array( 285 'label' => __( 'Deposit', 'rentbaik' ), 286 'title' => __( 'Deposit not given back yet', 'rentbaik' ), 287 'tone' => 'warn', 288 'at' => 'end', 289 ); 290 } 291 return $flags; 227 292 } 228 293 … … 474 539 echo '<span class="rentbaik-cal__nounit">' . esc_html__( 'No unit', 'rentbaik' ) . '</span>'; 475 540 } 476 if ( $segment['unpaid'] ) { 477 echo '<i class="rentbaik-cal__unpaid" title="' . esc_attr__( 'Not fully paid', 'rentbaik' ) . '"></i>'; 541 $tags = array(); 542 foreach ( (array) ( $segment['flags'] ?? array() ) as $key => $flag ) { 543 // Pick-up labels sit on the first piece of a bar that crosses weeks, the others on the last piece. 544 if ( ( 'start' === $flag['at'] && ! $segment['cont_in'] ) || ( 'end' === $flag['at'] && ! $segment['cont_out'] ) ) { 545 $tags[ $key ] = $flag; 546 } 547 } 548 if ( ! empty( $tags ) ) { 549 echo '<span class="rentbaik-cal__tags">'; 550 foreach ( $tags as $key => $flag ) { 551 echo '<span class="rentbaik-cal__tag rentbaik-cal__tag--' . esc_attr( $flag['tone'] ) . ' rentbaik-cal__tag--' . esc_attr( $key ) . '" title="' . esc_attr( $flag['title'] ) . '">' . esc_html( $flag['label'] ) . '</span>'; 552 } 553 echo '</span>'; 478 554 } 479 555 if ( $grip_b ) { … … 708 784 echo '<li><i class="rentbaik-cal__swatch rentbaik-cal__swatch--' . esc_attr( $key ) . '" aria-hidden="true"></i>' . esc_html( $label ) . '</li>'; 709 785 } 710 echo '<li><i class="rentbaik-cal__unpaid" aria-hidden="true"></i>' . esc_html__( 'Not fully paid', 'rentbaik' ) . '</li>'; 786 foreach ( array( 787 array( 'alert', __( 'Late', 'rentbaik' ), __( 'Late return', 'rentbaik' ) ), 788 array( 'info', '↑↓ ' . __( 'Today', 'rentbaik' ), __( 'Picked up or comes back today', 'rentbaik' ) ), 789 array( 'warn', __( 'Unpaid', 'rentbaik' ), __( 'Not fully paid', 'rentbaik' ) ), 790 array( 'warn', __( 'Deposit', 'rentbaik' ), __( 'Deposit not given back yet', 'rentbaik' ) ), 791 ) as $tag ) { 792 echo '<li><span class="rentbaik-cal__tag rentbaik-cal__tag--' . esc_attr( $tag[0] ) . '" aria-hidden="true">' . esc_html( $tag[1] ) . '</span>' . esc_html( $tag[2] ) . '</li>'; 793 } 711 794 if ( $fleet > 0 ) { 712 795 echo '<li><span class="rentbaik-cal__occ">' . esc_html( '1/' . $fleet ) . '</span> ' . esc_html__( 'vehicles out / fleet', 'rentbaik' ) . '</li>'; -
rentbaik/trunk/includes/Admin/DashboardPage.php
r3790685 r3790901 215 215 216 216 echo '<div class="rentbaik-grid">'; 217 SitePages::render_card(); 217 218 if ( $done < count( $steps ) ) { 218 219 self::render_setup( $steps, $done ); -
rentbaik/trunk/includes/Admin/DayAgenda.php
r3790685 r3790901 229 229 echo '<span class="rentbaik-agenda__tags">'; 230 230 View::print_status( $status, $labels[ $status ] ?? $status ); 231 if ( $row['unpaid']) {232 echo ' < i class="rentbaik-cal__unpaid" title="' . esc_attr__( 'Not fully paid', 'rentbaik' ) . '"></i><span class="screen-reader-text">' . esc_html__( 'Not fully paid', 'rentbaik') . '</span>';231 foreach ( (array) ( $row['flags'] ?? array() ) as $key => $flag ) { 232 echo ' <span class="rentbaik-cal__tag rentbaik-cal__tag--' . esc_attr( $flag['tone'] ) . ' rentbaik-cal__tag--' . esc_attr( $key ) . '" title="' . esc_attr( $flag['title'] ) . '">' . esc_html( $flag['label'] ) . '</span>'; 233 233 } 234 234 echo '</span></button></li>'; -
rentbaik/trunk/includes/Customers/Photos.php
r3790685 r3790901 263 263 * @return void 264 264 */ 265 p rivatestatic function make_thumb( string $path ): void {265 public static function make_thumb( string $path ): void { 266 266 $editor = wp_get_image_editor( $path ); 267 267 if ( is_wp_error( $editor ) ) { … … 278 278 * @return string 279 279 */ 280 p rivatestatic function thumb_path( string $path ): string {280 public static function thumb_path( string $path ): string { 281 281 $info = pathinfo( $path ); 282 282 return $info['dirname'] . '/' . $info['filename'] . '-thumb.' . ( $info['extension'] ?? 'jpg' ); -
rentbaik/trunk/includes/Setup/SampleData.php
r3790685 r3790901 1 1 <?php 2 2 /** 3 * Sample data: example vehicles, units, extras and bookings for a first look, removable in one click.3 * Sample data: example vehicles, units, extras and a month of bookings for a first look, removable in one click. 4 4 * 5 5 * @package Rentbaik … … 8 8 namespace Rentbaik\Setup; 9 9 10 use DateTimeImmutable; 10 11 use Rentbaik\Availability\BlockRepository; 11 12 use Rentbaik\Booking\BookingRepository; 12 13 use Rentbaik\Booking\BookingService; 13 14 use Rentbaik\Customers\CustomerRepository; 15 use Rentbaik\Customers\Photos; 14 16 use Rentbaik\Database\Repository; 15 17 use Rentbaik\Fleet\UnitRepository; … … 36 38 37 39 /** 38 * Post meta that marks a sample vehicle .40 * Post meta that marks a sample vehicle (and sample images in the Media Library). 39 41 */ 40 42 public const META = '_rentbaik_sample'; … … 88 90 89 91 /** 92 * Payment methods paid before the pick-up (the others are paid at the counter). 93 */ 94 private const PREPAID = array( 'bank_transfer', 'woocommerce', 'qris' ); 95 96 /** 97 * Media Library images made in this request: asset name => attachment ID. 98 * 99 * @var array<string, int> 100 */ 101 private static $media = array(); 102 103 /** 90 104 * Saved record of the sample data. 91 105 * … … 101 115 'bookings' => array(), 102 116 'customers' => array(), 117 'media' => array(), 118 'blocks' => array(), 103 119 ); 104 120 foreach ( $defaults as $key => $empty ) { … … 144 160 $types = array_keys( Vehicle::enabled_types() ); 145 161 $plate = 100; 146 $first = array(); 162 $models = array(); 163 $extras = array(); 147 164 148 165 Emails::mute( true ); … … 157 174 } 158 175 $data['vehicles'][] = $vehicle_id; 159 if ( ! isset( $first[ $definition['type'] ] ) ) { 160 $first[ $definition['type'] ] = $vehicle_id; 161 } 176 self::add_vehicle_images( $vehicle_id, $definition ); 177 $models[ $definition['art'] ] = $vehicle_id; 162 178 for ( $i = 0; $i < $definition['units']; $i++ ) { 163 179 $unit_id = self::create_unit( $vehicle_id, $location, $plate, $definition['colors'][ $i % count( $definition['colors'] ) ] ); … … 165 181 if ( $unit_id > 0 ) { 166 182 $data['units'][] = $unit_id; 183 // A photo of this unit in its own colour. 184 $art_color = $definition['art_colors'][ $i % count( $definition['art_colors'] ) ]; 185 $photo = self::media( $definition['art'] . '-' . $art_color, (string) $definition['title'] ); 186 if ( $photo > 0 ) { 187 UnitRepository::save( $unit_id, array( 'photo_id' => $photo ) ); 188 } 167 189 } 168 190 } 169 191 } 170 192 171 foreach ( self::extra_definitions() as $ extra ) {193 foreach ( self::extra_definitions() as $key => $extra ) { 172 194 if ( empty( array_intersect( $extra['vehicle_types'], $types ) ) ) { 173 195 continue; … … 190 212 if ( $extra_id > 0 ) { 191 213 $data['extras'][] = $extra_id; 214 $extras[ $key ] = $extra_id; 192 215 } 193 216 } 194 217 195 218 // Save what exists so far, so a failure below still leaves everything removable. 219 $data['media'] = array_values( array_unique( array_merge( $data['media'], array_values( self::$media ) ) ) ); 196 220 update_option( self::OPTION, $data, false ); 197 221 198 $vehicles = array_values( $first ); 199 if ( ! empty( $vehicles ) ) { 200 $data = self::create_bookings( $data, $vehicles, $location ); 222 if ( ! empty( $models ) ) { 223 $data = self::create_bookings( $data, $models, $extras, $location ); 224 update_option( self::OPTION, $data, false ); 225 $data = self::add_service_block( $data, $models ); 201 226 } 202 227 } finally { … … 204 229 } 205 230 231 $data['media'] = array_values( array_unique( array_merge( $data['media'], array_values( self::$media ) ) ) ); 206 232 update_option( self::OPTION, $data, false ); 207 233 Repository::bump_data_version(); … … 222 248 public static function remove(): void { 223 249 $data = self::data(); 250 251 foreach ( $data['blocks'] as $block_id ) { 252 BlockRepository::delete( $block_id ); 253 } 224 254 225 255 foreach ( $data['bookings'] as $booking_id ) { … … 233 263 foreach ( $data['customers'] as $customer_id ) { 234 264 if ( empty( BookingRepository::for_customer( $customer_id ) ) ) { 265 // Sample photos and payment proofs in the private folder. 266 Photos::remove_for_customer( $customer_id ); 235 267 CustomerRepository::delete( $customer_id ); 236 268 } … … 270 302 } 271 303 304 foreach ( $data['media'] as $attachment_id ) { 305 if ( '1' === (string) get_post_meta( $attachment_id, self::META, true ) ) { 306 wp_delete_attachment( $attachment_id, true ); 307 } 308 } 309 272 310 delete_option( self::OPTION ); 273 311 Repository::bump_data_version(); 312 } 313 314 /** 315 * Path of a sample illustration shipped with the plugin. 316 * 317 * @param string $name Asset name without extension. 318 * @return string '' when missing. 319 */ 320 private static function asset( string $name ): string { 321 $path = RENTBAIK_PATH . 'assets/sample/' . sanitize_file_name( $name ) . '.png'; 322 return file_exists( $path ) ? $path : ''; 323 } 324 325 /** 326 * Media Library image from a sample illustration (made once per request and reused). 327 * 328 * @param string $name Asset name. 329 * @param string $title Image title. 330 * @return int Attachment ID (0 when it could not be made). 331 */ 332 private static function media( string $name, string $title ): int { 333 if ( isset( self::$media[ $name ] ) ) { 334 return self::$media[ $name ]; 335 } 336 $source = self::asset( $name ); 337 if ( '' === $source ) { 338 return 0; 339 } 340 $upload = wp_upload_bits( 'rentbaik-sample-' . $name . '.png', null, (string) Photos::filesystem()->get_contents( $source ) ); 341 if ( ! empty( $upload['error'] ) ) { 342 return 0; 343 } 344 $attachment_id = wp_insert_attachment( 345 array( 346 'post_mime_type' => 'image/png', 347 'post_title' => $title, 348 'post_status' => 'inherit', 349 ), 350 $upload['file'] 351 ); 352 if ( ! $attachment_id || is_wp_error( $attachment_id ) ) { 353 return 0; 354 } 355 require_once ABSPATH . 'wp-admin/includes/image.php'; 356 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) ); 357 update_post_meta( $attachment_id, self::META, '1' ); 358 update_post_meta( $attachment_id, '_wp_attachment_image_alt', $title ); 359 self::$media[ $name ] = (int) $attachment_id; 360 return (int) $attachment_id; 361 } 362 363 /** 364 * Featured image and gallery of a sample vehicle: its colours and the gear that comes with it. 365 * 366 * @param int $vehicle_id Vehicle post ID. 367 * @param array<string, mixed> $definition Vehicle definition. 368 * @return void 369 */ 370 private static function add_vehicle_images( int $vehicle_id, array $definition ): void { 371 $title = (string) $definition['title']; 372 $featured = self::media( $definition['art'] . '-' . $definition['art_colors'][0], $title ); 373 if ( $featured > 0 ) { 374 set_post_thumbnail( $vehicle_id, $featured ); 375 } 376 $gallery = array(); 377 foreach ( array_slice( $definition['art_colors'], 1 ) as $color ) { 378 $gallery[] = self::media( $definition['art'] . '-' . $color, $title ); 379 } 380 $gear = array( 381 'motorcycle' => 'gear-scooter', 382 'car' => 'gear-car', 383 ); 384 $gallery[] = self::media( $gear[ $definition['type'] ] ?? 'gear-bike', __( 'Included gear (sample)', 'rentbaik' ) ); 385 $gallery = array_filter( $gallery ); 386 if ( ! empty( $gallery ) ) { 387 update_post_meta( $vehicle_id, '_rentbaik_gallery', implode( ',', $gallery ) ); 388 } 389 } 390 391 /** 392 * Copy a sample illustration into the private folder. 393 * 394 * @param string $name Asset name. 395 * @param string $prefix File name prefix (photo, proof). 396 * @return string File name in the private folder ('' when missing). 397 */ 398 private static function private_copy( string $name, string $prefix ): string { 399 $source = self::asset( $name ); 400 if ( '' === $source ) { 401 return ''; 402 } 403 $file = $prefix . '-' . strtolower( wp_generate_password( 24, false ) ) . '.png'; 404 $path = Photos::dir() . '/' . $file; 405 if ( ! Photos::filesystem()->put_contents( $path, (string) Photos::filesystem()->get_contents( $source ), FS_CHMOD_FILE ) ) { 406 return ''; 407 } 408 Photos::make_thumb( $path ); 409 return $file; 410 } 411 412 /** 413 * Customer photo taken at the hand-over and the transfer receipt a customer uploaded, as on a real booking. 414 * 415 * @param int $booking_id Booking ID. 416 * @param array<string, mixed> $plan Plan. 417 * @return void 418 */ 419 private static function add_booking_files( int $booking_id, array $plan ): void { 420 $booking = BookingRepository::find( $booking_id ); 421 if ( ! $booking ) { 422 return; 423 } 424 $add = static function ( string $asset, string $prefix, string $type, string $status, string $when ) use ( $booking_id, $booking ): void { 425 $file = self::private_copy( $asset, $prefix ); 426 if ( '' !== $file ) { 427 Photos::insert( 428 array( 429 'customer_id' => (int) $booking['customer_id'], 430 'booking_id' => $booking_id, 431 'doc_type' => $type, 432 'file_name' => $file, 433 'mime_type' => 'image/png', 434 'status' => $status, 435 'created_at' => $when, 436 ), 437 array( '%d', '%d', '%s', '%s', '%s', '%s', '%s' ) 438 ); 439 } 440 }; 441 if ( ! empty( $plan['proof'] ) ) { 442 $add( 'receipt-sample', 'proof', 'payment_proof', (string) $plan['proof'], self::created_at( $booking ) ); 443 } 444 if ( ! empty( $plan['photo'] ) && in_array( $plan['status'], array( 'active', 'returned', 'completed' ), true ) ) { 445 $add( 'customer-' . (int) $plan['photo'], 'photo', 'photo', 'valid', (string) $booking['pickup_at'] ); 446 } 274 447 } 275 448 … … 283 456 return array( 284 457 array( 285 'type' => 'motorcycle', 286 'title' => __( 'Automatic scooter 125 cc (sample)', 'rentbaik' ), 287 'content' => __( 'Easy automatic scooter for the city. Two helmets included.', 'rentbaik' ) . "\n\n" . $note, 288 'specs' => array( 458 'type' => 'motorcycle', 459 'art' => 'scooter-125', 460 'art_colors' => array( 'white', 'red', 'black' ), 461 'title' => __( 'Automatic scooter 125 cc (sample)', 'rentbaik' ), 462 'content' => __( 'Easy automatic scooter for the city. Two helmets included.', 'rentbaik' ) . "\n\n" . $note, 463 'specs' => array( 289 464 'engine_cc' => 125, 290 465 'transmission' => 'automatic', 291 466 'helmets' => 2, 292 467 ), 293 'rates' => array(468 'rates' => array( 294 469 'hour' => 4, 295 470 'day' => 15, 296 471 'week' => 90, 297 472 ), 298 'deposit' => 50,299 'units' => 3,300 'colors' => array( __( 'White', 'rentbaik' ), __( 'Red', 'rentbaik' ), __( 'Black', 'rentbaik' ) ),473 'deposit' => 50, 474 'units' => 3, 475 'colors' => array( __( 'White', 'rentbaik' ), __( 'Red', 'rentbaik' ), __( 'Black', 'rentbaik' ) ), 301 476 ), 302 477 array( 303 'type' => 'motorcycle', 304 'title' => __( 'Comfort scooter 160 cc (sample)', 'rentbaik' ), 305 'content' => __( 'Bigger scooter with a large seat and storage, good for longer trips.', 'rentbaik' ) . "\n\n" . $note, 306 'specs' => array( 478 'type' => 'motorcycle', 479 'art' => 'scooter-160', 480 'art_colors' => array( 'grey', 'blue' ), 481 'title' => __( 'Comfort scooter 160 cc (sample)', 'rentbaik' ), 482 'content' => __( 'Bigger scooter with a large seat and storage, good for longer trips.', 'rentbaik' ) . "\n\n" . $note, 483 'specs' => array( 307 484 'engine_cc' => 160, 308 485 'transmission' => 'automatic', 309 486 'helmets' => 2, 310 487 ), 311 'rates' => array(488 'rates' => array( 312 489 'day' => 22, 313 490 'week' => 130, 314 491 ), 315 'deposit' => 80,316 'units' => 2,317 'colors' => array( __( 'Grey', 'rentbaik' ), __( 'Blue', 'rentbaik' ) ),492 'deposit' => 80, 493 'units' => 2, 494 'colors' => array( __( 'Grey', 'rentbaik' ), __( 'Blue', 'rentbaik' ) ), 318 495 ), 319 496 array( 320 'type' => 'car', 321 'title' => __( 'Compact car, automatic (sample)', 'rentbaik' ), 322 'content' => __( 'Small and easy to park. Air conditioning, five seats.', 'rentbaik' ) . "\n\n" . $note, 323 'specs' => array( 497 'type' => 'car', 498 'art' => 'car-compact', 499 'art_colors' => array( 'silver', 'white' ), 500 'title' => __( 'Compact car, automatic (sample)', 'rentbaik' ), 501 'content' => __( 'Small and easy to park. Air conditioning, five seats.', 'rentbaik' ) . "\n\n" . $note, 502 'specs' => array( 324 503 'seats' => 5, 325 504 'doors' => 5, … … 329 508 'luggage' => 2, 330 509 ), 331 'rates' => array(510 'rates' => array( 332 511 'day' => 45, 333 512 'week' => 270, 334 513 ), 335 'deposit' => 200,336 'units' => 2,337 'colors' => array( __( 'Silver', 'rentbaik' ), __( 'White', 'rentbaik' ) ),514 'deposit' => 200, 515 'units' => 2, 516 'colors' => array( __( 'Silver', 'rentbaik' ), __( 'White', 'rentbaik' ) ), 338 517 ), 339 518 array( 340 'type' => 'car', 341 'title' => __( 'Family car, 7 seats (sample)', 'rentbaik' ), 342 'content' => __( 'Room for the whole family and their bags.', 'rentbaik' ) . "\n\n" . $note, 343 'specs' => array( 519 'type' => 'car', 520 'art' => 'car-family', 521 'art_colors' => array( 'black' ), 522 'title' => __( 'Family car, 7 seats (sample)', 'rentbaik' ), 523 'content' => __( 'Room for the whole family and their bags.', 'rentbaik' ) . "\n\n" . $note, 524 'specs' => array( 344 525 'seats' => 7, 345 526 'doors' => 5, … … 349 530 'luggage' => 3, 350 531 ), 351 'rates' => array(532 'rates' => array( 352 533 'day' => 65, 353 534 'week' => 390, 354 535 ), 355 'deposit' => 300,356 'units' => 1,357 'colors' => array( __( 'Black', 'rentbaik' ) ),536 'deposit' => 300, 537 'units' => 1, 538 'colors' => array( __( 'Black', 'rentbaik' ) ), 358 539 ), 359 540 array( 360 'type' => 'bicycle', 361 'title' => __( 'City bike (sample)', 'rentbaik' ), 362 'content' => __( 'Comfortable bike with a basket and a lock.', 'rentbaik' ) . "\n\n" . $note, 363 'specs' => array( 541 'type' => 'bicycle', 542 'art' => 'bike-city', 543 'art_colors' => array( 'green' ), 544 'title' => __( 'City bike (sample)', 'rentbaik' ), 545 'content' => __( 'Comfortable bike with a basket and a lock.', 'rentbaik' ) . "\n\n" . $note, 546 'specs' => array( 364 547 'frame_size' => 'M', 365 548 'gears' => 7, 366 549 ), 367 'rates' => array(550 'rates' => array( 368 551 'hour' => 3, 369 552 'day' => 10, 370 553 ), 371 'deposit' => 0,372 'units' => 2,373 'colors' => array( __( 'Green', 'rentbaik' ) ),554 'deposit' => 0, 555 'units' => 2, 556 'colors' => array( __( 'Green', 'rentbaik' ) ), 374 557 ), 375 558 array( 376 'type' => 'ebike', 377 'title' => __( 'Electric bike (sample)', 'rentbaik' ), 378 'content' => __( 'Pedal assist up to 60 km per charge.', 'rentbaik' ) . "\n\n" . $note, 379 'specs' => array( 559 'type' => 'ebike', 560 'art' => 'ebike', 561 'art_colors' => array( 'teal' ), 562 'title' => __( 'Electric bike (sample)', 'rentbaik' ), 563 'content' => __( 'Pedal assist up to 60 km per charge.', 'rentbaik' ) . "\n\n" . $note, 564 'specs' => array( 380 565 'frame_size' => 'M', 381 566 'range_km' => 60, 382 567 ), 383 'rates' => array(568 'rates' => array( 384 569 'hour' => 6, 385 570 'day' => 25, 386 571 ), 387 'deposit' => 100,388 'units' => 2,389 'colors' => array( __( 'White', 'rentbaik' ) ),390 ), 391 ); 392 } 393 394 /** 395 * Sample extras .396 * 397 * @return array< int, array<string, mixed>>572 'deposit' => 100, 573 'units' => 2, 574 'colors' => array( __( 'White', 'rentbaik' ) ), 575 ), 576 ); 577 } 578 579 /** 580 * Sample extras, by key. 581 * 582 * @return array<string, array<string, mixed>> 398 583 */ 399 584 private static function extra_definitions(): array { 400 585 return array( 401 array(586 'helmet' => array( 402 587 'name' => __( 'Extra helmet (sample)', 'rentbaik' ), 403 588 'description' => __( 'Clean helmet for a passenger.', 'rentbaik' ), … … 407 592 'vehicle_types' => array( 'motorcycle', 'bicycle', 'ebike' ), 408 593 ), 409 array(594 'poncho' => array( 410 595 'name' => __( 'Rain poncho (sample)', 'rentbaik' ), 411 596 'description' => __( 'Yours to keep.', 'rentbaik' ), … … 415 600 'vehicle_types' => array( 'motorcycle', 'bicycle', 'ebike' ), 416 601 ), 417 array(602 'child_seat' => array( 418 603 'name' => __( 'Child seat (sample)', 'rentbaik' ), 419 604 'description' => __( 'For children from 9 to 18 kg.', 'rentbaik' ), … … 490 675 491 676 /** 492 * Three sample bookings: one picked up today, one returning today and one waiting for confirmation. 677 * A month of sample bookings in every common situation, around today. 678 * 679 * Keys: model (vehicle art), offset (days from today), hour, days, status, source, method (payment), paid 680 * (none, part, full, refunded), part (share paid), deposit (none, held, returned), proof (pending, valid), 681 * photo (customer portrait 1-8), extra (extra key), no_unit (taken although no unit is free). 682 * 683 * @return array<int, array<string, mixed>> 684 */ 685 private static function plans(): array { 686 $keys = array( 'first_name', 'country', 'phone', 'model', 'offset', 'hour', 'days', 'status', 'source', 'method', 'paid', 'part', 'deposit', 'proof', 'photo', 'extra', 'no_unit' ); 687 $rows = array( 688 // Finished last week: paid in cash, deposit given back. 689 array( 'Emma', 'GB', '15550106', 'scooter-125', -7, 10, 2, 'completed', 'whatsapp', 'cash', 'full', 0, 'returned', '', 1, 'helmet', false ), 690 // Finished: paid by card at the counter. 691 array( 'Olivia', 'US', '15550108', 'car-compact', -6, 9, 2, 'completed', 'web', 'card', 'full', 0, 'returned', '', 4, 'child_seat', false ), 692 // Returned two days ago, the deposit is still to be given back. 693 array( 'Lucas', 'FR', '15550103', 'scooter-160', -4, 9, 2, 'returned', 'admin', 'cash', 'full', 0, 'held', '', 3, '', false ), 694 // Cancelled by the customer: the transfer was refunded. 695 array( 'Liam', 'NZ', '15550107', 'bike-city', -3, 10, 1, 'cancelled', 'web', 'bank_transfer', 'refunded', 0.5, 'none', '', 0, '', false ), 696 // Did not come. 697 array( 'Wei', 'CN', '15550109', 'ebike', -2, 11, 1, 'no_show', 'web', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 698 // Should have come back yesterday: late, deposit held, rent not paid yet. 699 array( 'Noah', 'US', '15550105', 'car-compact', -3, 9, 2, 'active', 'admin', 'pay_at_pickup', 'none', 0, 'held', '', 7, '', false ), 700 // On rent, comes back today; half paid with a QR code. 701 array( 'Maria', 'DE', '15550101', 'scooter-125', -2, 16, 2, 'active', 'whatsapp', 'qris', 'part', 0.5, 'held', '', 2, 'poncho', false ), 702 // Long rental under way, paid in advance by bank transfer. 703 array( 'Sofia', 'IT', '15550104', 'car-family', -5, 10, 12, 'active', 'web', 'bank_transfer', 'full', 0, 'held', 'valid', 5, '', false ), 704 // Picks up today, pays at the counter. 705 array( 'Alex', 'AU', '15550100', 'scooter-125', 0, 10, 3, 'confirmed', 'web', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 706 // Picks up this afternoon, already paid online. 707 array( 'Aisha', 'AE', '15550110', 'scooter-160', 0, 15, 2, 'confirmed', 'web', 'woocommerce', 'full', 0, 'none', '', 0, '', false ), 708 // Taken by phone although the only family car is out: needs a unit before pick-up. 709 array( 'Omar', 'EG', '15550111', 'car-family', 2, 10, 3, 'confirmed', 'admin', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', true ), 710 // Waiting for confirmation, transfer receipt uploaded but not checked yet. 711 array( 'Kenji', 'JP', '15550102', 'ebike', 3, 11, 4, 'pending', 'web', 'bank_transfer', 'none', 0, 'none', 'pending', 0, '', false ), 712 // Confirmed with a part payment by transfer. 713 array( 'Priya', 'IN', '15550112', 'car-compact', 6, 10, 5, 'confirmed', 'web', 'bank_transfer', 'part', 0.3, 'none', 'valid', 0, 'child_seat', false ), 714 // A week by WhatsApp, pays at pick-up. 715 array( 'Mateo', 'MX', '15550113', 'scooter-125', 9, 9, 7, 'confirmed', 'whatsapp', 'pay_at_pickup', 'none', 0, 'none', '', 0, 'helmet', false ), 716 // Waiting for confirmation from WhatsApp. 717 array( 'Putri', 'ID', '15550114', 'bike-city', 12, 8, 1, 'pending', 'whatsapp', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 718 // Paid online for later this month. 719 array( 'Lena', 'SE', '15550115', 'car-compact', 16, 10, 4, 'confirmed', 'web', 'woocommerce', 'full', 0, 'none', '', 0, '', false ), 720 // A returning customer books again. 721 array( 'Emma', 'GB', '15550106', 'scooter-160', 19, 10, 3, 'pending', 'whatsapp', 'pay_at_pickup', 'none', 0, 'none', '', 0, '', false ), 722 ); 723 return array_map( 724 static function ( array $row ) use ( $keys ): array { 725 return array_combine( $keys, $row ); 726 }, 727 $rows 728 ); 729 } 730 731 /** 732 * The planned model, or a similar one when its vehicle type is switched off (a shop with only cars still gets 733 * every situation). 734 * 735 * @param string $model Vehicle art. 736 * @param array<string, int> $models Created models: art => vehicle ID. 737 * @return string '' when no model exists. 738 */ 739 private static function model_for( string $model, array $models ): string { 740 $similar = array( 741 'scooter-125' => array( 'scooter-160', 'bike-city', 'ebike' ), 742 'scooter-160' => array( 'scooter-125', 'ebike', 'bike-city' ), 743 'car-compact' => array( 'car-family', 'scooter-160', 'scooter-125' ), 744 'car-family' => array( 'car-compact', 'scooter-160', 'scooter-125' ), 745 'bike-city' => array( 'ebike', 'scooter-125', 'scooter-160' ), 746 'ebike' => array( 'bike-city', 'scooter-160', 'scooter-125' ), 747 ); 748 foreach ( array_merge( array( $model ), $similar[ $model ] ?? array(), array_keys( $models ) ) as $candidate ) { 749 if ( isset( $models[ $candidate ] ) ) { 750 return $candidate; 751 } 752 } 753 return ''; 754 } 755 756 /** 757 * Days from today for a plan, kept inside the current month where its situation allows it. 758 * 759 * @param array<string, mixed> $plan Plan. 760 * @return int 761 */ 762 private static function offset_in_month( array $plan ): int { 763 $today = new DateTimeImmutable( 'today', wp_timezone() ); 764 $offset = (int) $plan['offset']; 765 $days = (int) $plan['days']; 766 if ( $offset > 0 ) { 767 // Later this month: keep the whole rental before the end of the month when there is room. 768 $left = (int) $today->format( 't' ) - (int) $today->format( 'j' ); 769 return max( 1, min( $offset, $left - $days ) ); 770 } 771 if ( $offset + $days <= 0 ) { 772 // Already over: as early as needed, but not before the first day of the month when it fits. 773 $first = 1 - (int) $today->format( 'j' ); 774 return max( $offset, $first ) + $days <= 0 ? max( $offset, $first ) : $offset; 775 } 776 return $offset; 777 } 778 779 /** 780 * Create the sample bookings and bring each to the situation of its plan. 493 781 * 494 782 * @param array<string, int[]> $data Sample data record. 495 * @param int[] $vehicles Sample vehicle IDs to book. 783 * @param array<string, int> $models Vehicle art => vehicle ID. 784 * @param array<string, int> $extras Extra key => extra ID. 496 785 * @param int $location_id Location ID. 497 786 * @return array<string, int[]> 498 787 */ 499 private static function create_bookings( array $data, array $vehicles, int $location_id ): array { 500 $plans = array( 501 array( 502 'first_name' => 'Alex', 503 'country' => 'AU', 504 'phone' => '15550100', 505 'offset' => 0, 506 'hour' => 10, 507 'days' => 3, 508 'status' => 'confirmed', 509 'source' => 'web', 510 ), 511 array( 512 'first_name' => 'Maria', 513 'country' => 'DE', 514 'phone' => '15550101', 515 'offset' => -2, 516 'hour' => 16, 517 'days' => 2, 518 'status' => 'active', 519 'source' => 'whatsapp', 520 ), 521 array( 522 'first_name' => 'Kenji', 523 'country' => 'JP', 524 'phone' => '15550102', 525 'offset' => 3, 526 'hour' => 11, 527 'days' => 4, 528 'status' => 'pending', 529 'source' => 'web', 530 ), 531 ); 532 533 foreach ( $plans as $index => $plan ) { 534 $vehicle_id = $vehicles[ $index % count( $vehicles ) ]; 788 private static function create_bookings( array $data, array $models, array $extras, int $location_id ): array { 789 foreach ( self::plans() as $plan ) { 790 $plan['model'] = self::model_for( (string) $plan['model'], $models ); 791 if ( '' === $plan['model'] ) { 792 continue; 793 } 794 $offset = self::offset_in_month( $plan ); 535 795 $booking_id = 0; 536 // Move a day later when the location is closed on the planned day .796 // Move a day later when the location is closed on the planned day or no unit is free. 537 797 for ( $shift = 0; $shift < 7 && $booking_id <= 0; $shift++ ) { 538 $pickup = Time::local_midnight( $plan['offset'] + $shift )->modify( '+' . $plan['hour'] . ' hours' ); 539 $result = BookingService::create( 540 array( 541 'vehicle_id' => $vehicle_id, 542 'pickup' => $pickup, 543 'return' => $pickup->modify( '+' . $plan['days'] . ' days' ), 544 'location_id' => $location_id, 545 'customer' => array( 546 'first_name' => $plan['first_name'], 547 'last_name' => __( 'Sample', 'rentbaik' ), 548 'phone' => $plan['phone'], 549 'country' => $plan['country'], 550 'notes' => __( 'Sample data', 'rentbaik' ), 551 ), 552 'status' => 'pending' === $plan['status'] ? 'pending' : 'confirmed', 553 'source' => $plan['source'], 554 'payment_method' => 'pay_at_pickup', 555 'internal_note' => __( 'Sample booking. It is removed together with the sample data.', 'rentbaik' ), 556 'allow_past' => true, 557 ) 798 $pickup = Time::local_midnight( $offset + $shift )->modify( '+' . $plan['hour'] . ' hours' ); 799 $input = array( 800 'vehicle_id' => $models[ $plan['model'] ], 801 'pickup' => $pickup, 802 'return' => $pickup->modify( '+' . $plan['days'] . ' days' ), 803 'location_id' => $location_id, 804 'customer' => array( 805 'first_name' => $plan['first_name'], 806 'last_name' => __( 'Sample', 'rentbaik' ), 807 'phone' => $plan['phone'], 808 'country' => $plan['country'], 809 'notes' => __( 'Sample data', 'rentbaik' ), 810 ), 811 'status' => 'pending' === $plan['status'] ? 'pending' : 'confirmed', 812 'source' => $plan['source'], 813 'payment_method' => $plan['method'], 814 'internal_note' => __( 'Sample booking. It is removed together with the sample data.', 'rentbaik' ), 815 'allow_past' => true, 816 'allow_unassigned' => ! empty( $plan['no_unit'] ), 558 817 ); 818 if ( ! empty( $plan['extra'] ) && isset( $extras[ $plan['extra'] ] ) ) { 819 $input['extras'] = array( $extras[ $plan['extra'] ] => 1 ); 820 } 821 $result = BookingService::create( $input ); 822 // An extra the vehicle type does not allow: try again without it. 823 if ( ! is_int( $result ) && isset( $input['extras'] ) ) { 824 unset( $input['extras'] ); 825 $result = BookingService::create( $input ); 826 } 559 827 $booking_id = is_int( $result ) ? $result : 0; 560 828 } … … 567 835 $data['customers'][] = (int) $booking['customer_id']; 568 836 } 569 if ( 'active' === $plan['status'] ) { 570 BookingService::set_status( $booking_id, 'active' ); 837 self::play_out( $booking_id, $plan ); 838 self::add_booking_files( $booking_id, $plan ); 839 } 840 return $data; 841 } 842 843 /** 844 * When the sample booking was made: three days before the pick-up, never later than an hour ago. 845 * 846 * @param array<string, mixed> $booking Booking row. 847 * @return string UTC (Y-m-d H:i:s). 848 */ 849 private static function created_at( array $booking ): string { 850 $pickup = Time::from_utc( (string) $booking['pickup_at'] ); 851 $latest = Time::now()->modify( '-1 hour' ); 852 $made = $pickup ? $pickup->modify( '-3 days' ) : $latest; 853 return Time::to_mysql( $made < $latest ? $made : $latest ); 854 } 855 856 /** 857 * Bring a sample booking to the situation of its plan: handed over, paid, returned, cancelled or not shown. 858 * Dates are set to when it would have happened, so the reports and the calendar look like a real month. 859 * 860 * @param int $booking_id Booking ID. 861 * @param array<string, mixed> $plan Plan. 862 * @return void 863 */ 864 private static function play_out( int $booking_id, array $plan ): void { 865 $booking = BookingRepository::find( $booking_id ); 866 if ( ! $booking ) { 867 return; 868 } 869 $now = Time::to_mysql( Time::now() ); 870 $made = self::created_at( $booking ); 871 $pickup = min( (string) $booking['pickup_at'], $now ); 872 $return = min( (string) $booking['return_at'], $now ); 873 $method = (string) $plan['method']; 874 $paid = (string) ( $plan['paid'] ?? 'none' ); 875 $deposit = (string) ( $plan['deposit'] ?? 'none' ); 876 // Money paid in advance arrives two hours after booking; the rest at the counter. 877 $prepaid = Time::to_mysql( min( Time::now(), Time::from_utc( $made )->modify( '+2 hours' ) ) ); 878 $pay = static function ( string $type, string $how, int $amount, string $when, string $note = '' ) use ( $booking_id ): void { 879 if ( $amount > 0 ) { 880 BookingService::add_payment( 881 $booking_id, 882 array( 883 'payment_type' => $type, 884 'method' => $how, 885 'amount' => $amount, 886 'paid_at' => $when, 887 'reference' => 'cash' === $how ? '' : 'SAMPLE-' . $booking_id, 888 'note' => '' === $note ? __( 'Sample payment', 'rentbaik' ) : $note, 889 ) 890 ); 891 } 892 }; 893 894 BookingRepository::patch( $booking_id, array( 'created_at' => $made ) ); 895 896 $total = (int) $booking['total']; 897 $share = 'full' === $paid ? $total : (int) round( $total * (float) ( $plan['part'] ?? 0 ) ); 898 if ( in_array( $paid, array( 'full', 'part', 'refunded' ), true ) && in_array( $method, self::PREPAID, true ) ) { 899 $pay( 'payment', $method, $share, $prepaid ); 900 } 901 902 if ( in_array( $plan['status'], array( 'active', 'returned', 'completed' ), true ) ) { 903 BookingService::set_status( $booking_id, 'active' ); 904 BookingRepository::patch( $booking_id, array( 'actual_pickup_at' => $pickup ) ); 905 if ( in_array( $paid, array( 'full', 'part' ), true ) && ! in_array( $method, self::PREPAID, true ) ) { 906 $pay( 'payment', $method, $share, $pickup ); 907 } 908 if ( 'none' !== $deposit ) { 909 $pay( 'deposit', 'cash', (int) $booking['deposit_amount'], $pickup, __( 'Deposit at pick-up', 'rentbaik' ) ); 910 } 911 } 912 913 if ( in_array( $plan['status'], array( 'returned', 'completed' ), true ) ) { 914 BookingService::set_status( $booking_id, 'returned' ); 915 BookingRepository::patch( $booking_id, array( 'actual_return_at' => $return ) ); 916 if ( 'returned' === $deposit ) { 917 $pay( 'deposit_refund', 'cash', (int) $booking['deposit_amount'], $return, __( 'Deposit given back', 'rentbaik' ) ); 918 } 919 if ( 'completed' === $plan['status'] ) { 920 BookingService::set_status( $booking_id, 'completed' ); 921 } 922 } 923 924 if ( 'cancelled' === $plan['status'] ) { 925 BookingService::set_status( $booking_id, 'cancelled' ); 926 if ( 'refunded' === $paid ) { 927 $pay( 'refund', $method, $share, max( $prepaid, Time::to_mysql( Time::now()->modify( '-1 day' ) ) ), __( 'Refund after cancelling', 'rentbaik' ) ); 928 } 929 } 930 if ( 'no_show' === $plan['status'] ) { 931 BookingService::set_status( $booking_id, 'no_show' ); 932 } 933 } 934 935 /** 936 * Service for a scooter over the next two days, so the calendar shows a unit that cannot be rented. 937 * 938 * @param array<string, int[]> $data Sample data record. 939 * @param array<string, int> $models Vehicle art => vehicle ID. 940 * @return array<string, int[]> 941 */ 942 private static function add_service_block( array $data, array $models ): array { 943 $vehicle_id = $models['scooter-160'] ?? ( $models['scooter-125'] ?? 0 ); 944 if ( $vehicle_id <= 0 ) { 945 return $data; 946 } 947 $start = Time::to_mysql( Time::local_midnight( 1 )->modify( '+8 hours' ) ); 948 $end = Time::to_mysql( Time::local_midnight( 3 )->modify( '+8 hours' ) ); 949 $units = array(); 950 foreach ( $data['units'] as $unit_id ) { 951 $unit = UnitRepository::find( $unit_id ); 952 if ( $unit && (int) $unit['vehicle_id'] === $vehicle_id ) { 953 $units[] = $unit_id; 954 } 955 } 956 $busy = BlockRepository::busy_units( $units, $start, $end ); 957 foreach ( $units as $unit_id ) { 958 if ( ! in_array( $unit_id, $busy, true ) ) { 959 $block_id = BlockRepository::add( $unit_id, $start, $end, 'maintenance', 0, null, __( 'Oil change (sample)', 'rentbaik' ) ); 960 if ( $block_id > 0 ) { 961 $data['blocks'][] = $block_id; 962 } 963 break; 571 964 } 572 965 } … … 580 973 * @return int 581 974 */ 582 p rivatestatic function price( float $usd ): int {975 public static function price( float $usd ): int { 583 976 if ( $usd <= 0 ) { 584 977 return 0; … … 586 979 $currency = Format::currency(); 587 980 $amount = $usd * (float) ( self::USD_RATES[ $currency ] ?? 1 ); 588 $step = $amount >= 100 ? 10 ** ( (int) floor( log10( $amount ) ) - 1 ) : 1; 589 $amount = max( $step, round( $amount / $step ) * $step ); 981 // Small amounts keep halves (5.50), larger ones are rounded to two significant digits (88,000). 982 $step = $amount >= 100 ? 10 ** ( (int) floor( log10( $amount ) ) - 1 ) : ( $amount < 10 ? 0.5 : 1 ); 983 $amount = max( $step, round( $amount / $step ) * $step ); 590 984 return (int) round( $amount * ( 10 ** Format::decimals() ) ); 591 985 } -
rentbaik/trunk/languages/rentbaik.pot
r3790685 r3790901 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: Rentbaik 1.0. 0\n"5 "Project-Id-Version: Rentbaik 1.0.1\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/rentbaik\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 2026-11-0 3T05:38:00+00:00\n"12 "POT-Creation-Date: 2026-11-08T10:09:24+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.12.0\n" … … 18 18 #. Author of the plugin 19 19 #: rentbaik.php 20 #: includes/Admin/Menu.php:4 621 #: includes/Admin/Menu.php:4 720 #: includes/Admin/Menu.php:44 21 #: includes/Admin/Menu.php:45 22 22 #: includes/Admin/View.php:31 23 23 #: includes/Privacy/Privacy.php:170 … … 114 114 msgstr "" 115 115 116 #: includes/Admin/Admin.php:4 7117 #: includes/Admin/Menu.php:1 22116 #: includes/Admin/Admin.php:48 117 #: includes/Admin/Menu.php:132 118 118 #: includes/Admin/SettingsPage.php:480 119 119 msgid "Settings" 120 120 msgstr "" 121 121 122 #: includes/Admin/Admin.php:5 3122 #: includes/Admin/Admin.php:54 123 123 #: includes/Admin/DashboardPage.php:186 124 #: includes/Admin/Menu.php:5 8124 #: includes/Admin/Menu.php:56 125 125 msgid "Dashboard" 126 126 msgstr "" … … 177 177 #: includes/Admin/BookingsPage.php:331 178 178 #: includes/Admin/BookingsPage.php:732 179 #: includes/Admin/CalendarView.php:285 180 #: includes/Admin/CalendarView.php:790 179 181 msgid "Deposit" 180 182 msgstr "" … … 305 307 #: includes/Admin/ExtrasPage.php:59 306 308 #: includes/Admin/ExtrasPage.php:111 307 #: includes/Fleet/VehiclePostType.php:39 3309 #: includes/Fleet/VehiclePostType.php:395 308 310 msgid "Price" 309 311 msgstr "" … … 311 313 #: includes/Admin/BookingMove.php:191 312 314 #: includes/Admin/BookingsPage.php:162 313 #: includes/Admin/CalendarView.php:5 21315 #: includes/Admin/CalendarView.php:597 314 316 msgid "Unit" 315 317 msgstr "" … … 518 520 #: includes/Admin/BookingPanel.php:201 519 521 #: includes/Admin/ExtrasPage.php:52 520 #: includes/Admin/Menu.php: 86521 #: includes/Admin/Menu.php: 87522 #: includes/Admin/Menu.php:96 523 #: includes/Admin/Menu.php:97 522 524 #: templates/booking-form.php:96 523 525 msgid "Extras" … … 688 690 #: includes/Admin/CustomersPage.php:63 689 691 #: includes/Admin/CustomersPage.php:117 690 #: includes/Admin/Menu.php:6 6691 #: includes/Admin/Menu.php:6 7692 #: includes/Admin/Menu.php:64 693 #: includes/Admin/Menu.php:65 692 694 msgid "Bookings" 693 695 msgstr "" … … 782 784 783 785 #: includes/Admin/BookingsPage.php:190 784 #: includes/Admin/CalendarView.php:7 03786 #: includes/Admin/CalendarView.php:779 785 787 #: includes/Booking/BookingService.php:44 786 788 #: includes/Frontend/BookingForm.php:311 … … 825 827 826 828 #: includes/Admin/BookingsPage.php:286 827 #: includes/Admin/DashboardPage.php:39 0829 #: includes/Admin/DashboardPage.php:391 828 830 #: includes/Admin/ExtrasPage.php:79 831 #: includes/Admin/SitePages.php:162 829 832 #: includes/Admin/UnitsTable.php:368 830 833 msgid "Edit" … … 925 928 #: includes/Admin/SetupWizard.php:455 926 929 #: includes/Admin/UnitsTable.php:54 927 #: includes/Fleet/VehiclePostType.php:39 2930 #: includes/Fleet/VehiclePostType.php:394 928 931 #: includes/Frontend/VehicleContent.php:49 929 932 msgid "Type" … … 1082 1085 1083 1086 #: includes/Admin/CalendarView.php:117 1084 #: includes/Admin/Menu.php: 751085 #: includes/Admin/Menu.php: 761087 #: includes/Admin/Menu.php:85 1088 #: includes/Admin/Menu.php:86 1086 1089 #: includes/Admin/UnitsPage.php:59 1087 #: includes/Fleet/VehiclePostType.php:39 41090 #: includes/Fleet/VehiclePostType.php:396 1088 1091 msgid "Units" 1089 1092 msgstr "" … … 1094 1097 1095 1098 #: includes/Admin/CalendarView.php:124 1096 #: includes/Admin/DashboardPage.php:312 1099 #: includes/Admin/CalendarView.php:260 1100 #: includes/Admin/CalendarView.php:268 1101 #: includes/Admin/CalendarView.php:788 1102 #: includes/Admin/DashboardPage.php:313 1097 1103 msgid "Today" 1098 1104 msgstr "" … … 1110 1116 1111 1117 #. translators: %s: date and time. 1112 #: includes/Admin/CalendarView.php:19 01118 #: includes/Admin/CalendarView.php:193 1113 1119 #: includes/Notifications/WhatsApp.php:74 1114 1120 #, php-format … … 1117 1123 1118 1124 #. translators: %s: date and time. 1119 #: includes/Admin/CalendarView.php:19 21125 #: includes/Admin/CalendarView.php:195 1120 1126 #: includes/Notifications/WhatsApp.php:79 1121 1127 #, php-format … … 1123 1129 msgstr "" 1124 1130 1131 #: includes/Admin/CalendarView.php:252 1132 #: includes/Admin/CalendarView.php:787 1133 msgid "Late" 1134 msgstr "" 1135 1136 #. translators: %d: number of hours. 1137 #: includes/Admin/CalendarView.php:254 1138 #, php-format 1139 msgid "Late by %d hour" 1140 msgid_plural "Late by %d hours" 1141 msgstr[0] "" 1142 msgstr[1] "" 1143 1144 #: includes/Admin/CalendarView.php:261 1145 msgid "Comes back today" 1146 msgstr "" 1147 1148 #: includes/Admin/CalendarView.php:269 1149 msgid "Picked up today" 1150 msgstr "" 1151 1152 #: includes/Admin/CalendarView.php:276 1153 #: includes/Booking/BookingService.php:600 1154 msgid "Part paid" 1155 msgstr "" 1156 1157 #: includes/Admin/CalendarView.php:276 1158 #: includes/Admin/CalendarView.php:789 1159 #: includes/Booking/BookingService.php:599 1160 msgid "Unpaid" 1161 msgstr "" 1162 1163 #: includes/Admin/CalendarView.php:277 1164 #: includes/Admin/CalendarView.php:789 1165 msgid "Not fully paid" 1166 msgstr "" 1167 1168 #: includes/Admin/CalendarView.php:286 1169 #: includes/Admin/CalendarView.php:790 1170 msgid "Deposit not given back yet" 1171 msgstr "" 1172 1125 1173 #. translators: %s: date. 1126 #: includes/Admin/CalendarView.php: 3661127 #: includes/Admin/CalendarView.php: 5351174 #: includes/Admin/CalendarView.php:431 1175 #: includes/Admin/CalendarView.php:611 1128 1176 #, php-format 1129 1177 msgid "Show the agenda of %s" … … 1131 1179 1132 1180 #. translators: 1: vehicles out, 2: fleet size. 1133 #: includes/Admin/CalendarView.php: 3711181 #: includes/Admin/CalendarView.php:436 1134 1182 #, php-format 1135 1183 msgid "%1$d of %2$d vehicles out" 1136 1184 msgstr "" 1137 1185 1138 #: includes/Admin/CalendarView.php: 3741186 #: includes/Admin/CalendarView.php:439 1139 1187 msgid "Closed" 1140 1188 msgstr "" 1141 1189 1142 1190 #. translators: %s: date. 1143 #: includes/Admin/CalendarView.php: 3851191 #: includes/Admin/CalendarView.php:450 1144 1192 #, php-format 1145 1193 msgid "Add booking on %s" … … 1147 1195 1148 1196 #. translators: %d: number of bookings not shown. 1149 #: includes/Admin/CalendarView.php: 3971197 #: includes/Admin/CalendarView.php:462 1150 1198 #, php-format 1151 1199 msgid "+%d more" 1152 1200 msgstr "" 1153 1201 1154 #: includes/Admin/CalendarView.php: 4571202 #: includes/Admin/CalendarView.php:522 1155 1203 msgid "Drag to change the pick-up date" 1156 1204 msgstr "" 1157 1205 1158 #: includes/Admin/CalendarView.php: 4741206 #: includes/Admin/CalendarView.php:539 1159 1207 msgid "No unit" 1160 1208 msgstr "" 1161 1209 1162 #: includes/Admin/CalendarView.php:477 1163 #: includes/Admin/CalendarView.php:710 1164 #: includes/Admin/DayAgenda.php:232 1165 msgid "Not fully paid" 1166 msgstr "" 1167 1168 #: includes/Admin/CalendarView.php:480 1210 #: includes/Admin/CalendarView.php:556 1169 1211 msgid "Drag to change the return date" 1170 1212 msgstr "" 1171 1213 1172 1214 #. translators: %d: number of units. 1173 #: includes/Admin/CalendarView.php: 5541215 #: includes/Admin/CalendarView.php:630 1174 1216 #, php-format 1175 1217 msgid "%d unit" … … 1178 1220 msgstr[1] "" 1179 1221 1180 #: includes/Admin/CalendarView.php: 5701222 #: includes/Admin/CalendarView.php:646 1181 1223 msgid "No unit yet" 1182 1224 msgstr "" 1183 1225 1184 #: includes/Admin/CalendarView.php: 5711226 #: includes/Admin/CalendarView.php:647 1185 1227 msgid "drag to a unit" 1186 1228 msgstr "" 1187 1229 1188 #: includes/Admin/CalendarView.php: 6701230 #: includes/Admin/CalendarView.php:746 1189 1231 #: includes/Admin/UnitsPage.php:166 1190 1232 #: includes/Admin/UnitsPage.php:222 1191 #: includes/Admin/UnitsPage.php:38 81233 #: includes/Admin/UnitsPage.php:389 1192 1234 msgid "Service" 1193 1235 msgstr "" 1194 1236 1195 #: includes/Admin/CalendarView.php: 6701237 #: includes/Admin/CalendarView.php:746 1196 1238 #: includes/Admin/UnitsPage.php:167 1197 #: includes/Admin/UnitsPage.php:38 81239 #: includes/Admin/UnitsPage.php:389 1198 1240 msgid "Blocked" 1199 1241 msgstr "" 1200 1242 1201 #: includes/Admin/CalendarView.php: 6881243 #: includes/Admin/CalendarView.php:764 1202 1244 msgid "No units yet. Add the units (plate numbers) of your vehicles first." 1203 1245 msgstr "" 1204 1246 1205 #: includes/Admin/CalendarView.php:7 021247 #: includes/Admin/CalendarView.php:778 1206 1248 #: includes/Booking/BookingService.php:43 1207 1249 msgid "Waiting" 1208 1250 msgstr "" 1209 1251 1210 #: includes/Admin/CalendarView.php:7 041252 #: includes/Admin/CalendarView.php:780 1211 1253 #: includes/Booking/BookingService.php:45 1212 1254 #: includes/Fleet/UnitRepository.php:41 … … 1215 1257 msgstr "" 1216 1258 1217 #: includes/Admin/CalendarView.php:705 1259 #: includes/Admin/CalendarView.php:781 1260 #: includes/Admin/CalendarView.php:787 1218 1261 #: includes/Admin/UnitsTable.php:312 1219 1262 msgid "Late return" 1220 1263 msgstr "" 1221 1264 1222 #: includes/Admin/CalendarView.php:7 061265 #: includes/Admin/CalendarView.php:782 1223 1266 msgid "Returned / completed" 1224 1267 msgstr "" 1225 1268 1226 #: includes/Admin/CalendarView.php:712 1269 #: includes/Admin/CalendarView.php:788 1270 msgid "Picked up or comes back today" 1271 msgstr "" 1272 1273 #: includes/Admin/CalendarView.php:795 1227 1274 msgid "vehicles out / fleet" 1228 1275 msgstr "" 1229 1276 1230 1277 #. translators: %d: number of pick-ups. 1231 #: includes/Admin/CalendarView.php: 7361278 #: includes/Admin/CalendarView.php:819 1232 1279 #, php-format 1233 1280 msgid "%d pick-up" … … 1237 1284 1238 1285 #. translators: %d: number of returns. 1239 #: includes/Admin/CalendarView.php: 7381286 #: includes/Admin/CalendarView.php:821 1240 1287 #, php-format 1241 1288 msgid "%d return" … … 1245 1292 1246 1293 #. translators: %d: number of bookings waiting for confirmation. 1247 #: includes/Admin/CalendarView.php: 7401294 #: includes/Admin/CalendarView.php:823 1248 1295 #, php-format 1249 1296 msgid "%d waiting" … … 1253 1300 1254 1301 #: includes/Admin/CustomersPage.php:58 1255 #: includes/Admin/Menu.php: 951256 #: includes/Admin/Menu.php: 961302 #: includes/Admin/Menu.php:105 1303 #: includes/Admin/Menu.php:106 1257 1304 msgid "Customers" 1258 1305 msgstr "" … … 1358 1405 1359 1406 #: includes/Admin/DashboardPage.php:65 1360 #: includes/Fleet/VehiclePostType.php:14 61407 #: includes/Fleet/VehiclePostType.php:148 1361 1408 msgid "Units (plate numbers)" 1362 1409 msgstr "" … … 1505 1552 msgstr "" 1506 1553 1507 #: includes/Admin/DashboardPage.php:23 51554 #: includes/Admin/DashboardPage.php:236 1508 1555 msgid "Pick-ups today" 1509 1556 msgstr "" 1510 1557 1511 #: includes/Admin/DashboardPage.php:23 61558 #: includes/Admin/DashboardPage.php:237 1512 1559 msgid "Returns today" 1513 1560 msgstr "" 1514 1561 1515 #: includes/Admin/DashboardPage.php:23 71562 #: includes/Admin/DashboardPage.php:238 1516 1563 msgid "Late returns" 1517 1564 msgstr "" 1518 1565 1519 #: includes/Admin/DashboardPage.php:23 81566 #: includes/Admin/DashboardPage.php:239 1520 1567 msgid "Waiting for confirmation" 1521 1568 msgstr "" 1522 1569 1523 1570 #. translators: %d: number of more bookings. 1524 #: includes/Admin/DashboardPage.php:26 11571 #: includes/Admin/DashboardPage.php:262 1525 1572 #, php-format 1526 1573 msgid "+ %d more" 1527 1574 msgstr "" 1528 1575 1529 #: includes/Admin/DashboardPage.php:26 51576 #: includes/Admin/DashboardPage.php:266 1530 1577 msgid "Review and confirm" 1531 1578 msgstr "" 1532 1579 1533 #: includes/Admin/DashboardPage.php:26 71580 #: includes/Admin/DashboardPage.php:268 1534 1581 msgid "Everything is back on time." 1535 1582 msgstr "" 1536 1583 1537 #: includes/Admin/DashboardPage.php:26 71584 #: includes/Admin/DashboardPage.php:268 1538 1585 msgid "Nothing planned." 1539 1586 msgstr "" 1540 1587 1541 #: includes/Admin/DashboardPage.php:28 31588 #: includes/Admin/DashboardPage.php:284 1542 1589 msgid "Fleet right now" 1543 1590 msgstr "" 1544 1591 1545 #: includes/Admin/DashboardPage.php:28 31592 #: includes/Admin/DashboardPage.php:284 1546 1593 #: includes/Admin/UnitsPage.php:94 1547 1594 msgid "All units" 1548 1595 msgstr "" 1549 1596 1550 #: includes/Admin/DashboardPage.php:28 51597 #: includes/Admin/DashboardPage.php:286 1551 1598 msgid "No units yet." 1552 1599 msgstr "" 1553 1600 1554 1601 #. translators: %d: percentage of the fleet on rent. 1555 #: includes/Admin/DashboardPage.php:29 01602 #: includes/Admin/DashboardPage.php:291 1556 1603 #, php-format 1557 1604 msgid "%d%% on rent" 1558 1605 msgstr "" 1559 1606 1560 #: includes/Admin/DashboardPage.php:29 11607 #: includes/Admin/DashboardPage.php:292 1561 1608 msgid "Fleet status" 1562 1609 msgstr "" 1563 1610 1564 #: includes/Admin/DashboardPage.php:31 21611 #: includes/Admin/DashboardPage.php:313 1565 1612 msgid "vs yesterday" 1566 1613 msgstr "" 1567 1614 1568 #: includes/Admin/DashboardPage.php:31 31615 #: includes/Admin/DashboardPage.php:314 1569 1616 msgid "Last 7 days" 1570 1617 msgstr "" 1571 1618 1572 #: includes/Admin/DashboardPage.php:31 31619 #: includes/Admin/DashboardPage.php:314 1573 1620 msgid "vs the 7 days before" 1574 1621 msgstr "" 1575 1622 1576 #: includes/Admin/DashboardPage.php:31 41623 #: includes/Admin/DashboardPage.php:315 1577 1624 msgid "This month" 1578 1625 msgstr "" 1579 1626 1580 #: includes/Admin/DashboardPage.php:31 41627 #: includes/Admin/DashboardPage.php:315 1581 1628 msgid "vs the same days last month" 1582 1629 msgstr "" 1583 1630 1584 #: includes/Admin/DashboardPage.php:31 61631 #: includes/Admin/DashboardPage.php:317 1585 1632 msgid "Income received" 1586 1633 msgstr "" 1587 1634 1588 #: includes/Admin/DashboardPage.php:32 61635 #: includes/Admin/DashboardPage.php:327 1589 1636 msgid "Rental payments recorded in Rentbaik, without deposits." 1590 1637 msgstr "" 1591 1638 1592 #: includes/Admin/DashboardPage.php:34 71639 #: includes/Admin/DashboardPage.php:348 1593 1640 msgid "Welcome to Rentbaik" 1594 1641 msgstr "" 1595 1642 1596 #: includes/Admin/DashboardPage.php:34 81643 #: includes/Admin/DashboardPage.php:349 1597 1644 msgid "Answer a few simple questions and your booking page is ready. Not sure yet? Try it with sample data first." 1598 1645 msgstr "" 1599 1646 1600 #: includes/Admin/DashboardPage.php:3 491647 #: includes/Admin/DashboardPage.php:350 1601 1648 msgid "About 5 minutes" 1602 1649 msgstr "" 1603 1650 1604 #: includes/Admin/DashboardPage.php:3 491651 #: includes/Admin/DashboardPage.php:350 1605 1652 msgid "Every step can be skipped" 1606 1653 msgstr "" 1607 1654 1608 #: includes/Admin/DashboardPage.php:3 491655 #: includes/Admin/DashboardPage.php:350 1609 1656 msgid "Change everything later" 1610 1657 msgstr "" 1611 1658 1612 #: includes/Admin/DashboardPage.php:35 11659 #: includes/Admin/DashboardPage.php:352 1613 1660 msgid "Start the setup wizard" 1614 1661 msgstr "" 1615 1662 1616 #: includes/Admin/DashboardPage.php:35 61663 #: includes/Admin/DashboardPage.php:357 1617 1664 msgid "Hide this" 1618 1665 msgstr "" 1619 1666 1620 #: includes/Admin/DashboardPage.php:37 01667 #: includes/Admin/DashboardPage.php:371 1621 1668 msgid "Get ready to take bookings" 1622 1669 msgstr "" 1623 1670 1624 1671 #. translators: 1: completed steps, 2: total steps. 1625 #: includes/Admin/DashboardPage.php:37 21672 #: includes/Admin/DashboardPage.php:373 1626 1673 #, php-format 1627 1674 msgid "%1$d of %2$d done" 1628 1675 msgstr "" 1629 1676 1630 #: includes/Admin/DashboardPage.php:37 41677 #: includes/Admin/DashboardPage.php:375 1631 1678 msgid "Prefer step by step? Open the setup wizard." 1632 1679 msgstr "" 1633 1680 1634 #: includes/Admin/DashboardPage.php:39 01681 #: includes/Admin/DashboardPage.php:391 1635 1682 msgid "Set up" 1636 1683 msgstr "" 1637 1684 1638 #: includes/Admin/DashboardPage.php:40 41685 #: includes/Admin/DashboardPage.php:405 1639 1686 msgid "System status" 1640 1687 msgstr "" 1641 1688 1642 #: includes/Admin/DashboardPage.php:40 71689 #: includes/Admin/DashboardPage.php:408 1643 1690 msgid "OK" 1644 1691 msgstr "" 1645 1692 1646 #: includes/Admin/DashboardPage.php:40 71693 #: includes/Admin/DashboardPage.php:408 1647 1694 msgid "Check" 1648 1695 msgstr "" 1649 1696 1650 #: includes/Admin/DashboardPage.php:40 71697 #: includes/Admin/DashboardPage.php:408 1651 1698 msgid "Optional" 1652 1699 msgstr "" … … 1699 1746 #: includes/Admin/ExtrasPage.php:187 1700 1747 #: includes/Admin/UnitsPage.php:51 1701 #: includes/Admin/UnitsPage.php:4 291748 #: includes/Admin/UnitsPage.php:430 1702 1749 msgid "You are not allowed to manage the fleet." 1703 1750 msgstr "" … … 1763 1810 #: includes/Admin/ExtrasPage.php:112 1764 1811 #: includes/Admin/SettingsPage.php:110 1765 #: includes/Fleet/VehiclePostType.php:21 31812 #: includes/Fleet/VehiclePostType.php:215 1766 1813 #: includes/Frontend/VehicleContent.php:74 1767 1814 msgid "Per day" … … 1810 1857 msgstr "" 1811 1858 1812 #: includes/Admin/Menu.php:5 71859 #: includes/Admin/Menu.php:55 1813 1860 msgid "Rentbaik Dashboard" 1814 1861 msgstr "" 1815 1862 1816 #: includes/Admin/Menu.php:104 1817 #: includes/Fleet/VehiclePostType.php:115 1863 #: includes/Admin/Menu.php:76 1864 #: includes/Admin/Menu.php:77 1865 #: includes/Admin/SetupWizard.php:78 1866 #: includes/Fleet/VehiclePostType.php:80 1867 #: includes/Fleet/VehiclePostType.php:90 1868 #: includes/Fleet/VehiclePostType.php:91 1869 msgid "Vehicles" 1870 msgstr "" 1871 1872 #: includes/Admin/Menu.php:114 1873 #: includes/Fleet/VehiclePostType.php:117 1818 1874 msgid "Vehicle categories" 1819 1875 msgstr "" 1820 1876 1821 #: includes/Admin/Menu.php:1 051822 #: includes/Fleet/VehiclePostType.php:11 71877 #: includes/Admin/Menu.php:115 1878 #: includes/Fleet/VehiclePostType.php:119 1823 1879 msgid "Categories" 1824 1880 msgstr "" 1825 1881 1826 #: includes/Admin/Menu.php:1 121882 #: includes/Admin/Menu.php:122 1827 1883 #: includes/Admin/SetupWizard.php:140 1828 1884 #: includes/Admin/SetupWizard.php:162 … … 1830 1886 msgstr "" 1831 1887 1832 #: includes/Admin/Menu.php:1 131888 #: includes/Admin/Menu.php:123 1833 1889 #: includes/Admin/SettingsPage.php:480 1834 1890 msgid "Setup wizard" 1835 1891 msgstr "" 1836 1892 1837 #: includes/Admin/Menu.php:1 211893 #: includes/Admin/Menu.php:131 1838 1894 msgid "Rentbaik Settings" 1895 msgstr "" 1896 1897 #: includes/Admin/PrivateCheck.php:111 1898 msgid "Customer files are not protected yet." 1899 msgstr "" 1900 1901 #: includes/Admin/PrivateCheck.php:112 1902 msgid "Your web server opens files in the Rentbaik private folder directly. Customer photos and payment receipts have random names that cannot be guessed, but to block the folder completely add this rule to your nginx configuration (or ask your host):" 1903 msgstr "" 1904 1905 #: includes/Admin/PrivateCheck.php:114 1906 msgid "Check again" 1907 msgstr "" 1908 1909 #: includes/Admin/PrivateCheck.php:124 1910 msgid "You are not allowed to do this." 1911 msgstr "" 1912 1913 #: includes/Admin/PrivateCheck.php:129 1914 msgid "The private folder is protected." 1915 msgstr "" 1916 1917 #: includes/Admin/PrivateCheck.php:131 1918 msgid "The check could not reach your site. Please try again later." 1839 1919 msgstr "" 1840 1920 … … 1920 2000 1921 2001 #: includes/Admin/SettingsPage.php:111 1922 #: includes/Fleet/VehiclePostType.php:21 22002 #: includes/Fleet/VehiclePostType.php:214 1923 2003 #: includes/Frontend/VehicleContent.php:73 1924 2004 msgid "Per hour" … … 2026 2106 2027 2107 #: includes/Admin/SettingsPage.php:175 2028 #: includes/Fleet/VehiclePostType.php:2 282108 #: includes/Fleet/VehiclePostType.php:230 2029 2109 msgid "Minimum rental (hours)" 2030 2110 msgstr "" 2031 2111 2032 2112 #: includes/Admin/SettingsPage.php:177 2033 #: includes/Fleet/VehiclePostType.php:2 292113 #: includes/Fleet/VehiclePostType.php:231 2034 2114 msgid "Maximum rental (days)" 2035 2115 msgstr "" … … 2064 2144 2065 2145 #: includes/Admin/SettingsPage.php:194 2066 #: includes/Fleet/VehiclePostType.php:22 72146 #: includes/Fleet/VehiclePostType.php:229 2067 2147 msgid "Security deposit" 2068 2148 msgstr "" … … 2336 2416 2337 2417 #: includes/Admin/SettingsPage.php:587 2418 #: includes/Admin/SitePages.php:159 2338 2419 msgid "View" 2339 2420 msgstr "" … … 2482 2563 #: includes/Admin/SetupWizard.php:76 2483 2564 msgid "Pick-up place" 2484 msgstr ""2485 2486 #: includes/Admin/SetupWizard.php:782487 #: includes/Fleet/VehiclePostType.php:802488 #: includes/Fleet/VehiclePostType.php:902489 #: includes/Fleet/VehiclePostType.php:912490 msgid "Vehicles"2491 2565 msgstr "" 2492 2566 … … 2717 2791 2718 2792 #: includes/Admin/SetupWizard.php:521 2793 #: includes/Admin/SitePages.php:159 2794 #: includes/Admin/SitePages.php:177 2719 2795 #: templates/booking-form.php:69 2720 2796 msgid "(opens in a new tab)" … … 2798 2874 msgstr "" 2799 2875 2876 #: includes/Admin/SitePages.php:32 2877 msgid "Customers choose dates and see the free vehicles with the price." 2878 msgstr "" 2879 2880 #: includes/Admin/SitePages.php:33 2881 msgid "Booking form in three steps; the confirmation and the booking details open here too." 2882 msgstr "" 2883 2884 #: includes/Admin/SitePages.php:34 2885 msgid "Customers look up their booking with its number and their email or phone." 2886 msgstr "" 2887 2888 #: includes/Admin/SitePages.php:56 2889 msgid "Vehicle pages" 2890 msgstr "" 2891 2892 #. translators: %s: vehicle name. 2893 #: includes/Admin/SitePages.php:58 2894 #, php-format 2895 msgid "Every vehicle has its own page with photos, prices, availability and a booking button. Example: %s" 2896 msgstr "" 2897 2898 #: includes/Admin/SitePages.php:126 2899 msgid "Your pages for customers" 2900 msgstr "" 2901 2902 #: includes/Admin/SitePages.php:128 2903 msgid "Change pages" 2904 msgstr "" 2905 2906 #: includes/Admin/SitePages.php:131 2907 msgid "Rentbaik made these pages when it was activated. Add them to your site menu, or put the shortcodes on any page or in any page builder." 2908 msgstr "" 2909 2910 #. translators: %d: number of pages. 2911 #: includes/Admin/SitePages.php:136 2912 #, php-format 2913 msgid "%d page is missing." 2914 msgid_plural "%d pages are missing." 2915 msgstr[0] "" 2916 msgstr[1] "" 2917 2918 #: includes/Admin/SitePages.php:139 2919 msgid "Create them in Settings → Display" 2920 msgstr "" 2921 2922 #: includes/Admin/SitePages.php:148 2923 msgid "Not published" 2924 msgstr "" 2925 2926 #: includes/Admin/SitePages.php:151 2927 msgid "Missing" 2928 msgstr "" 2929 2930 #. translators: %s: booking number. 2931 #: includes/Admin/SitePages.php:174 2932 #, php-format 2933 msgid "See sample booking %s as the customer sees it" 2934 msgstr "" 2935 2800 2936 #: includes/Admin/UnitsPage.php:59 2801 2937 msgid "Every real vehicle with its plate number, kilometres and status." … … 2805 2941 #: includes/Admin/UnitsPage.php:93 2806 2942 #: includes/Admin/UnitsPage.php:142 2807 #: includes/Fleet/VehiclePostType.php:29 62943 #: includes/Fleet/VehiclePostType.php:298 2808 2944 msgid "Add unit" 2809 2945 msgstr "" … … 2951 3087 2952 3088 #. translators: %s: unit status. 2953 #: includes/Admin/UnitsPage.php:34 43089 #: includes/Admin/UnitsPage.php:345 2954 3090 #, php-format 2955 3091 msgid "Status set to %s (bulk action)." … … 2957 3093 2958 3094 #. translators: %d: number of units. 2959 #: includes/Admin/UnitsPage.php:34 73095 #: includes/Admin/UnitsPage.php:348 2960 3096 #, php-format 2961 3097 msgid "%d unit updated." … … 2964 3100 msgstr[1] "" 2965 3101 2966 #: includes/Admin/UnitsPage.php:37 13102 #: includes/Admin/UnitsPage.php:372 2967 3103 msgid "Choose a start and an end; the end must be after the start." 2968 3104 msgstr "" 2969 3105 2970 #: includes/Admin/UnitsPage.php:37 63106 #: includes/Admin/UnitsPage.php:377 2971 3107 msgid "Dates blocked. Note: this unit already has a booking or block in that period." 2972 3108 msgstr "" 2973 3109 2974 #: includes/Admin/UnitsPage.php:37 83110 #: includes/Admin/UnitsPage.php:379 2975 3111 msgid "Dates blocked." 2976 3112 msgstr "" 2977 3113 2978 3114 #. translators: 1: block type, 2: start, 3: end. 2979 #: includes/Admin/UnitsPage.php:38 73115 #: includes/Admin/UnitsPage.php:388 2980 3116 #, php-format 2981 3117 msgid "Blocked (%1$s): %2$s – %3$s." … … 2983 3119 2984 3120 #. translators: 1: start, 2: end. 2985 #: includes/Admin/UnitsPage.php:41 03121 #: includes/Admin/UnitsPage.php:411 2986 3122 #, php-format 2987 3123 msgid "Block removed: %1$s – %2$s." 2988 3124 msgstr "" 2989 3125 2990 #: includes/Admin/UnitsPage.php:41 13126 #: includes/Admin/UnitsPage.php:412 2991 3127 msgid "Block removed." 2992 3128 msgstr "" … … 3077 3213 3078 3214 #: includes/Admin/UnitsTable.php:330 3079 #: includes/Fleet/VehiclePostType.php:26 73215 #: includes/Fleet/VehiclePostType.php:269 3080 3216 msgid "No units yet. A unit is one real vehicle with its own plate number." 3081 3217 msgstr "" … … 3218 3354 msgstr "" 3219 3355 3220 #: includes/Booking/BookingService.php:5993221 msgid "Unpaid"3222 msgstr ""3223 3224 #: includes/Booking/BookingService.php:6003225 msgid "Part paid"3226 msgstr ""3227 3228 3356 #: includes/Capabilities.php:70 3229 3357 msgid "Rental Manager" … … 3395 3523 msgstr "" 3396 3524 3397 #: includes/Fleet/VehiclePostType.php:11 63525 #: includes/Fleet/VehiclePostType.php:118 3398 3526 msgid "Vehicle category" 3399 3527 msgstr "" 3400 3528 3401 #: includes/Fleet/VehiclePostType.php:1 183529 #: includes/Fleet/VehiclePostType.php:120 3402 3530 msgid "Add category" 3403 3531 msgstr "" 3404 3532 3405 #: includes/Fleet/VehiclePostType.php:1 193533 #: includes/Fleet/VehiclePostType.php:121 3406 3534 msgid "Edit category" 3407 3535 msgstr "" 3408 3536 3409 #: includes/Fleet/VehiclePostType.php:12 03537 #: includes/Fleet/VehiclePostType.php:122 3410 3538 msgid "Search categories" 3411 3539 msgstr "" 3412 3540 3413 #: includes/Fleet/VehiclePostType.php:14 33541 #: includes/Fleet/VehiclePostType.php:145 3414 3542 msgid "Vehicle details" 3415 3543 msgstr "" 3416 3544 3417 #: includes/Fleet/VehiclePostType.php:14 43545 #: includes/Fleet/VehiclePostType.php:146 3418 3546 #: templates/vehicle-details.php:35 3419 3547 msgid "Prices" 3420 3548 msgstr "" 3421 3549 3422 #: includes/Fleet/VehiclePostType.php:14 53550 #: includes/Fleet/VehiclePostType.php:147 3423 3551 msgid "Photo gallery" 3424 3552 msgstr "" 3425 3553 3426 #: includes/Fleet/VehiclePostType.php:16 23554 #: includes/Fleet/VehiclePostType.php:164 3427 3555 msgid "Vehicle type" 3428 3556 msgstr "" 3429 3557 3430 #: includes/Fleet/VehiclePostType.php:16 73558 #: includes/Fleet/VehiclePostType.php:169 3431 3559 #: includes/Frontend/VehicleContent.php:53 3432 3560 msgid "Brand" 3433 3561 msgstr "" 3434 3562 3435 #: includes/Fleet/VehiclePostType.php:16 73563 #: includes/Fleet/VehiclePostType.php:169 3436 3564 msgid "e.g. Honda" 3437 3565 msgstr "" 3438 3566 3439 #: includes/Fleet/VehiclePostType.php:1 683567 #: includes/Fleet/VehiclePostType.php:170 3440 3568 #: includes/Frontend/VehicleContent.php:56 3441 3569 msgid "Model year" … … 3443 3571 3444 3572 #. translators: %s: vehicle type, e.g. Car. 3445 #: includes/Fleet/VehiclePostType.php:1 783573 #: includes/Fleet/VehiclePostType.php:180 3446 3574 #, php-format 3447 3575 msgid "%s specifications" 3448 3576 msgstr "" 3449 3577 3450 #: includes/Fleet/VehiclePostType.php:21 43578 #: includes/Fleet/VehiclePostType.php:216 3451 3579 #: includes/Frontend/VehicleContent.php:75 3452 3580 msgid "Per week (7 days)" 3453 3581 msgstr "" 3454 3582 3455 #: includes/Fleet/VehiclePostType.php:21 53583 #: includes/Fleet/VehiclePostType.php:217 3456 3584 #: includes/Frontend/VehicleContent.php:76 3457 3585 msgid "Per month (30 days)" 3458 3586 msgstr "" 3459 3587 3460 #: includes/Fleet/VehiclePostType.php:22 03588 #: includes/Fleet/VehiclePostType.php:222 3461 3589 msgid "Leave a price empty if you do not offer that period. Longer rentals automatically use the cheaper weekly or monthly price when it is set." 3462 3590 msgstr "" 3463 3591 3464 #: includes/Fleet/VehiclePostType.php:23 13592 #: includes/Fleet/VehiclePostType.php:233 3465 3593 msgid "Empty deposit, minimum and maximum use the defaults from Settings → Rental rules. Maximum 0 means no limit." 3466 3594 msgstr "" 3467 3595 3468 #: includes/Fleet/VehiclePostType.php:2 493596 #: includes/Fleet/VehiclePostType.php:251 3469 3597 msgid "Choose photos" 3470 3598 msgstr "" 3471 3599 3472 #: includes/Fleet/VehiclePostType.php:25 03600 #: includes/Fleet/VehiclePostType.php:252 3473 3601 msgid "Remove all" 3474 3602 msgstr "" 3475 3603 3476 #: includes/Fleet/VehiclePostType.php:25 13604 #: includes/Fleet/VehiclePostType.php:253 3477 3605 msgid "The featured image is the main photo. Add more photos here for the vehicle page." 3478 3606 msgstr "" 3479 3607 3480 #: includes/Fleet/VehiclePostType.php:28 53608 #: includes/Fleet/VehiclePostType.php:287 3481 3609 msgid "Save the vehicle first, then add its units." 3482 3610 msgstr "" 3483 3611 3484 3612 #. translators: %s: price. 3485 #: includes/Fleet/VehiclePostType.php:41 63613 #: includes/Fleet/VehiclePostType.php:418 3486 3614 #, php-format 3487 3615 msgid "%s / day" … … 3489 3617 3490 3618 #. translators: %s: price. 3491 #: includes/Fleet/VehiclePostType.php:4 193619 #: includes/Fleet/VehiclePostType.php:421 3492 3620 #, php-format 3493 3621 msgid "%s / hour" 3494 3622 msgstr "" 3495 3623 3496 #: includes/Fleet/VehiclePostType.php:42 13624 #: includes/Fleet/VehiclePostType.php:423 3497 3625 msgid "No price yet" 3498 3626 msgstr "" 3499 3627 3500 3628 #. translators: 1: ready units, 2: all units. 3501 #: includes/Fleet/VehiclePostType.php:4 283629 #: includes/Fleet/VehiclePostType.php:430 3502 3630 #, php-format 3503 3631 msgid "%1$d ready of %2$d" … … 3952 4080 msgstr "" 3953 4081 3954 #: includes/Setup/SampleData.php:282 4082 #: includes/Setup/SampleData.php:384 4083 msgid "Included gear (sample)" 4084 msgstr "" 4085 4086 #: includes/Setup/SampleData.php:455 3955 4087 msgid "This is sample data from Rentbaik. Change it, or remove all sample data from the Rentbaik dashboard." 3956 4088 msgstr "" 3957 4089 3958 #: includes/Setup/SampleData.php: 2864090 #: includes/Setup/SampleData.php:461 3959 4091 msgid "Automatic scooter 125 cc (sample)" 3960 4092 msgstr "" 3961 4093 3962 #: includes/Setup/SampleData.php: 2874094 #: includes/Setup/SampleData.php:462 3963 4095 msgid "Easy automatic scooter for the city. Two helmets included." 3964 4096 msgstr "" 3965 4097 3966 #: includes/Setup/SampleData.php: 3003967 #: includes/Setup/SampleData.php: 3373968 #: includes/Setup/SampleData.php: 3894098 #: includes/Setup/SampleData.php:475 4099 #: includes/Setup/SampleData.php:516 4100 #: includes/Setup/SampleData.php:574 3969 4101 msgid "White" 3970 4102 msgstr "" 3971 4103 3972 #: includes/Setup/SampleData.php: 3004104 #: includes/Setup/SampleData.php:475 3973 4105 msgid "Red" 3974 4106 msgstr "" 3975 4107 3976 #: includes/Setup/SampleData.php: 3003977 #: includes/Setup/SampleData.php: 3574108 #: includes/Setup/SampleData.php:475 4109 #: includes/Setup/SampleData.php:538 3978 4110 msgid "Black" 3979 4111 msgstr "" 3980 4112 3981 #: includes/Setup/SampleData.php: 3044113 #: includes/Setup/SampleData.php:481 3982 4114 msgid "Comfort scooter 160 cc (sample)" 3983 4115 msgstr "" 3984 4116 3985 #: includes/Setup/SampleData.php: 3054117 #: includes/Setup/SampleData.php:482 3986 4118 msgid "Bigger scooter with a large seat and storage, good for longer trips." 3987 4119 msgstr "" 3988 4120 3989 #: includes/Setup/SampleData.php: 3174121 #: includes/Setup/SampleData.php:494 3990 4122 msgid "Grey" 3991 4123 msgstr "" 3992 4124 3993 #: includes/Setup/SampleData.php: 3174125 #: includes/Setup/SampleData.php:494 3994 4126 msgid "Blue" 3995 4127 msgstr "" 3996 4128 3997 #: includes/Setup/SampleData.php: 3214129 #: includes/Setup/SampleData.php:500 3998 4130 msgid "Compact car, automatic (sample)" 3999 4131 msgstr "" 4000 4132 4001 #: includes/Setup/SampleData.php: 3224133 #: includes/Setup/SampleData.php:501 4002 4134 msgid "Small and easy to park. Air conditioning, five seats." 4003 4135 msgstr "" 4004 4136 4005 #: includes/Setup/SampleData.php: 3374137 #: includes/Setup/SampleData.php:516 4006 4138 msgid "Silver" 4007 4139 msgstr "" 4008 4140 4009 #: includes/Setup/SampleData.php: 3414141 #: includes/Setup/SampleData.php:522 4010 4142 msgid "Family car, 7 seats (sample)" 4011 4143 msgstr "" 4012 4144 4013 #: includes/Setup/SampleData.php: 3424145 #: includes/Setup/SampleData.php:523 4014 4146 msgid "Room for the whole family and their bags." 4015 4147 msgstr "" 4016 4148 4017 #: includes/Setup/SampleData.php: 3614149 #: includes/Setup/SampleData.php:544 4018 4150 msgid "City bike (sample)" 4019 4151 msgstr "" 4020 4152 4021 #: includes/Setup/SampleData.php: 3624153 #: includes/Setup/SampleData.php:545 4022 4154 msgid "Comfortable bike with a basket and a lock." 4023 4155 msgstr "" 4024 4156 4025 #: includes/Setup/SampleData.php: 3734157 #: includes/Setup/SampleData.php:556 4026 4158 msgid "Green" 4027 4159 msgstr "" 4028 4160 4029 #: includes/Setup/SampleData.php: 3774161 #: includes/Setup/SampleData.php:562 4030 4162 msgid "Electric bike (sample)" 4031 4163 msgstr "" 4032 4164 4033 #: includes/Setup/SampleData.php: 3784165 #: includes/Setup/SampleData.php:563 4034 4166 msgid "Pedal assist up to 60 km per charge." 4035 4167 msgstr "" 4036 4168 4037 #: includes/Setup/SampleData.php: 4024169 #: includes/Setup/SampleData.php:587 4038 4170 msgid "Extra helmet (sample)" 4039 4171 msgstr "" 4040 4172 4041 #: includes/Setup/SampleData.php: 4034173 #: includes/Setup/SampleData.php:588 4042 4174 msgid "Clean helmet for a passenger." 4043 4175 msgstr "" 4044 4176 4045 #: includes/Setup/SampleData.php: 4104177 #: includes/Setup/SampleData.php:595 4046 4178 msgid "Rain poncho (sample)" 4047 4179 msgstr "" 4048 4180 4049 #: includes/Setup/SampleData.php: 4114181 #: includes/Setup/SampleData.php:596 4050 4182 msgid "Yours to keep." 4051 4183 msgstr "" 4052 4184 4053 #: includes/Setup/SampleData.php: 4184185 #: includes/Setup/SampleData.php:603 4054 4186 msgid "Child seat (sample)" 4055 4187 msgstr "" 4056 4188 4057 #: includes/Setup/SampleData.php: 4194189 #: includes/Setup/SampleData.php:604 4058 4190 msgid "For children from 9 to 18 kg." 4059 4191 msgstr "" 4060 4192 4061 #: includes/Setup/SampleData.php: 4864062 #: includes/Setup/SampleData.php: 5504193 #: includes/Setup/SampleData.php:671 4194 #: includes/Setup/SampleData.php:809 4063 4195 msgid "Sample data" 4064 4196 msgstr "" 4065 4197 4066 #: includes/Setup/SampleData.php: 5474198 #: includes/Setup/SampleData.php:806 4067 4199 msgid "Sample" 4068 4200 msgstr "" 4069 4201 4070 #: includes/Setup/SampleData.php: 5554202 #: includes/Setup/SampleData.php:814 4071 4203 msgid "Sample booking. It is removed together with the sample data." 4204 msgstr "" 4205 4206 #: includes/Setup/SampleData.php:888 4207 msgid "Sample payment" 4208 msgstr "" 4209 4210 #: includes/Setup/SampleData.php:909 4211 msgid "Deposit at pick-up" 4212 msgstr "" 4213 4214 #: includes/Setup/SampleData.php:917 4215 msgid "Deposit given back" 4216 msgstr "" 4217 4218 #: includes/Setup/SampleData.php:927 4219 msgid "Refund after cancelling" 4220 msgstr "" 4221 4222 #: includes/Setup/SampleData.php:959 4223 msgid "Oil change (sample)" 4072 4224 msgstr "" 4073 4225 -
rentbaik/trunk/readme.txt
r3790685 r3790901 1 1 === Rentbaik === 2 2 Contributors: rentbaik 3 Tags: rental, booking, car rental, bike rental, scooter rental3 Tags: rental, booking, car rental, motorbike rental, scooter rental 4 4 Requires at least: 6.5 5 5 Tested up to: 7.1 6 6 Requires PHP: 8.3 7 Stable tag: 1.0. 07 Stable tag: 1.0.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 68 68 = Can I try Rentbaik before adding my own vehicles? = 69 69 70 Yes. Click **Add sample data** in the setup wizard or on the Rentbaik dashboard. Rentbaik adds example vehicles marked "(sample)", units, extras and three bookings, without sending any email. **Remove sample data** deletes all of it again; your own vehicles and bookings are not touched. 70 Yes. Click **Add sample data** in the setup wizard or on the Rentbaik dashboard. Rentbaik adds example vehicles marked "(sample)" with pictures, units with their own photo, extras, a service day and a month of bookings in every common situation: picked up today, coming back today, late, part paid, deposit still to give back, waiting for a unit, waiting for confirmation with a transfer receipt, cancelled with a refund and not shown. Customer photos and receipts are drawings marked "SAMPLE", not real people or documents. No email is sent. **Remove sample data** deletes all of it again; your own vehicles and bookings are not touched. 71 72 = Where are the pages for my customers? = 73 74 Rentbaik creates the pages Rentals, Book a rental and My booking when it is activated. The Rentbaik dashboard lists them with their shortcodes and links to view or edit them, plus an example vehicle page. Add them to your site menu, or place the shortcodes on any page. 71 75 72 76 = Do I need WooCommerce or Elementor? = … … 150 154 == Changelog == 151 155 156 = 1.0.1 = 157 * Fix: the booking details panel and the day agenda on the Rentbaik dashboard did not open on sites with "Plain" permalinks ("The booking could not be loaded"). 158 * New: "Your pages for customers" card on the Rentbaik dashboard. It lists the Rentals, Book a rental and My booking pages with their shortcodes and View and Edit links, an example vehicle page, and a link to see a sample booking as the customer sees it. 159 * New: labels on the calendar bars and in the day agenda: Late, Today (pick-up or return), Unpaid or Part paid, and Deposit (not given back yet). 160 * Improved: the sample data now fills the current month with 17 bookings in every common situation: completed, returned with the deposit still held, cancelled with a refund, no-show, late, coming back today, picked up today, without a unit, waiting with a transfer receipt, part paid and paid online. 161 * Improved: sample vehicles and units have pictures, and sample customers have photos and transfer receipts. All are drawings marked "SAMPLE", not real people or documents. Remove sample data deletes them too. 162 * Improved: when a vehicle type is switched off, the sample bookings use a similar vehicle. 163 * Translation: complete Indonesian translation. 164 * Tip: if you added sample data with 1.0.0, remove it and add it again to see the new example month. 165 152 166 = 1.0.0 = 153 167 * First release. … … 155 169 == Upgrade Notice == 156 170 171 = 1.0.1 = 172 Fixes the booking panel on sites with Plain permalinks. Adds a card with your customer pages, labels on the calendar and a full month of sample data with pictures. 173 157 174 = 1.0.0 = 158 175 First release. -
rentbaik/trunk/rentbaik.php
r3790685 r3790901 3 3 * Plugin Name: Rentbaik 4 4 * Description: Run a bike, scooter or car rental business in WordPress: fleet by plate number, rates, availability, bookings and local payment options. Works with any theme. 5 * Version: 1.0. 05 * Version: 1.0.1 6 6 * Requires at least: 6.5 7 7 * Requires PHP: 8.3 … … 17 17 defined( 'ABSPATH' ) || exit; 18 18 19 define( 'RENTBAIK_VERSION', '1.0. 0' );19 define( 'RENTBAIK_VERSION', '1.0.1' ); 20 20 define( 'RENTBAIK_FILE', __FILE__ ); 21 21 define( 'RENTBAIK_PATH', plugin_dir_path( __FILE__ ) );
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)