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,100 @@
// File: payroll_list_model.dart
import '../../../../widgets/multipal payment mathods/model/payment_transaction_model.dart';
class PayrollListModel {
PayrollListModel({
this.message,
this.data,
});
PayrollListModel.fromJson(dynamic json) {
message = json['message'];
if (json['data'] != null) {
data = [];
json['data'].forEach((v) {
data?.add(PayrollData.fromJson(v));
});
}
}
String? message;
List<PayrollData>? data;
}
class PayrollData {
PayrollData({
this.id,
this.businessId,
this.branchId,
this.employeeId,
this.paymentTypeId,
this.month,
this.puid,
this.date,
this.amount,
this.payemntYear,
this.note,
this.createdAt,
this.updatedAt,
this.employee,
this.transactions,
this.branch,
});
PayrollData.fromJson(dynamic json) {
id = json['id'];
businessId = json['business_id'];
branchId = json['branch_id'];
employeeId = json['employee_id'];
paymentTypeId = json['payment_type_id'];
month = json['month'];
puid = json['puid'];
date = json['date'];
amount = json['amount'];
payemntYear = json['payemnt_year'];
note = json['note'];
createdAt = json['created_at'];
updatedAt = json['updated_at'];
employee = json['employee'] != null ? Employee.fromJson(json['employee']) : null;
if (json['transactions'] != null) {
transactions = [];
json['transactions'].forEach((v) {
transactions?.add(PaymentsTransaction.fromJson(v));
});
}
branch = json['branch'];
}
num? id;
num? businessId;
dynamic branchId;
num? employeeId;
num? paymentTypeId;
String? month;
String? puid;
String? date;
num? amount;
String? payemntYear;
String? note;
String? createdAt;
String? updatedAt;
Employee? employee;
List<PaymentsTransaction>? transactions;
dynamic branch;
}
// Reusing Employee model from previous section (assuming it's available)
class Employee {
Employee({this.id, this.name});
Employee.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
}
num? id;
String? name;
}
// Placeholder for list consumption
class PaymentTypeData extends PaymentType {
PaymentTypeData.fromJson(dynamic json) : super.fromJson(json);
}

View File

