feat : chicken root page , inventory , sale in province

This commit is contained in:
MrM
2025-06-05 23:26:44 +03:30
parent 5849466e3b
commit 1cfcf9fa8f
68 changed files with 16615 additions and 126 deletions

View File

@@ -0,0 +1,213 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_auth/data/utils/safe_call.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/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/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class SalesWithinProvinceLogic extends GetxController {
var rootLogic = Get.find<RootLogic>();
Rxn<AllocatedMadeModel> allocatedMadeModel = Rxn<AllocatedMadeModel>();
RxList<ProductModel> rolesProductsModel = RxList<ProductModel>();
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
Rxn<ProductModel> selectedProductModel = Rxn<ProductModel>();
Rxn<GuildModel> selectedGuildModel = Rxn<GuildModel>();
Rxn<GuildProfile> guildProfile = Rxn<GuildProfile>();
RxInt saleType = 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();
@override
void onInit() {
super.onInit();
rootLogic.getInventory();
getAllocatedMade();
getRolesProducts();
getGuilds();
getGuildProfile();
ever(saleType, (callback) {
getGuilds();
});
weight.listen((num) {
totalCost.value = num * pricePerKilo.value;
});
pricePerKilo.listen((num) {
totalCost.value = num * weight.value;
});
totalCost.listen((data) {
totalCostController.text = data.toString();
isValid.value =
weight.value > 0 &&
pricePerKilo.value > 0 &&
totalCost.value > 0 &&
selectedProductModel.value != null &&
selectedGuildModel.value != null;
});
}
Future<void> getAllocatedMade() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getAllocatedMade(
token: rootLogic.tokenService.accessToken.value!,
page: 1,
),
onSuccess: (result) {
if (result != null) {
allocatedMadeModel.value = result;
}
},
onError: (error, stacktrace) {},
);
}
void checkVerfication() {
isValid.value =
weight.value > 0 &&
pricePerKilo.value > 0 &&
totalCost.value > 0 &&
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:
allocatedMadeModel.value?.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;
}
},
onError: (error, stacktrace) {},
);
}
Future<void> getGuilds() async {
safeCall(
call: () async => await rootLogic.chickenRepository.getGuilds(
token: rootLogic.tokenService.accessToken.value!,
isFree: saleType.value == 2 ? true : false,
),
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;
},
);
}
Future<void> submitAllocation() async {
SubmitStewardAllocation stewardAllocation = SubmitStewardAllocation(
approvedPriceStatus: false,
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,
guildKey: selectedGuildModel.value?.key,
productKey: selectedProductModel.value?.key,
date: DateTime.now().formattedGregorianDate,
type: "manual",
);
safeCall(
call: () async =>
await rootLogic.chickenRepository.postSubmitStewardAllocation(
token: rootLogic.tokenService.accessToken.value!,
request: stewardAllocation,
),
onSuccess: (result) {
getAllocatedMade();
},
onError: (error, stackTrace) {},
);
}
}

View File

