feat: Add estimated weekly total label and refactor cost calculations for one-time and recurring orders
This commit is contained in:
@@ -417,6 +417,7 @@
|
||||
"positions": "POSITIONS",
|
||||
"total": "Total",
|
||||
"estimated_total": "Estimated Total",
|
||||
"estimated_weekly_total": "Estimated Weekly Total",
|
||||
"post_order": "Post Order"
|
||||
},
|
||||
"rapid_draft": {
|
||||
|
||||
@@ -417,6 +417,7 @@
|
||||
"positions": "POSICIONES",
|
||||
"total": "Total",
|
||||
"estimated_total": "Total Estimado",
|
||||
"estimated_weekly_total": "Total Semanal Estimado",
|
||||
"post_order": "Publicar Orden"
|
||||
},
|
||||
"rapid_draft": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../../utils/time_parsing_utils.dart';
|
||||
|
||||
enum OneTimeOrderStatus { initial, loading, success, failure }
|
||||
@@ -162,9 +163,8 @@ class OneTimeOrderState extends Equatable {
|
||||
buffer.write('$minutes min');
|
||||
}
|
||||
|
||||
if (first.lunchBreak != null &&
|
||||
first.lunchBreak != 'NO_BREAK' &&
|
||||
first.lunchBreak!.isNotEmpty) {
|
||||
if (first.lunchBreak != 'NO_BREAK' &&
|
||||
first.lunchBreak.isNotEmpty) {
|
||||
buffer.write(' (${first.lunchBreak} break)');
|
||||
}
|
||||
|
||||
|
||||
@@ -148,8 +148,8 @@ class PermanentOrderState extends Equatable {
|
||||
sum + (p.count * roleCostById(p.role)),
|
||||
);
|
||||
|
||||
/// Estimated total cost: sum of (count * costPerHour * hours) per position.
|
||||
double get estimatedTotal {
|
||||
/// Daily cost: sum of (count * costPerHour * hours) per position.
|
||||
double get dailyCost {
|
||||
double total = 0;
|
||||
for (final PermanentOrderPosition p in positions) {
|
||||
final double hours = parseHoursFromTimes(p.startTime, p.endTime);
|
||||
@@ -158,6 +158,12 @@ class PermanentOrderState extends Equatable {
|
||||
return total;
|
||||
}
|
||||
|
||||
/// Estimated weekly total cost for the permanent order.
|
||||
///
|
||||
/// Calculated as [dailyCost] multiplied by the number of selected
|
||||
/// [permanentDays] per week.
|
||||
double get estimatedTotal => dailyCost * permanentDays.length;
|
||||
|
||||
/// Formatted repeat days (e.g. "Mon, Tue, Wed").
|
||||
String get formattedRepeatDays => permanentDays.map(
|
||||
(String day) => day[0] + day.substring(1).toLowerCase(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../utils/schedule_utils.dart';
|
||||
import '../../utils/time_parsing_utils.dart';
|
||||
|
||||
enum RecurringOrderStatus { initial, loading, success, failure }
|
||||
@@ -155,8 +156,8 @@ class RecurringOrderState extends Equatable {
|
||||
sum + (p.count * roleCostById(p.role)),
|
||||
);
|
||||
|
||||
/// Estimated total cost: sum of (count * costPerHour * hours) per position.
|
||||
double get estimatedTotal {
|
||||
/// Daily cost: sum of (count * costPerHour * hours) per position.
|
||||
double get dailyCost {
|
||||
double total = 0;
|
||||
for (final RecurringOrderPosition p in positions) {
|
||||
final double hours = parseHoursFromTimes(p.startTime, p.endTime);
|
||||
@@ -165,6 +166,31 @@ class RecurringOrderState extends Equatable {
|
||||
return total;
|
||||
}
|
||||
|
||||
/// Total number of working days between [startDate] and [endDate]
|
||||
/// (inclusive) that match the selected [recurringDays].
|
||||
///
|
||||
/// Iterates day-by-day and counts each date whose weekday label
|
||||
/// (e.g. "MON", "TUE") appears in [recurringDays].
|
||||
int get totalWorkingDays {
|
||||
final Set<String> selectedSet = recurringDays.toSet();
|
||||
int count = 0;
|
||||
for (
|
||||
DateTime day = startDate;
|
||||
!day.isAfter(endDate);
|
||||
day = day.add(const Duration(days: 1))
|
||||
) {
|
||||
if (selectedSet.contains(weekdayLabel(day))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/// Estimated total cost for the entire recurring order period.
|
||||
///
|
||||
/// Calculated as [dailyCost] multiplied by [totalWorkingDays].
|
||||
double get estimatedTotal => dailyCost * totalWorkingDays;
|
||||
|
||||
/// Formatted repeat days (e.g. "Mon, Tue, Wed").
|
||||
String get formattedRepeatDays => recurringDays.map(
|
||||
(String day) => day[0] + day.substring(1).toLowerCase(),
|
||||
|
||||
@@ -25,6 +25,7 @@ class ReviewOrderArguments {
|
||||
this.scheduleStartDate,
|
||||
this.scheduleEndDate,
|
||||
this.scheduleRepeatDays,
|
||||
this.totalLabel,
|
||||
});
|
||||
|
||||
final ReviewOrderType orderType;
|
||||
@@ -45,4 +46,7 @@ class ReviewOrderArguments {
|
||||
final String? scheduleStartDate;
|
||||
final String? scheduleEndDate;
|
||||
final String? scheduleRepeatDays;
|
||||
|
||||
/// Optional label override for the total banner (e.g. "Estimated Weekly Total").
|
||||
final String? totalLabel;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
@@ -143,6 +144,7 @@ class PermanentOrderPage extends StatelessWidget {
|
||||
estimatedTotal: state.estimatedTotal,
|
||||
scheduleStartDate: DateFormat.yMMMd().format(state.startDate),
|
||||
scheduleRepeatDays: state.formattedRepeatDays,
|
||||
totalLabel: t.client_create_order.review.estimated_weekly_total,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ class ReviewOrderPage extends StatelessWidget {
|
||||
totalWorkers: args.totalWorkers,
|
||||
totalCostPerHour: args.totalCostPerHour,
|
||||
estimatedTotal: args.estimatedTotal,
|
||||
totalLabel: args.totalLabel,
|
||||
showEditButtons: showEdit,
|
||||
onEditBasics: showEdit ? () => Modular.to.popSafe() : null,
|
||||
onEditSchedule: showEdit ? () => Modular.to.popSafe() : null,
|
||||
|
||||
@@ -5,14 +5,20 @@ import 'package:flutter/material.dart';
|
||||
/// A highlighted banner displaying the estimated total cost.
|
||||
///
|
||||
/// Uses the primary inverse background color with a bold price display.
|
||||
/// An optional [label] can override the default "Estimated Total" text.
|
||||
class ReviewOrderTotalBanner extends StatelessWidget {
|
||||
const ReviewOrderTotalBanner({
|
||||
required this.totalAmount,
|
||||
this.label,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The total monetary amount to display.
|
||||
final double totalAmount;
|
||||
|
||||
/// Optional label override. Defaults to the localized "Estimated Total".
|
||||
final String? label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -28,7 +34,7 @@ class ReviewOrderTotalBanner extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.client_create_order.review.estimated_total,
|
||||
label ?? t.client_create_order.review.estimated_total,
|
||||
style: UiTypography.body2m,
|
||||
),
|
||||
Text(
|
||||
|
||||
@@ -32,6 +32,7 @@ class ReviewOrderView extends StatelessWidget {
|
||||
this.onEditSchedule,
|
||||
this.onEditPositions,
|
||||
this.submitLabel,
|
||||
this.totalLabel,
|
||||
this.isLoading = false,
|
||||
super.key,
|
||||
});
|
||||
@@ -51,6 +52,10 @@ class ReviewOrderView extends StatelessWidget {
|
||||
final VoidCallback? onEditSchedule;
|
||||
final VoidCallback? onEditPositions;
|
||||
final String? submitLabel;
|
||||
|
||||
/// Optional label override for the total banner. When `null`, the default
|
||||
/// localized "Estimated Total" text is used.
|
||||
final String? totalLabel;
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
@@ -92,7 +97,10 @@ class ReviewOrderView extends StatelessWidget {
|
||||
onEdit: showEditButtons ? onEditPositions : null,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
ReviewOrderTotalBanner(totalAmount: estimatedTotal),
|
||||
ReviewOrderTotalBanner(
|
||||
totalAmount: estimatedTotal,
|
||||
label: totalLabel,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user