@@ -0,0 +1,383 @@
// File: add_new_payroll.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconly/iconly.dart';
import 'package:intl/intl.dart';
import 'package:mobile_pos/widgets/multipal%20payment%20mathods/multi_payment_widget.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
// --- Local Imports ---
import '../../../constant.dart';
// --- Data Layer Imports ---
import 'package:mobile_pos/Screens/hrm/payroll/repo/payroll_repo.dart';
import 'package:mobile_pos/Screens/hrm/employee/model/employee_list_model.dart';
import 'package:mobile_pos/Screens/hrm/employee/provider/emplpyee_list_provider.dart';
import 'Model/payroll_lsit_model.dart';
class AddNewPayroll extends ConsumerStatefulWidget {
final PayrollData? payrollData;
const AddNewPayroll({super.key, this.payrollData});
@override
ConsumerState<AddNewPayroll> createState() => _AddNewPayrollState();
}
class _AddNewPayrollState extends ConsumerState<AddNewPayroll> {
// --- Form Controllers ---
final GlobalKey<FormState> _key = GlobalKey();
final GlobalKey<MultiPaymentWidgetState> _paymentKey = GlobalKey<MultiPaymentWidgetState>();
final dateController = TextEditingController();
final amountController = TextEditingController();
final noteController = TextEditingController();
// --- Selected Values (API payload) ---
EmployeeData? _selectedEmployee;
String? _selectedYear;
String? _selectedMonth;
DateTime? _selectedDate;
// --- UI/API Helpers ---
final DateFormat _displayDateFormat = DateFormat('dd/MM/yyyy');
final DateFormat _apiFormat = DateFormat('yyyy-MM-dd');
final List<String> _months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
bool get isEditing => widget.payrollData != null;
@override
void initState() {
super.initState();
if (isEditing) {
final data = widget.payrollData!;
amountController.text = data.amount?.toString() ?? '';
noteController.text = data.note ?? '';
_selectedYear = data.payemntYear;
_selectedMonth = data.month != null ? data.month![0].toUpperCase() + data.month!.substring(1) : null;
try {
if (data.date != null) {
_selectedDate = DateTime.parse(data.date!);
dateController.text = _displayDateFormat.format(_selectedDate!);
}
} catch (e) {
debugPrint('Error parsing date for editing: $e');
}
}
}
@override
void dispose() {
dateController.dispose();
amountController.dispose();
noteController.dispose();
super.dispose();
}
// --- Year Generation Logic ---
List<String> _getYearOptions() {
final currentYear = DateTime.now().year;
List<String> years = [];
for (int i = 0; i <= 5; i++) {
years.add((currentYear - i).toString());
}
years.insert(0, (currentYear + 1).toString());
return years.toSet().toList().reversed.toList();
}
// --- Date Picker Logic ---
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
initialDate: _selectedDate ?? DateTime.now(),
firstDate: DateTime(2015, 1),
lastDate: DateTime(2101),
context: context,
);
setState(() {
if (picked != null) {
_selectedDate = picked;
dateController.text = _displayDateFormat.format(picked);
}
});
}
// --- Submission Logic ---
void _submit() async {
if (_key.currentState!.validate() && _selectedEmployee != null && _selectedDate != null) {
// Get Payments from the MultiPaymentWidget
List<PaymentEntry> payments = _paymentKey.currentState?.getPaymentEntries() ?? [];
// Validation: Ensure payments are added
if (payments.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please add a payment method')));
return;
}
final repo = PayrollRepo();
// Convert payments to JSON list for API
final paymentListJson = payments.map((e) => e.toJson()).toList();
final payload = {
'employee_id': _selectedEmployee!.id!,
'month': _selectedMonth!.toLowerCase(),
'date': _apiFormat.format(_selectedDate!),
'amount': amountController.text,
'payemnt_year': _selectedYear!,
'note': noteController.text,
'payments': paymentListJson,
};
if (isEditing) {
await repo.updatePayroll(
ref: ref,
context: context,
id: widget.payrollData!.id!,
employeeId: payload['employee_id'] as num,
month: payload['month'] as String,
date: payload['date'] as String,
amount: payload['amount'] as String,
paymentYear: payload['payemnt_year'] as String,
note: payload['note'] as String?,
payments: paymentListJson,
);
} else {
await repo.createPayroll(
ref: ref,
context: context,
employeeId: payload['employee_id'] as num,
month: payload['month'] as String,
date: payload['date'] as String,
amount: payload['amount'] as String,
paymentYear: payload['payemnt_year'] as String,
note: payload['note'] as String?,
payments: paymentListJson,
);
}
}
}
void _resetForm() {
if (!isEditing) {
setState(() {
_key.currentState?.reset();
dateController.clear();
amountController.clear();
noteController.clear();
_selectedEmployee = null;
_selectedYear = null;
_selectedMonth = null;
_selectedDate = null;
// _paymentKey state resets automatically on rebuild or you can clear manually if needed
});
} else {
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
// Watch required providers
final employeesAsync = ref.watch(employeeListProvider);
final _lang = lang.S.of(context);
return Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
centerTitle: true,
title: Text(isEditing ? _lang.editPayroll : _lang.addNewPayroll),
bottom: const PreferredSize(
preferredSize: Size.fromHeight(1),
child: Divider(height: 2, color: kBackgroundColor),
),
),
body: employeesAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error loading employees: $err')),
data: (employeeModel) {
final employees = employeeModel.employees ?? [];
if (isEditing) {
final data = widget.payrollData!;
_selectedEmployee = employees.firstWhere(
(e) => e.id == data.employeeId,
orElse: () => _selectedEmployee ?? employees.first,
);
amountController.text = _selectedEmployee?.amount.toString() ?? '';
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _key,
child: Column(
children: [
// 1. Employee Dropdown
_buildEmployeeDropdown(employees),
const SizedBox(height: 20),
// 2. Payment Year Dropdown
_buildYearDropdown(),
const SizedBox(height: 20),
// 3. Month & Date
Row(
children: [
Expanded(child: _buildMonthDropdown()),
const SizedBox(width: 16),
Expanded(child: _buildDateInput()),
],
),
const SizedBox(height: 20),
// 4. Amount Field (Read Only - Based on Employee)
_buildAmountInput(),
const SizedBox(height: 20),
// 5. Payment Methods
MultiPaymentWidget(
key: _paymentKey,
hideAddButton: true,
disableDropdown: widget.payrollData != null,
showWalletOption: false,
showChequeOption: false,
totalAmountController: amountController,
initialTransactions: widget.payrollData?.transactions, // <--- Passing data for Edit
),
const SizedBox(height: 20),
// 6. Note
_buildNoteInput(),
const SizedBox(height: 20),
// 7. Action Buttons
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _resetForm, child: Text(isEditing ? _lang.cancel : _lang.resets))),
const SizedBox(width: 16),
Expanded(
child:
ElevatedButton(onPressed: _submit, child: Text(isEditing ? _lang.update : _lang.save))),
],
),
],
),
),
);
},
),
);
}
// --- Widget Builder Helpers ---
Widget _buildEmployeeDropdown(List<EmployeeData> employees) {
return DropdownButtonFormField<EmployeeData>(
value: _selectedEmployee,
icon: const Icon(Icons.keyboard_arrow_down, color: kNeutral800),
decoration: InputDecoration(labelText: lang.S.of(context).employee, hintText: lang.S.of(context).selectOne),
validator: (value) => value == null ? lang.S.of(context).pleaseSelectAnEmployee : null,
items: employees.map((entry) {
return DropdownMenuItem<EmployeeData>(
value: entry,
child: Text(entry.name ?? 'N/A'),
);
}).toList(),
onChanged: (EmployeeData? value) {
setState(() {
_selectedEmployee = value;
amountController.text = _selectedEmployee?.amount.toString() ?? '0';
});
},
);
}
Widget _buildYearDropdown() {
return DropdownButtonFormField<String>(
value: _selectedYear,
icon: const Icon(Icons.keyboard_arrow_down, color: kNeutral800),
decoration: InputDecoration(labelText: lang.S.of(context).paymentYear, hintText: lang.S.of(context).selectOne),
validator: (value) => value == null ? lang.S.of(context).pleaseSelectPaymentYear : null,
items: _getYearOptions().map((entry) {
return DropdownMenuItem(value: entry, child: Text(entry));
}).toList(),
onChanged: (String? value) {
setState(() => _selectedYear = value);
},
);
}
Widget _buildMonthDropdown() {
return DropdownButtonFormField<String>(
value: _selectedMonth,
icon: const Icon(Icons.keyboard_arrow_down, color: kNeutral800),
decoration: InputDecoration(labelText: lang.S.of(context).month, hintText: lang.S.of(context).selectOne),
validator: (value) => value == null ? lang.S.of(context).pleaseSelectAnMonth : null,
items: _months.map((entry) {
return DropdownMenuItem(value: entry, child: Text(entry));
}).toList(),
onChanged: (String? value) {
setState(() => _selectedMonth = value);
},
);
}
Widget _buildDateInput() {
return TextFormField(
keyboardType: TextInputType.name,
readOnly: true,
controller: dateController,
decoration: InputDecoration(
labelText: lang.S.of(context).month,
hintText: 'DD/MM/YYYY',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
padding: EdgeInsets.zero,
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
onPressed: () => _selectDate(context),
icon: const Icon(IconlyLight.calendar, size: 22),
),
),
validator: (value) => value!.isEmpty ? lang.S.of(context).pleaseEnterADate : null,
);
}
Widget _buildAmountInput() {
return TextFormField(
readOnly: true,
controller: amountController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: lang.S.of(context).totalSalaryAmount,
hintText: lang.S.of(context).selectEmployeeFirst,
border: OutlineInputBorder(),
),
);
}
Widget _buildNoteInput() {
return TextFormField(
controller: noteController,
maxLines: 2,
decoration: InputDecoration(
labelText: lang.S.of(context).note,
hintText: lang.S.of(context).enterNote,
border: OutlineInputBorder(),
),
);
}
}

