first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,466 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Screens/vat_&_tax/provider/text_repo.dart';
import 'package:mobile_pos/Screens/vat_&_tax/repo/tax_repo.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../http_client/custome_http_client.dart';
import '../../service/check_user_role_permission_provider.dart';
import 'model/vat_model.dart';
class AddGroupTax extends ConsumerStatefulWidget {
const AddGroupTax({
super.key,
this.taxModel,
});
final VatModel? taxModel;
@override
AddTaxGroupState createState() => AddTaxGroupState();
}
class AddTaxGroupState extends ConsumerState<AddGroupTax> {
List<VatModel> subTaxList = [];
TextEditingController nameController = TextEditingController();
bool status = true;
final GlobalKey<FormState> _fromKey = GlobalKey<FormState>();
void _saveTax({required BuildContext context, required WidgetRef ref}) async {}
@override
void initState() {
super.initState();
if (widget.taxModel?.subTax != null) {
Future.microtask(() async {
final data = await ref.read(singleTaxProvider.future);
List<VatModel> matchingItems = [];
for (var element in widget.taxModel!.subTax!) {
try {
VatModel matchingItem = data.firstWhere(
(item) => element.id == item.id,
orElse: () => VatModel(),
);
if (matchingItem.id != null) {
matchingItems.add(matchingItem);
}
} catch (_) {}
}
setState(() {
subTaxList = matchingItems;
});
});
nameController.text = widget.taxModel?.name ?? '';
status = widget.taxModel?.status ?? false;
}
}
@override
Widget build(BuildContext context) {
final _lang = lang.S.of(context);
final permissionService = PermissionService(ref);
return Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
title: Text(
widget.taxModel == null ? _lang.addTaxGroup : _lang.editTaxGroup,
// style: GoogleFonts.poppins(
// color: Colors.white,
// ),
),
// iconTheme: const IconThemeData(color: Colors.white),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0.0,
),
body: Container(
padding: const EdgeInsets.all(15),
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(30),
topLeft: Radius.circular(30),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//___________________________________Tax Rates______________________________
Text('${widget.taxModel == null ? _lang.add : _lang.edit} ${_lang.taxWithSingleMultipleTaxType}',
style: const TextStyle(color: kTitleColor, fontWeight: FontWeight.bold)),
const SizedBox(height: 10.0),
Text('${lang.S.of(context).name}*', style: const TextStyle(color: kTitleColor)),
const SizedBox(height: 8.0),
Form(
key: _fromKey,
child: TextFormField(
controller: nameController,
keyboardType: TextInputType.text,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Tax name is required';
}
return null;
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(left: 8, right: 8.0),
border: const OutlineInputBorder(),
// hintText: 'Enter Name',
hintText: lang.S.of(context).enterName,
),
),
),
const SizedBox(height: 20.0),
Text('${_lang.subTaxes}*', style: TextStyle(color: kTitleColor)),
const SizedBox(height: 8.0),
Consumer(builder: (context, ref, __) {
final taxes = ref.watch(singleTaxProvider);
return taxes.when(
data: (taxes) {
return GestureDetector(
onTap: () async {
subTaxList = await getTaxesModalSheet(mainContext: context, ref: ref, oldList: subTaxList, taxList: taxes);
setState(() {
subTaxList;
});
},
child: Container(
padding: const EdgeInsets.only(left: 10),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0), color: Colors.transparent, border: Border.all(color: kBorderColorTextField)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
subTaxList.isNotEmpty
? Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(
children: List.generate(
subTaxList.length,
(index) {
final category = subTaxList[index];
return Padding(
padding: const EdgeInsets.only(right: 5.0),
child: Container(
height: 30,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0), color: kMainColor),
child: Row(
children: [
IconButton(
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
padding: EdgeInsets.zero,
onPressed: () {
setState(() {
subTaxList.removeAt(index);
});
},
icon: const Icon(
Icons.close,
color: kWhite,
size: 16,
),
),
Text(
category.name ?? '',
style: const TextStyle(color: kWhite),
),
const SizedBox(width: 8)
],
),
),
);
},
),
),
),
)
: Text(_lang.noSubTaxSelected, style: TextStyle(color: kTitleColor)),
//___________________________________________showModalBottomSheet______________________
const Padding(
padding: EdgeInsets.all(11.0),
child: Icon(
Icons.keyboard_arrow_down_rounded,
color: kGreyTextColor,
),
),
],
),
),
);
},
error: (error, stackTrace) {
return Text(error.toString());
},
loading: () => Container(
padding: const EdgeInsets.only(left: 10),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0), color: Colors.transparent, border: Border.all(color: kBorderColorTextField)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(_lang.noSubTaxSelected, style: TextStyle(color: kTitleColor)),
//___________________________________________showModalBottomSheet______________________
Padding(
padding: EdgeInsets.all(11.0),
child: Icon(
Icons.keyboard_arrow_down_rounded,
color: kGreyTextColor,
),
),
],
),
));
// loading: () => Skeletonizer(
// enabled: true,
// child: Container(
// padding: const EdgeInsets.only(left: 10),
// decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0), color: Colors.transparent, border: Border.all(color: kBorderColorTextField)),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(_lang.noSubTaxSelected, style: TextStyle(color: kTitleColor)),
//
// //___________________________________________showModalBottomSheet______________________
// Padding(
// padding: EdgeInsets.all(11.0),
// child: Icon(
// Icons.keyboard_arrow_down_rounded,
// color: kGreyTextColor,
// ),
// ),
// ],
// ),
// ),
// ));
}),
const SizedBox(height: 20.0),
Row(
children: [
Text(
_lang.status,
style: TextStyle(color: kTitleColor),
),
const SizedBox(width: 8.0),
Switch(
value: status,
onChanged: (value) {
setState(() {
status = value;
});
},
)
],
),
//___________________________________________save_button______________________
const Spacer(),
Padding(
padding: const EdgeInsets.all(10.0),
child: SizedBox(
height: 45.0,
width: MediaQuery.of(context).size.width,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: 2, right: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
backgroundColor: kMainColor,
elevation: 1.0,
foregroundColor: kGreyTextColor.withValues(alpha: 0.1),
shadowColor: kMainColor,
animationDuration: const Duration(milliseconds: 300),
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Display', fontSize: 16, fontWeight: FontWeight.bold),
),
onPressed: () async {
if (widget.taxModel == null) {
if (!permissionService.hasPermission(Permit.vatsCreate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to create tax.'),
),
);
return;
}
} else {
if (!permissionService.hasPermission(Permit.vatsUpdate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to update tax.'),
),
);
return;
}
}
if (_fromKey.currentState!.validate()) {
if (subTaxList.isNotEmpty) {
EasyLoading.show();
TaxRepo repo = TaxRepo();
List<num> ids = [];
for (var element in subTaxList) {
ids.add(element.id!);
}
if (widget.taxModel != null) {
await repo.updateGroupTax(id: widget.taxModel!.id!, ref: ref, context: context, taxName: nameController.text, taxIds: ids, status: status);
} else {
await repo.createGroupTax(ref: ref, context: context, taxName: nameController.text, taxIds: ids, status: status);
}
EasyLoading.dismiss();
Navigator.pop(context);
} else {
EasyLoading.showError('Please select taxes');
}
}
},
child: Text(
lang.S.of(context).save,
style: const TextStyle(color: kWhite, fontSize: 12, fontWeight: FontWeight.bold),
),
),
),
),
],
),
),
);
}
}
Future<List<VatModel>> getTaxesModalSheet({
required BuildContext mainContext,
required WidgetRef ref,
required List<VatModel> oldList,
required List<VatModel> taxList,
}) async {
List<VatModel> subTaxList = [...oldList];
bool? isDone = await showModalBottomSheet(
isScrollControlled: true,
useSafeArea: true,
backgroundColor: Colors.white,
context: mainContext,
builder: (BuildContext context) {
final _lang = lang.S.of(context);
return StatefulBuilder(
builder: (BuildContext context, StateSetter setNewState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20.0, 13.0, 0.0, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_lang.subTaxList,
style: TextStyle(color: kTitleColor, fontWeight: FontWeight.w600, fontSize: 20),
),
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(
Icons.close_rounded,
size: 21,
color: kTitleColor,
),
padding: EdgeInsets.zero,
),
],
),
),
const Divider(color: kBorderColorTextField),
// const SizedBox(height: 5),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
itemCount: taxList.length,
itemBuilder: (context, index) {
final category = taxList[index];
return Column(
children: [
CheckboxListTile(
contentPadding: EdgeInsets.zero,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
checkboxShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),
),
checkColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.0),
),
// fillColor: WidgetStateProperty.all(
// subTaxList.contains(category) ? kMainColor : Colors.transparent,
// ),
fillColor: WidgetStatePropertyAll(subTaxList.contains(category) ? kMainColor : kBackgroundColor),
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
side: const BorderSide(color: kBorderColorTextField),
title: Text(category.name ?? '', style: const TextStyle(color: kTitleColor, overflow: TextOverflow.ellipsis)),
subtitle: Text('${_lang.taxPercent}: ${category.rate}%', style: const TextStyle(color: kGreyTextColor)),
value: subTaxList.contains(category),
onChanged: (isChecked) {
setNewState(() {
if (isChecked!) {
if (!subTaxList.contains(category)) {
subTaxList.add(category); // Add only the TaxModel instance
}
} else {
subTaxList.remove(category);
}
});
},
),
const Divider(
color: kBorderColorTextField,
height: 0.0,
)
],
);
},
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: SizedBox(
height: 45.0,
width: MediaQuery.of(context).size.width,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: 2, right: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
backgroundColor: kMainColor,
elevation: 1.0,
foregroundColor: kGreyTextColor.withValues(alpha: 0.1),
shadowColor: kMainColor,
animationDuration: const Duration(milliseconds: 300),
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Display', fontSize: 16, fontWeight: FontWeight.bold),
),
onPressed: () {
Navigator.pop(context, true);
},
child: Text(_lang.done, style: TextStyle(color: kWhite, fontWeight: FontWeight.bold)),
),
),
),
],
);
},
);
},
);
return (isDone ?? false) ? subTaxList : oldList;
}

