Files
prosappweb/lib/models/profesional.dart
T
Lizandro GuarnizoandClaude Sonnet 4.6 920ae7144d configurable slot duration: UI picker + genRanges stepMinutes
- Fix TimeOfDayExtension.add() to handle minute overflow via total-minutes math
- genRanges() now takes stepMinutes param (default 30, clamp 5-480)
- Profesional model: slotDurationMinutes field (parsed from slot_duration_minutes)
- ProfessionalFormProvider: saves slot duration alongside schedules on Guardar
- schedule_view: _SlotDurationPicker chip selector (15/20/30/45/60/90/120 min)
- ProfessionalCalendarView + CalendarView: pass slotDurationMinutes to genRanges

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 19:25:08 -05:00

252 lines
9.1 KiB
Dart

import 'package:prosapp_web_app/models/location_preferences.dart';
import 'package:prosapp_web_app/models/payment_method_entity.dart';
import 'package:prosapp_web_app/models/schedules.dart';
import 'package:prosapp_web_app/models/schedules_entity.dart';
class Profesional {
final String id;
final String identification;
final String rethusCode;
final bool rethusValidated;
final String address;
final String aditionalAddress;
final String profession;
final bool ratePreferences;
final String rate;
final LocationPreferences locationPreferences;
final String bannerPicture;
final String identificationPicture;
final String certificatePicture;
final double latitude;
final double longitude;
final List<String> specializations;
final List<String> specializationsPictures;
final Schedules schedules;
final PaymentMethodEntity paymentMethods;
final int slotDurationMinutes;
const Profesional({
required this.id,
required this.identification,
required this.rethusCode,
required this.rethusValidated,
required this.address,
required this.aditionalAddress,
required this.profession,
required this.ratePreferences,
required this.rate,
required this.locationPreferences,
required this.bannerPicture,
required this.identificationPicture,
required this.certificatePicture,
required this.latitude,
required this.longitude,
required this.specializations,
required this.specializationsPictures,
required this.schedules,
required this.paymentMethods,
this.slotDurationMinutes = 30,
});
static Profesional empty() => Profesional(
id: '',
identification: '',
rethusCode: '',
rethusValidated: false,
address: '',
aditionalAddress: '',
profession: '',
ratePreferences: false,
rate: '',
locationPreferences: LocationPreferences.office,
bannerPicture: '',
identificationPicture: '',
certificatePicture: '',
latitude: 0.0,
longitude: 0.0,
specializations: [],
specializationsPictures: [],
schedules: Schedules.empty,
paymentMethods: PaymentMethodEntity.empty,
);
static Profesional fromDocument(Map<String, dynamic> doc) {
// Backend returns schedules as array [{day_of_week, enabled, ...}]; convert to Schedules
Schedules parsedSchedules = Schedules.empty;
final rawSchedules = doc['schedules'];
if (rawSchedules is List && rawSchedules.isNotEmpty) {
final days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
final map = <int, Map<String, dynamic>>{};
for (final s in rawSchedules) {
map[(s['day_of_week'] as int)] = s as Map<String, dynamic>;
}
ScheduleEntity _se(int day) {
final s = map[day];
if (s == null) return ScheduleEntity.empty;
return ScheduleEntity(
enabled: (s['enabled'] as bool?) ?? false,
continuousDay: (s['continuous_day'] as bool?) ?? false,
range1Hour1: ScheduleEntity.parseTime(s['range1_hour1']?.toString()),
range1Hour2: ScheduleEntity.parseTime(s['range1_hour2']?.toString()),
range2Hour1: ScheduleEntity.parseTime(s['range2_hour1']?.toString()),
range2Hour2: ScheduleEntity.parseTime(s['range2_hour2']?.toString()),
);
}
parsedSchedules = Schedules(
monday: _se(0), tuesday: _se(1), wednesday: _se(2), thursday: _se(3),
friday: _se(4), saturday: _se(5), sunday: _se(6),
);
} else if (rawSchedules is Map) {
parsedSchedules = Schedules.fromDocument(rawSchedules as Map<String, dynamic>);
}
// Specializations: array of objects [{name, picture}] or list of strings
List<String> specs = [];
List<String> specPics = [];
final rawSpecs = doc['specializations'];
if (rawSpecs is List) {
for (final s in rawSpecs) {
if (s is Map) {
specs.add((s['name'] as String?) ?? '');
specPics.add((s['picture'] as String?) ?? '');
} else {
specs.add(s.toString());
}
}
}
final rawSpecPics = doc['specializations_pictures'];
if (specPics.isEmpty && rawSpecPics is List) {
specPics = List<String>.from(rawSpecPics);
}
// payment_methods: object {nequi, datafono, transferencia} or array with one element or null
PaymentMethodEntity pm = PaymentMethodEntity.empty;
final rawPm = doc['payment_methods'];
if (rawPm is Map<String, dynamic>) {
pm = PaymentMethodEntity.fromDocument(rawPm);
} else if (rawPm is List && rawPm.isNotEmpty) {
pm = PaymentMethodEntity.fromDocument(rawPm.first as Map<String, dynamic>);
}
return Profesional(
id: doc['id'] as String,
identification: (doc['identification'] as String?) ?? '',
rethusCode: (doc['rethus_code'] as String?) ?? '',
rethusValidated: (doc['rethus_validated'] as bool?) ?? false,
address: (doc['address'] as String?) ?? '',
aditionalAddress: (doc['additional_address'] as String?) ?? '',
profession: (doc['profession'] as String?) ?? '',
ratePreferences: doc['rate_preferences'] is bool
? doc['rate_preferences'] as bool
: doc['rate_preferences'] == 'true',
rate: doc['rate']?.toString() ?? '',
locationPreferences: locationPrefsFromValue(doc['location_preferences']),
bannerPicture: (doc['banner_picture'] as String?) ?? '',
identificationPicture: (doc['identification_picture'] as String?) ?? '',
certificatePicture: (doc['certificate_picture'] as String?) ?? '',
latitude: double.tryParse(doc['latitude']?.toString() ?? '') ?? 0.0,
longitude: double.tryParse(doc['longitude']?.toString() ?? '') ?? 0.0,
specializations: specs,
specializationsPictures: specPics,
schedules: parsedSchedules,
paymentMethods: pm,
slotDurationMinutes: (doc['slot_duration_minutes'] as int?) ?? 30,
);
}
Profesional copyWith({
String? id,
String? identification,
String? rethusCode,
bool? rethusValidated,
String? address,
String? aditionalAddress,
String? profession,
bool ratePreferences = false,
String? rate,
LocationPreferences? locationPreferences,
String? bannerPicture,
String? identificationPicture,
String? certificatePicture,
double? latitude,
double? longitude,
List<String>? specializations,
List<String>? specializationsPictures,
Schedules? schedules,
PaymentMethodEntity? paymentMethods,
int? slotDurationMinutes,
}) {
return Profesional(
id: id ?? this.id,
identification: identification ?? this.identification,
rethusCode: rethusCode ?? this.rethusCode,
rethusValidated: rethusValidated ?? this.rethusValidated,
address: address ?? this.address,
aditionalAddress: aditionalAddress ?? this.aditionalAddress,
profession: profession ?? this.profession,
ratePreferences: ratePreferences,
rate: rate ?? this.rate,
locationPreferences: locationPreferences ?? this.locationPreferences,
bannerPicture: bannerPicture ?? this.bannerPicture,
identificationPicture:
identificationPicture ?? this.identificationPicture,
certificatePicture: certificatePicture ?? this.certificatePicture,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
specializations: specializations ?? this.specializations,
specializationsPictures:
specializationsPictures ?? this.specializationsPictures,
schedules: schedules ?? this.schedules,
paymentMethods: paymentMethods ?? this.paymentMethods,
slotDurationMinutes: slotDurationMinutes ?? this.slotDurationMinutes,
);
}
Map<String, dynamic> toDocument() {
return {
'id': id,
'identification': identification,
'rethus_code': rethusCode,
'rethus_validated': rethusValidated,
'address': address,
'additional_address': aditionalAddress,
'profession': profession,
'rate_preferences': ratePreferences,
'rate': double.tryParse(rate) ?? 0.0,
'location_preferences': locationPrefsToString(locationPreferences),
'banner_picture': bannerPicture,
'identification_picture': identificationPicture,
'certificate_picture': certificatePicture,
'latitude': latitude,
'longitude': longitude,
'specializations': specializations,
'specializations_pictures': specializationsPictures,
'schedules': schedules.toJson(),
'payment_methods': paymentMethods.toDocument(),
};
}
@override
String toString() {
return '''Profesional{ {
id: $id,
identification: $identification,
address: $address,
aditional_address: $aditionalAddress,
profession: $profession,
rate_preferences: $ratePreferences,
rate: $rate,
location_preferences: $locationPreferences,
bannerPicture: $bannerPicture,
identificationPicture: $identificationPicture,
certificatePicture: $certificatePicture,
latitude: $latitude,
longitude: $longitude,
specializations: $specializations,
specializationsPictures: $specializationsPictures,
schedule: $schedules,
paymentMethods: $paymentMethods
}''';
}
}