View File

@@ -0,0 +1,436 @@
// File: payroll_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hugeicons/hugeicons.dart';
import 'package:mobile_pos/currency.dart';
// --- Local Imports ---
import 'package:mobile_pos/Screens/hrm/payroll/add_new_payroll.dart';
import 'package:mobile_pos/Screens/hrm/widgets/filter_dropdown.dart';
import 'package:mobile_pos/Screens/hrm/widgets/model_bottom_sheet.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../../service/check_user_role_permission_provider.dart';
import '../../../widgets/empty_widget/_empty_widget.dart';
import '../widgets/deleteing_alart_dialog.dart';
// --- Data Layer Imports ---
import 'package:mobile_pos/Screens/hrm/payroll/repo/payroll_repo.dart';
import 'package:mobile_pos/Screens/hrm/payroll/provider/payroll_provider.dart';
import 'package:mobile_pos/Screens/hrm/employee/provider/emplpyee_list_provider.dart';
import 'package:mobile_pos/Screens/hrm/employee/model/employee_list_model.dart';
import 'Model/payroll_lsit_model.dart';
class PayrollScreen extends ConsumerStatefulWidget {
const PayrollScreen({super.key});
@override
ConsumerState<PayrollScreen> createState() => _PayrollScreenState();
}
class _PayrollScreenState extends ConsumerState<PayrollScreen> {
// --- Filter State ---
String? _selectedEmployeeFilter;
String? _selectedMonthFilter;
List<PayrollData> _filteredList = [];
final List<String> _monthFilters = [
'All Month',
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
@override
void initState() {
super.initState();
_selectedEmployeeFilter = 'All Employee';
_selectedMonthFilter = 'All Month';
}
void _applyFilters() {
setState(() {}); // Trigger rebuild to apply filters
}
void _filterPayrolls(List<PayrollData> allPayrolls) {
_filteredList = allPayrolls.where((payroll) {
final employeeName = (payroll.employee?.name ?? '').toLowerCase();
final month = (payroll.month ?? '').toLowerCase();
// 1. Employee Filter
final employeeMatches =
_selectedEmployeeFilter == 'All Employee' || employeeName == _selectedEmployeeFilter!.toLowerCase();
// 2. Month Filter
final monthMatches = _selectedMonthFilter == 'All Month' || month == _selectedMonthFilter!.toLowerCase();
return employeeMatches && monthMatches;
}).toList();
}
Color _getStatusColor(String? status) {
if (status?.toLowerCase() == 'paid') return kSuccessColor;
return Colors.orange; // Defaulting any other status to orange/pending
}
Future<void> _refreshData() async {
ref.invalidate(payrollListProvider);
return ref.watch(payrollListProvider.future);
}
@override
Widget build(BuildContext context) {
final _lang = lang.S.of(context);
final theme = Theme.of(context);
final payrollsAsync = ref.watch(payrollListProvider);
final employeesAsync = ref.watch(employeeListProvider);
final permissionService = PermissionService(ref);
final combinedAsync = payrollsAsync.asData != null && employeesAsync.asData != null
? AsyncValue.data(true)
: payrollsAsync.hasError || employeesAsync.hasError
? AsyncValue.error(payrollsAsync.error ?? employeesAsync.error!, StackTrace.current)
: const AsyncValue.loading();
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: true,
title: Text(_lang.payrollList),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(65),
child: Column(
children: [
const Divider(thickness: 1.5, color: kBackgroundColor, height: 1),
Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 13),
child: Row(
children: [
// Employee Filter Dropdown
Expanded(
flex: 6,
child: FilterDropdownButton<String>(
value: _selectedEmployeeFilter,
items: [
DropdownMenuItem(value: 'All Employee', child: Text(_lang.allEmployee)),
...(employeesAsync.value?.employees ?? [])
.map((e) => DropdownMenuItem(value: e.name, child: Text(e.name ?? 'n/a')))
.toList(),
]
.map((item) => item.value != null
? DropdownMenuItem(
value: item.value,
child: Text(item.value!,
style: theme.textTheme.bodyLarge?.copyWith(color: kNeutral800),
maxLines: 1,
overflow: TextOverflow.ellipsis),
)
: item)
.toList(),
onChanged: (String? value) {
setState(() {
_selectedEmployeeFilter = value;
_applyFilters();
});
},
),
),
const SizedBox(width: 10),
// Month Filter Dropdown
Expanded(
flex: 4,
child: FilterDropdownButton<String>(
buttonDecoration: BoxDecoration(
color: kBackgroundColor,
borderRadius: BorderRadius.circular(5),
border: Border.all(color: kBorderColor),
),
value: _selectedMonthFilter,
items: _monthFilters.map((entry) {
return DropdownMenuItem(
value: entry,
child: Text(entry,
style: theme.textTheme.bodyLarge?.copyWith(color: kNeutral800),
maxLines: 1,
overflow: TextOverflow.ellipsis),
);
}).toList(),
onChanged: (String? value) {
setState(() {
_selectedMonthFilter = value;
_applyFilters();
});
},
),
),
],
),
),
const Divider(thickness: 1.5, color: kBackgroundColor, height: 1),
],
)),
),
body: combinedAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: Failed to load data.')),
data: (_) {
if (!permissionService.hasPermission(Permit.payrollsRead.value)) {
return const Center(child: PermitDenyWidget());
}
_filterPayrolls(payrollsAsync.value?.data ?? []);
if (_filteredList.isEmpty) {
return RefreshIndicator(
onRefresh: _refreshData,
child: Center(
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(), child: Text(lang.S.of(context).noPayrollFound))),
);
}
return RefreshIndicator(
onRefresh: _refreshData,
child: ListView.separated(
padding: EdgeInsets.zero,
itemCount: _filteredList.length,
separatorBuilder: (_, __) => const Divider(color: kBackgroundColor, height: 1.5),
itemBuilder: (_, index) => _buildPayrollItem(
context: context,
ref: ref,
payroll: _filteredList[index],
),
),
);
},
),
bottomNavigationBar: permissionService.hasPermission(Permit.payrollsCreate.value)
? Padding(
padding: const EdgeInsets.all(16),
child: ElevatedButton.icon(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const AddNewPayroll()),
),
icon: const Icon(Icons.add, color: Colors.white),
label: Text(_lang.addNewPayroll),
),
)
: null,
);
}
// --- List Item Builder ---
Widget _buildPayrollItem({
required BuildContext context,
required WidgetRef ref,
required PayrollData payroll,
}) {
final theme = Theme.of(context);
final status = payroll.amount != null ? lang.S.of(context).paid : lang.S.of(context).unPaid;
final statusColor = _getStatusColor(status);
// Logic to prepare Detail String for transactions
String paymentDetails = "";
if (payroll.transactions != null && payroll.transactions!.isNotEmpty) {
// Create a list of strings like "Cash: 500", "Bank: 200"
List<String> details = payroll.transactions!.map((t) {
return (t.transactionType == 'cash_payment') ? 'Cash' : (t.paymentType?.name ?? 'Unknown');
}).toList();
paymentDetails = details.join('\n');
} else {
// Fallback for old data or no transactions
paymentDetails = "N/A";
}
// Logic for Summary text in the List view
String paymentSummary = "N/A";
if (payroll.transactions != null && payroll.transactions!.isNotEmpty) {
paymentSummary = (payroll.transactions!.first.transactionType == 'cash_payment')
? 'Cash'
: (payroll.transactions!.first.paymentType?.name ?? 'Unknown');
if (payroll.transactions!.length > 1) {
paymentSummary += " +${payroll.transactions!.length - 1}";
}
}
return InkWell(
onTap: () => viewModalSheet(
context: context,
item: {
lang.S.of(context).employee: payroll.employee?.name ?? 'N/A',
lang.S.of(context).paymentYear: payroll.payemntYear ?? 'N/A',
lang.S.of(context).month: payroll.month ?? 'N/A',
lang.S.of(context).date: payroll.date ?? 'N/A',
lang.S.of(context).amount: '$currency${payroll.amount?.toStringAsFixed(2) ?? '0.00'}',
lang.S.of(context).paymentDetails: paymentDetails, // Showing Full Details Here
},
descriptionTitle: '${lang.S.of(context).paymentDetails} : ',
description: payroll.note ?? 'N/A',
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13.5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
payroll.employee?.name ?? 'N/A Employee',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
payroll.date ?? 'N/A Date',
style: theme.textTheme.bodyMedium?.copyWith(color: kNeutral800),
),
],
),
_buildActionButtons(context, ref, payroll),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildTimeColumn(
time: '$currency${payroll.amount?.toStringAsFixed(2) ?? '0.00'}',
label: lang.S.of(context).amount,
theme: theme,
),
_buildTimeColumn(
time: paymentSummary, // Showing Summary Here
label: lang.S.of(context).payment,
theme: theme,
),
_buildTimeColumn(
time: status,
label: lang.S.of(context).status,
titleColor: statusColor,
theme: theme,
),
],
),
],
),
),
);
}
Widget _buildTimeColumn({
required String time,
required String label,
required ThemeData theme,
Color? titleColor,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
time,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: titleColor ?? kTitleColor,
),
),
const SizedBox(height: 6),
Text(
label,
style: theme.textTheme.bodyMedium?.copyWith(
color: kNeutral800,
),
),
],
);
}
Widget _buildActionButtons(BuildContext context, WidgetRef ref, PayrollData payroll) {
final permissionService = PermissionService(ref);
return Column(
children: [
GestureDetector(
onTap: () {
if (!permissionService.hasPermission(Permit.payrollsUpdate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(lang.S.of(context).youDoNotHaveUpdatePayroll),
),
);
return;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddNewPayroll(payrollData: payroll),
),
);
},
child: const HugeIcon(
icon: HugeIcons.strokeRoundedPencilEdit02,
color: kSuccessColor,
size: 20,
),
),
const SizedBox(height: 8),
GestureDetector(
onTap: () {
if (!permissionService.hasPermission(Permit.payrollsDelete.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(lang.S.of(context).youDoNotHavePermissionToDeletePayroll),
),
);
return;
}
if (payroll.id != null) {
_showDeleteConfirmationDialog(
context,
ref,
payroll.id!,
payroll.employee?.name ?? lang.S.of(context).payrollRecord,
);
}
},
child: const HugeIcon(
icon: HugeIcons.strokeRoundedDelete03,
color: Colors.red,
size: 20,
),
),
],
);
}
void _showDeleteConfirmationDialog(BuildContext context, WidgetRef ref, num id, String name) async {
bool result = await showDeleteConfirmationDialog(
context: context,
itemName: name,
);
if (result) {
final repo = PayrollRepo();
await repo.deletePayroll(id: id, context: context, ref: ref);
ref.invalidate(payrollListProvider);
}
}
}