View File

@@ -0,0 +1,248 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Screens/vat_&_tax/model/vat_model.dart';
import 'package:mobile_pos/Screens/vat_&_tax/repo/tax_repo.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../http_client/custome_http_client.dart';
import '../../service/check_user_role_permission_provider.dart';
class CreateSingleTax extends ConsumerStatefulWidget {
const CreateSingleTax({super.key, this.taxModel});
final VatModel? taxModel;
@override
ConsumerState<CreateSingleTax> createState() => _CreateSingleTaxState();
}
class _CreateSingleTaxState extends ConsumerState<CreateSingleTax> {
final _formKey = GlobalKey<FormState>();
late TextEditingController taxNameController;
late TextEditingController taxRateController;
bool status = true;
@override
void initState() {
super.initState();
taxNameController = TextEditingController(text: widget.taxModel?.name ?? '');
taxRateController = TextEditingController(
text: widget.taxModel?.rate != null ? widget.taxModel!.rate.toString() : '',
);
status = widget.taxModel?.status ?? true;
}
@override
void dispose() {
taxNameController.dispose();
taxRateController.dispose();
super.dispose();
}
Future<void> _saveTax({required BuildContext context, required WidgetRef ref}) async {}
@override
Widget build(BuildContext context) {
final _lang = lang.S.of(context);
final permissionService = PermissionService(ref);
return Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
title: Text(
widget.taxModel == null ? _lang.addTax : _lang.editTax,
),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
elevation: 0.0,
),
body: Container(
padding: const EdgeInsets.all(15),
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(30),
topLeft: Radius.circular(30),
),
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.taxModel == null ? _lang.addNewTax : _lang.editTax,
// 'Add New Tax',
style: const TextStyle(color: kTitleColor, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20.0),
// Tax Name Field
Text(
'${lang.S.of(context).name}*',
style: const TextStyle(color: kTitleColor),
),
const SizedBox(height: 8.0),
TextFormField(
controller: taxNameController,
keyboardType: TextInputType.text,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(horizontal: 8.0),
border: const OutlineInputBorder(),
hintText: lang.S.of(context).enterName,
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Tax name is required';
}
return null;
},
),
const SizedBox(height: 20.0),
// Tax Rate Field
Text(
'${_lang.taxRates}*',
style: TextStyle(color: kTitleColor),
),
const SizedBox(height: 8.0),
TextFormField(
controller: taxRateController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 8.0),
border: OutlineInputBorder(),
hintText: _lang.enterTaxRates,
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Tax rate is required';
}
if (double.tryParse(value.trim()) == null) {
return 'Enter a valid number';
}
return null;
},
),
const SizedBox(height: 20.0),
Row(
children: [
Text(
_lang.status,
style: TextStyle(color: kTitleColor),
),
const SizedBox(width: 8.0),
Switch(
value: status,
onChanged: (value) {
setState(() {
status = value;
});
},
)
],
),
const Spacer(),
// Save Button
Consumer(builder: (context1, ref, __) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: SizedBox(
height: 45.0,
width: MediaQuery.of(context).size.width,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
backgroundColor: kMainColor,
elevation: 1.0,
shadowColor: kMainColor,
animationDuration: const Duration(milliseconds: 300),
),
onPressed: () async {
if (widget.taxModel == null) {
if (!permissionService.hasPermission(Permit.vatsCreate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to create tax.'),
),
);
return;
}
} else {
if (!permissionService.hasPermission(Permit.vatsUpdate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to update tax.'),
),
);
return;
}
}
if (!_formKey.currentState!.validate()) return;
EasyLoading.show();
TaxRepo repo = TaxRepo();
final taxRate = num.tryParse(taxRateController.text) ?? 0;
final taxName = taxNameController.text;
try {
if (widget.taxModel == null) {
await repo.createSingleTax(
ref: ref,
context: context,
taxRate: taxRate,
taxName: taxName,
status: status,
);
} else {
await repo.updateSingleTax(
ref: ref,
context: context,
rate: taxRate,
name: taxName,
id: widget.taxModel!.id!,
status: status,
);
}
EasyLoading.dismiss();
Navigator.pop(context);
} catch (e) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('An error occurred: $e'),
),
);
}
},
child: Text(
_lang.save,
style: TextStyle(
color: kWhite,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
),
);
}),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,34 @@
import 'package:mobile_pos/Screens/vat_&_tax/model/vat_model.dart';
class GroupTaxModel {
late String name;
late num taxRate;
late String id;
List<VatModel>? subTaxes;
GroupTaxModel({
required this.name,
required this.taxRate,
required this.id,
required this.subTaxes,
});
GroupTaxModel.fromJson(Map<String, dynamic> json) {
name = json['name'];
taxRate = json['rate'];
id = json['id'];
if (json['subTax'] != null) {
subTaxes = <VatModel>[];
json['subTax'].forEach((v) {
subTaxes!.add(VatModel.fromJson(v));
});
}
}
Map<String, dynamic> toJson() => <String, dynamic>{
'name': name,
'rate': taxRate,
'id': id,
'subTax': subTaxes?.map((e) => e.toJson()).toList(),
};
}

