fortenew commit prod
parent
1a39e1eed4
commit
7c7e50b78e
|
|
@ -3,6 +3,7 @@
|
||||||
import 'package:aman_kassa_flutter/core/services/BankService.dart' as bank;
|
import 'package:aman_kassa_flutter/core/services/BankService.dart' as bank;
|
||||||
import 'package:aman_kassa_flutter/core/services/ForteService.dart' as forte;
|
import 'package:aman_kassa_flutter/core/services/ForteService.dart' as forte;
|
||||||
import 'package:aman_kassa_flutter/core/services/DataService.dart';
|
import 'package:aman_kassa_flutter/core/services/DataService.dart';
|
||||||
|
import 'package:aman_kassa_flutter/core/services/NctService.dart';
|
||||||
import 'package:aman_kassa_flutter/core/services/blue_print_service.dart';
|
import 'package:aman_kassa_flutter/core/services/blue_print_service.dart';
|
||||||
|
|
||||||
import '../core/services/DbService.dart';
|
import '../core/services/DbService.dart';
|
||||||
|
|
@ -35,6 +36,8 @@ class LocatorInjector {
|
||||||
// depencies
|
// depencies
|
||||||
|
|
||||||
|
|
||||||
|
_log.d('Initializing NctService Service');
|
||||||
|
locator.registerLazySingleton<NctService>(() => NctService());
|
||||||
_log.d('Initializing DataService Service');
|
_log.d('Initializing DataService Service');
|
||||||
locator.registerLazySingleton<DataService>(() => DataService());
|
locator.registerLazySingleton<DataService>(() => DataService());
|
||||||
_log.d('Initializing BankService Service');
|
_log.d('Initializing BankService Service');
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ class CheckItem {
|
||||||
final num price;
|
final num price;
|
||||||
final int articul;
|
final int articul;
|
||||||
final String? excise;
|
final String? excise;
|
||||||
CheckItem({required this.name, required this.cnt, required this.price, required this.articul, this.excise});
|
final String? ntin;
|
||||||
|
|
||||||
|
CheckItem({required this.name, required this.cnt, required this.price, required this.articul, this.excise, this.ntin});
|
||||||
|
|
||||||
static CheckItem fromJson(Map<String, dynamic> json) {
|
static CheckItem fromJson(Map<String, dynamic> json) {
|
||||||
return CheckItem(
|
return CheckItem(
|
||||||
|
|
@ -13,14 +15,17 @@ class CheckItem {
|
||||||
price: json['price'],
|
price: json['price'],
|
||||||
articul: json['articul'],
|
articul: json['articul'],
|
||||||
excise: json['excise'],
|
excise: json['excise'],
|
||||||
|
ntin: json['ntin'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() =>
|
Map<String, dynamic> toJson() =>
|
||||||
{
|
{
|
||||||
'name': name,
|
'name': name,
|
||||||
'cnt': cnt,
|
'cnt': cnt,
|
||||||
'price': price,
|
'price': price,
|
||||||
'articul': articul,
|
'articul': articul,
|
||||||
'excise' : excise
|
'excise': excise,
|
||||||
|
'ntin': ntin ?? '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
class NctProduct {
|
||||||
|
final int? id;
|
||||||
|
final String? nameRu;
|
||||||
|
final String? nameKk;
|
||||||
|
final String? gtin;
|
||||||
|
final String? ntinCode;
|
||||||
|
final bool? isSocial;
|
||||||
|
final int? status;
|
||||||
|
final bool? isMarkedEac;
|
||||||
|
final String? measure;
|
||||||
|
final bool? ntinIsDeactivated;
|
||||||
|
final String? ntinDeactivationReason;
|
||||||
|
final String? ntinDuplicateOfProduct;
|
||||||
|
|
||||||
|
NctProduct({
|
||||||
|
this.id,
|
||||||
|
this.nameRu,
|
||||||
|
this.nameKk,
|
||||||
|
this.gtin,
|
||||||
|
this.ntinCode,
|
||||||
|
this.isSocial,
|
||||||
|
this.status,
|
||||||
|
this.isMarkedEac,
|
||||||
|
this.measure,
|
||||||
|
this.ntinIsDeactivated,
|
||||||
|
this.ntinDeactivationReason,
|
||||||
|
this.ntinDuplicateOfProduct,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory NctProduct.fromJson(Map<String, dynamic> json) {
|
||||||
|
return NctProduct(
|
||||||
|
id: json['id'] is int ? json['id'] as int : null,
|
||||||
|
nameRu: json['name_ru'] is String ? json['name_ru'] as String : null,
|
||||||
|
nameKk: json['name_kk'] is String ? json['name_kk'] as String : null,
|
||||||
|
gtin: json['gtin'] is String ? json['gtin'] as String : null,
|
||||||
|
ntinCode: json['ntin_code'] is String ? json['ntin_code'] as String : null,
|
||||||
|
isSocial: json['is_social'] is bool ? json['is_social'] as bool : null,
|
||||||
|
status: json['status'] is int ? json['status'] as int : null,
|
||||||
|
isMarkedEac: json['is_markedeac'] is bool ? json['is_markedeac'] as bool : null,
|
||||||
|
measure: _parseStringOrObject(json['measure']),
|
||||||
|
ntinIsDeactivated: json['ntin_isdeactivated'] is bool ? json['ntin_isdeactivated'] as bool : null,
|
||||||
|
ntinDeactivationReason: _parseStringOrObject(json['ntin_deactivationreason']),
|
||||||
|
ntinDuplicateOfProduct: _parseStringOrObject(json['ntin_duplicateofproduct']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _parseStringOrObject(dynamic value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value is String) return value;
|
||||||
|
if (value is Map) return value['name_ru']?.toString() ?? value['name']?.toString() ?? value.toString();
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,7 @@ class ProductDao {
|
||||||
final Good? good;
|
final Good? good;
|
||||||
final Service? service;
|
final Service? service;
|
||||||
final String? excise;
|
final String? excise;
|
||||||
|
final String? ntin;
|
||||||
|
|
||||||
|
ProductDao({required this.name, required this.price, required this.count, required this.total, this.good, this.service, this.excise, this.ntin});
|
||||||
ProductDao( {required this.name, required this.price, required this.count, required this.total, this.good, this.service, this.excise });
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -147,8 +147,11 @@ class ApiService extends BaseService {
|
||||||
return Response.fromJsonDynamic(json.decode(response));
|
return Response.fromJsonDynamic(json.decode(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Response<dynamic>> sellReturn(String token, String checkData) async {
|
Future<Response<dynamic>> sellReturn(String token, String checkData, {String? ticketNumber}) async {
|
||||||
Map<String, String> requestBody = <String, String>{'api_token': token, 'data': checkData};
|
Map<String, String> requestBody = <String, String>{'api_token': token, 'data': checkData};
|
||||||
|
if (ticketNumber != null && ticketNumber.isNotEmpty) {
|
||||||
|
requestBody['ticketNumber'] = ticketNumber;
|
||||||
|
}
|
||||||
var response = await requestFormData('/sell_return', requestBody);
|
var response = await requestFormData('/sell_return', requestBody);
|
||||||
return Response.fromJsonDynamic(json.decode(response));
|
return Response.fromJsonDynamic(json.decode(response));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ class DataService extends BaseService {
|
||||||
price: el.price,
|
price: el.price,
|
||||||
articul: articul,
|
articul: articul,
|
||||||
excise: el.excise,
|
excise: el.excise,
|
||||||
|
ntin: el.ntin,
|
||||||
));
|
));
|
||||||
summ += el.total;
|
summ += el.total;
|
||||||
iterator++;
|
iterator++;
|
||||||
|
|
@ -191,6 +192,7 @@ class DataService extends BaseService {
|
||||||
{String? paymentType,
|
{String? paymentType,
|
||||||
String? tradeType,
|
String? tradeType,
|
||||||
String? contragent,
|
String? contragent,
|
||||||
|
String? ticketNumber,
|
||||||
required String token,
|
required String token,
|
||||||
required List<ProductDao> kassaItems,
|
required List<ProductDao> kassaItems,
|
||||||
required List<CalcModel> calcItems,
|
required List<CalcModel> calcItems,
|
||||||
|
|
@ -217,7 +219,7 @@ class DataService extends BaseService {
|
||||||
// log.i('data: $data');
|
// log.i('data: $data');
|
||||||
Response<dynamic> response = await (operationType == OperationTypePay
|
Response<dynamic> response = await (operationType == OperationTypePay
|
||||||
? _api.sell(token, data)
|
? _api.sell(token, data)
|
||||||
: _api.sellReturn(token, data));
|
: _api.sellReturn(token, data, ticketNumber: ticketNumber));
|
||||||
// log.i('response status: ${response.status}');
|
// log.i('response status: ${response.status}');
|
||||||
// log.i('response operation: ${response.operation}');
|
// log.i('response operation: ${response.operation}');
|
||||||
if (response.status == 200 && response.operation == true) {
|
if (response.status == 200 && response.operation == true) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:aman_kassa_flutter/core/base/base_service.dart';
|
||||||
|
import 'package:aman_kassa_flutter/core/models/nct_product.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/io_client.dart';
|
||||||
|
|
||||||
|
class NctService extends BaseService {
|
||||||
|
static const String _baseUrl = 'https://nct.gov.kz/api/integration/ofd/search_ofd/';
|
||||||
|
|
||||||
|
Future<List<NctProduct>> searchByNtin(String ntin) async {
|
||||||
|
final uri = Uri.parse(_baseUrl).replace(queryParameters: {'tin': ntin});
|
||||||
|
log.i('NCT request: $uri');
|
||||||
|
|
||||||
|
http.Client client = _buildClient();
|
||||||
|
late http.Response response;
|
||||||
|
try {
|
||||||
|
response = await client.get(uri, headers: {'Accept': 'application/json'});
|
||||||
|
} on TlsException catch (e) {
|
||||||
|
log.e('NCT TLS error', e);
|
||||||
|
throw NctServiceException('Ошибка SSL при подключении к NCT: ${e.message}');
|
||||||
|
} on SocketException catch (e) {
|
||||||
|
log.e('NCT network error', e);
|
||||||
|
throw NctServiceException('Ошибка сети при подключении к NCT: ${e.message}');
|
||||||
|
} catch (e) {
|
||||||
|
log.e('NCT request error', e);
|
||||||
|
throw NctServiceException('Ошибка запроса к NCT: $e');
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.i('NCT status: ${response.statusCode}');
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw NctServiceException('NCT вернул статус ${response.statusCode}');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final bodyString = utf8.decode(response.bodyBytes);
|
||||||
|
log.i('NCT body: ${bodyString.length > 300 ? bodyString.substring(0, 300) : bodyString}');
|
||||||
|
final decoded = json.decode(bodyString);
|
||||||
|
List<dynamic> list;
|
||||||
|
if (decoded is List) {
|
||||||
|
list = decoded;
|
||||||
|
} else if (decoded is Map && decoded.containsKey('results')) {
|
||||||
|
list = decoded['results'] as List<dynamic>;
|
||||||
|
} else {
|
||||||
|
log.w('NCT unexpected response format: ${decoded.runtimeType}');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
.map((e) => NctProduct.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
log.e('NCT JSON parse error', e);
|
||||||
|
throw NctServiceException('Ошибка разбора ответа NCT: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Client _buildClient() {
|
||||||
|
final httpClient = HttpClient()
|
||||||
|
..badCertificateCallback = (cert, host, port) {
|
||||||
|
log.w('NCT bad certificate for $host:$port — subject: ${cert.subject}');
|
||||||
|
return host == 'nct.gov.kz';
|
||||||
|
};
|
||||||
|
return IOClient(httpClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NctServiceException implements Exception {
|
||||||
|
final String message;
|
||||||
|
NctServiceException(this.message);
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
@ -36,10 +36,10 @@ Future<void> cleanKassaItems(Store<AppState> store) async {
|
||||||
store.dispatch(SetKassaStateAction(KassaState(kassaItems: [])));
|
store.dispatch(SetKassaStateAction(KassaState(kassaItems: [])));
|
||||||
}
|
}
|
||||||
|
|
||||||
ThunkAction<AppState> addCustomProductToKassaItems(String name, double count, double price, double total) {
|
ThunkAction<AppState> addCustomProductToKassaItems(String name, double count, double price, double total, {String? ntin}) {
|
||||||
return (Store<AppState> store) async {
|
return (Store<AppState> store) async {
|
||||||
List<ProductDao> items = store.state.kassaState!.kassaItems!;
|
List<ProductDao> items = store.state.kassaState!.kassaItems!;
|
||||||
items.add(new ProductDao(name: name, count: count, price: price, total: total));
|
items.add(new ProductDao(name: name, count: count, price: price, total: total, ntin: ntin));
|
||||||
store.dispatch(SetKassaStateAction(KassaState(kassaItems: items)));
|
store.dispatch(SetKassaStateAction(KassaState(kassaItems: items)));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:aman_kassa_flutter/core/entity/Goods.dart';
|
import 'package:aman_kassa_flutter/core/entity/Goods.dart';
|
||||||
import 'package:aman_kassa_flutter/core/locator.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/models/product_dao.dart';
|
import 'package:aman_kassa_flutter/core/models/product_dao.dart';
|
||||||
import 'package:aman_kassa_flutter/core/route_names.dart';
|
import 'package:aman_kassa_flutter/core/route_names.dart';
|
||||||
import 'package:aman_kassa_flutter/core/services/DataService.dart';
|
import 'package:aman_kassa_flutter/core/services/DataService.dart';
|
||||||
|
import 'package:aman_kassa_flutter/core/services/NctService.dart' show NctService, NctServiceException;
|
||||||
import 'package:aman_kassa_flutter/core/services/dialog_service.dart';
|
import 'package:aman_kassa_flutter/core/services/dialog_service.dart';
|
||||||
import 'package:aman_kassa_flutter/core/services/navigator_service.dart';
|
import 'package:aman_kassa_flutter/core/services/navigator_service.dart';
|
||||||
import 'package:aman_kassa_flutter/redux/actions/kassa_actions.dart';
|
import 'package:aman_kassa_flutter/redux/actions/kassa_actions.dart';
|
||||||
|
|
@ -30,6 +32,7 @@ class KassaTab extends StatelessWidget {
|
||||||
final NavigatorService _navigatorService = locator<NavigatorService>();
|
final NavigatorService _navigatorService = locator<NavigatorService>();
|
||||||
final DialogService _dialogService = locator<DialogService>();
|
final DialogService _dialogService = locator<DialogService>();
|
||||||
final DataService _dataService = locator<DataService>();
|
final DataService _dataService = locator<DataService>();
|
||||||
|
final NctService _nctService = locator<NctService>();
|
||||||
|
|
||||||
final int index;
|
final int index;
|
||||||
|
|
||||||
|
|
@ -221,8 +224,20 @@ class KassaTab extends StatelessWidget {
|
||||||
if (goods != null && goods.isNotEmpty) {
|
if (goods != null && goods.isNotEmpty) {
|
||||||
await Redux.store!.dispatch(addProductToKassaItems(goods.first, dataMatrix));
|
await Redux.store!.dispatch(addProductToKassaItems(goods.first, dataMatrix));
|
||||||
} else {
|
} else {
|
||||||
_dialogService.showDialog(
|
try {
|
||||||
description: 'Товар не найден: $barcode');
|
List<NctProduct> nctResults = await _nctService.searchByNtin(barcode);
|
||||||
|
if (nctResults.isNotEmpty) {
|
||||||
|
final NctProduct nctProduct = nctResults.first;
|
||||||
|
final String? name = nctProduct.nameRu ?? nctProduct.nameKk;
|
||||||
|
_openAddSheetWithName(name, ntinCode: nctProduct.ntinCode);
|
||||||
|
} else {
|
||||||
|
_dialogService.showDialog(description: 'Товар не найден: $barcode');
|
||||||
|
}
|
||||||
|
} on NctServiceException catch (e) {
|
||||||
|
_dialogService.showDialog(description: e.message);
|
||||||
|
} catch (e) {
|
||||||
|
_dialogService.showDialog(description: 'Ошибка поиска NCT: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (result.type == ResultType.Error) {
|
} else if (result.type == ResultType.Error) {
|
||||||
_dialogService.showDialog(description: 'Не верный формат QR кода');
|
_dialogService.showDialog(description: 'Не верный формат QR кода');
|
||||||
|
|
@ -247,6 +262,12 @@ class KassaTab extends StatelessWidget {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void _openAddSheetWithName(String? name, {String? ntinCode}) {
|
||||||
|
_navigatorService.navigateToPage(
|
||||||
|
MaterialPageRoute(builder: (_) => ProductAddBottomSheet(initialName: name, initialNtinCode: ntinCode))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void showModalBottomSheetCatalog(BuildContext context, String action) {
|
void showModalBottomSheetCatalog(BuildContext context, String action) {
|
||||||
|
|
||||||
if (action == 'add') {
|
if (action == 'add') {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
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/actions/kassa_actions.dart';
|
||||||
import 'package:aman_kassa_flutter/redux/store.dart';
|
import 'package:aman_kassa_flutter/redux/store.dart';
|
||||||
import 'package:aman_kassa_flutter/shared/app_colors.dart';
|
import 'package:aman_kassa_flutter/shared/app_colors.dart';
|
||||||
|
|
@ -7,29 +10,39 @@ import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class ProductAddBottomSheet extends StatefulWidget {
|
class ProductAddBottomSheet extends StatefulWidget {
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
|
final String? initialName;
|
||||||
|
final String? initialNtinCode;
|
||||||
|
|
||||||
ProductAddBottomSheet({this.scrollController});
|
ProductAddBottomSheet({this.scrollController, this.initialName, this.initialNtinCode});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ProductAddBottomSheetState createState() => _ProductAddBottomSheetState();
|
_ProductAddBottomSheetState createState() => _ProductAddBottomSheetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
||||||
|
final NctService _nctService = locator<NctService>();
|
||||||
|
|
||||||
|
late TextEditingController ntinController;
|
||||||
late TextEditingController nameController;
|
late TextEditingController nameController;
|
||||||
late TextEditingController countController;
|
late TextEditingController countController;
|
||||||
late TextEditingController priceController;
|
late TextEditingController priceController;
|
||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
|
bool _isSearching = false;
|
||||||
|
String? _foundNtinCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
nameController = new TextEditingController();
|
ntinController = new TextEditingController();
|
||||||
|
nameController = new TextEditingController(text: widget.initialName ?? '');
|
||||||
countController = new TextEditingController();
|
countController = new TextEditingController();
|
||||||
priceController = new TextEditingController();
|
priceController = new TextEditingController();
|
||||||
|
_foundNtinCode = widget.initialNtinCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
ntinController.dispose();
|
||||||
nameController.dispose();
|
nameController.dispose();
|
||||||
countController.dispose();
|
countController.dispose();
|
||||||
priceController.dispose();
|
priceController.dispose();
|
||||||
|
|
@ -55,6 +68,44 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
||||||
child: ListView(
|
child: ListView(
|
||||||
controller: widget.scrollController,
|
controller: widget.scrollController,
|
||||||
children: <Widget>[
|
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(
|
TextField(
|
||||||
decoration: new InputDecoration(
|
decoration: new InputDecoration(
|
||||||
border: new OutlineInputBorder(
|
border: new OutlineInputBorder(
|
||||||
|
|
@ -78,8 +129,6 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
||||||
decimal: true,
|
decimal: true,
|
||||||
),
|
),
|
||||||
inputFormatters: <TextInputFormatter>[
|
inputFormatters: <TextInputFormatter>[
|
||||||
// WhitelistingTextInputFormatter.digitsOnly
|
|
||||||
// FilteringTextInputFormatter.digitsOnly
|
|
||||||
FilteringTextInputFormatter.allow(RegExp("^[0-9.]*")),
|
FilteringTextInputFormatter.allow(RegExp("^[0-9.]*")),
|
||||||
],
|
],
|
||||||
controller: countController,
|
controller: countController,
|
||||||
|
|
@ -152,17 +201,52 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
void submit() {
|
||||||
if (nameController.text.isEmpty ||
|
if (nameController.text.isEmpty ||
|
||||||
countController.text.isEmpty ||
|
countController.text.isEmpty ||
|
||||||
priceController.text.isEmpty) {
|
priceController.text.isEmpty) {
|
||||||
_showDialog();
|
_showErrorDialog('Введите наименование, количество и цену');
|
||||||
} else {
|
} else {
|
||||||
Redux.store!.dispatch(addCustomProductToKassaItems(
|
Redux.store!.dispatch(addCustomProductToKassaItems(
|
||||||
nameController.text,
|
nameController.text,
|
||||||
double.parse(countController.text),
|
double.parse(countController.text),
|
||||||
double.parse(priceController.text),
|
double.parse(priceController.text),
|
||||||
sum));
|
sum,
|
||||||
|
ntin: _foundNtinCode));
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -185,18 +269,17 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDialog() {
|
void _showErrorDialog(String message) {
|
||||||
// flutter defined function
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: new Text("Aman Касса"),
|
title: new Text("Aman Касса"),
|
||||||
content: new Text("Введите наименование, количество и цену"),
|
content: new Text(message),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
FlatButton(
|
TextButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
"ОK",
|
"ОК",
|
||||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
dynamic _bankService;
|
dynamic _bankService;
|
||||||
final NavigatorService _navigatorService = locator<NavigatorService>();
|
final NavigatorService _navigatorService = locator<NavigatorService>();
|
||||||
final TextEditingController _iinController = new TextEditingController();
|
final TextEditingController _iinController = new TextEditingController();
|
||||||
|
final TextEditingController _ticketNumberController = new TextEditingController();
|
||||||
late bool isBusy;
|
late bool isBusy;
|
||||||
late bool isBankApiAccess;
|
late bool isBankApiAccess;
|
||||||
|
|
||||||
|
|
@ -72,6 +73,7 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_iinController.dispose();
|
_iinController.dispose();
|
||||||
|
_ticketNumberController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,6 +179,24 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get _isReturn => widget.model.operationType == OperationTypeReturn;
|
||||||
|
|
||||||
|
bool _canProceed() {
|
||||||
|
if (_isReturn && _ticketNumberController.text.trim().isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPaymentPressed(String type, CardData? cardData) {
|
||||||
|
if (!_canProceed()) {
|
||||||
|
_dialogService.showDialog(
|
||||||
|
description: 'Введите фискальный признак для выполнения возврата.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pressPayment(type, cardData);
|
||||||
|
}
|
||||||
|
|
||||||
Expanded _buildBodyContent() {
|
Expanded _buildBodyContent() {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -188,6 +208,15 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'ИИН Покупателя', hintText: "Введите ИИН покупателя"),
|
labelText: 'ИИН Покупателя', hintText: "Введите ИИН покупателя"),
|
||||||
),
|
),
|
||||||
|
if (_isReturn)
|
||||||
|
TextField(
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
controller: _ticketNumberController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Фискальный признак *',
|
||||||
|
hintText: 'Введите фискальный признак'),
|
||||||
|
),
|
||||||
|
if (_isReturn) verticalSpaceSmall,
|
||||||
Container(
|
Container(
|
||||||
height: 150,
|
height: 150,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -197,7 +226,7 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
child: BusyButton(
|
child: BusyButton(
|
||||||
title: 'Оплатить картой',
|
title: 'Оплатить картой',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pressPayment('card', null);
|
_onPaymentPressed('card', null);
|
||||||
},
|
},
|
||||||
mainColor: primaryColor,
|
mainColor: primaryColor,
|
||||||
)),
|
)),
|
||||||
|
|
@ -206,7 +235,7 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
child: BusyButton(
|
child: BusyButton(
|
||||||
title: 'Мобильный',
|
title: 'Мобильный',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pressPayment('mobile', null);
|
_onPaymentPressed('mobile', null);
|
||||||
},
|
},
|
||||||
mainColor: redColor,
|
mainColor: redColor,
|
||||||
)),
|
)),
|
||||||
|
|
@ -215,7 +244,7 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
child: BusyButton(
|
child: BusyButton(
|
||||||
title: 'Наличными',
|
title: 'Наличными',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pressPayment('cash', null);
|
_onPaymentPressed('cash', null);
|
||||||
},
|
},
|
||||||
mainColor: greenColor,
|
mainColor: greenColor,
|
||||||
)),
|
)),
|
||||||
|
|
@ -360,7 +389,8 @@ class _PaymentViewState extends State<PaymentView> {
|
||||||
calcItems: calcItems,
|
calcItems: calcItems,
|
||||||
mode: _mode,
|
mode: _mode,
|
||||||
cardData: cardData,
|
cardData: cardData,
|
||||||
contragent: _iinController.text
|
contragent: _iinController.text,
|
||||||
|
ticketNumber: _isReturn ? _ticketNumberController.text.trim() : null,
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
isBusy = false;
|
isBusy = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue