refactor: update routing and logic for steward features, including route imports and path adjustments

This commit is contained in:
2025-12-08 11:09:24 +03:30
parent 2f4edc1b6e
commit 890be0ded6
56 changed files with 519 additions and 305 deletions

View File

@@ -0,0 +1,36 @@
import 'package:flutter/services.dart';
import 'package:rasadyar_core/core.dart';
class BuyLogic extends GetxController {
List<String> routesName = ['خرید'];
DateTime? _lastBackPressed;
@override
void onReady() {
fLog('BuyLogic onReady');
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
void onPopScopTaped() async {
final now = DateTime.now();
if (_lastBackPressed == null || now.difference(_lastBackPressed!) > Duration(seconds: 2)) {
_lastBackPressed = now;
Get.snackbar(
'خروج از برنامه',
'برای خروج دوباره بازگشت را بزنید',
snackPosition: SnackPosition.TOP,
duration: Duration(seconds: 2),
backgroundColor: AppColor.warning,
);
} else {
await SystemNavigator.pop();
}
}
}

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyPage extends GetView<BuyLogic> {
const BuyPage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
routes: controller.routesName,
isBase: true,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 21.w,
children: [
GlassMorphismCardIcon(
title: 'خرید داخل استان',
vecIcon: Assets.vec.map1Svg.path,
gradient: LinearGradient(
colors: [Color(0xFF00E096), Color(0xFF007D5E)],
stops: [0.0, 0.95],
begin: AlignmentGeometry.topLeft,
end: AlignmentGeometry.bottomRight,
),
onTap: () {
Get.toNamed(
StewardRoutes.buysInProvinceSteward,
id: stewardFirstKey,
);
},
),
GlassMorphismCardIcon(
title: 'خرید خارج استان',
vecIcon: Assets.vec.buyOutProvinceSvg.path,
gradient: LinearGradient(
colors: [Color(0xFF00E096), Color(0xFF007D5E)],
stops: [0.0, 0.95],
begin: AlignmentGeometry.topLeft,
end: AlignmentGeometry.bottomRight,
),
onTap: () {
Get.toNamed(
StewardRoutes.buysOutOfProvinceSteward,
id: stewardFirstKey,
);
},
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,89 @@
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province_all/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province_waiting/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class BuyInProvinceLogic extends GetxController {
RxList<String> routesName = RxList();
RxList<int> isExpandedList = <int>[].obs;
RxnString searchedValue = RxnString();
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
BuyLogic get buyLogic => Get.find<BuyLogic>();
RxInt selectedSegmentIndex = 0.obs;
BuyInProvinceAllLogic buyAllLogic = Get.find<BuyInProvinceAllLogic>();
BuyInProvinceWaitingLogic buyWaitingLogic = Get.find<BuyInProvinceWaitingLogic>();
@override
void onInit() {
super.onInit();
routesName.value = [...buyLogic.routesName, 'داخل استان'].toList();
routesName.add(selectedSegmentIndex.value == 0 ? 'در انتظار' : 'کل خریدها');
ever(selectedSegmentIndex, (callback) {
routesName.removeLast();
routesName.add(callback == 0 ? 'در انتظار' : 'کل خریدها');
});
ever(fromDateFilter, (callback) => _setFromDateFilter(callback));
ever(toDateFilter, (callback) => _setToDateFilter(callback));
}
@override
void onReady() {
fLog('BuyInProvinceLogic onReady');
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
void _setFromDateFilter(Jalali jalali) {
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.fromDateFilter.value = fromDateFilter.value;
} else {
buyAllLogic.fromDateFilter.value = fromDateFilter.value;
}
}
void _setToDateFilter(Jalali jalali) {
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.toDateFilter.value = fromDateFilter.value;
} else {
buyAllLogic.toDateFilter.value = fromDateFilter.value;
}
}
Future<void> submitFilter() async {
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.getWaitingArrivals();
} else {
buyAllLogic.getAllArrivals();
}
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
final isWaiting = selectedSegmentIndex.value == 0;
if (isWaiting) {
buyWaitingLogic.searchedValue.value = searchedValue.value;
} else {
buyAllLogic.searchedValue.value = searchedValue.value;
}
}
Future<void> onRefresh() async {
await rootLogic.onRefresh();
await Future.wait([buyWaitingLogic.getWaitingArrivals(), buyAllLogic.getAllArrivals()]);
}
}

View File

@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province_all/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province_waiting/view.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyInProvincePage extends GetView<BuyInProvinceLogic> {
const BuyInProvincePage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
routesWidget: ContainerBreadcrumb(rxRoutes: controller.routesName),
onSearchChanged: (data) => controller.setSearchValue(data),
hasBack: true,
backId: stewardFirstKey,
onFilterTap: () {
Get.bottomSheet(filterBottomSheet());
},
onRefresh: controller.onRefresh,
child: Column(
children: [
inventoryWidget(controller.rootLogic),
segmentWidget(),
ObxValue((index) {
return Expanded(
child: index.value == 0 ? BuyInProvinceWaitingPage() : BuyInProvinceAllPage(),
);
}, controller.selectedSegmentIndex),
],
)
);
}
Padding segmentWidget() {
return Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
child: Row(
children: [
Expanded(
child: RSegment(
children: ['در انتظار', 'کل خریدها'],
selectedIndex: 0,
borderColor: const Color(0xFFB4B4B4),
selectedBorderColor: AppColor.blueNormal,
selectedBackgroundColor: AppColor.blueLight,
onSegmentSelected: (index) => controller.selectedSegmentIndex.value = index,
backgroundColor: AppColor.whiteGreyNormal,
),
),
],
),
);
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 200,
child: Column(
spacing: 16,
children: [
Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Row(
spacing: 8,
children: [
Expanded(
child: dateFilterWidget(
date: controller.fromDateFilter,
onChanged: (jalali) => controller.fromDateFilter.value = jalali,
),
),
Expanded(
child: dateFilterWidget(
isFrom: false,
date: controller.toDateFilter,
onChanged: (jalali) => controller.toDateFilter.value = jalali,
),
),
],
),
RElevated(
text: 'اعمال فیلتر',
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
onPressed: () {
controller.submitFilter();
Get.back();
},
height: 40,
),
SizedBox(height: 16),
],
),
);
}
}

View File

@@ -0,0 +1,180 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/steward_allocation/steward_allocation_request.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class BuyInProvinceAllLogic extends GetxController {
RxInt isExpandedListIndex = (-1).obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxMap<String, bool> isLoadingConfirmMap = RxMap();
final RxBool isLoadingMoreAllocationsMade = false.obs;
RxInt currentPage = 1.obs;
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
Rx<Resource<PaginationModel<WaitingArrivalModel>>> allProduct =
Resource<PaginationModel<WaitingArrivalModel>>.loading().obs;
TextEditingController weightController = TextEditingController();
TextEditingController countController = TextEditingController();
TextEditingController lossController = TextEditingController();
TextEditingController approvedWithOtpController = TextEditingController();
RxBool approvedWithOtpCode = false.obs;
@override
void onInit() {
super.onInit();
getAllArrivals();
}
@override
void onReady() {
debounce(searchedValue, (callback) => getAllArrivals(), time: Duration(milliseconds: 2000));
super.onReady();
ever(approvedWithOtpCode, (callback) {
if (callback == false) {
approvedWithOtpController.clear();
}
});
}
Future<void> getAllArrivals([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
allProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1;
}
safeCall(
call: () async => await rootLogic.chickenRepository.getWaitingArrivals(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
queryParams: {'type': 'all'},
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 200));
if ((res?.count ?? 0) == 0) {
allProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.empty();
} else {
allProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.success(
PaginationModel<WaitingArrivalModel>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [...(allProduct.value.data?.results ?? []), ...(res?.results ?? [])],
),
);
}
},
);
}
Future<void> acceptEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'accepted',
receiverRealNumberOfCarcasses: model.realNumberOfCarcasses ?? 0,
receiverRealWeightOfCarcasses: model.realWeightOfCarcasses?.toInt() ?? 0,
registrationCode: approvedWithOtpCode.value
? int.parse(approvedWithOtpController.text)
: null,
weightLossOfCarcasses: model.weightLossOfCarcasses?.toInt() ?? 0,
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onSuccess: (result) {
getAllArrivals();
rootLogic.onRefresh();
clearApprovedController();
toggleExpansion();
Get.back();
},
);
}
Future<void> denyEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'rejected',
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onError: (error, stackTrace) {
eLog(error);
},
onSuccess: (result) {
getAllArrivals();
rootLogic.onRefresh();
},
);
}
String getVecPathItem(String? item) {
switch (item) {
case 'pending':
return Assets.vec.timerSvg.path;
case 'accepted':
return Assets.vec.checkSquareSvg.path;
case 'rejected':
return Assets.vec.closeCircleSvg.path;
default:
return Assets.vec.timerSvg.path;
}
}
void clearApprovedController() {
weightController.clear();
countController.clear();
lossController.clear();
approvedWithOtpController.clear();
approvedWithOtpCode.value = false;
}
void toggleExpansion({int? index}) {
if (isExpandedListIndex.value == index || index == null) {
isExpandedListIndex.value = -1;
} else {
isExpandedListIndex.value = index;
}
}
Color getLabelColor(String? receiverState) {
if (receiverState == 'pending') {
return AppColor.yellowNormal2;
}
return AppColor.mediumGreyDarkHover;
}
}

View File

@@ -0,0 +1,381 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyInProvinceAllPage extends GetView<BuyInProvinceAllLogic> {
const BuyInProvinceAllPage({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.value == index,
onTap: () => controller.toggleExpansion(index: index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item),
labelColor: getLabelColor(item.receiverState),
labelIcon: controller.getVecPathItem(item.receiverState),
labelIconColor: controller.getLabelColor(item.receiverState),
);
}, controller.isExpandedListIndex);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
onLoadMore: () async => controller.getAllArrivals(true),
);
}, controller.allProduct),
);
}
Row itemListWidget(WaitingArrivalModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 3,
children: [
Text(
item.toSteward?.user?.fullname ?? 'N/A',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
spacing: 3,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
item.weightOfCarcasses?.separatedByCommaFa.addKg ?? 'N/A',
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
style: AppFonts.yekan12Bold.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
item.toSteward?.guildsName ?? 'N/Aaq',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 1,
child: Assets.vec.scanSvg.svg(
width: 32.w,
height: 32.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
],
);
}
Container itemListExpandedWidget(WaitingArrivalModel item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.toSteward?.user?.fullname ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
Spacer(),
Text(
item.receiverState?.faItem ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan10.copyWith(color: AppColor.darkGreyDark),
),
SizedBox(width: 7),
SvgGenImage.vec(controller.getVecPathItem(item.receiverState)).svg(
width: 16.w,
height: 16.h,
colorFilter: ColorFilter.mode(AppColor.darkGreyDark, BlendMode.srcIn),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'N/A',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.d} ${item.date?.toJalali.formatter.mN ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(title: 'مشخصات فروشنده', value: item.toSteward?.user?.fullname ?? 'N/A'),
buildRow(
title: 'تلفن فروشنده',
value: item.toSteward?.user?.mobile ?? 'N/A',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(title: 'نوع تخصیص', value: item.allocationType?.faAllocationType ?? 'N/A'),
buildRow(title: ' سهمیه', value: item.quota?.faTitle ?? 'N/A'),
buildRow(title: 'محصول', value: item.product?.name ?? 'N/A'),
buildRow(
title: 'وزن خریداری شده',
value: item.weightOfCarcasses?.separatedByCommaFa ?? 'N/A',
valueLabel: 'کیلوگرم',
),
buildRow(
title: 'قیمت هر کیلوگرم',
titleLabel: (item.approvedPriceStatus ?? false) ? 'مصوب' : 'آزاد',
titleLabelStyle: AppFonts.yekan14Bold.copyWith(color: AppColor.greenNormal),
value: item.amount?.separatedByCommaFa ?? 'N/A',
valueLabel: 'ریال',
),
buildRow(
title: 'قیمت کل',
value: item.totalAmount?.separatedByCommaFa ?? 'N/A',
valueLabel: 'ریال',
),
Visibility(
visible: item.receiverState == 'pending',
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
ObxValue((data) {
return RElevated(
text: 'تایید',
width: 150.w,
height: 40.h,
isLoading: data[item.key!] ?? false,
onPressed: () async {
await Get.bottomSheet(
conformationBottomSheet(item),
isScrollControlled: true,
).then((value) {
Get.back();
controller.clearApprovedController();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
);
}, controller.isLoadingConfirmMap),
ROutlinedElevated(
text: 'رد',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildWarningDialog(
title: 'اخطار',
middleText: 'آیا از رد شدن این مورد اطمینان دارید؟',
onConfirm: () => controller.denyEntries(item),
onRefresh: () => controller.getAllArrivals(),
);
},
borderColor: AppColor.redNormal,
),
],
),
),
],
),
);
}
Color getLabelColor(String? item) {
switch (item) {
case 'pending':
return AppColor.blueLight;
case 'accepted':
return AppColor.greenLightHover;
case 'rejected':
return AppColor.redLightHover;
default:
return AppColor.blueLight;
}
}
Widget conformationBottomSheet(WaitingArrivalModel item) {
controller.weightController.text = item.weightOfCarcasses.separatedByComma;
return BaseBottomSheet(
height: 430.h,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RTextField(
label: 'وزن(کیلوگرم)',
controller: controller.weightController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
),
SizedBox(height: 16.h),
RTextField(
label: 'حجم(قطعه)',
controller: controller.countController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
),
SizedBox(height: 16.h),
RTextField(
label: 'افت وزن(کیلوگرم)',
controller: controller.lossController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
),
SizedBox(height: 16.h),
Text('تایید ', style: AppFonts.yekan16Bold.copyWith(color: AppColor.textColor)),
ObxValue((data) {
return Column(
children: [
RadioGroup(
groupValue: data.value,
onChanged: (value) {
controller.approvedWithOtpCode.value = value ?? false;
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
radioRow(
value: true,
label: ' با کد احراز',
onTap: () {
controller.approvedWithOtpCode.value = true;
},
),
radioRow(
value: false,
label: 'بدون کد احراز',
onTap: () {
controller.approvedWithOtpCode.value = false;
},
),
],
),
),
Visibility(
child: RTextField(
controller: controller.approvedWithOtpController,
label: 'کد احراز',
keyboardType: TextInputType.number,
),
visible: data.value,
),
],
);
}, controller.approvedWithOtpCode),
SizedBox(height: 30.h),
ObxValue((data) {
return RElevated(
enabled: controller.approvedWithOtpCode.value
? controller.approvedWithOtpController.text.isNotEmpty
: true,
text: 'تایید',
onPressed: () async {
await controller.acceptEntries(item);
},
isFullWidth: true,
);
}, controller.approvedWithOtpCode),
SizedBox(height: 20.h),
],
),
);
}
Widget radioRow({
required bool value,
required String label,
TextStyle? textStyle,
GestureTapCallback? onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Row(
children: [
Radio(value: value),
Text(label, style: textStyle ?? AppFonts.yekan16.copyWith(color: AppColor.textColor)),
],
),
);
}
}

View File

@@ -0,0 +1,189 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/steward_allocation/steward_allocation_request.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class BuyInProvinceWaitingLogic extends GetxController {
RxInt isExpandedListIndex = (-1).obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxMap<String, bool> isLoadingConfirmMap = RxMap();
Rx<Color> bgConfirmAllColor = AppColor.blueNormal.obs;
RxInt currentPage = 1.obs;
final RxBool isLoadingMoreAllocationsMade = false.obs;
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
RxBool isButtonConfirm = false.obs;
Rx<Resource<PaginationModel<WaitingArrivalModel>>> waitingProduct =
Resource<PaginationModel<WaitingArrivalModel>>.loading().obs;
TextEditingController weightController = TextEditingController();
TextEditingController countController = TextEditingController();
TextEditingController lossController = TextEditingController();
TextEditingController approvedWithOtpController = TextEditingController();
RxBool approvedWithOtpCode = false.obs;
@override
void onInit() {
super.onInit();
debounce(
searchedValue,
(callback) => getWaitingArrivals(),
time: Duration(milliseconds: timeDebounce),
);
}
@override
void onReady() {
super.onReady();
getWaitingArrivals();
approvedWithOtpController.addListener(() {
isButtonConfirm.value = approvedWithOtpController.text.trim().length > 4;
});
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
}
Future<void> getWaitingArrivals([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
waitingProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1;
}
safeCall(
call: () async => await rootLogic.chickenRepository.getWaitingArrivals(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
queryParams: {'type': 'not_entered'},
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 200));
if ((res?.count ?? 0) == 0) {
waitingProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.empty();
} else {
waitingProduct.value = Resource<PaginationModel<WaitingArrivalModel>>.success(
PaginationModel<WaitingArrivalModel>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [...(waitingProduct.value.data?.results ?? []), ...(res?.results ?? [])],
),
);
flashingFabBgColor();
}
},
);
}
Future<void> acceptEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'accepted',
receiverRealNumberOfCarcasses: model.realNumberOfCarcasses ?? 0,
receiverRealWeightOfCarcasses: model.realWeightOfCarcasses?.toInt() ?? 0,
registrationCode: approvedWithOtpCode.value
? int.parse(approvedWithOtpController.text.trim())
: null,
weightLossOfCarcasses: model.weightLossOfCarcasses?.toInt() ?? 0,
stewardCheckAllocation: true,
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
showError: true,
showSuccess: true,
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onError: (error, stackTrace) {
eLog(error);
},
onSuccess: (result) {
getWaitingArrivals();
rootLogic.onRefresh();
clearApprovedController();
toggleExpansion();
Future.delayed(Duration(seconds: 3), () {
Get.back();
Get.back();
});
},
);
}
Future<void> denyEntries(WaitingArrivalModel model) async {
var request = StewardAllocationRequest(
allocationKey: model.key,
checkAllocation: true,
state: 'rejected',
).toJson();
request.removeWhere((key, value) => value == null);
safeCall(
call: () async => await rootLogic.chickenRepository.setSateForArrivals(
token: rootLogic.tokenService.accessToken.value!,
request: request,
),
onError: (error, stackTrace) {
eLog(error);
},
onSuccess: (result) {
getWaitingArrivals();
rootLogic.onRefresh();
},
);
}
void flashingFabBgColor() {
Timer.periodic(Duration(seconds: 2), (timer) {
if (bgConfirmAllColor.value == AppColor.blueNormal) {
bgConfirmAllColor.value = AppColor.blueLightHover;
} else {
bgConfirmAllColor.value = AppColor.blueNormal;
}
});
}
void clearApprovedController() {
weightController.clear();
countController.clear();
lossController.clear();
approvedWithOtpController.clear();
approvedWithOtpCode.value = false;
}
void toggleExpansion({int? index}) {
if (isExpandedListIndex.value == index || index == null) {
isExpandedListIndex.value = -1;
} else {
isExpandedListIndex.value = index;
}
}
}

View File

