feat: Refactor code structure and optimize performance across multiple modules

This commit is contained in:
Achintha Isuru
2025-11-17 23:29:28 -05:00
parent 831570f2e0
commit a64cbd9edf
1508 changed files with 105319 additions and 0 deletions

View File

@@ -0,0 +1,231 @@
import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart';
import 'package:krow/core/data/models/event/addon_model.dart';
import 'package:krow/core/data/models/event/business_member_model.dart';
import 'package:krow/core/data/models/event/event_model.dart';
import 'package:krow/core/data/models/event/hub_model.dart';
import 'package:krow/core/data/models/event/tag_model.dart';
import 'package:krow/core/entity/position_entity.dart';
import 'package:krow/core/entity/shift_entity.dart';
enum EventStatus {
pending,
assigned,
confirmed,
active,
finished,
completed,
closed,
canceled,
draft
}
class EventEntity {
final String id;
final EventStatus? status;
final String name;
DateTime? startDate;
DateTime? endDate;
final String? cursor;
final ValueNotifier<double> totalCost = ValueNotifier(0.0);
final HubModel? hub;
final List<AddonModel>? addons;
final BusinessMemberModel? completedBy;
final String? completedNode;
final String? additionalInfo;
// final EventContractType contractType;
final List<ShiftEntity>? shifts;
final String? poNumber;
// final String? contractNumber;
final EventModel? dto;
final List<TagModel>? tags;
EventEntity(
{required this.id,
this.status,
required this.name,
required this.startDate,
required this.endDate,
this.completedBy,
this.completedNode,
this.hub,
this.addons,
this.additionalInfo,
// this.contractType = EventContractType.direct,
this.shifts,
this.cursor,
this.poNumber,
// this.contractNumber,
this.dto,
this.tags});
static EventEntity fromEventDto(EventModel event, {String? cursor}) {
var date = DateFormat('yyyy-MM-dd').parse(event.date);
var entity = EventEntity(
id: event.id,
status: event.status,
name: event.name,
startDate: date,
endDate: date,
completedBy: null,
completedNode: null,
hub: event.hub,
shifts: event.shifts
.map<ShiftEntity>((shift) => ShiftEntity.fromDto(shift, date))
.toList(),
addons: event.addons,
additionalInfo: event.additionalInfo,
// contractType: event.contractType,
poNumber: event.purchaseOrder,
cursor: cursor,
dto: event,
tags: event.tags);
entity.totalCost.value =
EventEntity.getTotalCost(entity); // Calculate total cost
try {
entity.shifts?.forEach((element) {
element.parentEvent = entity;
});
} catch (e) {
print(e);
}
return entity;
}
EventEntity copyWith(
{String? id,
EventStatus? status,
String? name,
DateTime? startDate,
DateTime? endDate,
BusinessMemberModel? completedBy,
String? completedNode,
HubModel? hub,
List<AddonModel>? addons,
String? additionalInfo,
EventContractType? contractType,
List<ShiftEntity>? shifts,
String? poNumber,
String? contractNumber,
String? cursor,
List<TagModel>? tags}) {
var entity = EventEntity(
id: id ?? this.id,
status: status ?? this.status,
name: name ?? this.name,
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
hub: hub ?? this.hub,
completedBy: completedBy ?? this.completedBy,
completedNode: completedNode ?? this.completedNode,
addons: addons ?? this.addons,
additionalInfo: additionalInfo ?? this.additionalInfo,
// contractType: contractType ?? this.contractType,
shifts: shifts ?? this.shifts,
poNumber: poNumber ?? this.poNumber,
// contractNumber: contractNumber ?? this.contractNumber,
cursor: cursor ?? this.cursor,
tags: tags ?? this.tags);
entity.totalCost.value =
EventEntity.getTotalCost(entity); // Calculate total cost
entity.shifts?.forEach((element) {
element.parentEvent = entity;
});
return entity;
}
static empty() {
var entity = EventEntity(
id: '',
name: '',
startDate: null,
endDate: null,
hub: null,
completedBy: null,
completedNode: null,
addons: [],
additionalInfo: '',
// contractType: EventContractType.direct,
shifts: [ShiftEntity.empty()],
poNumber: '',
// contractNumber: '',
cursor: null,
tags: []);
entity.shifts?.forEach((element) {
element.parentEvent = entity;
});
return entity;
}
static double getTotalCost(EventEntity event) {
foldPosition(previousValue, PositionEntity position) {
return (previousValue ?? 0) +
((((position.businessSkill?.price ?? 0) * (position.count ?? 0)) /
60) *
(position.endTime.difference(position.startTime).inMinutes));
}
foldRoles(previousValue, ShiftEntity shift) {
return previousValue + (shift.positions.fold(0.0, foldPosition));
}
var eventCost = event.shifts?.fold(0.0, foldRoles) ?? 0;
double totalCost = eventCost +
(event.addons?.fold(0.0,
(previousValue, addon) => previousValue + (addon.price ?? 0)) ??
0);
return totalCost;
}
@override
int get hashCode =>
id.hashCode ^
status.hashCode ^
name.hashCode ^
startDate.hashCode ^
endDate.hashCode ^
cursor.hashCode ^
totalCost.hashCode ^
hub.hashCode ^
addons.hashCode ^
completedBy.hashCode ^
completedNode.hashCode ^
additionalInfo.hashCode ^
shifts.hashCode ^
poNumber.hashCode ^
dto.hashCode ^
tags.hashCode;
@override
bool operator ==(Object other) {
return super == (other) &&
other is EventEntity &&
id == other.id &&
status == other.status &&
name == other.name &&
startDate == other.startDate &&
endDate == other.endDate &&
cursor == other.cursor &&
totalCost.value == other.totalCost.value &&
hub == other.hub &&
addons == other.addons &&
completedBy == other.completedBy &&
completedNode == other.completedNode &&
additionalInfo == other.additionalInfo &&
shifts == other.shifts &&
poNumber == other.poNumber &&
dto == other.dto &&
tags == other.tags;
}
}