View File

@@ -0,0 +1,78 @@
class VatModel {
VatModel({
this.id,
this.name,
this.businessId,
this.rate,
this.subTax,
this.status,
this.createdAt,
this.updatedAt,
});
VatModel.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
businessId = json['business_id'];
rate = json['rate'];
if (json['sub_vat'] != null) {
subTax = [];
json['sub_vat'].forEach((v) {
subTax?.add(SubVat.fromJson(v));
});
}
status = json['status'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
num? id;
String? name;
num? businessId;
num? rate;
List<SubVat>? subTax;
bool? status;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['name'] = name;
map['business_id'] = businessId;
map['rate'] = rate;
if (subTax != null) {
map['sub_vat'] = subTax?.map((v) => v.toJson()).toList();
}
map['status'] = status;
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}
class SubVat {
SubVat({
this.id,
this.name,
this.rate,
});
SubVat.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
rate = json['rate'];
}
num? id;
String? name;
num? rate;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['name'] = name;
map['rate'] = rate;
return map;
}
}

View File

@@ -0,0 +1,11 @@
//_____________________________________________Tax_provider_____________________
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../model/vat_model.dart';
import '../repo/tax_repo.dart';
TaxRepo taxRepo = TaxRepo();
final taxProvider = FutureProvider<List<VatModel>>((ref) => taxRepo.fetchAllTaxes(taxType: ''));
//_____________________________________________Group_Tax_provider_____________________
final singleTaxProvider = FutureProvider.autoDispose<List<VatModel>>((ref) => taxRepo.fetchAllTaxes(taxType: 'single'));