@@ -0,0 +1,373 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyInProvinceWaitingPage extends GetView<BuyInProvinceWaitingLogic> {
const BuyInProvinceWaitingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: controller.isExpandedListIndex.value == index,
onTap: () => controller.toggleExpansion(index: index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.timerSvg.path,
labelIconColor: AppColor.yellowNormal2,
);
}, controller.isExpandedListIndex);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
onLoadMore: () async => controller.getWaitingArrivals(true),
);
}, controller.waitingProduct),
),
);
}
Row itemListWidget(WaitingArrivalModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 3,
children: [
Text(
'${controller.rootLogic.isKillHouse(item) ? 'کشتارگاه' : 'مباشر'} ${controller.rootLogic.isKillHouse(item) ? item.killHouse?.name : item.steward?.user?.fullname} ',
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
spacing: 3,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
item.weightOfCarcasses?.separatedByCommaFa.addKg ?? 'ندارد',
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
style: AppFonts.yekan12Bold.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
'${item.amount?.separatedByCommaFa} ریال',
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 3,
children: [
Text(
(item.approvedPriceStatus ?? false) ? 'دولتی' : 'آزاد',
style: AppFonts.yekan12Bold.copyWith(
color: (item.approvedPriceStatus ?? false)
? AppColor.blueNormal
: AppColor.greenNormal,
),
),
],
),
),
],
);
}
Container itemListExpandedWidget(WaitingArrivalModel item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'${controller.rootLogic.isKillHouse(item) ? item.killHouse?.name : item.steward?.user?.fullname} ',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
Spacer(),
Text(
'در انتظار',
textAlign: TextAlign.center,
style: AppFonts.yekan10.copyWith(color: AppColor.darkGreyDark),
),
SizedBox(width: 7),
Assets.vec.clockSvg.svg(width: 16.w, height: 16.h),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'ندارد',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.d} ${item.date?.toJalali.formatter.mN ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(
title: 'مشخصات فروشنده',
value:
'${controller.rootLogic.isKillHouse(item) ? item.killHouse?.killHouseOperator?.user?.fullname : item.steward?.user?.fullname} ',
),
buildRow(
title: 'تلفن فروشنده',
value:
'${controller.rootLogic.isKillHouse(item) ? item.killHouse?.killHouseOperator?.user?.mobile : item.steward?.user?.mobile} ',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(title: 'نوع تخصیص', value: item.allocationType?.faAllocationType ?? 'ندارد'),
buildRow(title: ' سهمیه', value: item.quota?.faTitle ?? 'ندارد'),
buildRow(title: 'نوع فروش', value: item.saleType == "governmental" ? 'دولتی' : 'آزاد'),
buildRow(title: 'محصول', value: item.product?.name ?? 'ندارد'),
buildRow(
title: 'تاریخ تولید گوشت',
value: item.productionDate?.toJalali.toJalaliDateTime() ?? 'ندارد',
),
buildRow(
title: 'وزن خریداری شده',
value: '${item.weightOfCarcasses?.separatedByCommaFa} کیلوگرم',
),
buildRow(
title: 'قیمت هر کیلوگرم',
titleLabel: (item.approvedPriceStatus ?? false) ? 'دولتی' : 'آزاد',
titleLabelStyle: AppFonts.yekan14Bold.copyWith(color: AppColor.greenNormal),
value: '${item.amount?.separatedByCommaFa} ریال',
),
buildRow(title: 'قیمت کل', value: '${item.totalAmount?.separatedByCommaFa} ریال'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
ObxValue((data) {
return RElevated(
text: 'تایید',
width: 150.w,
height: 40.h,
isLoading: data[item.key!] ?? false,
onPressed: () async {
await Get.bottomSheet(conformationBottomSheet(item), isScrollControlled: true);
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
);
}, controller.isLoadingConfirmMap),
ROutlinedElevated(
text: 'رد',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildWarningDialog(
title: 'اخطار',
middleText: 'آیا از رد شدن این مورد اطمینان دارید؟',
onConfirm: () => controller.denyEntries(item),
onRefresh: () => controller.getWaitingArrivals(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
Widget conformationBottomSheet(WaitingArrivalModel item) {
controller.weightController.text = item.weightOfCarcasses.separatedByComma;
return BaseBottomSheet(
height: 450.h,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10),
RTextField(
label: 'وزن(کیلوگرم)',
controller: controller.weightController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
),
SizedBox(height: 16.h),
RTextField(
label: 'حجم(قطعه)',
controller: controller.countController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
),
SizedBox(height: 16.h),
RTextField(
label: 'افت وزن(کیلوگرم)',
controller: controller.lossController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
),
SizedBox(height: 16.h),
Text('تایید ', style: AppFonts.yekan16Bold.copyWith(color: AppColor.textColor)),
ObxValue((data) {
return Column(
children: [
RadioGroup(
groupValue: data.value,
onChanged: (value) {
controller.approvedWithOtpCode.value = value ?? false;
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
radioRow(
value: true,
label: ' با کد احراز',
onTap: () {
controller.approvedWithOtpCode.value = true;
},
),
radioRow(
value: false,
label: 'بدون کد احراز',
onTap: () {
controller.approvedWithOtpCode.value = false;
},
),
],
),
),
Visibility(
child: RTextField(
controller: controller.approvedWithOtpController,
label: 'کد احراز',
keyboardType: TextInputType.number,
maxLength: 5,
),
visible: data.value,
),
],
);
}, controller.approvedWithOtpCode),
SizedBox(height: 30.h),
Obx(() {
return RElevated(
enabled: controller.approvedWithOtpCode.value
? controller.isButtonConfirm.value
: true,
text: 'تایید',
onPressed: () async {
await controller.acceptEntries(item);
},
isFullWidth: true,
);
}),
SizedBox(height: 20.h),
],
),
);
}
Widget radioRow({
required bool value,
required String label,
TextStyle? textStyle,
GestureTapCallback? onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Row(
children: [
Radio(value: value),
Text(label, style: textStyle ?? AppFonts.yekan16.copyWith(color: AppColor.textColor)),
],
),
);
}
}

View File

@@ -0,0 +1,303 @@
import 'package:flutter/cupertino.dart';
import 'package:rasadyar_chicken/data/models/request/create_steward_free_bar/create_steward_free_bar.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_bar/steward_free_bar.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class BuyOutOfProvinceLogic extends GetxController {
late List<String> routesName;
RxBool isSubmitButtonEnabled = false.obs;
RxInt expandedListIndex = (-1).obs;
final RxInt currentPage = 1.obs;
final RxBool isLoadingMoreAllocationsMade = false.obs;
final RxBool isOnLoadingSubmitOrEdit = false.obs;
//TODO add this to Di
ImagePicker imagePicker = ImagePicker();
Rx<Resource<PaginationModel<StewardFreeBar>>> purchaseOutOfProvinceList =
Resource<PaginationModel<StewardFreeBar>>.loading().obs;
Rxn<ProductModel> selectedProduct = Rxn();
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
Rxn<XFile> selectedImage = Rxn<XFile>();
final RxnString _base64Image = RxnString();
RxnString editImageUrl = RxnString();
RxnString editFreeBarKey = RxnString();
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
BuyLogic buyLogic = Get.find<BuyLogic>();
SaleLogic outOfTheProvinceLogic = Get.find<SaleLogic>();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController sellerNameController = TextEditingController();
TextEditingController sellerPhoneController = TextEditingController();
TextEditingController carcassWeightController = TextEditingController();
TextEditingController carcassCountController = TextEditingController();
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
@override
void onInit() {
super.onInit();
routesName = [...buyLogic.routesName, 'خارج استان'].toList();
}
@override
void onReady() {
super.onReady();
getStewardPurchaseOutOfProvince();
selectedProvince.listen((p0) => getCites());
selectedProduct.value = rootLogic.rolesProductsModel.first;
setupListeners();
debounce(
searchedValue,
(callback) => getStewardPurchaseOutOfProvince(),
time: Duration(milliseconds: timeDebounce),
);
}
@override
void onClose() {
sellerNameController.dispose();
sellerPhoneController.dispose();
carcassWeightController.dispose();
super.onClose();
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
}
Future<void> getStewardPurchaseOutOfProvince([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
purchaseOutOfProvinceList.value = Resource<PaginationModel<StewardFreeBar>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1; // Reset to first page if search value is set
}
await safeCall(
call: () => rootLogic.chickenRepository.getStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 500));
if ((res?.count ?? 0) == 0) {
purchaseOutOfProvinceList.value = Resource<PaginationModel<StewardFreeBar>>.empty();
} else {
purchaseOutOfProvinceList.value = Resource<PaginationModel<StewardFreeBar>>.success(
PaginationModel<StewardFreeBar>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [
...(purchaseOutOfProvinceList.value.data?.results ?? []),
...(res?.results ?? []),
],
),
);
}
},
);
}
Future<void> getCites() async {
await safeCall(
call: () =>
rootLogic.chickenRepository.getCity(provinceName: selectedProvince.value?.name ?? ''),
onSuccess: (result) {
if (result != null && result.isNotEmpty) {
cites.value = result;
}
},
);
}
void setupListeners() {
sellerNameController.addListener(() {
checkFormValid();
if (!isSubmitButtonEnabled.value) {
isSubmitButtonEnabled.value = !isSubmitButtonEnabled.value;
}
});
sellerPhoneController.addListener(checkFormValid);
carcassWeightController.addListener(checkFormValid);
carcassCountController.addListener(checkFormValid);
ever(selectedProvince, (_) => checkFormValid());
ever(selectedCity, (_) => checkFormValid());
ever(selectedProduct, (_) => checkFormValid());
ever(selectedImage, (data) async {
checkFormValid();
if (data?.path != null) {
_base64Image.value = await convertImageToBase64(data!.path);
}
});
}
void checkFormValid() {
isSubmitButtonEnabled.value =
sellerNameController.text.isNotEmpty &&
sellerPhoneController.text.isNotEmpty &&
carcassWeightController.text.isNotEmpty &&
carcassCountController.text.isNotEmpty &&
selectedProvince.value != null &&
selectedCity.value != null &&
selectedProduct.value != null &&
(selectedImage.value != null || editImageUrl.value != null);
if (isSubmitButtonEnabled.value) {
isOnLoadingSubmitOrEdit.value = false;
}
}
Future<bool> createStewardPurchaseOutOfProvince() async {
bool res = false;
isOnLoadingSubmitOrEdit.value = true;
if (!(formKey.currentState?.validate() ?? false)) {
isOnLoadingSubmitOrEdit.value = false;
return res;
}
await safeCall(
showError: true,
call: () async {
CreateStewardFreeBar createStewardFreeBar = CreateStewardFreeBar(
productKey: selectedProduct.value!.key,
killHouseName: sellerNameController.text,
killHouseMobile: sellerPhoneController.text,
province: selectedProvince.value!.name,
city: selectedCity.value!.name,
weightOfCarcasses: int.parse(carcassWeightController.text.clearComma),
numberOfCarcasses: int.parse(carcassCountController.text.clearComma),
barImage: _base64Image.value,
date: DateTime.now().formattedYHMS,
);
await rootLogic.chickenRepository.createStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
body: createStewardFreeBar,
);
},
onSuccess: (result) {
getStewardPurchaseOutOfProvince();
resetSubmitForm();
toggleExpansion();
res = true;
},
);
isOnLoadingSubmitOrEdit.value = false;
return res;
}
void resetSubmitForm() {
sellerNameController.clear();
sellerPhoneController.clear();
carcassWeightController.clear();
carcassCountController.clear();
selectedProvince.value = null;
selectedCity.value = null;
selectedImage.value = null;
_base64Image.value = null;
editImageUrl.value = null;
isSubmitButtonEnabled.value = false;
}
void setEditData(StewardFreeBar item) async {
editImageUrl.value = item.barImage;
sellerNameController.text = item.killHouseName ?? '';
sellerPhoneController.text = item.killHouseMobile ?? '';
carcassWeightController.text = item.weightOfCarcasses.separatedByComma;
carcassCountController.text = item.numberOfCarcasses.separatedByComma;
editFreeBarKey.value = item.key;
selectedProvince.value = IranProvinceCityModel(name: item.province);
selectedCity.value = IranProvinceCityModel(name: item.city);
selectedProduct.value = rootLogic.rolesProductsModel.firstWhere(
(element) => element.key == item.product!.key,
);
isSubmitButtonEnabled.value = true;
}
Future<void> editStewardPurchaseOutOfProvince() async {
CreateStewardFreeBar edit = CreateStewardFreeBar(
productKey: selectedProduct.value!.key,
key: editFreeBarKey.value,
killHouseName: sellerNameController.text,
killHouseMobile: sellerPhoneController.text,
province: selectedProvince.value!.name,
city: selectedCity.value!.name,
weightOfCarcasses: int.parse(carcassWeightController.text.clearComma),
numberOfCarcasses: int.parse(carcassCountController.text.clearComma),
date: DateTime.now().formattedYHMS,
);
if (_base64Image.value != null) {
edit = edit.copyWith(barImage: _base64Image.value);
}
await safeCall(
showError: true,
call: () => rootLogic.chickenRepository.editStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
body: edit,
),
onSuccess: (result) {
onRefresh();
rootLogic.onRefresh();
toggleExpansion();
},
);
}
Future<void> deleteStewardPurchaseOutOfProvince(String key) async {
await safeCall(
call: () => rootLogic.chickenRepository.deleteStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildRawQueryParams(queryParams: {'key': key}),
),
);
}
Future<void> onRefresh() async {
currentPage.value = 1;
await rootLogic.onRefresh();
await getStewardPurchaseOutOfProvince();
}
void toggleExpansion({int? index}) {
if (expandedListIndex.value == index || index == null) {
expandedListIndex.value = -1;
} else {
expandedListIndex.value = index;
}
}
}

View File

@@ -0,0 +1,634 @@
import 'dart:io';
import 'package:flutter/cupertino.dart' hide Image;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_bar/steward_free_bar.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class BuyOutOfProvincePage extends GetView<BuyOutOfProvinceLogic> {
const BuyOutOfProvincePage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
routes: controller.routesName,
backId: stewardFirstKey,
onRefresh: controller.onRefresh,
onSearchChanged: (data) => controller.setSearchValue(data),
onFilterTap: () {
Get.bottomSheet(filterBottomSheet());
},
child: Stack(
children: [
Positioned.fill(
child: Column(
children: [
inventoryWidget(controller.rootLogic),
ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.value == index,
onTap: () => controller.toggleExpansion(index: index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.truckFastOutlinedSvg.path,
);
}, controller.expandedListIndex);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
onLoadMore: () async => controller.getStewardPurchaseOutOfProvince(true),
);
}, controller.purchaseOutOfProvinceList),
],
),
),
Positioned(
right: 5,
bottom: 95,
child: RFab.add(
onPressed: () {
Get.bottomSheet(
addPurchasedInformationBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
).whenComplete(() {
controller.resetSubmitForm();
});
},
),
),
],
),
);
}
Container itemListExpandedWidget(StewardFreeBar item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'${item.province}-${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'ندارد',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.d} ${item.date?.toJalali.formatter.mN ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(title: 'مشخصات فروشنده', value: item.killHouseName ?? 'ندارد'),
buildRow(
title: 'تلفن فروشنده',
value: item.killHouseMobile ?? 'ندارد',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(title: 'محصول', value: item.product?.name ?? 'ندارد'),
buildRow(
title: 'وزن خریداری شده',
value: '${item.weightOfCarcasses?.separatedByCommaFa} کیلوگرم',
),
buildRow(
title: 'حجم خریداری شده',
value: '${item.numberOfCarcasses?.separatedByCommaFa} قطعه',
),
buildRowOnTapped(
title: 'مشاهده بارنامه',
titleStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
valueWidget: Assets.vec.clipboardEyeSvg.svg(
width: 20,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
onTap: () {
Get.bottomSheet(
BaseBottomSheet(
height: 400,
child: Column(
spacing: 16,
children: [
Text(
'بارنامه',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Image.network(
item.barImage ?? '',
fit: BoxFit.cover,
height: 300,
errorBuilder: (context, error, stackTrace) {
eLog(error.toString());
return Center(child: Text('خطایی پیش آمده!'));
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return CupertinoActivityIndicator();
},
),
],
),
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditData(item);
Get.bottomSheet(
addPurchasedInformationBottomSheet(true),
isScrollControlled: true,
).whenComplete(() {
controller.resetSubmitForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () => controller.deleteStewardPurchaseOutOfProvince(item.key!),
onRefresh: () async {
controller.rootLogic.onRefresh();
controller.onRefresh();
controller.toggleExpansion();
},
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
Row itemListWidget(StewardFreeBar item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 3,
children: [
Text(
item.killHouseName ?? 'ندارد',
textAlign: TextAlign.start,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 3,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
'${item.weightOfCarcasses?.separatedByCommaFa}kg',
textAlign: TextAlign.left,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
SizedBox(height: 2),
Text(
'${item.numberOfCarcasses.separatedByComma} ${'قطعه'}',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 3,
children: [
Text(
'${item.province}',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
Text(
'${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
],
);
}
Widget addPurchasedInformationBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text(
isOnEdit ? 'ویرایش اطلاعات خرید' : 'ثبت اطلاعات خرید',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
_productDropDown(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(spacing: 12, children: [_provinceWidget(), _cityWidget()]),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
RTextField(
controller: controller.sellerNameController,
label: 'نام فروشنده',
borderColor: AppColor.darkGreyLight,
filled: true,
filledColor: AppColor.bgLight,
),
RTextField(
controller: controller.sellerPhoneController,
label: 'تلفن فروشنده',
keyboardType: TextInputType.phone,
borderColor: AppColor.darkGreyLight,
maxLength: 11,
filled: true,
filledColor: AppColor.bgLight,
validator: (value) {
if (value == null || value.isEmpty) {
return 'لطفاً شماره موبایل را وارد کنید';
}
String cleaned = value.replaceAll(',', '');
if (cleaned.length != 11) {
return 'شماره موبایل باید ۱۱ رقم باشد';
}
if (!cleaned.startsWith('09')) {
return 'شماره موبایل باید با 09 شروع شود';
}
return null;
},
),
UnitTextField(
controller: controller.carcassWeightController,
hint: 'وزن',
unit: 'کیلوگرم',
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
),
UnitTextField(
controller: controller.carcassCountController,
hint: 'حجم',
unit: 'قطعه',
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
),
],
),
),
_imageCarcasesWidget(isOnEdit),
submitButtonWidget(isOnEdit),
SizedBox(),
],
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return Obx(() {
return RElevated(
text: isOnEdit ? 'ویرایش' : 'ثبت',
width: Get.width,
backgroundColor: AppColor.greenNormal,
isLoading: controller.isOnLoadingSubmitOrEdit.value,
enabled: controller.isSubmitButtonEnabled.value,
onPressed: isOnEdit
? () async {
await controller.editStewardPurchaseOutOfProvince();
Get.back();
}
: () async {
var res = await controller.createStewardPurchaseOutOfProvince();
if (res) {
Get.back();
}
},
height: 40,
);
});
}
Widget _productDropDown() {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rootLogic.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProduct.value = value;
},
selectedItem: controller.selectedProduct.value,
initialValue: controller.selectedProduct.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
Text(
'موجودی:${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByCommaFa ?? 0}',
),
],
),
);
});
}
Widget _provinceWidget() {
return Obx(() {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: controller.rootLogic.provinces,
onChanged: (value) {
controller.selectedProvince.value = value;
},
selectedItem: controller.selectedProvince.value,
itemBuilder: (item) => Text(
item.name ?? 'بدون نام',
style: AppFonts.yekan14.copyWith(color: AppColor.lightGreyDarker),
),
labelBuilder: (item) => item?.name != null
? Text(item!.name!, style: AppFonts.yekan14.copyWith(color: AppColor.textColor))
: Text(
'انتخاب استان',
style: AppFonts.yekan14.copyWith(color: AppColor.textColorLight),
),
);
});
}
Widget _cityWidget() {
return ObxValue((data) {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: data,
onChanged: (value) {
controller.selectedCity.value = value;
},
selectedItem: controller.selectedCity.value,
itemBuilder: (item) => Text(
item.name ?? 'بدون نام',
style: AppFonts.yekan14.copyWith(color: AppColor.lightGreyDarker),
),
labelBuilder: (item) => item?.name != null
? Text(item!.name!, style: AppFonts.yekan14.copyWith(color: AppColor.textColor))
: Text('انتخاب شهر', style: AppFonts.yekan14.copyWith(color: AppColor.textColorLight)),
);
}, controller.cites);
}
SizedBox _imageCarcasesWidget(bool isOnEdit) {
return SizedBox(
width: Get.width,
height: 370,
child: Card(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
spacing: 8,
children: [
Text('بارنامه', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Expanded(
child: ObxValue((data) {
return Container(
width: Get.width,
height: 270,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.blackLight),
),
child: Center(
child: isOnEdit
? Image.network(controller.editImageUrl.value ?? '')
: data.value == null
? Padding(
padding: const EdgeInsets.fromLTRB(30, 10, 10, 30),
child: Assets.vec.placeHolderSvg.svg(width: 200.w, height: 150.h),
)
: Image.file(File(data.value!.path), fit: BoxFit.cover),
),
);
}, controller.selectedImage),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
text: 'گالری',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
SizedBox(width: 16),
ROutlinedElevated(
text: 'دوربین',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: AppColor.blueNormal),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.camera,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
],
),
],
),
),
),
);
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 200,
child: Column(
spacing: 16,
children: [
Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Row(
spacing: 8,
children: [
Expanded(
child: dateFilterWidget(
date: controller.fromDateFilter,
onChanged: (jalali) => controller.fromDateFilter.value = jalali,
),
),
Expanded(
child: dateFilterWidget(
isFrom: false,
date: controller.toDateFilter,
onChanged: (jalali) => controller.toDateFilter.value = jalali,
),
),
],
),
RElevated(
text: 'اعمال فیلتر',
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
onPressed: () {
controller.getStewardPurchaseOutOfProvince();
Get.back();
},
height: 40,
),
],
),
);
}
}