View File

@@ -0,0 +1,221 @@
import 'package:intl/intl.dart';
import 'package:krow/core/data/models/shift/business_skill_model.dart';
import 'package:krow/core/data/models/shift/department_model.dart';
import 'package:krow/core/data/models/shift/event_shift_position_model.dart';
import 'package:krow/core/data/models/staff/pivot.dart';
import 'package:krow/core/entity/event_entity.dart';
import 'package:krow/core/entity/role_schedule_entity.dart';
import 'package:krow/core/entity/shift_entity.dart';
import 'package:krow/core/entity/staff_contact_entity.dart';
class PositionEntity {
final String id;
final DepartmentModel? department;
final int? count;
final int? breakDuration;
final DateTime startTime;
final DateTime endTime;
final double price;
final List<StaffContact> staffContacts;
final EventShiftPositionModel? dto;
final BusinessSkillModel? businessSkill;
final List<RoleScheduleEntity>? schedule;
ShiftEntity? parentShift;
PositionEntity(
{required this.id,
this.department,
this.count,
this.breakDuration = 15,
required this.startTime,
required this.endTime,
this.price = 0,
this.staffContacts = const [],
this.schedule,
this.businessSkill,
this.dto});
PositionEntity copyWith({
String? id,
String? name,
DepartmentModel? department,
int? count,
DateTime? startTime,
DateTime? endTime,
int? price,
int? breakDuration,
List<StaffContact>? staffContacts,
BusinessSkillModel? businessSkill,
EventShiftPositionModel? dto,
List<RoleScheduleEntity>? schedule,
}) {
if (schedule != null) {
schedule.sort((a, b) => a.dayIndex.compareTo(b.dayIndex));
}
var timeResult = _calcTimeSlot(startTime, endTime);
var start = timeResult.start;
var end = timeResult.end;
businessSkill = businessSkill ?? this.businessSkill;
var newEntity = PositionEntity(
id: id ?? this.id,
department: department ?? this.department,
count: count ?? this.count,
breakDuration: breakDuration ?? this.breakDuration,
startTime: start,
endTime: end,
price: _getTotalCost(
businessSkill?.price ?? 0, start, end, count ?? this.count ?? 0),
staffContacts: staffContacts ?? this.staffContacts,
businessSkill: businessSkill,
dto: dto ?? this.dto,
schedule: schedule ?? this.schedule,
);
newEntity.parentShift = parentShift;
int index =
parentShift?.positions.indexWhere((item) => item.id == this.id) ?? -1;
if (index != -1) {
parentShift?.positions[index] = newEntity;
}
var event = parentShift?.parentEvent;
event?.totalCost.value = EventEntity.getTotalCost(event);
return newEntity;
}
({DateTime start, DateTime end}) _calcTimeSlot(
DateTime? startTime, DateTime? endTime) {
if (startTime != null) {
startTime = startTime.copyWith(
day: this.endTime.day,
month: this.endTime.month,
year: this.endTime.year);
} else if (endTime != null) {
endTime = endTime.copyWith(
day: this.startTime.day,
month: this.startTime.month,
year: this.startTime.year);
}
const Duration minDuration = Duration(hours: 5);
DateTime? updatedStartTime = startTime ?? this.startTime;
DateTime? updatedEndTime = endTime ?? this.endTime;
if (startTime != null && this.startTime != startTime) {
Duration diff = updatedEndTime.difference(startTime);
if (diff < minDuration) {
updatedEndTime = startTime.add(minDuration);
}
} else if (endTime != null && this.endTime != endTime) {
Duration diff = endTime.difference(updatedStartTime);
if (diff < minDuration) {
updatedStartTime = endTime.subtract(minDuration);
}
}
if (updatedStartTime.day != updatedEndTime.day) {
final DateTime midnight = DateTime(
updatedStartTime.year,
updatedStartTime.month,
updatedStartTime.day,
0,
0,
);
updatedStartTime = midnight.subtract(const Duration(hours: 5));
updatedEndTime = midnight;
}
({DateTime start, DateTime end}) timeResult =
(start: updatedStartTime, end: updatedEndTime);
return timeResult;
}
static PositionEntity fromDto(EventShiftPositionModel model, DateTime date) {
final DateFormat timeFormat = DateFormat('HH:mm');
final DateTime start = timeFormat.parse(model.startTime);
final DateTime end = timeFormat.parse(model.endTime);
return PositionEntity(
id: model.id,
breakDuration: model.breakTime,
department: model.department,
dto: model,
startTime: DateTime(date.year, date.month, date.day, start.hour,
start.minute - start.minute % 10),
endTime: DateTime(date.year, date.month, date.day, end.hour,
end.minute - end.minute % 10),
count: model.count,
businessSkill: model.businessSkill,
price: _getTotalCost(
model.businessSkill.price ?? 0, start, end, model.count),
staffContacts: model.staff?.map((e) {
return StaffContact(
id: e.pivot?.id ?? '',
photoUrl: e.avatar ?? '',
firstName: e.firstName ?? '',
lastName: e.lastName ?? '',
phoneNumber: e.phone ?? '',
email: e.email ?? '',
rate: model.businessSkill.price ?? 0,
status: e.pivot?.status ?? PivotStatus.assigned,
startAt: e.pivot?.startAt ?? '',
endAt: e.pivot?.endAt ?? '',
//todo
isFavorite: false,
isBlackListed: false,
skillName: e.pivot?.position?.businessSkill.skill?.name ?? '',
)..rating.value = e.pivot?.rating?.rating ?? 0;
}).toList() ??
[],
);
}
static empty() {
return PositionEntity(
id: DateTime.now().millisecondsSinceEpoch.toString(),
startTime: DateTime.now().copyWith(hour: 9, minute: 0),
endTime: DateTime.now().copyWith(hour: 14, minute: 0),
);
}
static double _getTotalCost(
double ratePerHour, DateTime startTime, DateTime endTime, int count) {
final double hours = endTime.difference(startTime).inMinutes.abs() / 60.0;
final double price = hours * ratePerHour;
return price * count;
}
@override
// TODO: implement hashCode
int get hashCode =>
id.hashCode ^
department.hashCode ^
count.hashCode ^
breakDuration.hashCode ^
startTime.hashCode ^
endTime.hashCode ^
price.hashCode ^
staffContacts.hashCode ^
dto.hashCode ^
businessSkill.hashCode ^
schedule.hashCode;
@override
bool operator ==(Object other) {
return other is PositionEntity &&
id == other.id &&
department == other.department &&
count == other.count &&
breakDuration == other.breakDuration &&
startTime == other.startTime &&
endTime == other.endTime &&
price == other.price &&
staffContacts == other.staffContacts &&
dto == other.dto &&
businessSkill == other.businessSkill &&
schedule == other.schedule;
}
}