View File

@@ -0,0 +1,238 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import '../../../Const/api_config.dart';
import '../../../Repository/constant_functions.dart';
import '../../../http_client/custome_http_client.dart';
import '../../../http_client/customer_http_client_get.dart';
import '../model/vat_model.dart';
import '../provider/text_repo.dart';
class TaxRepo {
Future<List<VatModel>> fetchAllTaxes({String? taxType}) async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}/vats?type=$taxType');
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body) as Map<String, dynamic>;
final partyList = parsedData['data'] as List<dynamic>;
return partyList.map((category) => VatModel.fromJson(category)).toList();
// Parse into Party objects
} else {
throw Exception('Failed to fetch tax list');
}
}
Future<void> createSingleTax({
required WidgetRef ref,
required BuildContext context,
required num taxRate,
required String taxName,
required bool status,
}) async {
final uri = Uri.parse('${APIConfig.url}/vats');
final requestBody = jsonEncode({
'name': taxName,
'rate': taxRate,
});
try {
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
var responseData = await customHttpClient.post(
url: uri,
addContentTypeInHeader: true,
body: requestBody,
);
final parsedData = jsonDecode(responseData.body);
EasyLoading.dismiss();
if (responseData.statusCode == 200) {
ref.refresh(taxProvider);
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Tax creation failed: ${parsedData}')));
return;
}
} catch (error) {
// Handle unexpected errors gracefully
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('An error occurred: $error')));
// return null;
}
}
Future<void> createGroupTax({
required WidgetRef ref,
required BuildContext context,
required String taxName,
required List<num> taxIds,
required bool status,
}) async {
final uri = Uri.parse('${APIConfig.url}/vats');
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
var request = http.MultipartRequest('POST', uri)
..headers['Accept'] = 'application/json'
..headers['Authorization'] = await getAuthToken();
request.fields.addAll({
'name': taxName,
});
if (taxIds.isNotEmpty) {
int index = 0;
for (var element in taxIds) {
request.fields['vat_ids[$index]'] = element.toString();
index++;
}
}
try {
final response = await customHttpClient.uploadFile(
url: uri,
fields: request.fields,
);
// final response = await request.send();
final responseData = await response.stream.bytesToString();
final parsedData = jsonDecode(responseData);
EasyLoading.dismiss();
print(response.statusCode);
print(responseData);
if (response.statusCode == 200) {
print('45235');
ref.refresh(taxProvider);
} else if (response.statusCode == 403) {
throw Exception('Failed to update tax');
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Tax creation failed: ${parsedData['message']}')));
return;
}
} catch (error) {
// Handle unexpected errors gracefully
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('An error occurred: $error')));
// return null;
}
}
///________Update_Single_Tax__________________________________________
Future<void> updateSingleTax({
required num id,
required String name,
required num rate,
required bool status,
required WidgetRef ref,
required BuildContext context,
}) async {
final uri = Uri.parse('${APIConfig.url}/vats/$id');
final requestBody = jsonEncode({
'rate': rate,
'name': name,
'status': status,
'_method': 'put',
});
try {
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
final response = await customHttpClient.post(
url: uri,
addContentTypeInHeader: true,
body: requestBody,
);
if (response.statusCode == 200) {
ref.refresh(taxProvider);
} else {
throw Exception('Failed to update tax. Status Code: ${response.statusCode} - ${response.body}');
}
} catch (error) {
print('Error updating income: $error');
throw Exception('Error updating income: $error');
} finally {
EasyLoading.dismiss();
}
}
Future<void> updateGroupTax({
required WidgetRef ref,
required BuildContext context,
required num id,
required String taxName,
required List<num> taxIds,
required bool status,
}) async {
final uri = Uri.parse('${APIConfig.url}/vats/$id');
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
var request = http.MultipartRequest('POST', uri)
..headers['Accept'] = 'application/json'
..headers['Authorization'] = await getAuthToken();
request.fields.addAll({
'name': taxName,
'status': status ? '1' : "0",
'_method': 'put',
});
if (taxIds.isNotEmpty) {
int index = 0;
for (var element in taxIds) {
request.fields['vat_ids[$index]'] = element.toString();
index++;
}
}
try {
final response = await customHttpClient.uploadFile(
url: uri,
fields: request.fields,
);
// final response = await request.send();
final responseData = await response.stream.bytesToString();
final parsedData = jsonDecode(responseData);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ref.refresh(taxProvider);
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Tax creation failed: ${parsedData['message']}')));
return;
}
} catch (error) {
// Handle unexpected errors gracefully
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('An error occurred: $error')));
// return null;
}
}
///________Delete_Tax______________________________________________________
Future<bool> deleteTax({required String id, required BuildContext context, required WidgetRef ref}) async {
try {
final token = await getAuthToken();
if (token.isEmpty) {
throw Exception('Authentication token is missing or empty');
}
final url = Uri.parse('${APIConfig.url}/vats/$id');
CustomHttpClient customHttpClient = CustomHttpClient(ref: ref, context: context, client: http.Client());
final response = await customHttpClient.delete(url: url);
if (response.statusCode == 200) {
return true;
} else {
print('Error deleting tax: ${response.statusCode} - ${response.body}');
return false;
}
} catch (error) {
print('Error during delete operation: $error');
return false;
} finally {
EasyLoading.dismiss();
}
}
}