View File

@@ -0,0 +1,97 @@
import 'package:rasadyar_chicken/data/models/response/bar_information/bar_information.dart';
import 'package:rasadyar_chicken/data/models/response/kill_house_distribution_info/kill_house_distribution_info.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class HomeLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
RxnInt totalWeightTodayBars = RxnInt();
Rxn<KillHouseDistributionInfo> killHouseDistributionInfo = Rxn<KillHouseDistributionInfo>();
Rxn<BarInformation> barInformation = Rxn();
RxList<Map<String, String?>> inventoryItems = [
{'خریدهای دولتی داخل استان': null},
{'خریدهای آزاد داخل استان': null},
{'وزن خریدهای خارج استان': null},
{'کل ورودی به انبار': null},
{'کل فروش': null},
{'مانده انبار': null},
].obs;
RxList<Map<String, String>> broadcastItems = [
{'وزن دولتی': '2،225،256'},
{'وزن آزاد': '2،225،256'},
{'فروش دولتی': '2،225،256'},
{'فروش آزاد': '2،225،256'},
{'توزیع داخل استان': '2،225،256'},
{'توزیع خارج استان': '2،225،256'},
{'قطعه بندی': '2،225،256'},
].obs;
RxBool isExpanded = false.obs;
@override
void onReady() {
super.onReady();
refreshData();
}
Future<void> refreshData() async {
await Future.wait([
getGeneralBarsInformation(),
getTodayBars(),
getDistributionInformation(),
rootLogic.getRolesProducts(),
rootLogic.getInventory(),
]);
}
Future<void> getGeneralBarsInformation() async {
await safeCall<BarInformation?>(
call: () async => await rootLogic.chickenRepository.getGeneralBarInformation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
barInformation.value = result;
}
},
onError: (error, stackTrace) {},
);
}
Future<void> getTodayBars() async {
await safeCall<BarInformation?>(
call: () async => await rootLogic.chickenRepository.getGeneralBarInformation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
fromDate: DateTime.now(),
toDate: DateTime.now(),
role: 'Steward',
),
),
onSuccess: (result) {
if (result != null) {
totalWeightTodayBars.value = result.totalBarsWeight?.toInt();
}
},
onError: (error, stackTrace) {},
);
}
Future<void> getDistributionInformation() async {
await safeCall<KillHouseDistributionInfo?>(
call: () async => await rootLogic.chickenRepository.getKillHouseDistributionInfo(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
killHouseDistributionInfo.value = result;
}
},
onError: (error, stackTrace) {},
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:rasadyar_chicken/data/data_source/local/chicken_local.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/data/models/local/widely_used_local_model.dart';
import 'package:rasadyar_chicken/data/models/response/inventory/inventory_model.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_remain_weight/steward_remain_weight.dart';
import 'package:rasadyar_chicken/data/models/response/steward_sales_info_dashboard/steward_sales_info_dashboard.dart';
import 'package:rasadyar_chicken/data/models/response/waiting_arrival/waiting_arrival.dart'
hide ProductModel;
import 'package:rasadyar_chicken/data/repositories/chicken/chicken_repository.dart';
import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/home/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/segmentation/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
enum ErrorLocationType { serviceDisabled, permissionDenied, none }
class StewardRootLogic extends GetxController {
DateTime? _lastBackPressed;
RxInt currentPage = 2.obs;
List<Widget> pages = [
BuyPage(),
SalePage(),
HomePage(),
SegmentationPage(),
ProfilePage(),
];
final defaultRoutes = <int, String>{
0: StewardRoutes.buySteward,
1: StewardRoutes.saleSteward,
};
RxList<ProductModel> rolesProductsModel = RxList<ProductModel>();
Rxn<WidelyUsedLocalModel> widelyUsedList = Rxn<WidelyUsedLocalModel>();
Rxn<StewardSalesInfoDashboard> stewardSalesInfoDashboard =
Rxn<StewardSalesInfoDashboard>();
Rxn<StewardRemainWeight> stewardRemainWeight = Rxn<StewardRemainWeight>();
late DioRemote dioRemote;
var tokenService = Get.find<TokenStorageService>();
late ChickenRepository chickenRepository;
late ChickenLocalDataSource localDatasource;
RxList<ErrorLocationType> errorLocationType = RxList();
RxMap<int, dynamic> inventoryExpandedList = RxMap();
Rxn<InventoryModel> inventoryModel = Rxn<InventoryModel>();
RxList<IranProvinceCityModel> provinces = <IranProvinceCityModel>[].obs;
// Cancel tokens for API calls
CancelToken? _inventoryCancelToken;
CancelToken? _provincesCancelToken;
@override
void onInit() {
super.onInit();
localDatasource = diChicken.get<ChickenLocalDataSource>();
chickenRepository = diChicken.get<ChickenRepository>();
}
@override
void onReady() {
super.onReady();
if (provinces.isEmpty) {
getProvinces();
}
if (inventoryModel.value == null) {
getInventory();
}
if (rolesProductsModel.isEmpty) {
getRolesProducts();
}
getStewardSaleDashboard();
getStewardRemainWeightData();
if (widelyUsedList.value?.hasInit != true) {
//TODO
localDatasource.initWidleyUsed().then(
(value) => localDatasource.getAllWidely(),
);
}
}
@override
void onClose() {
// Cancel any ongoing requests when controller is disposed
_inventoryCancelToken?.cancel();
_provincesCancelToken?.cancel();
super.onClose();
}
Future<void> onRefresh() async {
await Future.wait([
getInventory(),
getRolesProducts(),
getStewardSaleDashboard(),
getStewardRemainWeightData(),
getProvinces(),
getStewardRemainWeightData(),
]);
}
void toggleExpanded(int index) {
if (inventoryExpandedList.keys.contains(index)) {
inventoryExpandedList.remove(index);
} else {
inventoryExpandedList[index] = false;
}
}
Future<void> getInventory() async {
// Cancel previous request if still running
_inventoryCancelToken?.cancel();
_inventoryCancelToken = CancelToken();
await safeCall<List<InventoryModel>?>(
call: () async => await chickenRepository.getInventory(
token: tokenService.accessToken.value!,
cancelToken: _inventoryCancelToken,
),
onSuccess: (result) {
if (result != null) {
inventoryModel.value = result.first;
}
},
onError: (error, stackTrace) {
if (error is DioException && error.type == DioExceptionType.cancel) {
// Request was cancelled, ignore the error
return;
}
},
);
}
void rootErrorHandler(DioException error) {
handleGeneric(error, () {
tokenService.deleteModuleTokens(Module.chicken);
});
}
void changePage(int index) {
currentPage.value = index;
}
Future<void> getProvinces() async {
// Cancel previous request if still running
_provincesCancelToken?.cancel();
_provincesCancelToken = CancelToken();
try {
final res = await chickenRepository.getProvince(
cancelToken: _provincesCancelToken,
);
if (res != null) {
provinces.clear();
provinces.value = res;
}
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) {
// Request was cancelled, ignore the error
return;
}
provinces.clear();
}
}
Future<void> getRolesProducts() async {
safeCall(
call: () async => await chickenRepository.getRolesProducts(
token: tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
rolesProductsModel.value = result;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> getStewardSaleDashboard() async {
safeCall(
call: () async => await chickenRepository.getStewardSalesInfoDashboard(
token: tokenService.accessToken.value!,
queryParameters: buildRawQueryParams(role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
stewardSalesInfoDashboard.value = result;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> getStewardRemainWeightData() async {
safeCall(
call: () async => await chickenRepository.getStewardRemainWeight(
token: tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
stewardRemainWeight.value = result;
}
},
onError: (error, stacktrace) {},
);
}
int getNestedKey() {
switch (currentPage.value) {
case 0:
return stewardFirstKey;
case 1:
return stewardSecondKey;
case 2:
return stewardThirdKey;
case 3:
return stewardFourthKey;
case 4:
return stewardFourthKey;
default:
return stewardThirdKey;
}
}
void onPopScopTaped() async {
final nestedKeyId = getNestedKey();
GlobalKey<NavigatorState>? currentNestedKey = Get.nestedKey(nestedKeyId);
if (currentNestedKey?.currentState?.canPop() == true) {
iLog(currentNestedKey?.currentState?.canPop());
iLog(currentNestedKey?.currentContext);
currentNestedKey?.currentState?.popUntil((route) => route.isFirst);
} else {
final now = DateTime.now();
if (_lastBackPressed == null ||
now.difference(_lastBackPressed!) > Duration(seconds: 2)) {
_lastBackPressed = now;
Get.snackbar(
'خروج از برنامه',
'برای خروج دوباره بازگشت را بزنید',
snackPosition: SnackPosition.TOP,
duration: Duration(seconds: 2),
backgroundColor: AppColor.warning,
);
} else {
await SystemNavigator.pop();
}
}
}
bool isKillHouse(WaitingArrivalModel model) =>
model.allocationType?.split("_")[0].toLowerCase() == "killhouse";
}

View File

@@ -0,0 +1,692 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/chicken.dart';
import 'package:rasadyar_chicken/data/models/response/kill_house_distribution_info/kill_house_distribution_info.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_core/core.dart';
class StewardRootPage extends GetView<StewardRootLogic> {
StewardRootPage({super.key});
@override
Widget build(BuildContext context) {
return ObxValue((data) {
return ChickenBasePage(
isFullScreen: true,
isBase: true,
onPopScopTaped: controller.onPopScopTaped,
child: Stack(
children: [
IndexedStack(
children: [
Navigator(
key: Get.nestedKey(stewardFirstKey),
onGenerateRoute: (settings) {
final page = ChickenPages.pages.firstWhere(
(e) => e.name == settings.name,
orElse: () => ChickenPages.pages.firstWhere(
(e) => e.name == StewardRoutes.buySteward,
),
);
return buildRouteFromGetPage(page);
},
),
Navigator(
key: Get.nestedKey(stewardSecondKey),
onGenerateRoute: (settings) {
final page = ChickenPages.pages.firstWhere(
(e) => e.name == settings.name,
orElse: () => ChickenPages.pages.firstWhere(
(e) => e.name == StewardRoutes.saleSteward,
),
);
return buildRouteFromGetPage(page);
},
),
Navigator(
key: Get.nestedKey(stewardThirdKey),
onGenerateRoute: (settings) =>
GetPageRoute(page: () => controller.pages[2]),
),
Navigator(
key: Get.nestedKey(stewardFourthKey),
onGenerateRoute: (settings) =>
GetPageRoute(page: () => controller.pages[3]),
),
Navigator(
key: Get.nestedKey(stewardFifthKey),
onGenerateRoute: (settings) =>
GetPageRoute(page: () => controller.pages[4]),
),
],
index: data.value,
),
Positioned(
bottom: 0,
right: 0,
left: 0,
child: RBottomNavigation(
items: [
RBottomNavigationItem(
label: 'خرید',
icon: Assets.vec.buySvg.path,
isSelected: controller.currentPage.value == 0,
onTap: () {
Get.nestedKey(
stewardFirstKey,
)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(
stewardSecondKey,
)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(0);
},
),
RBottomNavigationItem(
label: 'فروش',
icon: Assets.vec.saleSvg.path,
isSelected: controller.currentPage.value == 1,
onTap: () {
Get.nestedKey(
stewardFirstKey,
)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(
stewardSecondKey,
)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(1);
},
),
RBottomNavigationItem(
label: 'خانه',
icon: Assets.vec.homeSvg.path,
isSelected: controller.currentPage.value == 2,
onTap: () {
Get.nestedKey(
stewardSecondKey,
)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(
stewardFirstKey,
)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(2);
},
),
RBottomNavigationItem(
label: 'قطعه بندی',
icon: Assets.vec.convertCubeSvg.path,
isSelected: controller.currentPage.value == 3,
onTap: () {
Get.nestedKey(
stewardSecondKey,
)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(
stewardFirstKey,
)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(3);
},
),
RBottomNavigationItem(
label: 'پروفایل',
icon: Assets.vec.profileCircleSvg.path,
isSelected: controller.currentPage.value == 4,
onTap: () {
Get.nestedKey(
stewardSecondKey,
)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(
stewardFirstKey,
)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(4);
},
),
],
),
),
],
),
);
}, controller.currentPage);
}
Container _todayShipmentWidget() {
return Container(
height: 70,
width: Get.width / 2,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
clipBehavior: Clip.hardEdge,
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [const Color(0xFFEAEFFF), Colors.white],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Assets.icons.cubeScan.svg(width: 30.w, height: 30),
Text(
'بارهای امروز',
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(
color: AppColor.blueNormal,
),
),
],
),
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
'2،225،256',
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(color: AppColor.textColor),
),
Text(
'کیلوگرم',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.textColor),
),
],
),
),
],
),
);
}
Container _informationLabelCard({
required String title,
required String description,
String unit = 'کیلوگرم',
required String iconPath,
required Color iconColor,
required Color bgDescriptionColor,
required Color bgLabelColor,
}) {
return Container(
height: 82,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
child: Row(
children: [
// Left side with icon and title
Expanded(
child: Container(
height: 82,
decoration: BoxDecoration(
color: bgLabelColor,
borderRadius: BorderRadius.only(
topRight: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
),
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(
color: AppColor.mediumGreyDarkActive,
),
),
],
),
),
),
// Right side with description and unit
Expanded(
child: Container(
decoration: BoxDecoration(
color: bgDescriptionColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
bottomLeft: Radius.circular(8),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
description,
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(
color: AppColor.mediumGreyDarkActive,
),
),
Text(
unit,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(
color: AppColor.mediumGreyDarkActive,
),
),
],
),
),
),
],
),
);
}
Container _informationIconCard({
required String title,
required String description,
String unit = 'کیلوگرم',
required String iconPath,
required Color iconColor,
required Color bgDescriptionColor,
required Color bgLabelColor,
}) {
return Container(
height: 110,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
child: Stack(
alignment: Alignment.topCenter,
children: [
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Container(
height: 91,
decoration: BoxDecoration(
color: bgDescriptionColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.25, color: const Color(0xFFB4B4B4)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(
color: AppColor.mediumGreyDarkActive,
),
),
Text(
description,
textAlign: TextAlign.right,
style: AppFonts.yekan16.copyWith(
color: AppColor.mediumGreyDarkActive,
),
),
Text(
unit,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(
color: AppColor.mediumGreyDarkActive,
),
),
],
),
),
),
Positioned(
top: 0,
child: Container(
width: 32,
height: 32,
decoration: ShapeDecoration(
color: bgLabelColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
side: BorderSide(width: 0.25, color: const Color(0xFFD5D5D5)),
),
),
child: Center(
child: SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
),
),
),
),
],
),
);
}
Widget widelyUsed({
required String title,
required String iconPath,
required VoidCallback onTap,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 4,
children: [
Container(
width: 48,
height: 48,
padding: EdgeInsets.all(4),
decoration: ShapeDecoration(
color: const Color(0xFFBECDFF),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
color: AppColor.blueNormal,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: SvgGenImage.vec(iconPath).svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
fit: BoxFit.cover,
),
),
),
Text(
title,
style: AppFonts.yekan10.copyWith(color: AppColor.blueNormal),
),
],
);
}
Widget addWidelyUsed({required VoidCallback onTap}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
spacing: 4,
children: [
Container(
width: 48,
height: 48,
padding: EdgeInsets.all(4),
decoration: ShapeDecoration(
color: const Color(0xFFD9F7F0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Assets.vec.messageAddSvg.svg(
width: 40,
height: 40,
colorFilter: ColorFilter.mode(
AppColor.greenNormal,
BlendMode.srcIn,
),
fit: BoxFit.cover,
),
),
Text(
'افزودن',
style: AppFonts.yekan10.copyWith(color: AppColor.greenDarkHover),
),
],
);
}
/*Column oldPage() {
return Column(
children: [
inventoryWidget(),
ObxValue((data) => broadcastInformationWidget(data.value), controller.killHouseDistributionInfo),
SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
cardWidget(
title: 'ورود به انبار',
iconPath: Assets.icons.whareHouse.path,
onTap: () {
Get.toNamed(ChickenRoutes.enteringTheWarehouse);
},
),
cardWidget(
title: 'فروش داخل استان',
iconPath: Assets.icons.inside.path,
onTap: () {
Get.toNamed(ChickenRoutes.salesInProvince);
},
),
cardWidget(
title: 'فروش خارج استان',
iconPath: Assets.icons.outside.path,
onTap: () {
Get.toNamed(ChickenRoutes.salesOutOfProvince);
},
),
],
),
),
],
);
}
Widget inventoryWidget() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
children: [
const SizedBox(height: 20),
Align(
alignment: Alignment.centerRight,
child: Text('موجودی انبار', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
),
SizedBox(height: 4),
ObxValue(
(data) =>
data.isEmpty
? Container(
margin: const EdgeInsets.symmetric(vertical: 2),
height: 80,
padding: EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
child: Center(child: CircularProgressIndicator()),
)
: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: controller.inventoryList.length,
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemBuilder: (context, index) {
return ObxValue((expand) {
return GestureDetector(
onTap: () {
controller.toggleExpanded(index);
},
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
onEnd: () {
controller.inventoryExpandedList[index] = !controller.inventoryExpandedList[index]!;
},
margin: const EdgeInsets.symmetric(vertical: 2),
padding: EdgeInsets.all(6),
curve: Curves.easeInOut,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
duration: const Duration(seconds: 1),
height: expand.keys.contains(index) ? 250 : 80,
child: inventoryItem(
isExpanded: expand.keys.contains(index) && expand[index]!,
index: index,
model: controller.inventoryList[index],
),
),
);
}, controller.inventoryExpandedList);
},
),
controller.inventoryList,
),
],
),
);
}
Widget inventoryItem({required bool isExpanded, required int index, required InventoryModel model}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8,
children: [
buildRow('نام محصول', model.name ?? ''),
Visibility(
visible: isExpanded,
child: Column(
spacing: 8,
children: [
buildRow('وزن خریدهای دولتی داخل استان (کیلوگرم)', '0326598653'),
buildRow('وزن خریدهای آزاد داخل استان (کیلوگرم)', model.receiveFreeCarcassesWeight.toString()),
buildRow('وزن خریدهای خارج استان (کیلوگرم)', model.freeBuyingCarcassesWeight.toString()),
buildRow('کل ورودی به انبار (کیلوگرم)', model.totalFreeBarsCarcassesWeight.toString()),
buildRow('کل فروش (کیلوگرم)', model.realAllocatedWeight.toString()),
buildRow('مانده انبار (کیلوگرم)', model.totalRemainWeight.toString()),
],
),
),
],
);
}*/
Widget buildRow(String title, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 2,
child: Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(
color: AppColor.darkGreyDarkHover,
),
),
),
Flexible(
flex: 1,
child: Text(
value,
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(
color: AppColor.darkGreyDarkHover,
),
),
),
],
),
);
}
Widget broadcastInformationWidget(KillHouseDistributionInfo? model) {
return Container(
height: 140,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
child: model != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
Text(
'اطلاعات ارسالی',
textAlign: TextAlign.right,
style: AppFonts.yekan16Bold.copyWith(
color: AppColor.blueNormal,
),
),
const SizedBox(height: 12),
buildRow(
'فروش و توزیع داخل استان (کیلوگرم)',
model.stewardAllocationsWeight!.toInt().toString(),
),
buildRow(
'فروش و توزیع خارج استان (کیلوگرم)',
model.freeSalesWeight!.toInt().toString(),
),
],
)
: const Center(child: CircularProgressIndicator()),
);
}
Widget cardWidget({
required String title,
required String iconPath,
required VoidCallback onTap,
}) {
return Container(
width: Get.width / 4,
height: 130,
child: GestureDetector(
onTap: onTap,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(width: 1, color: AppColor.blueNormal),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgGenImage(iconPath).svg(width: 50, height: 50),
SizedBox(height: 4),
Text(
title,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.blueNormal),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,137 @@
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/request/conform_allocation/conform_allocation.dart';
import 'package:rasadyar_chicken/data/models/response/allocated_made/allocated_made.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_bar_dashboard/steward_free_bar_dashboard.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class SaleLogic extends GetxController {
Rxn<List<AllocatedMadeModel>?> allocatedMadeModel = Rxn<List<AllocatedMadeModel>?>();
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
Rxn<StewardFreeBarDashboard> stewardFreeDashboard = Rxn<StewardFreeBarDashboard>();
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
List<String> routesName = ['فروش'];
DateTime? _lastBackPressed;
@override
void onReady() {
super.onReady();
getStewardDashBord();
}
Future<void> getAllocatedMade() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getAllocatedMade(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(page: 1, pageSize: 20, search: 'filter', role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
allocatedMadeModel.value = result.results;
}
},
onError: (error, stacktrace) {},
);
}
void checkVerfication() {}
void confirmAllocation(ConformAllocation allocation) {
safeCall(
call: () async => await rootLogic.chickenRepository.confirmAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocation: allocation.toJson(),
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
void denyAllocation(String token) {
safeCall(
call: () async => await rootLogic.chickenRepository.denyAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationToken: token,
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> confirmAllAllocations() async {
safeCall(
call: () async => await rootLogic.chickenRepository.confirmAllAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationTokens: allocatedMadeModel.value?.map((e) => e.key!).toList() ?? [],
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> getGuilds() async {}
Future<void> addSale() async {}
void setSelectedGuild(GuildModel value) {}
void setSelectedProduct(ProductModel value) {}
Future<void> getStewardDashBord() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getStewardDashboard(
token: rootLogic.tokenService.accessToken.value!,
stratDate: DateTime.now().formattedDashedGregorian,
endDate: DateTime.now().formattedDashedGregorian,
),
onSuccess: (result) {
if (result != null) {
stewardFreeDashboard.value = result;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> submitAllocation() async {}
@override
void dispose() {
rootLogic.inventoryExpandedList.clear();
super.dispose();
}
void onPopScopTaped() async {
final now = DateTime.now();
if (_lastBackPressed == null || now.difference(_lastBackPressed!) > Duration(seconds: 2)) {
_lastBackPressed = now;
Get.snackbar(
'خروج از برنامه',
'برای خروج دوباره بازگشت را بزنید',
snackPosition: SnackPosition.TOP,
duration: Duration(seconds: 2),
backgroundColor: AppColor.warning,
);
} else {
await SystemNavigator.pop();
}
}
}

View File

@@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_bar_dashboard/steward_free_bar_dashboard.dart';
import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalePage extends GetView<SaleLogic> {
SalePage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
routes: controller.routesName,
isBase: true,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 21,
children: [
GlassMorphismCardIcon(
title: 'فروش داخل استان',
vecIcon: Assets.vec.map2Svg.path,
onTap: () {
Get.toNamed(
StewardRoutes.salesInProvinceSteward,
id: stewardSecondKey,
);
},
),
GlassMorphismCardIcon(
title: 'فروش خارج استان',
vecIcon: Assets.vec.saleOutProvinceSvg.path,
onTap: () {
Get.toNamed(
StewardRoutes.salesOutOfProvinceSteward,
id: stewardSecondKey,
);
},
),
],
),
],
),
);
}
Widget addSaleOutOfTheProvinceBottomSheet() {
return BaseBottomSheet(
child: Column(
children: [
const SizedBox(height: 20),
Align(
alignment: Alignment.centerRight,
child: Text(
'ثبت فروش خارج استان',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
),
SizedBox(height: 4),
RElevated(text: 'ثبت توزیع/ فروش', onPressed: () {}),
],
),
);
}
Widget _typeOuterInfoCard({
required String title,
required String iconPath,
required Color foregroundColor,
VoidCallback? onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
height: 180,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: foregroundColor),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Positioned(
top: -41,
child: SvgGenImage.vec(iconPath).svg(
width: 45,
height: 45,
colorFilter: ColorFilter.mode(
foregroundColor,
BlendMode.srcIn,
),
),
),
Assets.vec.shoppingBasketSvg.svg(
width: 55,
height: 60,
colorFilter: ColorFilter.mode(
foregroundColor,
BlendMode.srcIn,
),
fit: BoxFit.cover,
),
],
),
const SizedBox(height: 15),
Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan16Bold.copyWith(color: foregroundColor),
),
],
),
),
);
}
Widget summaryOfInformation(StewardFreeBarDashboard? model) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
children: [
Text(
'خلاصه اطلاعات',
style: AppFonts.yekan16Bold.copyWith(
color: AppColor.blueNormal,
),
),
],
),
),
Container(
height: 140,
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.blueNormal, width: 1),
),
child: model == null
? const Center(child: CircularProgressIndicator())
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
const SizedBox(height: 12),
buildRow(
'تعداد کل بارها',
model.totalQuantity?.toString() ?? '0',
),
buildRow('تعداد کل', model.totalBars?.toString() ?? '0'),
buildRow(
'وزن کل (کیلوگرم)',
model.totalWeight?.toString() ?? '0',
),
],
),
),
],
);
}
Widget buildRow(String title, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 2,
child: Text(
title,
textAlign: TextAlign.right,
style: AppFonts.yekan14.copyWith(
color: AppColor.darkGreyDarkHover,
),
),
),
Flexible(
flex: 2,
child: Text(
value,
textAlign: TextAlign.left,
style: AppFonts.yekan14.copyWith(
color: AppColor.darkGreyDarkHover,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,505 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/conform_allocation/conform_allocation.dart';
import 'package:rasadyar_chicken/data/models/request/submit_steward_allocation/submit_steward_allocation.dart';
import 'package:rasadyar_chicken/data/models/response/allocated_made/allocated_made.dart';
import 'package:rasadyar_chicken/data/models/response/broadcast_price/broadcast_price.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/guild_profile/guild_profile.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_remain_weight/steward_remain_weight.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SalesInProvinceLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
SaleLogic saleLogic = Get.find<SaleLogic>();
RxnString searchedValue = RxnString();
RxInt expandedListIndex = (-1).obs;
RxList<String> routesName = RxList();
Rx<Color> bgConfirmAllColor = AppColor.blueNormal.obs;
final RxBool isLoadingMoreAllocationsMade = false.obs;
Timer? _flashingTimer;
Rx<Resource<PaginationModel<AllocatedMadeModel>>> allocatedList =
Resource<PaginationModel<AllocatedMadeModel>>.loading().obs;
RxList<ProductModel> rolesProductsModel = RxList<ProductModel>();
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
GlobalKey<FormState> formKey = GlobalKey<FormState>();
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
Rxn<ProductModel> selectedProductModel = Rxn<ProductModel>();
Rxn<GuildModel> selectedGuildModel = Rxn<GuildModel>();
Rxn<GuildProfile> guildProfile = Rxn<GuildProfile>();
RxInt saleType = 1.obs;
RxInt priceType = 2.obs;
RxInt quotaType = 1.obs;
RxInt weight = 0.obs;
RxInt pricePerKilo = 0.obs;
RxInt totalCost = 0.obs;
RxBool isValid = false.obs;
final weightController = TextEditingController();
final pricePerKiloController = TextEditingController();
final totalCostController = TextEditingController();
final ScrollController scrollControllerAllocationsMade = ScrollController();
final RxInt currentPage = 1.obs;
final RxBool addPageAllocationsMade = false.obs;
final RxBool hasMoreDataAllocationsMade = true.obs;
Rxn<BroadcastPrice> broadcastPrice = Rxn<BroadcastPrice>();
Rxn<AllocatedMadeModel> selectedAllocationModelForUpdate = Rxn<AllocatedMadeModel>();
SubmitStewardAllocation? tmpStewardAllocation;
Rxn<Jalali> productionDate = Rxn(null);
Rxn<int> remainingStock = Rxn(null);
Map<String, DayData> freeProductionDateData = {};
Map<String, DayData> governmentalProductionDateData = {};
@override
void onInit() {
super.onInit();
routesName.value = [...saleLogic.routesName, 'داخل استان'].toList();
getAllocatedMade();
getRolesProducts();
getGuilds();
getGuildProfile();
getBroadcastPrice();
ever(saleType, (callback) {
getGuilds();
});
ever(quotaType, (_) {
remainingStock.value = null;
productionDate.value = null;
});
debounce(weight, time: Duration(milliseconds: 110), (callback) {
totalCost.value = callback * pricePerKilo.value;
});
debounce(pricePerKilo, time: Duration(milliseconds: 100), (callback) {
totalCost.value = callback * weight.value;
});
totalCost.listen((data) {
totalCostController.text = data.toString().separatedByComma;
isValid.value =
weight.value > 0 &&
pricePerKilo.value > 0 &&
totalCost.value > 0 &&
selectedProductModel.value != null &&
selectedGuildModel.value != null;
});
everAll([
totalCost,
weight,
pricePerKilo,
totalCost,
selectedProductModel,
selectedGuildModel,
productionDate,
], (callback) => checkVerification());
scrollControllerAllocationsMade.addListener(() {
if (scrollControllerAllocationsMade.position.pixels >=
scrollControllerAllocationsMade.position.maxScrollExtent - 100) {
addPageAllocationsMade.value = true;
getAllocatedMade();
}
});
debounce(
searchedValue,
(callback) => getAllocatedMade(),
time: Duration(milliseconds: timeDebounce),
);
_updateGovernmentalProductionDateData();
_updateFreeProductionDateData();
ever(rootLogic.stewardRemainWeight, (callback) {
_updateGovernmentalProductionDateData();
_updateFreeProductionDateData();
});
}
void _updateGovernmentalProductionDateData() {
List<RemainWeightDay> dates = rootLogic.stewardRemainWeight.value?.governmental ?? [];
governmentalProductionDateData = {
for (var element in dates)
element.day.toString().toJalali.formatCompactDate(): DayData(
value: element.amount?.toInt(),
),
};
}
void _updateFreeProductionDateData() {
var dates = rootLogic.stewardRemainWeight.value?.free ?? [];
freeProductionDateData = {
for (var element in dates)
element.day.toString().toJalali.formatCompactDate(): DayData(
value: element.amount?.toInt(),
),
};
}
Future<void> getAllocatedMade([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
allocatedList.value = Resource<PaginationModel<AllocatedMadeModel>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1; // Reset to first page if search value is set
}
safeCall(
call: () async => await rootLogic.chickenRepository.getAllocatedMade(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
page: currentPage.value,
pageSize: 20,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) async {
await Future.delayed(Duration(milliseconds: 200));
if ((res?.count ?? 0) == 0) {
allocatedList.value = Resource<PaginationModel<AllocatedMadeModel>>.empty();
} else {
allocatedList.value = Resource<PaginationModel<AllocatedMadeModel>>.success(
PaginationModel<AllocatedMadeModel>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: isLoadingMore
? [...(allocatedList.value.data?.results ?? []), ...(res?.results ?? [])]
: res?.results ?? [],
),
);
isLoadingMoreAllocationsMade.value = false;
if ((allocatedList.value.data?.results?.length ?? 0) > 1) {
flashingFabBgColor();
}
}
},
onError: (error, stacktrace) {
isLoadingMoreAllocationsMade.value = false;
},
);
}
void checkVerification() {
var hasWeight = quotaType.value == 1
? weight.value <=
(governmentalProductionDateData[productionDate.value?.formatCompactDate()]?.value ??
0)
: weight.value <=
(freeProductionDateData[productionDate.value?.formatCompactDate()]?.value ?? 0);
isValid.value =
weight.value > 0 &&
pricePerKilo.value > 0 &&
totalCost.value > 0 &&
hasWeight &&
selectedProductModel.value != null &&
selectedGuildModel.value != null;
}
void confirmAllocation(ConformAllocation allocation) {
safeCall(
call: () async => await rootLogic.chickenRepository.confirmAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocation: allocation.toJson(),
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
void denyAllocation(String token) {
safeCall(
call: () async => await rootLogic.chickenRepository.denyAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationToken: token,
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> confirmAllAllocations() async {
safeCall(
call: () async => await rootLogic.chickenRepository.confirmAllAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationTokens: allocatedList.value.data?.results?.map((e) => e.key!).toList() ?? [],
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stacktrace) {},
);
}
Future<void> getRolesProducts() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getRolesProducts(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
rolesProductsModel.value = result;
selectedProductModel.value = result.first;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> getGuilds() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getGuilds(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
//queryParams: {'free': saleType.value == 2 ? true : false},
queryParams: {'all': true},
role: 'Steward',
),
),
onSuccess: (result) {
if (result != null) {
guildsModel.clear();
guildsModel.addAll(result);
}
},
onError: (error, stacktrace) {},
);
}
Future<void> addSale() async {}
void setSelectedGuild(GuildModel value) {
selectedGuildModel.value = value;
update();
}
void setSelectedProduct(ProductModel value) {
selectedProductModel.value = value;
update();
}
Future<void> getGuildProfile() async {
await safeCall(
call: () async => await rootLogic.chickenRepository.getProfile(
token: rootLogic.tokenService.accessToken.value!,
),
onError: (error, stackTrace) {},
onSuccess: (result) {
guildProfile.value = result;
},
);
}
void setSubmitData() {
tmpStewardAllocation = SubmitStewardAllocation(
approvedPriceStatus: priceType.value == 1,
allocationType:
'${guildProfile.value?.steward == true ? "steward" : "guild"}_${selectedGuildModel.value?.steward == true ? "steward" : "guild"}',
sellerType: guildProfile.value?.steward == true ? "Steward" : "Guild",
buyerType: selectedGuildModel.value?.steward == true ? "Steward" : "Guild",
amount: pricePerKilo.value,
totalAmount: totalCost.value,
weightOfCarcasses: weight.value,
sellType: saleType.value == 2 ? "free" : 'exclusive',
numberOfCarcasses: 0,
productionDate: productionDate.value?.toDateTime().formattedDashedGregorian,
quota: quotaType.value == 1 ? 'governmental' : 'free',
guildKey: selectedGuildModel.value?.key,
productKey: selectedProductModel.value?.key,
date: DateTime.now().formattedDashedGregorian,
type: "manual",
);
}
Future<void> submitAllocation() async {
setSubmitData();
safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.postSubmitStewardAllocation(
token: rootLogic.tokenService.accessToken.value!,
request: tmpStewardAllocation!,
),
onSuccess: (result) {
clearForm();
onRefresh();
rootLogic.onRefresh();
Future.delayed(Duration(seconds: 1), () => defaultShowSuccessMessage("ثبت موفق بود"));
Get.back();
},
onError: (error, stackTrace) {},
);
}
Future<void> deleteAllocation(AllocatedMadeModel model) async {
safeCall(
call: () async => await rootLogic.chickenRepository.deleteStewardAllocation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: {'steward_allocation_key': model.key},
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stackTrace) {},
);
}
@override
void dispose() {
rootLogic.inventoryExpandedList.clear();
stopFlashing();
super.dispose();
}
void setEditData(AllocatedMadeModel item) {
selectedAllocationModelForUpdate.value = item;
selectedProductModel.value = rolesProductsModel.first;
selectedGuildModel.value = GuildModel(guildsName: 'tst');
weight.value = item.weightOfCarcasses ?? 0;
pricePerKilo.value = item.amount ?? 0;
totalCost.value = item.totalAmount ?? 0;
weightController.text = weight.value.toString().separatedByComma;
pricePerKiloController.text = pricePerKilo.value.toString().separatedByComma;
totalCostController.text = totalCost.value.toString().separatedByComma;
isValid.value = true;
productionDate.value = item.productionDate.toJalali;
}
void clearForm() {
selectedGuildModel.value = null;
weight.value = 0;
totalCost.value = 0;
weightController.clear();
if (broadcastPrice.value?.active == false) {
pricePerKilo.value = 0;
pricePerKiloController.clear();
}
totalCostController.clear();
isValid.value = false;
productionDate.value = null;
quotaType.value = 1;
priceType.value = 2;
saleType.value = 2;
}
Future<void> updateAllocation() async {
ConformAllocation updatedAllocationModel = ConformAllocation(
allocation_key: selectedAllocationModelForUpdate.value?.key,
amount: pricePerKilo.value,
total_amount: totalCost.value,
number_of_carcasses: 0,
weight_of_carcasses: weight.value,
);
safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.updateStewardAllocation(
token: rootLogic.tokenService.accessToken.value!,
request: updatedAllocationModel,
),
onSuccess: (result) {
clearForm();
onRefresh();
rootLogic.onRefresh();
Future.delayed(Duration(seconds: 1), () => defaultShowSuccessMessage("ویرایش موفق بود"));
Get.back();
},
onError: (error, stackTrace) {},
);
}
void setSearchValue(String? data) {
searchedValue.value = data?.trim();
}
void flashingFabBgColor() {
_flashingTimer?.cancel();
_flashingTimer = Timer.periodic(Duration(seconds: 2), (timer) {
if (bgConfirmAllColor.value == AppColor.blueNormal) {
bgConfirmAllColor.value = AppColor.blueLightHover;
} else {
bgConfirmAllColor.value = AppColor.blueNormal;
}
});
}
void stopFlashing() {
_flashingTimer?.cancel();
_flashingTimer = null;
bgConfirmAllColor.value = AppColor.blueNormal; // بازگرداندن به رنگ پیش‌فرض
}
Steward? getBuyerInformation(AllocatedMadeModel model) {
if (model.allocationType?.buyerIsGuild ?? false) {
return model.toGuilds;
} else {
return model.steward;
}
}
Future<void> getBroadcastPrice() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getBroadcastPrice(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
broadcastPrice.value = result;
if (broadcastPrice.value?.active == true) {
pricePerKilo.value = broadcastPrice.value?.stewardPrice ?? 0;
pricePerKiloController.text = pricePerKilo.value.toString().separatedByComma;
priceType.value = 2;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> onRefresh() async {
toggleExpansion();
currentPage.value = 1;
hasMoreDataAllocationsMade.value = true;
await Future.wait([getAllocatedMade(), getRolesProducts(), rootLogic.onRefresh()]);
}
void toggleExpansion({int? index}) {
if (expandedListIndex.value == index || index == null) {
expandedListIndex.value = -1;
} else {
expandedListIndex.value = index;
}
}
}

View File

@@ -0,0 +1,488 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/allocated_made/allocated_made.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_in_province/widgets/cu_sale_in_provience.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/utils/string_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/steward/inventory_widget.dart';
import 'package:rasadyar_core/core.dart' hide modalDatePicker;
import 'logic.dart';
class SalesInProvincePage extends GetView<SalesInProvinceLogic> {
SalesInProvincePage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
routes: controller.routesName,
backId: stewardSecondKey,
onSearchChanged: (data) => controller.setSearchValue(data),
onRefresh: controller.onRefresh,
onFilterTap: () {
Get.bottomSheet(filterBottomSheet());
},
child: Stack(
children: [
Positioned.fill(
child: Column(
children: [
inventoryWidget(controller.rootLogic),
Expanded(
child: ObxValue((data) {
return RPaginatedListView(
listType: ListType.separated,
resource: data.value,
hasMore: data.value.data?.next != null,
isPaginating: controller.isLoadingMoreAllocationsMade.value,
onLoadMore: () async {
controller.currentPage.value++;
await controller.getAllocatedMade(true);
},
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.value == index,
onTap: () => controller.toggleExpansion(index: index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.timerSvg.path,
labelIconColor: item.registrationCode == null
? AppColor.darkGreyDark
: AppColor.error,
);
}, controller.expandedListIndex);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
);
}, controller.allocatedList),
),
],
),
),
Positioned(
right: 5,
bottom: 95,
child: SizedBox(
width: Get.width - 30,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RFab.add(
onPressed: () {
Get.bottomSheet(
addOrEditBottomSheet(controller),
isScrollControlled: true,
backgroundColor: Colors.transparent,
).whenComplete(() {
controller.clearForm();
});
},
),
ObxValue((data) {
return Visibility(
visible: (data.value.data?.results?.length ?? 0) > 1,
child: AnimatedFab(
onPressed: () async {
Get.defaultDialog(
title: 'تایید یکجا',
middleText: 'آیا از تایید تمامی تخصیص ها اطمینان دارید؟',
confirm: ElevatedButton(
onPressed: () async {
await controller.confirmAllAllocations();
controller.getAllocatedMade();
controller.rootLogic.getInventory();
Get.back();
},
child: Text('تایید'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.blueNormal,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
cancel: OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.error,
enableFeedback: true,
side: BorderSide(color: AppColor.error, width: 1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: () {
Get.back();
},
child: Text('لغو'),
),
);
},
message: 'تایید یکجا',
icon: Assets.vec.clipboardTaskSvg.svg(width: 40.w, height: 40.h),
backgroundColor: controller.bgConfirmAllColor.value,
),
);
}, controller.allocatedList),
],
),
),
),
],
),
);
}
Row itemListWidget(AllocatedMadeModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 20),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
controller.getBuyerInformation(item)?.user?.fullname ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
SizedBox(height: 2),
Text(
item.createDate?.formattedJalaliDate ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 6,
children: [
Visibility(
visible: item.product?.name?.contains('مرغ گرم') ?? false,
child: Assets.vec.hotChickenSvg.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
Text(
item.weightOfCarcasses!.separatedByCommaFa.addKg,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,
style: AppFonts.yekan12Bold.copyWith(color: AppColor.blueNormal),
),
],
),
SizedBox(height: 2),
Text(
item.amount.separatedByCommaFa.addReal,
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.darkGreyDark),
),
],
),
),
SizedBox(width: 8),
Expanded(
flex: 2,
child: Column(
spacing: 3,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.approvedPriceStatus == true
? 'دولتی'
: item.approvedPriceStatus == false
? 'آزاد'
: '-',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(
color: item.approvedPriceStatus == true
? AppColor.blueNormal
: AppColor.greenNormal,
),
),
],
),
),
SizedBox(width: 8),
],
);
}
Container itemListExpandedWidget(AllocatedMadeModel item, int index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
controller.getBuyerInformation(item)?.user?.fullname ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
Spacer(),
Text(
item.registrationCode == null ? 'در انتظار' : 'در انتظار تایید خریدار',
textAlign: TextAlign.center,
style: AppFonts.yekan10.copyWith(
color: item.registrationCode == null ? AppColor.darkGreyDark : AppColor.error,
),
),
SizedBox(width: 7),
Assets.vec.clockSvg.svg(
width: 16.w,
height: 16.h,
colorFilter: ColorFilter.mode(
item.registrationCode == null ? AppColor.darkGreyDark : AppColor.error,
BlendMode.srcIn,
),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'ندارد',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.d} ${item.date?.toJalali.formatter.mN ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(
title: 'تلفن خریدار',
value: controller.getBuyerInformation(item)?.user?.mobile ?? 'ندارد',
valueStyle: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
buildRow(title: 'محصول', value: item.product?.name ?? 'ندارد'),
buildRow(
title: 'تاریخ تولید گوشت',
value: item.productionDate?.toJalali.formatCompactDate() ?? 'ندارد',
),
buildRow(title: 'نوع تخصیص', value: item.allocationType?.faAllocationType ?? 'ندارد'),
buildRow(
title: 'نوع فروش',
value: (item.approvedPriceStatus ?? false) ? 'دولتی' : 'آزاد',
),
buildRow(title: 'نوع انبار', value: (item.quota == 'governmental') ? 'دولتی' : 'آزاد'),
buildRow(
title: 'وزن خریداری شده',
value: '${item.weightOfCarcasses?.separatedByCommaFa} کیلوگرم',
),
buildRow(
title: 'افت وزن(کیلوگرم)',
value: item.weightLossOfCarcasses?.toInt().toString() ?? 'ندارد',
),
buildRow(
title: 'قیمت هر کیلوگرم',
titleLabel: (item.approvedPriceStatus ?? false) ? 'دولتی' : 'آزاد',
titleLabelStyle: AppFonts.yekan14Bold.copyWith(
color: (item.approvedPriceStatus ?? false)
? AppColor.blueNormal
: AppColor.greenNormal,
),
value: '${item.amount?.separatedByCommaFa} ریال',
),
buildRow(title: 'قیمت کل', value: '${item.totalAmount?.separatedByCommaFa} ریال'),
buildRow(title: 'کداحراز', value: item.registrationCode?.toString() ?? 'ندارد'),
buildRow(
title: 'وضعیت کد احراز',
value: item.systemRegistrationCode == true ? "ارسال شده" : "ارسال نشده",
),
Visibility(
visible: item.registrationCode == null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditData(item);
Get.bottomSheet(
addOrEditBottomSheet(controller, isEditMode: true),
isScrollControlled: true,
backgroundColor: Colors.transparent,
).whenComplete(() {
controller.clearForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () async {
controller.toggleExpansion(index: index);
await controller.deleteAllocation(item);
},
onRefresh: controller.onRefresh,
);
},
borderColor: AppColor.redNormal,
),
],
),
),
],
),
);
}
Widget filterBottomSheet() {
return BaseBottomSheet(
height: 200,
child: Column(
spacing: 16,
children: [
Text('فیلترها', style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)),
Row(
spacing: 8,
children: [
Expanded(
child: timeFilterWidget(
controller: controller,
date: controller.fromDateFilter,
onChanged: (jalali) => controller.fromDateFilter.value = jalali,
),
),
Expanded(
child: timeFilterWidget(
controller: controller,
isFrom: false,
date: controller.toDateFilter,
onChanged: (jalali) => controller.toDateFilter.value = jalali,
),
),
],
),
RElevated(
text: 'اعمال فیلتر',
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
onPressed: () {
controller.getAllocatedMade();
Get.back();
},
height: 40,
),
],
),
);
}
GestureDetector timeFilterWidget({
required SalesInProvinceLogic controller,
isFrom = true,
required Rx<Jalali> date,
required Function(Jalali jalali) onChanged,
}) {
return GestureDetector(
onTap: () {
Get.bottomSheet(modalDatePicker((value) => onChanged(value)));
},
child: Container(
height: 35,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.blueNormal),
),
padding: EdgeInsets.symmetric(horizontal: 11, vertical: 4),
child: Row(
spacing: 8,
children: [
Assets.vec.calendarSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
Text(
isFrom ? 'از' : 'تا',
style: AppFonts.yekan16.copyWith(color: AppColor.blueNormal),
),
Expanded(
child: ObxValue((data) {
return Text(
date.value.formatCompactDate(),
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.lightGreyNormalActive),
);
}, date),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,515 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_in_province/logic.dart';
import 'package:rasadyar_core/core.dart';
Widget addOrEditBottomSheet(SalesInProvinceLogic controller, {bool isEditMode = false}) {
return BaseBottomSheet(
height: Get.height * (isEditMode ? 0.60 : 0.75),
child: Form(
key: controller.formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${isEditMode ? 'ویرایش' : 'ثبت'} توزیع/ فروش',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
const SizedBox(height: 12),
productDropDown(controller),
const SizedBox(height: 12),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
const SizedBox(height: 8),
ObxValue((data) {
return RTextField(
controller: TextEditingController(),
filledColor: AppColor.bgLight,
filled: true,
label: 'تاریخ',
onTap: () {
Get.bottomSheet(
modalDatePicker((value) {
controller.fromDateFilter.value = value;
controller.fromDateFilter.refresh();
}),
);
},
borderColor: AppColor.darkGreyLight,
initText: (data.value).formatCompactDate(),
);
}, controller.fromDateFilter),
Visibility(
visible: isEditMode == false,
child: Container(
height: 50.h,
clipBehavior: Clip.none,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Positioned(
child: Container(color: Colors.white, child: Text("انبار")),
top: -10,
right: 8,
),
Obx(() {
return RadioGroup(
groupValue: controller.quotaType.value,
onChanged: (value) {
controller.quotaType.value = value ?? 0;
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: GestureDetector(
onTap: () {
controller.quotaType.value = 1;
},
child: Row(
children: [
Radio(value: 1),
Text('دولتی', style: AppFonts.yekan14),
],
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
controller.quotaType.value = 2;
},
child: Row(
children: [
Radio(value: 2),
Text('آزاد', style: AppFonts.yekan14),
],
),
),
),
],
),
);
}),
],
),
),
),
Obx(() {
return MonthlyDataCalendar(
label: 'تاریخ تولید گوشت',
selectedDate: controller.productionDate.value?.formatCompactDate(),
onDateSelect: (value) {
controller.productionDate.value = value.date;
controller.remainingStock.value = value.remainingStock;
},
dayData: controller.quotaType.value == 1
? controller.governmentalProductionDateData
: controller.freeProductionDateData,
);
}),
Visibility(visible: isEditMode == false, child: guildsDropDown(controller)),
RTextField(
controller: controller.weightController,
keyboardType: TextInputType.number,
autoValidateMode: AutovalidateMode.onUserInteraction,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
validator: (value) {
if ((int.tryParse(value?.clearComma ?? '0') ?? 0) >
(controller.remainingStock.value ?? 0)) {
return 'وزن تخصیصی بیشتر از موجودی انبار است';
}
return null;
},
onChanged: (p0) {
controller.weight.value = int.tryParse(p0.clearComma) ?? 0;
},
label: 'وزن لاشه (کیلوگرم)',
),
Visibility(
visible: isEditMode == false,
child: Container(
height: 58.h,
clipBehavior: Clip.none,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Positioned(
child: Container(color: Colors.white, child: Text("فروش")),
top: -10,
right: 8,
),
Obx(() {
return RadioGroup(
groupValue: controller.priceType.value,
onChanged: (value) {
controller.priceType.value = value!;
},
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: (controller.broadcastPrice.value?.active ?? false)
? () {
controller.priceType.value = 1;
}
: null,
child: Row(
children: [
Radio(
value: 1,
enabled: controller.broadcastPrice.value?.active ?? false,
),
Text(
'قیمت مصوب',
style: AppFonts.yekan14.copyWith(
color:
(controller.broadcastPrice.value?.active ?? false)
? AppColor.textColor
: AppColor.labelTextColor,
),
),
],
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
controller.priceType.value = 2;
},
child: Row(
children: [
Radio(value: 2),
Text('قیمت آزاد', style: AppFonts.yekan14),
],
),
),
),
],
),
);
}),
],
),
),
),
ObxValue((data) {
return RTextField(
variant: RTextFieldVariant.noBorder,
controller: controller.pricePerKiloController,
borderColor: AppColor.darkGreyLight,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
filledColor: AppColor.bgLight,
filled: true,
readonly: data.value == 1,
onChanged: (p0) {
controller.pricePerKilo.value = int.tryParse(p0.clearComma) ?? 0;
},
keyboardType: TextInputType.number,
label: 'قیمت هر کیلو (ريال)',
);
}, controller.priceType),
RTextField(
variant: RTextFieldVariant.noBorder,
enabled: false,
keyboardType: TextInputType.number,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
controller: controller.totalCostController,
label: 'هزینه کل (ريال)',
),
],
),
),
SizedBox(height: 12.h),
ObxValue((data) {
return RElevated(
text: isEditMode ? 'ویرایش' : 'ثبت',
isFullWidth: true,
textStyle: AppFonts.yekan16.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
height: 40,
enabled: data.value,
onPressed: isEditMode
? () async {
await controller.updateAllocation();
Get.back();
}
: () async {
await controller.submitAllocation();
Get.back();
},
);
}, controller.isValid),
const SizedBox(height: 20),
],
),
),
);
}
Widget guildsDropDown(SalesInProvinceLogic controller) {
return Obx(() {
final item = controller.selectedGuildModel.value;
return SearchableDropdown(
onChanged: (value) {
controller.selectedGuildModel.value = value;
},
selectedItem: [?item],
singleSelect: false,
items: controller.guildsModel,
hintText: 'انتخاب مباشر/صنف',
itemBuilder: (item) => Text(
item.user != null
? '${item.steward == true ? 'مباشر' : 'صنف'} ${item.user!.fullname} (${item.user!.mobile})'
: 'بدون نام',
),
multiLabelBuilder: (item) => Container(
decoration: BoxDecoration(
color: AppColor.bgLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight),
),
padding: EdgeInsets.all(4),
child: Row(
children: [
Text(
item?.user != null
? '${item?.steward == true ? 'مباشر' : 'صنف'} ${item?.user!.fullname}'
: 'بدون نام',
style: AppFonts.yekan14,
),
SizedBox(width: 4.w),
Icon(Icons.close, size: 16, color: AppColor.labelTextColor),
],
),
),
onSearch: (query) async {
return Future.microtask(() {
return RxList(
controller.guildsModel
.where((element) => element.user?.fullname?.contains(query) ?? false)
.toList(),
);
});
},
);
});
}
Widget productDropDown(SalesInProvinceLogic controller) {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProductModel.value = value;
},
selectedItem: controller.selectedProductModel.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
ObxValue((data) {
return Visibility(visible: data.value != null, child: Text('موجودی: $data'));
}, controller.remainingStock),
],
),
);
});
}
Container modalDatePicker(ValueChanged<Jalali> onDateSelected) {
Jalali? tempPickedDate;
return Container(
height: 250,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: Row(
children: [
SizedBox(width: 20),
RElevated(
height: 35,
width: 70,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'تایید',
),
Spacer(),
RElevated(
height: 35,
width: 70,
backgroundColor: AppColor.error,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'لغو',
),
SizedBox(width: 20),
],
),
),
Divider(height: 0, thickness: 1),
Expanded(
child: Container(
child: PersianCupertinoDatePicker(
initialDateTime: Jalali.now(),
minimumDate: Jalali.now().add(days: -1),
maximumDate: Jalali.now(),
mode: PersianCupertinoDatePickerMode.date,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}
Widget show2StepAddBottomSheet(SalesInProvinceLogic controller) {
return BaseBottomSheet(
height: Get.height * .39,
child: Column(
spacing: 8,
children: [
buildRow(
title: 'تاریخ ثبت',
value: controller.tmpStewardAllocation?.date?.formattedJalaliDate ?? 'ندارد',
),
buildRow(
title: 'نام و نام خانوادگی خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.fullname ??
'ندارد',
),
buildRow(
title: 'شماره خریدار',
value:
controller.guildsModel
.firstWhere((p0) => p0.key == controller.tmpStewardAllocation?.guildKey)
.user
?.mobile ??
'ندارد',
),
buildRow(
title: 'قیمت هر کیلو',
value: '${controller.tmpStewardAllocation?.amount.separatedByCommaFa ?? 0} ریال ',
),
buildRow(
title: 'وزن تخصیصی',
value:
'${controller.tmpStewardAllocation?.weightOfCarcasses?.toInt().separatedByCommaFa ?? 0} کیلوگرم',
),
buildRow(
title: 'قیمت کل',
value: '${controller.tmpStewardAllocation?.totalAmount.separatedByCommaFa ?? 0} ریال',
),
Row(
spacing: 10,
children: [
Expanded(
child: RElevated(
backgroundColor: AppColor.greenNormal,
height: 40,
text: 'ثبت',
textStyle: AppFonts.yekan18.copyWith(color: Colors.white),
onPressed: () async {
await controller.submitAllocation();
Get
..back()
..back();
},
),
),
Expanded(
child: ROutlinedElevated(
height: 40,
borderColor: AppColor.error,
text: ' بازگشت',
textStyle: AppFonts.yekan18.copyWith(color: AppColor.error),
onPressed: () {
Get
..back()
..back();
},
),
),
],
),
],
),
);
}

View File

@@ -0,0 +1,382 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/steward_free_sale_bar/steward_free_sale_bar_request.dart';
import 'package:rasadyar_chicken/data/models/response/broadcast_price/broadcast_price.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/out_province_carcasses_buyer/out_province_carcasses_buyer.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_sale_bar/steward_free_sale_bar.dart';
import 'package:rasadyar_chicken/data/models/response/steward_remain_weight/steward_remain_weight.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province_buyers/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province_sales_list/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SalesOutOfProvinceLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
SaleLogic saleLogic = Get.find<SaleLogic>();
SalesOutOfProvinceSalesListLogic saleListLogic = Get.find<SalesOutOfProvinceSalesListLogic>();
SalesOutOfProvinceBuyersLogic buyerLogic = Get.find<SalesOutOfProvinceBuyersLogic>();
RxBool isExpanded = false.obs;
RxInt currentPage = 1.obs;
RxBool isSaleSubmitButtonEnabled = false.obs;
RxInt expandedListIndex = (-1).obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxList<String> routesName = RxList();
RxBool isLoadingMoreAllocationsMade = false.obs;
Rxn<IranProvinceCityModel> selectedCity = Rxn();
Rxn<BroadcastPrice> broadcastPrice = Rxn<BroadcastPrice>();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController quarantineCodeController = TextEditingController();
TextEditingController saleWeightController = TextEditingController();
TextEditingController saleCountController = TextEditingController();
TextEditingController pricePerKiloController = TextEditingController();
TextEditingController totalCostController = TextEditingController();
TextEditingController otpCodeSell = TextEditingController();
Rx<Jalali> saleDate = Jalali.now().obs;
String? key;
RxInt pricePerKilo = 0.obs;
RxInt totalCost = 0.obs;
RxInt weight = 0.obs;
RxString otpCode = ''.obs;
Rx<Resource<PaginationModel<StewardFreeSaleBar>>> salesList =
Resource<PaginationModel<StewardFreeSaleBar>>.loading().obs;
Rxn<ProductModel> selectedProduct = Rxn();
Rxn<OutProvinceCarcassesBuyer> selectedBuyer = Rxn();
RxInt saleType = 2.obs;
RxInt quotaType = 1.obs;
Rxn<Jalali> productionDate = Rxn();
Rxn<int> remainingStock = Rxn(null);
Map<String, DayData> freeProductionDateData = {};
Map<String, DayData> governmentalProductionDateData = {};
@override
void onInit() {
super.onInit();
routesName.value = [...saleLogic.routesName, 'خارج استان'].toList();
}
@override
void onReady() {
super.onReady();
getOutProvinceSales();
getBroadcastPrice();
selectedProduct.value = rootLogic.rolesProductsModel.first;
debounce(
searchedValue,
(callback) => getOutProvinceSales(),
time: Duration(milliseconds: timeDebounce),
);
setupListeners();
_updateGovernmentalProductionDateData();
_updateFreeProductionDateData();
ever(rootLogic.stewardRemainWeight, (callback) {
_updateGovernmentalProductionDateData();
_updateFreeProductionDateData();
});
ever(quotaType, (_) {
remainingStock.value = null;
productionDate.value = null;
});
debounce(pricePerKilo, time: Duration(milliseconds: 100), (callback) {
totalCost.value = callback * (weight.value);
});
ever(totalCost, (callback) {
totalCostController.text = callback.separatedByComma;
});
}
void _updateGovernmentalProductionDateData() {
List<RemainWeightDay> dates = rootLogic.stewardRemainWeight.value?.governmental ?? [];
governmentalProductionDateData = {
for (var element in dates)
element.day.toString().toJalali.formatCompactDate(): DayData(
value: element.amount?.toInt(),
),
};
}
void _updateFreeProductionDateData() {
var dates = rootLogic.stewardRemainWeight.value?.free ?? [];
freeProductionDateData = {
for (var element in dates)
element.day.toString().toJalali.formatCompactDate(): DayData(
value: element.amount?.toInt(),
),
};
}
void setSearchValue(String? value) {
searchedValue.value = value?.trim();
}
void submitFilter() {
fromDateFilter.value = fromDateFilter.value;
toDateFilter.value = toDateFilter.value;
getOutProvinceSales();
}
Future<void> getOutProvinceSales([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
salesList.value = Resource<PaginationModel<StewardFreeSaleBar>>.loading();
}
await safeCall(
call: () => rootLogic.chickenRepository.getStewardFreeSaleBar(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 20,
page: currentPage.value,
state: 'buyer-list',
search: 'filter',
role: 'Steward',
value: searchedValue.value ?? '',
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) {
if ((res?.count ?? 0) == 0) {
salesList.value = Resource<PaginationModel<StewardFreeSaleBar>>.empty();
} else {
salesList.value = Resource<PaginationModel<StewardFreeSaleBar>>.success(
PaginationModel<StewardFreeSaleBar>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [...(salesList.value.data?.results ?? []), ...(res?.results ?? [])],
),
);
isLoadingMoreAllocationsMade.value = false;
}
},
);
}
void setupListeners() {
saleWeightController.addListener(checkSalesFormValid);
quarantineCodeController.addListener(checkSalesFormValid);
saleWeightController.addListener(() {
checkSalesFormValid();
weight.value = int.parse(saleWeightController.text.clearComma);
var res = (weight / selectedProduct.value!.weightAverage!.toInt()).round();
saleCountController.text = res.separatedByComma;
});
ever(selectedBuyer, (_) => checkSalesFormValid);
ever(selectedProduct, (_) => checkSalesFormValid);
ever(saleDate, (_) => checkSalesFormValid());
}
void checkSalesFormValid() {
isSaleSubmitButtonEnabled.value =
saleDate.value.toString().isNotEmpty &&
selectedProduct.value != null &&
selectedBuyer.value != null &&
saleWeightController.text.isNotEmpty &&
quarantineCodeController.text.isNotEmpty;
}
void setEditDataSales(StewardFreeSaleBar item) {
quarantineCodeController.text = item.clearanceCode ?? '';
saleWeightController.text = item.weightOfCarcasses?.toInt().toString() ?? '';
saleDate.value = Jalali.fromDateTime(DateTime.parse(item.date!));
selectedCity.value = IranProvinceCityModel(name: item.city);
selectedBuyer.value = buyerLogic.buyerList.value.data?.results?.firstWhere(
(element) => element.key == item.buyer?.key,
);
selectedProduct.value = rootLogic.rolesProductsModel.first;
key = item.key;
saleType.value = item.saleType == 'free' ? 2 : 1;
quotaType.value = item.quota == 'governmental' ? 1 : 2;
isSaleSubmitButtonEnabled.value = true;
productionDate.value = item.productionDate.toJalali;
pricePerKiloController.text = pricePerKilo.value.toString().separatedByComma;
totalCostController.text = totalCost.value.toString().separatedByComma;
}
Future<void> deleteStewardPurchaseOutOfProvince(String key) async {
await safeCall(
call: () => rootLogic.chickenRepository.deleteOutProvinceStewardFreeBar(
token: rootLogic.tokenService.accessToken.value!,
key: key,
),
);
}
Future<bool> createSale() async {
bool res = false;
StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest(
buyerKey: selectedBuyer.value?.key,
numberOfCarcasses: int.tryParse(saleCountController.text.clearComma),
weightOfCarcasses: int.tryParse(saleWeightController.text.clearComma),
date: saleDate.value.toDateTime().formattedDashedGregorian,
clearanceCode: quarantineCodeController.text,
productKey: selectedProduct.value?.key,
saleType: saleType.value == 2 ? 'free' : 'exclusive',
quota: quotaType.value == 1 ? 'governmental' : 'free',
productionDate: productionDate.value?.toDateTime().formattedDashedGregorian,
);
await safeCall(
showError: true,
call: () => rootLogic.chickenRepository.createOutProvinceStewardFreeBar(
token: rootLogic.tokenService.accessToken.value!,
body: requestBody,
),
onSuccess: (_) {
res = true;
onRefresh();
rootLogic.onRefresh();
Future.delayed(
Duration(seconds: 1),
() => defaultShowSuccessMessage("عملیات با موفقیت انجام شد"),
);
Get.back();
},
);
return res;
}
void clearSaleForm() {
quarantineCodeController.clear();
saleWeightController.clear();
saleDate.value = Jalali.now();
productionDate.value = null;
saleType.value = 2;
quotaType.value = 2;
selectedBuyer.value = null;
}
Future<bool> editSale() async {
bool res = false;
StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest(
numberOfCarcasses: int.tryParse(saleCountController.text.clearComma),
weightOfCarcasses: int.tryParse(saleWeightController.text.clearComma),
date: saleDate.value.toDateTime().formattedDashedGregorian,
clearanceCode: quarantineCodeController.text,
saleType: saleType.value == 2 ? 'free' : 'exclusive',
quota: quotaType.value == 1 ? 'governmental' : 'free',
key: key,
);
await safeCall(
call: () => rootLogic.chickenRepository.updateOutProvinceStewardFreeBar(
token: rootLogic.tokenService.accessToken.value!,
body: requestBody,
),
onSuccess: (_) {
res = true;
onRefresh();
Future.delayed(
Duration(seconds: 1),
() => defaultShowSuccessMessage("عملیات با موفقیت انجام شد"),
);
Get.back();
},
);
return res;
}
Future sendSaleOtpCode(StewardFreeSaleBar item) async {
StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest(
buyerKey: item.buyer?.key,
buyerName: item.buyer?.fullname,
buyerMobile: item.buyer?.mobile,
numberOfCarcasses: item.numberOfCarcasses,
weightOfCarcasses: item.weightOfCarcasses?.toInt(),
date: item.date,
clearanceCode: item.clearanceCode,
registerCode: otpCode.value,
saleType: item.saleType,
quota: item.quota,
role: "Steward",
key: item.key,
city: item.city,
province: item.province,
);
await safeCall(
showError: true,
call: () => rootLogic.chickenRepository.updateOutProvinceStewardFreeBar(
token: rootLogic.tokenService.accessToken.value!,
body: requestBody,
),
onSuccess: (_) {
onRefresh();
Future.delayed(
Duration(seconds: 1),
() => defaultShowSuccessMessage("عملیات با موفقیت انجام شد"),
);
Get.back();
},
);
}
void resetSubmitForm() {
selectedCity.value = null;
key = null;
}
Future<void> onRefresh() async {
toggleExpansion();
currentPage.value = 1;
resetSubmitForm();
clearSaleForm();
await rootLogic.onRefresh();
await getOutProvinceSales();
}
void toggleExpansion({int? index}) {
if (expandedListIndex.value == index || index == null) {
expandedListIndex.value = -1;
} else {
expandedListIndex.value = index;
}
}
Future<void> getBroadcastPrice() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getBroadcastPrice(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
broadcastPrice.value = result;
},
onError: (error, stacktrace) {},
);
}
void setSaleDate(Jalali value) {
saleDate.value = value;
saleDate.refresh();
dateErrorDialog();
}
void setProductionDate(DayInfo value) {
productionDate.value = value.date;
remainingStock.value = value.remainingStock;
dateErrorDialog();
}
void dateErrorDialog() {
if ((productionDate.value?.distanceTo(saleDate.value) ?? 0) >= 1) {
saleDate.value = Jalali.now();
Future.delayed(
Duration(milliseconds: 300),
() => defaultShowErrorMessage("تاریخ تولید نمی تواند قبل از تاریخ فروش باشد"),
);
}
}
}