View File

@@ -0,0 +1,38 @@
import 'package:krow/core/sevices/time_slot_service.dart';
class RoleScheduleEntity {
final int dayIndex;
final DateTime startTime;
final DateTime endTime;
RoleScheduleEntity(
{required this.dayIndex, required this.startTime, required this.endTime});
RoleScheduleEntity copyWith({
int? dayIndex,
DateTime? startTime,
DateTime? endTime,
}) {
if (startTime != null) {
startTime =
startTime.copyWith(day: this.endTime.day, month: this.endTime.month);
} else if (endTime != null) {
endTime = endTime.copyWith(
day: this.startTime.day, month: this.startTime.month);
}
return RoleScheduleEntity(
dayIndex: dayIndex ?? this.dayIndex,
startTime: startTime ??
TimeSlotService.calcTime(
currentStartTime: this.startTime,
currentEndTime: this.endTime,
endTime: endTime ?? this.endTime),
endTime: endTime ??
TimeSlotService.calcTime(
currentStartTime: this.startTime,
currentEndTime: this.endTime,
startTime: startTime ?? this.startTime),
);
}
}

View File

@@ -0,0 +1,99 @@
import 'package:krow/core/data/models/event/business_member_model.dart';
import 'package:krow/core/data/models/event/full_address_model.dart';
import 'package:krow/core/data/models/shift/shift_model.dart';
import 'package:krow/core/entity/event_entity.dart';
import 'package:krow/core/entity/position_entity.dart';
class ShiftEntity {
final String id;
final FullAddress? fullAddress;
final List<BusinessMemberModel> managers;
final List<PositionEntity> positions;
EventEntity? parentEvent;
final ShiftModel? dto;
ShiftEntity({
required this.id,
this.fullAddress,
this.managers = const [],
this.positions = const [],
this.dto,
});
ShiftEntity copyWith({String? id,
FullAddress? fullAddress,
List<BusinessMemberModel>? managers,
List<PositionEntity>? positions,
ShiftModel? dto}) {
var newEntity = ShiftEntity(
id: id ?? this.id,
fullAddress: fullAddress ?? this.fullAddress,
managers: managers ?? this.managers,
positions: positions ?? this.positions,
dto: dto ?? this.dto,
);
newEntity.parentEvent = parentEvent;
int index =
parentEvent?.shifts?.indexWhere((item) => item.id == this.id) ?? -1;
if (index != -1) {
parentEvent?.shifts?[index] = newEntity;
}
return newEntity;
}
static fromDto(ShiftModel model, DateTime date) {
var entity = ShiftEntity(
id: model.id,
fullAddress: model.fullAddress,
managers: model.contacts ?? [],
positions: model.positions
?.map<PositionEntity>((e) => PositionEntity.fromDto(e, date))
.toList() ??
[],
dto: model,
);
for (var element in entity.positions) {
element.parentShift = entity;
for (var contact in element.staffContacts) {
contact.parentPosition = element;
}
}
return entity;
}
static empty() {
var entity = ShiftEntity(
id: DateTime
.now()
.millisecondsSinceEpoch
.toString(),
positions: [
PositionEntity.empty(),
]);
for (var element in entity.positions) {
element.parentShift = entity;
}
return entity;
}
@override
int get hashCode =>
id.hashCode ^ fullAddress.hashCode ^ managers.hashCode ^ positions
.hashCode ^ (dto?.hashCode ?? 0);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! ShiftEntity) return false;
return other.id == id &&
other.fullAddress == fullAddress &&
other.managers == managers &&
other.positions == positions &&
other.dto == dto;
}
}

