rows->fields(),'public_title','confirm_existing_bookings','target','resource_ids','group_uuid'];} protected function validate(array $input,array $current):array{return $input;} private function prepare(array $input,?int $id):array{ S\BookingValidator::fields($input,$this->fields());$old=$id?$this->get($id):[]; if(!empty($old['group_uuid'])){ $table=\ABeCe\Booking\Infrastructure\Database\Tables::name($this->db,$this->blocks?'resource_blocks':'schedule_exceptions');if((int)$this->db->get_var($this->db->prepare('SELECT COUNT(*) FROM %i WHERE group_uuid=%s',$table,$old['group_uuid']))>1)\ABeCe\Booking\Edition\Entitlements::current()->require('multi_resource_operations'); } if(($input['target']??'selected')!=='selected')\ABeCe\Booking\Edition\Entitlements::current()->require('multi_resource_operations'); $ids=$input['resource_ids']??[$input['resource_id']??$old['resource_id']??null];if(!is_array($ids)||!array_is_list($ids)||count($ids)!==1)\ABeCe\Booking\Edition\Entitlements::current()->require('multi_resource_operations'); $rid=S\ScheduleValidator::resource($ids[0],$this->resources,false);$data=array_intersect_key(array_replace($old,$input,['resource_id'=>$rid]),array_flip($this->rows->fields())); $from=$this->blocks?S\MembershipValidator::instant($data['starts_at']??null,'starts_at'):Time::sql(Time::day(S\MembershipValidator::date($data['exception_date']??null,'exception_date'))); $to=$this->blocks?S\MembershipValidator::instant($data['ends_at']??null,'ends_at'):Time::sql(Time::nextDay($data['exception_date'])); $conflicts=$this->db->get_results($this->db->prepare("SELECT b.id,b.booking_reference AS reference,b.resource_id,b.starts_at,b.ends_at,r.name AS resource_name,COALESCE(b.external_customer_label,u.display_name,b.booking_reference) AS customer FROM %i b JOIN %i r ON r.id=b.resource_id LEFT JOIN %i u ON u.ID=b.customer_user_id WHERE b.resource_id=%d AND b.status NOT IN ('cancelled','voided') AND b.starts_at<%s AND b.ends_at>%s ORDER BY b.starts_at,b.id",\ABeCe\Booking\Infrastructure\Database\Tables::name( $this->db, 'bookings' ),\ABeCe\Booking\Infrastructure\Database\Tables::name( $this->db, 'resources' ),$this->db->users,$rid,$to,$from),ARRAY_A); if($this->db->last_error)throw new CatalogException('abece_booking_database_error',__('The item could not be saved. Please try again.','abece-booking'),503); if(!$this->blocks&&empty($data['is_closed'])){ $a=Time::local($data['exception_date'],S\ScheduleValidator::clock($data['opens_at']??null,'opens_at',false));$b=Time::local($data['exception_date'],S\ScheduleValidator::clock($data['closes_at']??null,'closes_at',false));$conflicts=array_values(array_filter($conflicts,fn($v)=>!$a||!$b||$v['starts_at']Time::sql($b))); } return ['data'=>$data,'public_title'=>S\MembershipValidator::text($input['public_title']??$old['public_title']??null,'public_title',190,true),'conflicts'=>$conflicts,'resource_ids'=>[$rid],'target'=>'selected']; } public function preview(array $input,?int $id=null):array{return array_intersect_key($this->prepare($input,$id),array_flip(['conflicts','resource_ids','target']));} public function save(array $input,?int $id=null):array{return (new R\ResourceLockRepository($this->db))->batch(function()use($input,$id){$p=$this->prepare($input,$id);if($p['conflicts']&&!S\CatalogValidator::boolean($input['confirm_existing_bookings']??false,'confirm_existing_bookings'))throw new CatalogException('abece_booking_existing_bookings_confirmation_required',__('This change affects existing bookings. Review and explicitly confirm; bookings will remain unchanged.','abece-booking'),409,null,['conflicts'=>$p['conflicts']]);$row=$this->rows->save($p['data'],$id);$this->repository->save(['public_title'=>$p['public_title']],$row['id']);return $this->get($row['id'])+['conflicts'=>$p['conflicts']];});} public function delete(int $id):void{(new R\ResourceLockRepository($this->db))->batch(function()use($id){$this->prepare([],$id);$this->rows->delete($id);});} }