View File

@@ -0,0 +1,225 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/out_province_carcasses_buyer/out_province_carcasses_buyer.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SalesOutOfProvinceBuyersLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
SaleLogic get saleLogic => Get.find<SaleLogic>();
SalesOutOfProvinceLogic get saleOutOfProvince => Get.find<SalesOutOfProvinceLogic>();
RxInt currentPage = 1.obs;
RxInt expandedListIndex = (-1).obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxBool isLoadingMoreAllocationsMade = false.obs;
RxBool isBuyerSubmitButtonEnabled = false.obs;
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController buyerNameController = TextEditingController();
TextEditingController buyerLastNameController = TextEditingController();
TextEditingController buyerPhoneController = TextEditingController();
TextEditingController buyerUnitNameController = TextEditingController();
String? key;
Rx<Resource<PaginationModel<OutProvinceCarcassesBuyer>>> buyerList =
Resource<PaginationModel<OutProvinceCarcassesBuyer>>.loading().obs;
RxList<String> routesName = RxList();
@override
void onInit() {
super.onInit();
routesName.value = [...saleLogic.routesName, 'خریداران'].toList();
getOutProvinceCarcassesBuyer();
}
@override
void onReady() {
super.onReady();
selectedProvince.listen((p0) => getCites());
debounce(
searchedValue,
(callback) => getOutProvinceCarcassesBuyer(),
time: Duration(milliseconds: timeDebounce),
);
setupListeners();
}
@override
void onClose() {
buyerNameController.dispose();
buyerLastNameController.dispose();
buyerPhoneController.dispose();
buyerUnitNameController.dispose();
selectedCity.value = null;
selectedProvince.value = null;
super.onClose();
}
Future<void> getOutProvinceCarcassesBuyer([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
buyerList.value = Resource<PaginationModel<OutProvinceCarcassesBuyer>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1; // Reset to first page if search value is set
}
await safeCall(
call: () => rootLogic.chickenRepository.getOutProvinceCarcassesBuyer(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 20,
page: currentPage.value,
state: 'buyer-list',
search: 'filter',
role: 'Steward',
value: searchedValue.value ?? '',
),
),
onError: (error, stackTrace) => isLoadingMoreAllocationsMade.value = false,
onSuccess: (res) {
if ((res?.count ?? 0) == 0) {
buyerList.value = Resource<PaginationModel<OutProvinceCarcassesBuyer>>.empty();
} else {
buyerList.value = Resource<PaginationModel<OutProvinceCarcassesBuyer>>.success(
PaginationModel<OutProvinceCarcassesBuyer>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [...(buyerList.value.data?.results ?? []), ...(res?.results ?? [])],
),
);
isLoadingMoreAllocationsMade.value = false;
}
},
);
}
void resetSubmitForm() {
buyerNameController.clear();
buyerLastNameController.clear();
buyerPhoneController.clear();
buyerUnitNameController.clear();
selectedProvince.value = null;
selectedCity.value = null;
}
Future<void> getCites() async {
await safeCall(
call: () =>
rootLogic.chickenRepository.getCity(provinceName: selectedProvince.value?.name ?? ''),
onSuccess: (result) {
if (result != null && result.isNotEmpty) {
cites.value = result;
}
},
);
}
void setupListeners() {
buyerNameController.addListener(checkBuyerFormValid);
buyerLastNameController.addListener(checkBuyerFormValid);
buyerPhoneController.addListener(checkBuyerFormValid);
buyerUnitNameController.addListener(checkBuyerFormValid);
ever(selectedProvince, (_) => checkBuyerFormValid());
ever(selectedCity, (_) => checkBuyerFormValid());
}
void checkBuyerFormValid() {
isBuyerSubmitButtonEnabled.value =
buyerNameController.text.isNotEmpty &&
buyerLastNameController.text.isNotEmpty &&
buyerPhoneController.text.isNotEmpty &&
buyerUnitNameController.text.isNotEmpty &&
selectedProvince.value != null &&
selectedCity.value != null;
}
Future<bool> createBuyer() async {
bool res = false;
if (!(formKey.currentState?.validate() ?? false)) {
return res;
}
await safeCall(
call: () async {
OutProvinceCarcassesBuyer buyer = OutProvinceCarcassesBuyer(
province: selectedProvince.value!.name,
city: selectedCity.value!.name,
firstName: buyerNameController.text,
lastName: buyerLastNameController.text,
unitName: buyerUnitNameController.text,
mobile: buyerPhoneController.text,
role: 'Steward',
);
await rootLogic.chickenRepository.createOutProvinceCarcassesBuyer(
token: rootLogic.tokenService.accessToken.value!,
body: buyer,
);
},
onSuccess: (result) {
getOutProvinceCarcassesBuyer();
resetSubmitForm();
res = true;
},
);
return res;
}
void setEditDataBuyer(OutProvinceCarcassesBuyer item) {
buyerNameController.text = item.firstName ?? '';
buyerLastNameController.text = item.lastName ?? '';
buyerUnitNameController.text = item.unitName ?? '';
buyerPhoneController.text = item.mobile ?? '';
selectedProvince.value = IranProvinceCityModel(name: item.province);
selectedCity.value = IranProvinceCityModel(name: item.city);
isBuyerSubmitButtonEnabled.value = true;
}
void setSearchValue(String? value) {
searchedValue.value = value?.trim();
}
void submitFilter() {
fromDateFilter.value = fromDateFilter.value;
toDateFilter.value = toDateFilter.value;
getOutProvinceCarcassesBuyer();
}
Future<void> onRefresh() async {
currentPage.value = 1;
await rootLogic.onRefresh();
await getOutProvinceCarcassesBuyer();
}
void toggleExpansion({int? index}) {
if (expandedListIndex.value == index || index == null) {
expandedListIndex.value = -1;
} else {
expandedListIndex.value = index;
}
}
}