View File

@@ -0,0 +1,567 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Provider/profile_provider.dart';
import 'package:mobile_pos/Screens/vat_&_tax/add_group_tax.dart';
import 'package:mobile_pos/Screens/vat_&_tax/creating_single_tax.dart';
import 'package:mobile_pos/Screens/vat_&_tax/provider/text_repo.dart';
import 'package:mobile_pos/Screens/vat_&_tax/repo/tax_repo.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../http_client/custome_http_client.dart';
import '../../widgets/empty_widget/_empty_widget.dart';
import '../../service/check_user_role_permission_provider.dart';
import '../hrm/widgets/deleteing_alart_dialog.dart';
import 'model/vat_model.dart';
class TaxReport extends ConsumerStatefulWidget {
const TaxReport({super.key});
@override
ConsumerState<TaxReport> createState() => _TaxReportState();
}
class _TaxReportState extends ConsumerState<TaxReport> {
bool _isRefreshing = false; // Prevents multiple refresh calls
Future<void> refreshData(WidgetRef ref) async {
if (_isRefreshing) return; // Prevent duplicate refresh calls
_isRefreshing = true;
ref.refresh(taxProvider);
await Future.delayed(const Duration(seconds: 1)); // Optional delay
_isRefreshing = false;
}
@override
Widget build(BuildContext context) {
final _lang = lang.S.of(context);
final taxes = ref.watch(taxProvider);
final businessProviderData = ref.watch(businessInfoProvider);
ref.watch(getExpireDateProvider(ref));
final permissionService = PermissionService(ref);
return businessProviderData.when(data: (details) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
surfaceTintColor: Colors.white,
title: Text(
_lang.taxRates,
),
centerTitle: true,
backgroundColor: kWhite,
elevation: 0.0,
),
body: taxes.when(
data: (data) {
List<VatModel> singleTaxes = [];
List<VatModel> groupTaxes = [];
for (var element in data) {
if (element.subTax == null) {
singleTaxes.add(element);
} else {
groupTaxes.add(element);
}
}
if (!permissionService.hasPermission(Permit.vatsRead.value)) {
return Center(child: PermitDenyWidget());
}
return RefreshIndicator(
onRefresh: () => refreshData(ref),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//___________________________________Tax Rates______________________________
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
_lang.taxRatesMangeYourTaxRates,
),
),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: 2, right: 2),
minimumSize: Size(60, 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
backgroundColor: kSuccessColor,
elevation: 1.0,
foregroundColor: kGreyTextColor.withValues(alpha: 0.1),
shadowColor: kMainColor,
animationDuration: const Duration(milliseconds: 300),
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Display', fontSize: 16, fontWeight: FontWeight.bold),
),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CreateSingleTax(),
),
);
},
label: Text(
_lang.add,
style: TextStyle(color: kWhite, fontSize: 12, fontWeight: FontWeight.bold),
),
icon: const Icon(
FeatherIcons.plus,
size: 15,
color: kWhite,
),
),
],
),
const SizedBox(height: 10.0),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
headingRowColor: WidgetStateColor.resolveWith((states) => Colors.white),
border: TableBorder.all(borderRadius: BorderRadius.circular(2.0), color: kBorderColorTextField),
dividerThickness: 1.0,
sortAscending: true,
showCheckboxColumn: false,
horizontalMargin: 5.0,
columnSpacing: 10,
dataRowMinHeight: 45,
showBottomBorder: true,
checkboxHorizontalMargin: 0.0,
columns: <DataColumn>[
DataColumn(
label: Text(
lang.S.of(context).name,
// 'Name',
),
),
DataColumn(
label: Text(
'${_lang.taxRates} %',
),
),
DataColumn(
label: Text(
_lang.status,
),
),
DataColumn(
headingRowAlignment: MainAxisAlignment.center,
label: Text(
_lang.actions,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
],
rows: List.generate(
singleTaxes.length,
(index) => DataRow(
cells: [
DataCell(
SizedBox(
width: MediaQuery.of(context).size.width * .30,
child: Text(
'${singleTaxes[index].name}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.start,
),
),
),
DataCell(Center(
child: Text(
'${singleTaxes[index].rate.toString()}%',
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
)),
DataCell(Center(
child: Text(
(singleTaxes[index].status ?? false) ? _lang.active : _lang.disable,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
)),
DataCell(
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(50, 25),
padding: const EdgeInsets.only(left: 2, right: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
backgroundColor: kSuccessColor,
elevation: 1.0,
foregroundColor: kGreyTextColor.withValues(alpha: 0.1),
shadowColor: kMainColor,
animationDuration: const Duration(milliseconds: 300),
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Display', fontSize: 16, fontWeight: FontWeight.bold),
),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateSingleTax(taxModel: singleTaxes[index]),
),
);
},
child: Row(
children: [
const Icon(
FeatherIcons.edit,
size: 15,
color: kWhite,
),
const SizedBox(width: 4),
Text(
lang.S.of(context).edit,
//'Edit',
style: const TextStyle(color: kWhite, fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
),
const SizedBox(width: 5.0),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(50, 25),
padding: const EdgeInsets.only(left: 2, right: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
backgroundColor: Colors.red,
elevation: 1.0,
foregroundColor: Colors.white.withValues(alpha: 0.1),
shadowColor: Colors.red,
animationDuration: const Duration(milliseconds: 300),
),
onPressed: () async {
if (!permissionService.hasPermission(Permit.vatsDelete.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to delete tax.'),
),
);
return;
}
bool result = await showDeleteConfirmationDialog(context: context, itemName: 'vat_&_tax');
if (result) {
EasyLoading.show(status: _lang.deleting);
final repo = TaxRepo();
try {
final result = await repo.deleteTax(id: singleTaxes[index].id.toString(), ref: ref, context: context);
if (result) {
ref.refresh(taxProvider);
EasyLoading.showSuccess(_lang.deletedSuccessFully);
} else {
EasyLoading.showError(_lang.failedToDeleteTheTax);
}
} catch (e) {
EasyLoading.showError('${_lang.errorDeletingTax}: $e');
} finally {
EasyLoading.dismiss();
}
}
},
child: Row(
children: [
const Icon(
Icons.delete_outline,
size: 17,
color: kWhite,
),
const SizedBox(width: 4),
Text(lang.S.of(context).delete, style: const TextStyle(color: kWhite, fontSize: 12, fontWeight: FontWeight.bold)),
],
),
),
],
),
),
],
color: WidgetStateColor.resolveWith(
(Set<WidgetState> states) {
// Use index to determine whether the row is even or odd
return index % 2 == 0 ? Colors.grey.shade100 : Colors.white;
},
),
),
),
),
),
//___________________________________Tax Group______________________________
const SizedBox(height: 40.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_lang.taxGroup, style: TextStyle(color: kTitleColor, fontWeight: FontWeight.bold)),
Text('(${_lang.combinationOfTheMultipleTaxes})', style: TextStyle(color: kGreyTextColor)),
],
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: 2, right: 2),
minimumSize: Size(60, 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
backgroundColor: kSuccessColor,
elevation: 1.0,
foregroundColor: kGreyTextColor.withValues(alpha: 0.1),
shadowColor: kMainColor,
animationDuration: const Duration(milliseconds: 300),
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Display', fontSize: 16, fontWeight: FontWeight.bold),
),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AddGroupTax()),
);
},
child: Row(
children: [
Icon(
FeatherIcons.plus,
size: 15,
color: kWhite,
),
SizedBox(width: 4),
Text(_lang.add, style: TextStyle(color: kWhite, fontSize: 12, fontWeight: FontWeight.bold)),
],
),
),
],
),
const SizedBox(height: 20.0),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
headingRowColor: WidgetStateColor.resolveWith((states) => Colors.white),
border: TableBorder.all(borderRadius: BorderRadius.circular(2.0), color: kBorderColorTextField),
dividerThickness: 1.0,
sortAscending: true,
showCheckboxColumn: false,
horizontalMargin: 5.0,
columnSpacing: 10,
dataRowMinHeight: 45,
showBottomBorder: true,
checkboxHorizontalMargin: 0.0,
columns: <DataColumn>[
DataColumn(
label: Text(
lang.S.of(context).name,
),
),
DataColumn(
label: Text(
'${_lang.taxRates} %',
),
),
DataColumn(
label: Text(
_lang.subTaxes,
overflow: TextOverflow.ellipsis,
),
),
DataColumn(
headingRowAlignment: MainAxisAlignment.center,
label: Text(
_lang.action,
overflow: TextOverflow.ellipsis,
),
),
],
rows: List.generate(
groupTaxes.length,
(index) => DataRow(
cells: [
DataCell(
Text(
groupTaxes[index].name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.start,
style: const TextStyle(color: kGreyTextColor),
),
),
DataCell(
Center(
child: Text(
'${groupTaxes[index].rate.toString()}%',
textAlign: TextAlign.start,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: kGreyTextColor),
),
),
),
DataCell(
Wrap(
children: List.generate(
groupTaxes[index].subTax?.length ?? 0,
(i) {
return Text(
"${groupTaxes[index].subTax?[i].name ?? 'n/a'}, ",
maxLines: 1,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: kGreyTextColor),
);
},
),
),
),
DataCell(
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(50, 25),
padding: const EdgeInsets.only(left: 2, right: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
backgroundColor: Colors.green,
elevation: 1.0,
foregroundColor: kGreyTextColor.withValues(alpha: 0.1),
shadowColor: kMainColor,
animationDuration: const Duration(milliseconds: 300),
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Display', fontSize: 16, fontWeight: FontWeight.bold),
),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddGroupTax(taxModel: groupTaxes[index])),
);
},
child: Row(
children: [
const Icon(
FeatherIcons.edit,
size: 15,
color: kWhite,
),
const SizedBox(width: 4),
Text(
lang.S.of(context).edit,
//'Edit',
style: const TextStyle(color: kWhite, fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
),
const SizedBox(width: 5.0),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: 2, right: 2),
minimumSize: Size(50, 25),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.0),
),
backgroundColor: Colors.red,
elevation: 1.0,
foregroundColor: Colors.white.withValues(alpha: 0.1),
shadowColor: Colors.red,
animationDuration: const Duration(milliseconds: 300),
textStyle: const TextStyle(color: kWhite)),
onPressed: () async {
if (!permissionService.hasPermission(Permit.vatsDelete.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text('You do not have permission to delete tax.'),
),
);
return;
}
bool result = await showDeleteConfirmationDialog(context: context, itemName: 'vat_&_tax');
if (result) {
EasyLoading.show(status: _lang.deleting);
final repo = TaxRepo();
try {
final result = await repo.deleteTax(id: groupTaxes[index].id.toString(), context: context, ref: ref);
if (result) {
ref.refresh(taxProvider);
EasyLoading.showSuccess(_lang.deletedSuccessFully);
} else {
EasyLoading.showError(_lang.failedToDeleteTheTax);
}
} catch (e) {
EasyLoading.showError('${_lang.errorDeletingTax}: $e');
} finally {
EasyLoading.dismiss();
}
}
},
child: Row(
children: [
const Icon(
Icons.delete_outline,
size: 17,
color: kWhite,
),
const SizedBox(width: 4),
Text(
lang.S.of(context).delete,
//'Delete',
style: const TextStyle(color: kWhite, fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
),
],
),
),
],
color: WidgetStateColor.resolveWith(
(Set<WidgetState> states) {
// Use index to determine whether the row is even or odd
return index % 2 == 0 ? Colors.grey.shade100 : Colors.white;
},
),
),
),
),
)
],
),
),
);
},
error: (e, stackTrace) {
return Center(
child: Text(e.toString()),
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
));
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
});
}
}