295 lines
10 KiB
Dart
295 lines
10 KiB
Dart
import 'package:aman_kassa_flutter/core/locator.dart';
|
||
import 'package:aman_kassa_flutter/core/models/nct_product.dart';
|
||
import 'package:aman_kassa_flutter/core/services/NctService.dart' show NctService, NctServiceException;
|
||
import 'package:aman_kassa_flutter/redux/actions/kassa_actions.dart';
|
||
import 'package:aman_kassa_flutter/redux/store.dart';
|
||
import 'package:aman_kassa_flutter/shared/app_colors.dart';
|
||
import 'package:aman_kassa_flutter/shared/ui_helpers.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
|
||
class ProductAddBottomSheet extends StatefulWidget {
|
||
final ScrollController? scrollController;
|
||
final String? initialName;
|
||
final String? initialNtinCode;
|
||
|
||
ProductAddBottomSheet({this.scrollController, this.initialName, this.initialNtinCode});
|
||
|
||
@override
|
||
_ProductAddBottomSheetState createState() => _ProductAddBottomSheetState();
|
||
}
|
||
|
||
class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
||
final NctService _nctService = locator<NctService>();
|
||
|
||
late TextEditingController ntinController;
|
||
late TextEditingController nameController;
|
||
late TextEditingController countController;
|
||
late TextEditingController priceController;
|
||
double sum = 0.0;
|
||
bool _isSearching = false;
|
||
String? _foundNtinCode;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
ntinController = new TextEditingController();
|
||
nameController = new TextEditingController(text: widget.initialName ?? '');
|
||
countController = new TextEditingController();
|
||
priceController = new TextEditingController();
|
||
_foundNtinCode = widget.initialNtinCode;
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
ntinController.dispose();
|
||
nameController.dispose();
|
||
countController.dispose();
|
||
priceController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
decoration: BoxDecoration(color: whiteColor),
|
||
child: Scaffold(
|
||
appBar: AppBar(
|
||
iconTheme: IconThemeData(color: Colors.black),
|
||
backgroundColor: fillColor,
|
||
elevation: 3,
|
||
title: Text(
|
||
'Добавить товар/услугу',
|
||
style: TextStyle(color: Colors.black87),
|
||
),
|
||
),
|
||
body: Padding(
|
||
padding: EdgeInsets.only(top: 15, left: 10, right: 15, bottom: 0),
|
||
child: ListView(
|
||
controller: widget.scrollController,
|
||
children: <Widget>[
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: [
|
||
Expanded(
|
||
child: TextField(
|
||
decoration: new InputDecoration(
|
||
border: new OutlineInputBorder(
|
||
borderSide: new BorderSide(color: primaryColor)),
|
||
hintText: 'Введите NTIN/GTIN',
|
||
labelText: 'NTIN/GTIN',
|
||
prefixText: ' ',
|
||
),
|
||
keyboardType: TextInputType.text,
|
||
controller: ntinController,
|
||
),
|
||
),
|
||
SizedBox(width: 8),
|
||
SizedBox(
|
||
height: 56,
|
||
child: ElevatedButton(
|
||
onPressed: _isSearching ? null : _searchByNtin,
|
||
style: ElevatedButton.styleFrom(
|
||
primary: primaryColor,
|
||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||
),
|
||
child: _isSearching
|
||
? SizedBox(
|
||
width: 20,
|
||
height: 20,
|
||
child: CircularProgressIndicator(
|
||
color: Colors.white, strokeWidth: 2),
|
||
)
|
||
: Icon(Icons.search, color: Colors.white),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
verticalSpaceSmall,
|
||
TextField(
|
||
decoration: new InputDecoration(
|
||
border: new OutlineInputBorder(
|
||
borderSide: new BorderSide(color: primaryColor)),
|
||
hintText: 'Введите наименовение',
|
||
labelText: 'Наименование',
|
||
prefixText: ' ',
|
||
),
|
||
controller: nameController,
|
||
),
|
||
verticalSpaceSmall,
|
||
TextField(
|
||
decoration: new InputDecoration(
|
||
border: new OutlineInputBorder(
|
||
borderSide: new BorderSide(color: primaryColor)),
|
||
hintText: 'Введите количество',
|
||
labelText: 'Количество',
|
||
prefixText: ' ',
|
||
),
|
||
keyboardType: const TextInputType.numberWithOptions(
|
||
decimal: true,
|
||
),
|
||
inputFormatters: <TextInputFormatter>[
|
||
FilteringTextInputFormatter.allow(RegExp("^[0-9.]*")),
|
||
],
|
||
controller: countController,
|
||
onChanged: calcOnChange,
|
||
),
|
||
verticalSpaceSmall,
|
||
TextField(
|
||
decoration: new InputDecoration(
|
||
border: new OutlineInputBorder(
|
||
borderSide: new BorderSide(color: primaryColor)),
|
||
hintText: 'Введите цену за единицу',
|
||
labelText: 'Стоимость',
|
||
prefixText: ' ',
|
||
),
|
||
keyboardType:
|
||
const TextInputType.numberWithOptions(decimal: true),
|
||
inputFormatters: <TextInputFormatter>[
|
||
FilteringTextInputFormatter.allow(RegExp("^[0-9.]*")),
|
||
],
|
||
controller: priceController,
|
||
onChanged: calcOnChange,
|
||
),
|
||
const Divider(
|
||
height: 1.0,
|
||
),
|
||
new ListTile(
|
||
leading: const Icon(
|
||
Icons.account_balance_wallet,
|
||
color: primaryColor,
|
||
),
|
||
title: Text(sum == 0 ? '' : sum.toString()),
|
||
subtitle: const Text('Стоимость'),
|
||
),
|
||
verticalSpaceMedium,
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||
children: <Widget>[
|
||
RawMaterialButton(
|
||
onPressed: submit,
|
||
elevation: 2.0,
|
||
fillColor: greenColor,
|
||
child: Icon(
|
||
Icons.done,
|
||
size: 35.0,
|
||
color: whiteColor,
|
||
),
|
||
padding: EdgeInsets.all(15.0),
|
||
shape: CircleBorder(),
|
||
),
|
||
RawMaterialButton(
|
||
onPressed: () {
|
||
Navigator.pop(context);
|
||
},
|
||
elevation: 2.0,
|
||
fillColor: redColor,
|
||
child: Icon(
|
||
Icons.close,
|
||
size: 35.0,
|
||
color: whiteColor,
|
||
),
|
||
padding: EdgeInsets.all(15.0),
|
||
shape: CircleBorder(),
|
||
)
|
||
],
|
||
)
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _searchByNtin() async {
|
||
final ntin = ntinController.text.trim();
|
||
if (ntin.isEmpty) {
|
||
_showErrorDialog('Введите NTIN/GTIN для поиска.');
|
||
return;
|
||
}
|
||
setState(() => _isSearching = true);
|
||
try {
|
||
List<NctProduct> results = await _nctService.searchByNtin(ntin);
|
||
if (results.isNotEmpty) {
|
||
final product = results.first;
|
||
setState(() {
|
||
nameController.text = product.nameRu ?? product.nameKk ?? '';
|
||
_foundNtinCode = product.ntinCode;
|
||
});
|
||
if (product.ntinIsDeactivated == true) {
|
||
String reason = product.ntinDeactivationReason ?? '';
|
||
String duplicate = product.ntinDuplicateOfProduct != null
|
||
? '\nДубликат: ${product.ntinDuplicateOfProduct}'
|
||
: '';
|
||
_showErrorDialog('Карточка деактивирована.$reason$duplicate');
|
||
}
|
||
} else {
|
||
_showErrorDialog('Товар с NTIN/GTIN "$ntin" не найден.');
|
||
}
|
||
} on NctServiceException catch (e) {
|
||
_showErrorDialog(e.message);
|
||
} catch (e) {
|
||
_showErrorDialog('Ошибка поиска NCT: $e');
|
||
} finally {
|
||
setState(() => _isSearching = false);
|
||
}
|
||
}
|
||
|
||
void submit() {
|
||
if (nameController.text.isEmpty ||
|
||
countController.text.isEmpty ||
|
||
priceController.text.isEmpty) {
|
||
_showErrorDialog('Введите наименование, количество и цену');
|
||
} else {
|
||
Redux.store!.dispatch(addCustomProductToKassaItems(
|
||
nameController.text,
|
||
double.parse(countController.text),
|
||
double.parse(priceController.text),
|
||
sum,
|
||
ntin: _foundNtinCode));
|
||
Navigator.pop(context);
|
||
}
|
||
}
|
||
|
||
void calcOnChange(value) {
|
||
try {
|
||
setState(() {
|
||
sum = 0;
|
||
});
|
||
if (countController.text != '' && priceController.text != '') {
|
||
double count = double.parse(countController.text);
|
||
double price = double.parse(priceController.text);
|
||
double result = count * price;
|
||
setState(() {
|
||
sum = ((result * 100).roundToDouble()) / 100;
|
||
});
|
||
}
|
||
} catch (e) {
|
||
print(e);
|
||
}
|
||
}
|
||
|
||
void _showErrorDialog(String message) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (BuildContext context) {
|
||
return AlertDialog(
|
||
title: new Text("Aman Касса"),
|
||
content: new Text(message),
|
||
actions: <Widget>[
|
||
TextButton(
|
||
child: Text(
|
||
"ОК",
|
||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||
),
|
||
onPressed: () {
|
||
Navigator.of(context).pop();
|
||
},
|
||
),
|
||
],
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|