View File

@@ -0,0 +1,350 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/out_province_carcasses_buyer/out_province_carcasses_buyer.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/filter_bottom_sheet.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalesOutOfProvinceBuyersPage extends GetView<SalesOutOfProvinceBuyersLogic> {
const SalesOutOfProvinceBuyersPage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
routes: controller.routesName,
backId: stewardSecondKey,
onRefresh: controller.onRefresh,
onSearchChanged: (data) => controller.setSearchValue(data),
filteringWidget: filterBottomSheet(),
child: Stack(
children: [
Positioned.fill(
child: Column(
children: [
Container(
width: Get.width,
height: 39,
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: AppColor.greenLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.textColor, width: 0.5),
),
alignment: Alignment.center,
child: Text(
'لیست خریداران خارج از استان',
style: AppFonts.yekan16.copyWith(color: AppColor.mediumGreyDarkHover),
),
),
Expanded(
child: ObxValue((data) {
return RPaginatedListView(
onLoadMore: () async => controller.getOutProvinceCarcassesBuyer(true),
hasMore: data.value.data?.next != null,
listType: ListType.separated,
resource: data.value,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.value == index,
onTap: () => controller.toggleExpansion(index: index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.userRaduisSvg.path,
);
}, controller.expandedListIndex);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
);
}, controller.buyerList),
),
],
),
),
Positioned(
bottom: 100,
child: RFab.add(
onPressed: () {
Get.bottomSheet(
addOrEditBuyerBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
).whenComplete(() => controller.resetSubmitForm());
},
),
),
],
),
);
}
Widget addOrEditBuyerBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
height: 600,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text(
isOnEdit ? 'ویرایش خریدار' : 'افزودن خریدار',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(spacing: 12, children: [_provinceWidget(), _cityWidget()]),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
RTextField(
controller: controller.buyerNameController,
label: 'نام خریدار',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.buyerLastNameController,
label: 'نام خانوادگی خریدار',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.buyerPhoneController,
label: 'تلفن خریدار',
keyboardType: TextInputType.phone,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
maxLength: 11,
validator: (value) {
if (value == null || value.isEmpty) {
return 'لطفاً شماره موبایل را وارد کنید';
}
// حذف کاماها برای اعتبارسنجی
String cleaned = value.replaceAll(',', '');
if (cleaned.length != 11) {
return 'شماره موبایل باید ۱۱ رقم باشد';
}
if (!cleaned.startsWith('09')) {
return 'شماره موبایل باید با 09 شروع شود';
}
return null;
},
),
RTextField(
controller: controller.buyerUnitNameController,
label: 'نام واحد',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
submitButtonWidget(isOnEdit),
],
),
),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
text: isOnEdit ? 'ویرایش' : 'ثبت',
onPressed: data.value
? () async {
var res = await controller.createBuyer();
if (res) {
Get.back();
}
}
: null,
height: 40,
);
}, controller.isBuyerSubmitButtonEnabled);
}
Widget _provinceWidget() {
return Obx(() {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: controller.rootLogic.provinces,
onChanged: (value) {
controller.selectedProvince.value = value;
print('Selected Product: ${value.name}');
},
selectedItem: controller.selectedProvince.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Text(item?.name ?? 'انتخاب استان'),
);
});
}
Widget _cityWidget() {
return ObxValue((data) {
return OverlayDropdownWidget<IranProvinceCityModel>(
items: data,
onChanged: (value) {
controller.selectedCity.value = value;
print('Selected Product: ${value.name}');
},
selectedItem: controller.selectedCity.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Text(item?.name ?? 'انتخاب شهر'),
);
}, controller.cites);
}
Padding itemListWidget(OutProvinceCarcassesBuyer item) {
return Padding(
padding: const EdgeInsets.only(right: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 8),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.buyer?.fullname ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
SizedBox(height: 2),
Text(
item.buyer?.mobile ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
Expanded(
flex: 2,
child: Text(
'${item.unitName}',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
),
Expanded(
flex: 2,
child: Text(
'${item.buyer?.province}\n${item.buyer?.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.darkGreyDark),
),
),
],
),
);
}
Container itemListExpandedWidget(OutProvinceCarcassesBuyer item) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'${item.province}-${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
SizedBox(),
],
),
buildRow(title: 'مشخصات خریدار', value: item.fullname ?? 'N/A'),
buildRow(title: 'نام واحد', value: item.unitName ?? 'N/A'),
buildRow(
title: 'تعداد درخواست ها',
value: '${item.requestsInfo?.numberOfRequests.separatedByCommaFa}',
),
buildRow(
title: 'حجم تقریبی',
value: '${item.requestsInfo?.totalQuantity.separatedByCommaFa}',
valueLabel: 'قطعه',
),
buildRow(
title: 'وزن',
value: '${item.requestsInfo?.totalWeight.separatedByCommaFa}',
valueLabel: 'کیلوگرم',
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditDataBuyer(item);
Get.bottomSheet(
addOrEditBuyerBottomSheet(true),
isScrollControlled: true,
).whenComplete(() => controller.resetSubmitForm());
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
],
),
],
),
);
}
Widget filterBottomSheet() => filterBottomSheetWidget(
fromDate: controller.fromDateFilter,
onChangedFromDate: (jalali) => controller.fromDateFilter.value = jalali,
toDate: controller.toDateFilter,
onChangedToDate: (jalali) => controller.toDateFilter.value = jalali,
onSubmit: () => controller.submitFilter(),
);
}