View File

@@ -0,0 +1,9 @@
// File: payroll_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Screens/hrm/payroll/repo/payroll_repo.dart';
import '../Model/payroll_lsit_model.dart';
final repo = PayrollRepo();
final payrollListProvider = FutureProvider<PayrollListModel>((ref) => repo.fetchAllPayrolls());

View File

@@ -0,0 +1,183 @@
// File: payroll_repo.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../../Const/api_config.dart';
import '../../../../../http_client/custome_http_client.dart';
import '../../../../../http_client/customer_http_client_get.dart';
import '../Model/payroll_lsit_model.dart';
import '../provider/payroll_provider.dart';
class PayrollRepo {
static const String _endpoint = '/payrolls';
///---------------- FETCH ALL PAYROLLS (GET) ----------------///
Future<PayrollListModel> fetchAllPayrolls() async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}$_endpoint');
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body);
return PayrollListModel.fromJson(parsedData);
} else {
throw Exception('Failed to fetch payroll list. Status: ${response.statusCode}');
}
}
///---------------- CREATE PAYROLL (POST) ----------------///
Future<void> createPayroll({
required WidgetRef ref,
required BuildContext context,
required num employeeId,
required String month,
required String date, // YYYY-MM-DD
required String amount,
required String paymentYear,
required List<Map<String, dynamic>> payments,
String? note,
}) async {
final uri = Uri.parse('${APIConfig.url}$_endpoint');
final requestBody = jsonEncode({
'employee_id': employeeId,
'payments': payments,
'month': month.toLowerCase(),
'date': date,
'amount': amount,
'payemnt_year': paymentYear,
'note': note,
});
try {
EasyLoading.show(status: 'Creating Payroll...');
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
var responseData = await customHttpClient.post(
addContentTypeInHeader: true,
url: uri,
body: requestBody,
);
final parsedData = jsonDecode(responseData.body);
EasyLoading.dismiss();
if (responseData.statusCode == 200 || responseData.statusCode == 201) {
ref.invalidate(payrollListProvider);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(parsedData['message'] ?? 'Payroll created successfully')),
);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Creation failed: ${parsedData['message'] ?? 'Unknown error'}')),
);
}
} catch (error) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('An error occurred: $error')),
);
}
}
///---------------- UPDATE PAYROLL (POST with _method=put) ----------------///
Future<void> updatePayroll({
required WidgetRef ref,
required BuildContext context,
required num id,
required num employeeId,
required String month,
required String date,
required String amount,
required String paymentYear,
required List<Map<String, dynamic>> payments,
String? note,
}) async {
final uri = Uri.parse('${APIConfig.url}$_endpoint/$id');
final requestBody = jsonEncode({
'_method': 'put',
'employee_id': employeeId,
'payments': payments,
'month': month.toLowerCase(),
'date': date,
'amount': amount,
'payemnt_year': paymentYear,
'note': note,
});
try {
EasyLoading.show(status: 'Updating Payroll...');
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();
print('Payroll Update POST:-------> ${requestBody}');
print('Payroll Update:-------> ${parsedData}');
if (responseData.statusCode == 200) {
ref.invalidate(payrollListProvider);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(parsedData['message'] ?? 'Payroll updated successfully')),
);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Update failed: ${parsedData['message'] ?? 'Unknown error'}')),
);
}
} catch (error) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('An error occurred: $error')),
);
}
}
///---------------- DELETE PAYROLL ----------------///
Future<bool> deletePayroll({
required num id,
required BuildContext context,
required WidgetRef ref,
}) async {
try {
EasyLoading.show(status: 'Deleting...');
final url = Uri.parse('${APIConfig.url}$_endpoint/$id');
CustomHttpClient customHttpClient = CustomHttpClient(ref: ref, context: context, client: http.Client());
final response = await customHttpClient.delete(url: url);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ref.invalidate(payrollListProvider);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payroll deleted successfully')),
);
return true;
} else {
final parsedData = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Deletion failed: ${parsedData['message'] ?? 'Unknown error'}')),
);
return false;
}
} catch (error) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('An error occurred during deletion: $error')),
);
return false;
}
}
}