View File

@@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:krow/core/data/models/staff/pivot.dart';
import 'package:krow/core/entity/position_entity.dart';
import 'package:krow/core/presentation/styles/theme.dart';
class StaffContact {
final String id;
PivotStatus status;
final String? photoUrl;
final String firstName;
final String lastName;
final String? phoneNumber;
final String? email;
final double rate;
final ValueNotifier<double> rating = ValueNotifier(0);
final bool isFavorite;
final bool isBlackListed;
final String startAt;
final String endAt;
final String breakIn;
final String breakOut;
final String skillName;
PositionEntity? parentPosition;
StaffContact(
{required this.id,
required this.firstName,
required this.lastName,
this.photoUrl,
this.status = PivotStatus.assigned,
this.phoneNumber,
this.email,
this.rate = 0,
this.isFavorite = false,
this.isBlackListed = false,
this.startAt = '',
this.endAt = '',
this.breakIn = '',
this.breakOut = '',
this.parentPosition,
this.skillName = ''});
StaffContact copyWith({
String? id,
PivotStatus? status,
String? photoUrl,
String? firstName,
String? lastName,
String? phoneNumber,
String? email,
double? rate,
double? rating,
bool? isFavorite,
bool? isBlackListed,
String? startAt,
String? endAt,
String? skillName,
PositionEntity? parentPosition,
}) {
return StaffContact(
id: id ?? this.id,
status: status ?? this.status,
photoUrl: photoUrl ?? this.photoUrl,
firstName: firstName ?? this.firstName,
lastName: lastName ?? this.lastName,
phoneNumber: phoneNumber ?? this.phoneNumber,
email: email ?? this.email,
rate: rate ?? this.rate,
isFavorite: isFavorite ?? this.isFavorite,
isBlackListed: isBlackListed ?? this.isBlackListed,
startAt: startAt ?? this.startAt,
endAt: endAt ?? this.endAt,
skillName: skillName ?? this.skillName,
parentPosition: parentPosition ?? this.parentPosition,
);
}
}
extension PivotStatusStatusX on PivotStatus {
Color getStatusTextColor() {
return switch (this) {
PivotStatus.assigned || PivotStatus.confirmed => AppColors.primaryBlue,
PivotStatus.ongoing || PivotStatus.completed => AppColors.statusSuccess,
PivotStatus.canceledByStaff ||
PivotStatus.canceledByBusiness ||
PivotStatus.canceledByAdmin ||
PivotStatus.requestedReplace ||
PivotStatus.noShowed ||
PivotStatus.declineByStaff =>
AppColors.statusError
};
}
Color getStatusBorderColor() {
return switch (this) {
PivotStatus.assigned || PivotStatus.confirmed => AppColors.tintBlue,
PivotStatus.ongoing || PivotStatus.completed => AppColors.tintGreen,
PivotStatus.canceledByStaff ||
PivotStatus.canceledByBusiness ||
PivotStatus.canceledByAdmin ||
PivotStatus.declineByStaff ||
PivotStatus.noShowed ||
PivotStatus.requestedReplace =>
AppColors.tintRed
};
}
}