View File

@@ -0,0 +1,224 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/steward_free_sale_bar/steward_free_sale_bar_request.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/out_province_carcasses_buyer/out_province_carcasses_buyer.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_sale_bar/steward_free_sale_bar.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province_buyers/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SalesOutOfProvinceSalesListLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
SaleLogic saleLogic = Get.find<SaleLogic>();
SalesOutOfProvinceBuyersLogic buyerLogic = Get.find<SalesOutOfProvinceBuyersLogic>();
RxInt selectedSegmentIndex = 0.obs;
RxBool isExpanded = false.obs;
RxInt currentPage = 1.obs;
RxBool isSaleSubmitButtonEnabled = false.obs;
RxInt expandedListIndex = (-1).obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxList<String> routesName = RxList();
RxBool isLoadingMoreAllocationsMade = false.obs;
Rxn<IranProvinceCityModel> selectedCity = Rxn();
//TODO add this to Di
ImagePicker imagePicker = ImagePicker();
RxInt saleType = 1.obs;
RxInt quotaType = 1.obs;
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController quarantineCodeController = TextEditingController();
TextEditingController saleWeightController = TextEditingController();
TextEditingController saleCountController = TextEditingController();
Rx<Jalali> saleDate = Jalali.now().obs;
String? key;
Rx<Resource<PaginationModel<StewardFreeSaleBar>>> salesList =
Resource<PaginationModel<StewardFreeSaleBar>>.loading().obs;
Rxn<ProductModel> selectedProduct = Rxn();
Rxn<OutProvinceCarcassesBuyer> selectedBuyer = Rxn();
@override
void onInit() {
super.onInit();
getOutProvinceSales();
}
@override
void onReady() {
super.onReady();
selectedProduct.value = rootLogic.rolesProductsModel.first;
debounce(
searchedValue,
(callback) => getOutProvinceSales(),
time: Duration(milliseconds: timeDebounce),
);
}
@override
void onClose() {
// TODO: implement onClose
super.onClose();
}
Future<void> getOutProvinceSales([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
salesList.value = Resource<PaginationModel<StewardFreeSaleBar>>.loading();
}
await safeCall(
call: () => rootLogic.chickenRepository.getStewardFreeSaleBar(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 20,
page: currentPage.value,
state: 'buyer-list',
search: 'filter',
role: 'Steward',
value: searchedValue.value ?? '',
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (res) {
if ((res?.count ?? 0) == 0) {
salesList.value = Resource<PaginationModel<StewardFreeSaleBar>>.empty();
} else {
salesList.value = Resource<PaginationModel<StewardFreeSaleBar>>.success(
PaginationModel<StewardFreeSaleBar>(
count: res?.count ?? 0,
next: res?.next,
previous: res?.previous,
results: [...(salesList.value.data?.results ?? []), ...(res?.results ?? [])],
),
);
isLoadingMoreAllocationsMade.value = false;
}
},
);
}
void setupListeners() {
quarantineCodeController.addListener(checkSalesFormValid);
ever(selectedBuyer, (_) => checkSalesFormValid);
ever(selectedProduct, (_) => checkSalesFormValid);
ever(saleDate, (_) => checkSalesFormValid());
}
void checkSalesFormValid() {
isSaleSubmitButtonEnabled.value =
saleDate.value.toString().isNotEmpty &&
selectedProduct.value != null &&
selectedBuyer.value != null &&
saleWeightController.text.isNotEmpty &&
quarantineCodeController.text.isNotEmpty;
}
void setEditDataSales(StewardFreeSaleBar item) {
quarantineCodeController.text = item.clearanceCode ?? '';
saleWeightController.text = item.weightOfCarcasses?.toInt().toString() ?? '';
saleDate.value = Jalali.fromDateTime(DateTime.parse(item.date!));
selectedCity.value = IranProvinceCityModel(name: item.city);
selectedBuyer.value = buyerLogic.buyerList.value.data?.results?.firstWhere(
(element) => element.key == item.buyer?.key,
);
selectedProduct.value = rootLogic.rolesProductsModel.first;
key = item.key;
isSaleSubmitButtonEnabled.value = true;
}
Future<void> deleteStewardPurchaseOutOfProvince(String key) async {
//todo
/* await safeCall(
call: () => rootLogic.chickenRepository
.editStewardPurchasesOutSideOfTheProvince(
token: rootLogic.tokenService.accessToken.value!,
stewardFreeBarKey: key,
),
);*/
}
Future<bool> createSale() async {
bool res = false;
var tmpWight = int.tryParse(saleWeightController.text.clearComma);
var tmpCount = (tmpWight! / selectedProduct.value!.weightAverage!).round();
StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest(
buyerKey: selectedBuyer.value?.key,
numberOfCarcasses: tmpCount,
weightOfCarcasses: tmpWight,
date: saleDate.value.toDateTime().formattedDashedGregorian,
clearanceCode: quarantineCodeController.text,
productKey: selectedProduct.value?.key,
);
await safeCall(
call: () => rootLogic.chickenRepository.createOutProvinceStewardFreeBar(
token: rootLogic.tokenService.accessToken.value!,
body: requestBody,
),
onSuccess: (_) {
res = true;
},
);
return res;
}
void clearSaleForm() {
quarantineCodeController.clear();
saleWeightController.clear();
saleDate.value = Jalali.now();
selectedBuyer.value = null;
selectedProduct.value = null;
}
Future<bool> editSale() async {
bool res = false;
StewardFreeSaleBarRequest requestBody = StewardFreeSaleBarRequest(
numberOfCarcasses: 0,
weightOfCarcasses: int.tryParse(saleWeightController.text.clearComma),
date: saleDate.value.toDateTime().formattedDashedGregorian,
clearanceCode: quarantineCodeController.text,
key: key,
);
await safeCall(
call: () => rootLogic.chickenRepository.updateOutProvinceStewardFreeBar(
token: rootLogic.tokenService.accessToken.value!,
body: requestBody,
),
onSuccess: (_) {
res = true;
},
);
return res;
}
void resetSubmitForm() {
selectedCity.value = null;
selectedProduct.value = null;
key = null;
}
void toggleExpansion({int? index}) {
if (expandedListIndex.value == index || index == null) {
expandedListIndex.value = -1;
} else {
expandedListIndex.value = index;
}
}
}