@@ -0,0 +1,574 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/request/conform_allocation/conform_allocation.dart';
import 'package:rasadyar_chicken/data/models/response/guild/guild_model.dart';
import 'package:rasadyar_chicken/data/models/response/inventory/inventory_model.dart';
import 'package:rasadyar_chicken/data/models/response/roles_products/roles_products.dart';
import 'package:rasadyar_chicken/presentation/pages/entering_the_warehouse/string_utils.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class SalesWithinProvincePage extends GetView<SalesWithinProvinceLogic> {
SalesWithinProvincePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: RAppBar(title: 'فروش در استان'),
body: SingleChildScrollView(
child: Column(
children: [
inventoryWidget(),
allocationsMade(),
SizedBox(height: 20),
RElevated(
text: 'ثبت توزیع/ فروش',
onPressed: () {
showAddBottomSheet();
},
),
SizedBox(height: 40),
],
),
),
);
}
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.rootLogic.inventoryList.length,
separatorBuilder: (context, index) =>
const SizedBox(height: 8),
itemBuilder: (context, index) {
return ObxValue((expand) {
return GestureDetector(
onTap: () {
controller.rootLogic.toggleExpanded(index);
},
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
onEnd: () {
controller
.rootLogic
.inventoryExpandedList[index] = !controller
.rootLogic
.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.rootLogic.inventoryList[index],
),
),
);
}, controller.rootLogic.inventoryExpandedList);
},
),
controller.rootLogic.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: 2,
child: Text(
value,
textAlign: TextAlign.left,
style: AppFonts.yekan14.copyWith(
color: AppColor.darkGreyDarkHover,
),
),
),
],
),
);
}
Widget allocationsMade() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
children: [
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'تخصیصات صورت گرفته',
style: AppFonts.yekan16Bold.copyWith(
color: AppColor.blueNormal,
),
),
RElevated(
text: 'تایید یکجا',
height: 30,
onPressed: () {
controller.confirmAllAllocations();
},
),
],
),
SizedBox(height: 4),
ObxValue((data) {
if (data.value == null) {
return Container(
height: 80,
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: Center(child: CircularProgressIndicator()),
);
} else if (data.value?.results?.isEmpty ?? true) {
return Container(
height: 80,
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: Center(child: Text('هیچ تخصیصات صورت نگرفته است ')),
);
} else {
return Container(
margin: const EdgeInsets.symmetric(vertical: 2),
height: 700,
padding: const EdgeInsets.all(6),
child: ListView.separated(
padding: const EdgeInsets.all(8.0),
itemCount: data.value?.results?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
final result = data.value!.results![index];
return Card(
margin: const EdgeInsets.symmetric(vertical: 4.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: AppColor.blueNormal,
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildRow('ردیف', '${index + 1}'),
buildRow(
'تاریخ ثبت',
result.date!.formattedJalaliDate ?? 'N/A',
),
buildRow(
'نوع تخصیص',
result.allocation_type?.faAllocationType ?? 'N/A',
),
buildRow(
'مشخصات خریدار',
'${result.to_guilds?.user?.fullname} - ${result.to_guilds?.guilds_name}' ??
'N/A',
),
buildRow(
'مشخصات فروشنده',
result.steward?.user?.fullname ?? 'N/A',
),
buildRow(
'نوع فروش',
result.sell_type?.faItem ?? 'N/A',
),
buildRow(
'قیمت هر کیلو',
'${result.amount ?? 0} ریال ',
),
buildRow(
'قیمت کل',
'${result.total_amount ?? 0} ریال',
),
buildRow(
'وزن تخصیصی',
'${result.weight_of_carcasses?.toInt() ?? 0} کیلوگرم',
),
buildRow(
'کداحراز',
result.registration_code?.toString() ?? 'N/A',
),
buildRow(
'وضعیت کد احراز',
result.system_registration_code == true
? "ارسال شده"
: "ارسال نشده" ?? 'N/A',
),
buildRow(
'افت وزن(کیلوگرم)',
result.weight_loss_of_carcasses
?.toInt()
.toString() ??
'N/A',
),
buildRow(
'وضعیت',
result.receiver_state?.faItem ?? 'N/A',
),
Row(
spacing: 10,
children: [
Expanded(
child: RElevated(
height: 40,
text: 'تایید',
onPressed: () {
ConformAllocation confromation =
ConformAllocation(
allocation_key: result.key,
number_of_carcasses:
result.number_of_carcasses,
weight_of_carcasses: result
.weight_of_carcasses
?.toInt(),
amount: result.amount,
total_amount: result.total_amount,
);
controller.confirmAllocation(
confromation,
);
},
),
),
Expanded(
child: RElevated(
height: 40,
backgroundColor: AppColor.error,
text: 'رد',
onPressed: () {
controller.denyAllocation(
result.key ?? '',
);
},
),
),
],
),
],
),
),
);
},
separatorBuilder: (BuildContext context, int index) =>
SizedBox(height: 8),
),
);
}
}, controller.allocatedMadeModel),
],
),
);
}
void showAddBottomSheet() {
Get.bottomSheet(
SafeArea(
child: BaseBottomSheet(
height: 700,
child: Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
bottom: MediaQuery.of(Get.context!).viewInsets.bottom + 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'ثبت توزیع/ فروش',
style: AppFonts.yekan16Bold.copyWith(
color: AppColor.blueNormal,
),
),
const SizedBox(height: 12),
RTextField(
controller: TextEditingController(),
label: 'تاریخ',
enabled: false,
initText: Jalali.now().formatCompactDate(),
),
const SizedBox(height: 12),
Material(
type: MaterialType.transparency,
child: productDropDown(),
),
const SizedBox(height: 12),
SizedBox(
height: 40,
child: ObxValue((data) {
return Row(
children: [
Radio(
value: 1,
groupValue: controller.saleType.value,
onChanged: (value) {
controller.saleType.value = value!;
},
),
Text('فروش اختصاصی', style: AppFonts.yekan14),
SizedBox(width: 12),
Radio(
value: 2,
groupValue: controller.saleType.value,
onChanged: (value) {
controller.saleType.value = value!;
},
),
Text('فروش آزاد', style: AppFonts.yekan14),
],
);
}, controller.saleType),
),
const SizedBox(height: 12),
Material(
type: MaterialType.transparency,
child: guildsDropDown(),
),
const SizedBox(height: 12),
RTextField(
controller: controller.weightController,
keyboardType: TextInputType.number,
onChanged: (p0) {
controller.weight.value = int.tryParse(p0) ?? 0;
},
label: 'وزن لاشه',
),
const SizedBox(height: 12),
RTextField(
controller: controller.pricePerKiloController,
onChanged: (p0) {
controller.pricePerKilo.value = int.tryParse(p0) ?? 0;
},
keyboardType: TextInputType.number,
label: 'قیمت هر کیلو',
),
const SizedBox(height: 12),
ObxValue(
(p0) => RTextField(
enabled: false,
keyboardType: TextInputType.number,
initText: controller.totalCost.value.toString(),
controller: controller.totalCostController,
label: 'هزینه کل',
),
controller.totalCost,
),
const SizedBox(height: 20),
ObxValue((data) {
return RElevated(
text: 'ثبت',
onPressed: data.value
? () {
controller.submitAllocation();
}
: null,
);
}, controller.isValid),
const SizedBox(height: 20),
],
),
),
),
),
isScrollControlled: true,
backgroundColor: Colors.transparent,
);
}
Widget guildsDropDown() {
return ObxValue((p0) {
return DropdownButtonFormField<GuildModel>(
value: controller.selectedGuildModel.value,
decoration: const InputDecoration(
labelText: 'انتخاب مباشر/صنف',
border: OutlineInputBorder(),
),
isExpanded: true,
items: controller.guildsModel.map((guild) {
return DropdownMenuItem<GuildModel>(
value: guild,
child: Text(
'${guild.steward == true ? 'مباشر' : 'صنف'} ${guild.user?.fullname} (${guild.user?.mobile})',
),
);
}).toList(),
onChanged: (value) {
if (value != null) {
controller.setSelectedGuild(value);
controller.checkVerfication();
}
},
);
}, controller.guildsModel);
/* return GetBuilder<SalesWithinProvinceLogic>(
builder: (controller) {
return DropdownButtonFormField<GuildModel>(
value: controller.selectedGuildModel.value,
decoration: const InputDecoration(
labelText: 'انتخاب مباشر/صنف',
border: OutlineInputBorder(),
),
isExpanded: true,
items: controller.guildsModel.map((guild) {
return DropdownMenuItem<GuildModel>(
value: guild,
child: Text(
'${guild.steward == true ? 'مباشر' : 'صنف'} ${guild.user
?.fullname} (${guild.user?.mobile})',
),
);
}).toList(),
onChanged: (value) {
if (value != null) {
controller.setSelectedGuild(value);
}
},
);
},
);*/
}
Widget productDropDown() {
return GetBuilder<SalesWithinProvinceLogic>(
builder: (controller) {
return DropdownButtonFormField<ProductModel>(
value: controller.selectedProductModel.value,
decoration: const InputDecoration(
labelText: 'انتخاب محصول',
border: OutlineInputBorder(),
),
isExpanded: true,
items: controller.rolesProductsModel.map((guild) {
return DropdownMenuItem<ProductModel>(
value: guild,
child: Text('${guild.name}'),
);
}).toList(),
onChanged: (value) {
if (value != null) {
controller.setSelectedProduct(value);
controller.checkVerfication();
}
},
);
},
);
}
}