View File

@@ -0,0 +1,529 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/out_province_carcasses_buyer/out_province_carcasses_buyer.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/steward_free_sale_bar/steward_free_sale_bar.dart';
import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalesOutOfProvinceSalesListPage
extends GetView<SalesOutOfProvinceSalesListLogic> {
const SalesOutOfProvinceSalesListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ObxValue((data) {
return RPaginatedListView(
onLoadMore: () async => controller.getOutProvinceSales(true),
hasMore: data.value.data?.next != null,
listType: ListType.separated,
resource: data.value,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.value == index,
onTap: () => controller.toggleExpansion(),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index),
labelColor: AppColor.blueLight,
labelIcon: Assets.vec.timerSvg.path,
labelIconColor: AppColor.yellowNormal2,
);
}, controller.expandedListIndex);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
);
}, controller.salesList),
floatingActionButton: Row(
children: [
RFab.add(
onPressed: () {
Get.bottomSheet(
addOrEditSaleBottomSheet(),
ignoreSafeArea: false,
isScrollControlled: true,
).whenComplete(() {
controller.clearSaleForm();
});
},
),
Spacer(),
RFab(
icon: Icon(
CupertinoIcons.person_add_solid,
color: Colors.white,
size: 35.w,
),
backgroundColor: AppColor.blueNormal,
onPressed: () {
Get.toNamed(StewardRoutes.salesOutOfProvinceBuyerSteward, id: 1);
},
),
SizedBox(width: 25),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
);
}
Row itemListWidget(StewardFreeSaleBar item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 12),
Expanded(
flex: 3,
child: Text(
item.date?.formattedJalaliDate ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
),
SizedBox(width: 4),
Expanded(
flex: 5,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.buyer?.fullname ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
SizedBox(height: 2),
Text(
item.buyer?.mobile ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
SizedBox(width: 4),
Expanded(
flex: 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8,
children: [
Text(
item.buyer?.unitName ?? 'ندارد',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
Text(
'${item.weightOfCarcasses?.separatedByCommaFa ?? 0}KG',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
],
);
}
Container itemListExpandedWidget(StewardFreeSaleBar item, int index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
spacing: 8,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'${item.province}-${item.city}',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: AppColor.greenDark),
),
],
),
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
item.date?.toJalali.formatter.wN ?? 'ندارد',
style: AppFonts.yekan14.copyWith(
color: AppColor.textColor,
),
),
Text(
'${item.date?.toJalali.formatter.d} ${item.date?.toJalali.formatter.mN ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(
color: AppColor.blueNormal,
),
),
],
),
Text(
'${item.date?.toJalali.formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${item.date?.toJalali.formatter.tHH}:${item.date?.toJalali.formatter.tMM ?? 'ندارد'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(
title: 'مشخصات خریدار',
value: item.buyer?.fullname ?? 'ندارد',
),
buildRow(title: 'تلفن خریدار', value: item.buyer?.mobile ?? 'ندارد'),
buildRow(title: 'نام واحد', value: item.buyer?.unitName ?? 'ندارد'),
buildRow(
title: 'وزن لاشه',
value: '${item.weightOfCarcasses?.separatedByCommaFa}',
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditDataSales(item);
Get.bottomSheet(
addOrEditSaleBottomSheet(true),
isScrollControlled: true,
).whenComplete(() {
controller.resetSubmitForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () async {
controller.toggleExpansion();
controller.deleteStewardPurchaseOutOfProvince(item.key!);
},
onRefresh: () => controller.getOutProvinceSales(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
Widget addOrEditSaleBottomSheet([bool isOnEdit = false]) {
return BaseBottomSheet(
height: 500.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 16,
children: [
Text(
isOnEdit ? 'ویرایش فروش' : 'افزودن فروش',
style: AppFonts.yekan16Bold.copyWith(
color: AppColor.blueNormal,
),
),
_productDropDown(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
Row(
spacing: 8,
children: [
Expanded(
child: timeFilterWidget(
date: controller.saleDate,
onChanged: (jalali) =>
controller.saleDate.value = jalali,
),
),
],
),
_buyerWidget(),
RTextField(
controller: controller.saleWeightController,
label: 'وزن لاشه',
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
validator: (value) {
if (value == null) {
return 'لطفاً وزن لاشه را وارد کنید';
}
return null;
},
),
RTextField(
controller: controller.saleCountController,
label: 'حجم تقریبی(قطعه)',
keyboardType: TextInputType.number,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
SeparatorInputFormatter(),
],
validator: (value) {
if (value == null) {
return 'لطفاً وزن لاشه را وارد کنید';
}
return null;
},
),
RTextField(
controller: controller.quarantineCodeController,
label: 'کد قرنطینه',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null) {
return 'لطفاً کد قرنطینه را وارد کنید';
}
return null;
},
),
submitButtonWidget(isOnEdit),
],
),
),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(bool isOnEdit) {
return ObxValue((data) {
return RElevated(
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
text: isOnEdit ? 'ویرایش' : 'ثبت',
onPressed: data.value
? () async {
var res = isOnEdit
? await controller.editSale()
: await controller.createSale();
if (res) {
controller.getOutProvinceSales();
controller.clearSaleForm();
Get.back();
}
}
: null,
height: 40,
);
}, controller.isSaleSubmitButtonEnabled);
}
Widget _buyerWidget() {
return Obx(() {
return OverlayDropdownWidget<OutProvinceCarcassesBuyer>(
items: controller.buyerLogic.buyerList.value.data?.results ?? [],
onChanged: (value) {
controller.selectedBuyer.value = value;
},
selectedItem: controller.selectedBuyer.value,
itemBuilder: (item) => Text(item.buyer?.fullname ?? 'بدون نام'),
labelBuilder: (item) => Text(item?.buyer?.fullname ?? 'انتخاب خریدار'),
);
});
}
Widget _productDropDown() {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rootLogic.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProduct.value = value;
},
selectedItem: controller.selectedProduct.value,
initialValue: controller.selectedProduct.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
Text(
'موجودی:${controller.rootLogic.inventoryModel.value?.totalRemainWeight.separatedByCommaFa ?? 0}',
),
],
),
);
});
}
GestureDetector timeFilterWidget({
isFrom = true,
required Rx<Jalali> date,
required Function(Jalali jalali) onChanged,
}) {
return GestureDetector(
onTap: () {
Get.bottomSheet(modalDatePicker((value) => onChanged(value)));
},
child: Container(
height: 40,
decoration: BoxDecoration(
color: AppColor.bgLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.darkGreyLight),
),
padding: EdgeInsets.symmetric(horizontal: 11, vertical: 4),
child: Row(
spacing: 8,
children: [
Assets.vec.calendarSvg.svg(
width: 24,
height: 24,
colorFilter: const ColorFilter.mode(
AppColor.bgDark,
BlendMode.srcIn,
),
),
Text(
'تاریخ',
style: AppFonts.yekan16.copyWith(color: AppColor.bgDark),
),
Expanded(
child: ObxValue((data) {
return Text(
date.value.formatCompactDate(),
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(
color: AppColor.darkGreyDark,
),
);
}, date),
),
],
),
),
);
}
Container modalDatePicker(ValueChanged<Jalali> onDateSelected) {
Jalali? tempPickedDate;
return Container(
height: 250,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: Row(
children: [
SizedBox(width: 20),
RElevated(
height: 35,
width: 70,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'تایید',
),
Spacer(),
RElevated(
height: 35,
width: 70,
backgroundColor: AppColor.error,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'لغو',
),
SizedBox(width: 20),
],
),
),
Divider(height: 0, thickness: 1),
Expanded(
child: Container(
child: PersianCupertinoDatePicker(
initialDateTime: controller.saleDate.value,
mode: PersianCupertinoDatePickerMode.date,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,309 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/broadcast_price/broadcast_price.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/data/models/response/segmentation_model/segmentation_model.dart';
import 'package:rasadyar_chicken/data/models/response/steward_remain_weight/steward_remain_weight.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart';
class SegmentationLogic extends GetxController {
StewardRootLogic rootLogic = Get.find<StewardRootLogic>();
RxBool isLoadingMoreAllocationsMade = false.obs;
RxInt currentPage = 1.obs;
late List<String> routesName;
RxInt selectedSegmentIndex = 0.obs;
RxBool isExpanded = false.obs;
RxInt expandedListIndex = (-1).obs;
Rx<Jalali> fromDateFilter = Jalali.now().obs;
Rx<Jalali> toDateFilter = Jalali.now().obs;
RxnString searchedValue = RxnString();
RxInt segmentType = 1.obs;
RxInt priceType = 2.obs;
RxInt quotaType = 2.obs;
GlobalKey<FormState> formKey = GlobalKey<FormState>();
TextEditingController weightController = TextEditingController(text: '0');
RxBool isSubmitButtonEnabled = false.obs;
Rxn<GuildModel> selectedGuildModel = Rxn<GuildModel>();
Rxn<ProductModel> selectedProduct = Rxn<ProductModel>();
Rxn<SegmentationModel> selectedSegment = Rxn<SegmentationModel>();
Rxn<BroadcastPrice> broadcastPrice = Rxn<BroadcastPrice>();
Rx<Resource<PaginationModel<SegmentationModel>>> segmentationList =
Resource<PaginationModel<SegmentationModel>>.loading().obs;
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
Rx<Jalali> saleDate = Jalali.now().obs;
RxInt weight = 0.obs;
Rxn<Jalali> productionDate = Rxn(null);
Rxn<int> remainingStock = Rxn(null);
Map<String, DayData> freeProductionDateData = {};
Map<String, DayData> governmentalProductionDateData = {};
@override
void onInit() {
super.onInit();
routesName = ['قطعه‌بندی'].toList();
once(rootLogic.rolesProductsModel, (callback) => selectedProduct.value = callback.first);
getAllSegmentation();
getGuilds();
ever(quotaType, (_) {
remainingStock.value = null;
productionDate.value = null;
});
_updateGovernmentalProductionDateData();
_updateFreeProductionDateData();
ever(rootLogic.stewardRemainWeight, (callback) {
_updateGovernmentalProductionDateData();
_updateFreeProductionDateData();
});
}
void _updateGovernmentalProductionDateData() {
List<RemainWeightDay> dates = rootLogic.stewardRemainWeight.value?.governmental ?? [];
governmentalProductionDateData = {
for (var element in dates)
element.day.toString().toJalali.formatCompactDate(): DayData(
value: element.amount?.toInt(),
),
};
}
void _updateFreeProductionDateData() {
var dates = rootLogic.stewardRemainWeight.value?.free ?? [];
freeProductionDateData = {
for (var element in dates)
element.day.toString().toJalali.formatCompactDate(): DayData(
value: element.amount?.toInt(),
),
};
}
@override
void onReady() {
super.onReady();
setUpListener();
}
void setSearchValue(String? value) {
searchedValue.value = value?.trim();
}
void setUpListener() {
debounce(
searchedValue,
(callback) => getAllSegmentation(),
time: Duration(milliseconds: timeDebounce),
);
everAll([selectedSegment, quotaType, priceType], (_) {
validateForm();
});
weightController.addListener(() => validateForm());
}
void setEditData(SegmentationModel item) {
selectedSegment.value = item;
weightController.text = item.weight.toString();
}
void clearForm() {
weightController.text = '0';
selectedSegment.value = null;
selectedGuildModel.value = null;
productionDate.value = null;
segmentType.value = 1;
priceType.value = 2;
quotaType.value = 1;
remainingStock.value = null;
}
void validateForm() {
var weight = int.tryParse(weightController.text.trim().clearComma) ?? 0;
var hasWeight = (remainingStock.value ?? 0) > weight;
isSubmitButtonEnabled.value =
selectedProduct.value != null &&
weightController.text.isNotEmpty &&
hasWeight &&
productionDate.value != null &&
weight > 0 &&
(segmentType.value == 1 || (segmentType.value == 2 && selectedGuildModel.value != null));
}
Future<void> getAllSegmentation([bool isLoadingMore = false]) async {
if (isLoadingMore) {
isLoadingMoreAllocationsMade.value = true;
} else {
segmentationList.value = Resource<PaginationModel<SegmentationModel>>.loading();
}
if (searchedValue.value != null &&
searchedValue.value!.trim().isNotEmpty &&
currentPage.value > 1) {
currentPage.value = 1; // Reset to first page if search value is set
}
await safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.getSegmentation(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
pageSize: 20,
page: currentPage.value,
search: 'filter',
role: 'Steward',
value: searchedValue.value,
fromDate: fromDateFilter.value.toDateTime(),
toDate: toDateFilter.value.toDateTime(),
),
),
onSuccess: (result) {
if ((result?.count ?? 0) == 0) {
segmentationList.value = Resource<PaginationModel<SegmentationModel>>.empty();
} else {
segmentationList.value = Resource<PaginationModel<SegmentationModel>>.success(
PaginationModel<SegmentationModel>(
count: result?.count ?? 0,
next: result?.next,
previous: result?.previous,
results: [
...(segmentationList.value.data?.results ?? []),
...(result?.results ?? []),
],
),
);
isLoadingMoreAllocationsMade.value = false;
}
},
);
}
Future<void> deleteSegmentation(String key) async {
await safeCall(
showError: true,
showSuccess: true,
call: () => rootLogic.chickenRepository.deleteSegmentation(
token: rootLogic.tokenService.accessToken.value!,
key: key,
),
);
}
Future<bool> editSegment() async {
var res = true;
safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.editSegmentation(
token: rootLogic.tokenService.accessToken.value!,
model: SegmentationModel(
key: selectedSegment.value?.key,
weight: int.tryParse(weightController.text.clearComma) ?? 0,
productionDate: productionDate.value?.toDateTime().formattedDashedGregorian,
),
),
onSuccess: (result) {
res = true;
onRefresh();
},
onError: (error, stacktrace) {
res = false;
},
);
return res;
}
Future<bool> createSegment() async {
var res = true;
SegmentationModel segmentationModel = SegmentationModel(
productKey: selectedProduct.value?.key,
weight: int.tryParse(weightController.text.clearComma) ?? 0,
saleType: priceType.value == 1 ? 'governmental' : 'free',
quota: quotaType.value == 1 ? 'governmental' : 'free',
);
if (segmentType.value == 2) {
segmentationModel = segmentationModel.copyWith(guildKey: selectedGuildModel.value?.key);
}
segmentationModel = segmentationModel.copyWith(
productionDate: productionDate.value?.toDateTime().formattedDashedGregorian,
);
await safeCall(
showError: true,
call: () async => await rootLogic.chickenRepository.createSegmentation(
token: rootLogic.tokenService.accessToken.value!,
model: segmentationModel,
),
onSuccess: (result) async {
res = true;
isSubmitButtonEnabled.value = false;
onRefresh();
Future.delayed(
Duration(seconds: 1),
() => defaultShowSuccessMessage("قطعه‌بندی با موفقیت ثبت شد!"),
);
Get.back();
},
onError: (error, stacktrace) {
res = false;
},
);
return res;
}
Future<void> getGuilds() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getGuilds(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(queryParams: {'all': true}, role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
guildsModel.clear();
guildsModel.addAll(result);
}
},
onError: (error, stacktrace) {},
);
}
Future<void> onRefresh() async {
toggleExpansion();
currentPage.value = 1;
await rootLogic.onRefresh();
await getAllSegmentation();
await getBroadcastPrice();
_updateFreeProductionDateData();
_updateGovernmentalProductionDateData();
}
Future<void> getBroadcastPrice() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getBroadcastPrice(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
broadcastPrice.value = result;
if (broadcastPrice.value?.active == true) {
priceType.value = 2;
}
},
onError: (error, stacktrace) {},
);
}
void toggleExpansion({int? index}) {
if (expandedListIndex.value == index || index == null) {
expandedListIndex.value = -1;
} else {
expandedListIndex.value = index;
}
}
}

View File

@@ -0,0 +1,285 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/response/segmentation_model/segmentation_model.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/segmentation/widgets/cu_bottom_sheet.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/filter_bottom_sheet.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SegmentationPage extends GetView<SegmentationLogic> {
final today = Jalali.now();
final oneDayAgo = Jalali.now().addDays(-1);
final twoDaysAgo = Jalali.now().addDays(-2);
@override
Widget build(BuildContext context) {
return ChickenBasePage(
routes: controller.routesName,
onSearchChanged: (data) => controller.setSearchValue(data),
onFilterTap: () {
Get.bottomSheet(filterBottomSheet());
},
onRefresh: controller.onRefresh,
hasBack: false,
child: Stack(
children: [
Positioned.fill(
child: ObxValue((data) {
return RPaginatedListView(
onLoadMore: () async => controller.getAllSegmentation(true),
hasMore: data.value.data?.next != null,
listType: ListType.separated,
resource: data.value,
padding: EdgeInsets.fromLTRB(8, 8, 8, 80),
itemBuilder: (context, index) {
var item = data.value.data!.results![index];
return ObxValue((val) {
return ExpandableListItem2(
selected: val.value == index,
onTap: () => controller.toggleExpansion(index: index),
index: index,
child: itemListWidget(item),
secondChild: itemListExpandedWidget(item, index),
labelColor: AppColor.blueLight,
labelIconColor: AppColor.customGrey,
labelIcon: Assets.vec.convertCubeSvg.path,
);
}, controller.expandedListIndex);
},
itemCount: data.value.data?.results?.length ?? 0,
separatorBuilder: (context, index) => SizedBox(height: 8.h),
);
}, controller.segmentationList),
),
Positioned(
right: 10,
bottom: 90.h,
child: RFab.add(
onPressed: () {
Get.bottomSheet(
addOrEditBottomSheet(controller),
isScrollControlled: true,
ignoreSafeArea: false,
).whenComplete(() {
controller.clearForm();
//defaultShowSuccessMessage('با موفقیت ثبت شد');
});
},
),
),
],
),
);
}
Widget filterBottomSheet() => filterBottomSheetWidget(
fromDate: controller.fromDateFilter,
onChangedFromDate: (jalali) => controller.fromDateFilter.value = jalali,
toDate: controller.toDateFilter,
onChangedToDate: (jalali) => controller.toDateFilter.value = jalali,
onSubmit: () => controller.getAllSegmentation(),
);
Row itemListWidget(SegmentationModel item) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(width: 12),
Expanded(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 4,
children: [
Text(
item.toGuild != null ? 'قطعه‌بند' : 'مباشر',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
Text(
item.date?.formattedJalaliDate ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan12.copyWith(color: AppColor.bgDark),
),
],
),
),
SizedBox(width: 4),
Expanded(
flex: 5,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.toGuild != null
? item.toGuild?.user?.fullname ?? 'N/A'
: item.buyer?.fullname ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
SizedBox(height: 2),
Text(
item.toGuild != null
? item.toGuild?.guildsName ?? 'N/A'
: item.buyer?.shop ?? 'N/A',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark),
),
],
),
),
SizedBox(width: 4),
Expanded(
flex: 2,
child: Column(
spacing: 4,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
item.weight.separatedByCommaFa.addKg,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: AppFonts.yekan14Bold.copyWith(color: AppColor.blueNormal),
),
Text(
item.saleType == "governmental" ? 'دولتی' : 'آزاد',
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
style: AppFonts.yekan14Bold.copyWith(
color: item.saleType == "governmental"
? AppColor.blueNormal
: AppColor.greenNormal,
),
),
],
),
),
],
);
}
Container itemListExpandedWidget(SegmentationModel item, int index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
child: Column(
spacing: 8,
children: [
Container(
height: 32,
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: ShapeDecoration(
color: AppColor.blueLight,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: AppColor.blueLightHover),
borderRadius: BorderRadius.circular(8),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 3,
children: [
Text(
DateTimeExtensions(item.date)?.toJalali().formatter.wN ?? 'N/A',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
Text(
'${DateTimeExtensions(item.date)?.toJalali().formatter.d} ${DateTimeExtensions(item.date)?.toJalali().formatter.mN ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
Text(
'${DateTimeExtensions(item.date)?.toJalali().formatter.y}',
style: AppFonts.yekan20.copyWith(color: AppColor.textColor),
),
Text(
'${DateTimeExtensions(item.date)?.toJalali().formatter.tHH}:${DateTimeExtensions(item.date)?.toJalali().formatter.tMM ?? 'N/A'}',
style: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
buildRow(
title: 'مشخصات خریدار',
value: item.toGuild != null
? item.toGuild?.user?.fullname ?? 'N/A'
: item.buyer?.fullname ?? 'N/A',
),
buildRow(
title: 'تلفن خریدار',
value: item.toGuild != null
? item.toGuild?.user?.mobile ?? 'N/A'
: item.buyer?.mobile ?? 'N/A',
),
buildRow(
title: 'نام واحد',
value: item.toGuild != null
? item.toGuild?.guildsName ?? 'N/A'
: item.buyer?.shop ?? 'N/A',
),
buildRow(title: 'ماهیت', value: item.toGuild != null ? 'قطعه‌بند' : 'مباشر'),
buildRow(title: 'نوع فروش', value: item.saleType == "governmental" ? 'دولتی' : 'آزاد'),
buildRow(title: 'انبار فروش', value: item.quota == "governmental" ? 'دولتی' : 'آزاد'),
buildRow(
title: 'تاریخ تولید گوشت',
value: item.productionDate?.toJalali.formatCompactDate() ?? 'ندارد',
),
buildRow(
title: 'وزن قطعه‌بندی',
value: item.weight!.separatedByCommaFa,
valueLabel: 'کیلوگرم',
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.w,
children: [
RElevated(
text: 'ویرایش',
width: 150.w,
height: 40.h,
onPressed: () {
controller.setEditData(item);
Get.bottomSheet(
addOrEditBottomSheet(controller, isOnEdit: true),
isScrollControlled: true,
ignoreSafeArea: false,
).whenComplete(() {
controller.clearForm();
});
},
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
backgroundColor: AppColor.greenNormal,
),
ROutlinedElevated(
text: 'حذف',
textStyle: AppFonts.yekan20.copyWith(color: AppColor.redNormal),
width: 150.w,
height: 40.h,
onPressed: () {
buildDeleteDialog(
onConfirm: () async {
controller.toggleExpansion();
controller.deleteSegmentation(item.key!);
},
onRefresh: () => controller.onRefresh(),
);
},
borderColor: AppColor.redNormal,
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,395 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/segmentation/logic.dart';
import 'package:rasadyar_core/core.dart';
Widget addOrEditBottomSheet(SegmentationLogic controller, {bool isOnEdit = false}) {
return BaseBottomSheet(
height: isOnEdit ? 350.h : 600.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 16,
children: [
Text(
isOnEdit ? 'ویرایش قطعه‌بندی' : 'افزودن قطعه‌بندی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
_productDropDown(controller),
Visibility(
visible: isOnEdit == false,
child: Container(
height: 50.h,
clipBehavior: Clip.none,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Positioned(
child: Container(color: Colors.white, child: Text("انبار")),
top: -10,
right: 8,
),
Obx(() {
return RadioGroup(
groupValue: controller.quotaType.value,
onChanged: (value) {
controller.quotaType.value = value ?? 0;
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: GestureDetector(
onTap: () {
controller.quotaType.value = 1;
},
child: Row(
children: [
Radio(value: 1),
Text('دولتی', style: AppFonts.yekan14),
],
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
controller.quotaType.value = 2;
},
child: Row(
children: [
Radio(value: 2),
Text('آزاد', style: AppFonts.yekan14),
],
),
),
),
],
),
);
}),
],
),
),
),
Obx(() {
return MonthlyDataCalendar(
label: 'تاریخ تولید گوشت',
selectedDate: controller.productionDate.value?.formatCompactDate(),
onDateSelect: (value) {
controller.productionDate.value = value.date;
controller.remainingStock.value = value.remainingStock;
},
dayData: controller.quotaType.value == 1
? controller.governmentalProductionDateData
: controller.freeProductionDateData,
);
}),
RTextField(
controller: controller.weightController,
keyboardType: TextInputType.number,
autoValidateMode: AutovalidateMode.onUserInteraction,
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
inputFormatters: [FilteringTextInputFormatter.digitsOnly, SeparatorInputFormatter()],
validator: (value) {
if ((int.tryParse(value?.clearComma ?? '0') ?? 0) >
(controller.remainingStock.value ?? 0)) {
return 'وزن تخصیصی بیشتر از موجودی انبار است';
}
return null;
},
onChanged: (p0) {
controller.weight.value = int.tryParse(p0.clearComma) ?? 0;
},
label: 'وزن لاشه (کیلوگرم)',
),
Visibility(
visible: isOnEdit == false,
child: Container(
height: 58.h,
clipBehavior: Clip.none,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Positioned(
child: Container(color: Colors.white, child: Text("فروش")),
top: -10,
right: 8,
),
Obx(() {
return RadioGroup(
groupValue: controller.priceType.value,
onChanged: (value) {
controller.priceType.value = value!;
},
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: (controller.broadcastPrice.value?.active ?? false)
? () {
controller.priceType.value = 1;
}
: null,
child: Row(
children: [
Radio(
value: 1,
enabled: controller.broadcastPrice.value?.active ?? false,
),
Text(
'قیمت مصوب',
style: AppFonts.yekan14.copyWith(
color: (controller.broadcastPrice.value?.active ?? false)
? AppColor.textColor
: AppColor.labelTextColor,
),
),
],
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
controller.priceType.value = 2;
},
child: Row(
children: [
Radio(value: 2),
Text('قیمت آزاد', style: AppFonts.yekan14),
],
),
),
),
],
),
);
}),
],
),
),
),
Visibility(
visible: isOnEdit == false,
child: Column(
spacing: 12,
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
children: [
const SizedBox(height: 8),
SizedBox(
height: 40,
child: ObxValue((data) {
return RadioGroup(
onChanged: (value) {
controller.segmentType.value = value!;
controller.selectedGuildModel.value = null;
controller.selectedGuildModel.refresh();
},
groupValue: controller.segmentType.value,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Radio(value: 1),
Text('قطعه‌بندی(کاربر)', style: AppFonts.yekan14),
SizedBox(width: 12),
Radio(value: 2),
Text('تخصیص به قطعه‌بند', style: AppFonts.yekan14),
],
),
);
}, controller.priceType),
),
const SizedBox(height: 12),
ObxValue((data) {
return Visibility(
visible: data.value == 2,
child: guildsDropDown(controller),
);
}, controller.segmentType),
],
),
),
],
),
),
submitButtonWidget(controller, isOnEdit: isOnEdit),
SizedBox(),
],
),
),
),
);
}
Widget submitButtonWidget(SegmentationLogic controller, {bool isOnEdit = false}) {
return ObxValue((data) {
return RElevated(
isFullWidth: true,
backgroundColor: AppColor.greenNormal,
text: isOnEdit ? 'ویرایش' : 'ثبت',
onPressed: data.value
? () async {
var res = isOnEdit
? await controller.editSegment()
: await controller.createSegment();
if (res) {
Get.back();
}
}
: null,
height: 40,
);
}, controller.isSubmitButtonEnabled);
}
Widget _productDropDown(SegmentationLogic controller) {
return Obx(() {
return OverlayDropdownWidget<ProductModel>(
items: controller.rootLogic.rolesProductsModel,
height: 56,
hasDropIcon: false,
background: Colors.white,
onChanged: (value) {
controller.selectedProduct.value = value;
},
selectedItem: controller.selectedProduct.value,
initialValue: controller.selectedProduct.value,
itemBuilder: (item) => Text(item.name ?? 'بدون نام'),
labelBuilder: (item) => Row(
spacing: 8,
children: [
(item?.name?.contains('مرغ گرم') ?? false)
? Assets.images.chicken.image(width: 40, height: 40)
: Assets.vec.placeHolderSvg.svg(width: 40, height: 40),
Text(item?.name ?? 'انتخاب محصول'),
Spacer(),
ObxValue((data) {
return Visibility(visible: data.value != null, child: Text('موجودی: $data'));
}, controller.remainingStock),
],
),
);
});
}
Widget guildsDropDown(SegmentationLogic controller) {
return Obx(() {
final item = controller.selectedGuildModel.value;
return OverlayDropdownWidget<GuildModel>(
key: ValueKey(item?.user?.fullname ?? ''),
items: controller.guildsModel,
onChanged: (value) {
controller.selectedGuildModel.value = value;
},
selectedItem: item,
itemBuilder: (item) => Text(
item.user != null
? '${item.steward == true ? 'مباشر' : 'صنف'} ${item.user!.fullname} (${item.user!.mobile})'
: 'بدون نام',
),
labelBuilder: (item) => Text(
item?.user != null
? '${item?.steward == true ? 'مباشر' : 'صنف'} ${item?.user!.fullname} (${item?.user!.mobile})'
: 'انتخاب مباشر/صنف',
),
);
});
}
Container modalDatePicker(SegmentationLogic controller, ValueChanged<Jalali> onDateSelected) {
Jalali currentDate = Jalali.now();
Jalali? tempPickedDate;
return Container(
height: 250,
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: Row(
children: [
SizedBox(width: 20),
RElevated(
height: 35,
width: 70,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'تایید',
),
Spacer(),
RElevated(
height: 35,
width: 70,
backgroundColor: AppColor.error,
textStyle: AppFonts.yekan14.copyWith(color: Colors.white),
onPressed: () {
onDateSelected(tempPickedDate ?? Jalali.now());
Get.back();
},
text: 'لغو',
),
SizedBox(width: 20),
],
),
),
Divider(height: 0, thickness: 1),
Expanded(
child: Container(
child: PersianCupertinoDatePicker(
initialDateTime: controller.saleDate.value,
mode: PersianCupertinoDatePickerMode.date,
maximumDate: currentDate.addDays(3),
minimumDate: currentDate.toDateTime().subtract(Duration(days: 1)).toString().toJalali,
maximumYear: currentDate.year,
minimumYear: currentDate.year,
onDateTimeChanged: (dateTime) {
tempPickedDate = dateTime;
},
),
),
),
],
),
);
}

View File

@@ -0,0 +1,135 @@
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province_all/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_in_province_waiting/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_out_of_province/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/buy_out_of_province/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/home/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/home/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/root/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sale/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_in_province/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_in_province/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province_buyers/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province_buyers/view.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/sales_out_of_province_sales_list/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/pages/segmentation/logic.dart';
import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/routes/global_binding.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart';
import 'package:rasadyar_core/core.dart';
class StewardPages {
StewardPages._();
static List<GetPage> get pages => [
//region Steward Pages
GetPage(
name: StewardRoutes.initSteward,
page: () => StewardRootPage(),
middlewares: [AuthMiddleware()],
bindings: [
GlobalBinding(),
BindingsBuilder(() {
Get.lazyPut(() => ChickenBaseLogic(), fenix: true);
Get.lazyPut(() => StewardRootLogic());
Get.lazyPut(() => HomeLogic());
Get.lazyPut(() => BuyLogic());
Get.lazyPut(() => SaleLogic());
Get.lazyPut(() => SegmentationLogic());
}),
],
),
GetPage(
name: StewardRoutes.homeSteward,
page: () => HomePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.put(HomeLogic());
Get.lazyPut(() => ChickenBaseLogic());
}),
),
//sales
GetPage(
name: StewardRoutes.saleSteward,
page: () => SalePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => SaleLogic());
Get.lazyPut(() => ChickenBaseLogic());
Get.lazyPut(() => SalesOutOfProvinceLogic());
Get.lazyPut(() => SalesOutOfProvinceBuyersLogic());
Get.lazyPut(() => StewardRootLogic());
}),
),
GetPage(
name: StewardRoutes.salesOutOfProvinceSteward,
page: () => SalesOutOfProvincePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => SalesOutOfProvinceLogic());
Get.lazyPut(() => SalesOutOfProvinceBuyersLogic());
Get.lazyPut(() => SalesOutOfProvinceSalesListLogic());
}),
),
GetPage(
name: StewardRoutes.salesOutOfProvinceBuyerSteward,
page: () => SalesOutOfProvinceBuyersPage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => SalesOutOfProvinceLogic());
Get.lazyPut(() => SalesOutOfProvinceBuyersLogic());
Get.lazyPut(() => SalesOutOfProvinceSalesListLogic());
}),
),
GetPage(
name: StewardRoutes.salesInProvinceSteward,
page: () => SalesInProvincePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => ChickenBaseLogic());
Get.lazyPut(() => SalesInProvinceLogic());
}),
),
//buy
GetPage(
name: StewardRoutes.buySteward,
page: () => BuyPage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => ChickenBaseLogic());
Get.lazyPut(() => BuyLogic());
}),
),
GetPage(
name: StewardRoutes.buysOutOfProvinceSteward,
page: () => BuyOutOfProvincePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => ChickenBaseLogic());
Get.lazyPut(() => BuyOutOfProvinceLogic());
}),
),
GetPage(
name: StewardRoutes.buysInProvinceSteward,
page: () => BuyInProvincePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => ChickenBaseLogic());
Get.lazyPut(() => BuyInProvinceLogic());
Get.lazyPut(() => BuyInProvinceWaitingLogic());
Get.lazyPut(() => BuyInProvinceAllLogic());
}),
),
//endregion
];
}

View File

@@ -0,0 +1,20 @@
sealed class StewardRoutes {
StewardRoutes._();
static const _base = '/chicken/steward';
static const initSteward = '$_base/';
static const homeSteward = '$_base/home';
static const buySteward = '$_base/buy';
static const saleSteward = '$_base/sale';
static const segmentationSteward = '$_base/segmentation';
//buys
static const buysOutOfProvinceSteward = '$buySteward/buyOutOfProvince';
static const buysInProvinceSteward = '$buySteward/buyInProvince';
//sales
static const salesInProvinceSteward = '$saleSteward/SalesInProvince';
static const salesOutOfProvinceSteward = '$saleSteward/saleOutOfProvince';
static const salesOutOfProvinceBuyerSteward =
'$saleSteward/saleOutOfProvinceBuyer ';
}

View File

@@ -0,0 +1,2 @@
export 'presentation/routes/routes.dart';
export 'presentation/routes/pages.dart';