Compare commits

..

No commits in common. "913170ce20b020997b472e53ba56c4b1f1a58ae0" and "86adf1b2cfa7d4bfc63351600caf443013e45034" have entirely different histories.

13 changed files with 31 additions and 305 deletions

View File

@ -3,7 +3,6 @@
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/DataService.dart';
import 'package:aman_kassa_flutter/core/services/NctService.dart';
import 'package:aman_kassa_flutter/core/services/blue_print_service.dart';
import '../core/services/DbService.dart';
@ -36,8 +35,6 @@ class LocatorInjector {
// depencies
_log.d('Initializing NctService Service');
locator.registerLazySingleton<NctService>(() => NctService());
_log.d('Initializing DataService Service');
locator.registerLazySingleton<DataService>(() => DataService());
_log.d('Initializing BankService Service');

View File

@ -4,9 +4,7 @@ class CheckItem {
final num price;
final int articul;
final String? excise;
final String? ntin;
CheckItem({required this.name, required this.cnt, required this.price, required this.articul, this.excise, this.ntin});
CheckItem({required this.name, required this.cnt, required this.price, required this.articul, this.excise});
static CheckItem fromJson(Map<String, dynamic> json) {
return CheckItem(
@ -15,17 +13,14 @@ class CheckItem {
price: json['price'],
articul: json['articul'],
excise: json['excise'],
ntin: json['ntin'],
);
}
Map<String, dynamic> toJson() =>
{
'name': name,
'cnt': cnt,
'price': price,
'articul': articul,
'excise': excise,
'ntin': ntin ?? '',
'excise' : excise
};
}

View File

@ -1,53 +0,0 @@
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();
}
}

View File

@ -9,7 +9,8 @@ class ProductDao {
final Good? good;
final Service? service;
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 });
}

View File

@ -147,11 +147,8 @@ class ApiService extends BaseService {
return Response.fromJsonDynamic(json.decode(response));
}
Future<Response<dynamic>> sellReturn(String token, String checkData, {String? ticketNumber}) async {
Future<Response<dynamic>> sellReturn(String token, String checkData) async {
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);
return Response.fromJsonDynamic(json.decode(response));
}

View File

@ -71,7 +71,6 @@ class DataService extends BaseService {
price: el.price,
articul: articul,
excise: el.excise,
ntin: el.ntin,
));
summ += el.total;
iterator++;
@ -192,7 +191,6 @@ class DataService extends BaseService {
{String? paymentType,
String? tradeType,
String? contragent,
String? ticketNumber,
required String token,
required List<ProductDao> kassaItems,
required List<CalcModel> calcItems,
@ -219,7 +217,7 @@ class DataService extends BaseService {
// log.i('data: $data');
Response<dynamic> response = await (operationType == OperationTypePay
? _api.sell(token, data)
: _api.sellReturn(token, data, ticketNumber: ticketNumber));
: _api.sellReturn(token, data));
// log.i('response status: ${response.status}');
// log.i('response operation: ${response.operation}');
if (response.status == 200 && response.operation == true) {

View File

@ -1,75 +0,0 @@
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;
}

View File

@ -36,10 +36,10 @@ Future<void> cleanKassaItems(Store<AppState> store) async {
store.dispatch(SetKassaStateAction(KassaState(kassaItems: [])));
}
ThunkAction<AppState> addCustomProductToKassaItems(String name, double count, double price, double total, {String? ntin}) {
ThunkAction<AppState> addCustomProductToKassaItems(String name, double count, double price, double total) {
return (Store<AppState> store) async {
List<ProductDao> items = store.state.kassaState!.kassaItems!;
items.add(new ProductDao(name: name, count: count, price: price, total: total, ntin: ntin));
items.add(new ProductDao(name: name, count: count, price: price, total: total));
store.dispatch(SetKassaStateAction(KassaState(kassaItems: items)));
};
}

View File

@ -47,7 +47,7 @@ class BankState {
// Определяем тип сессии
dynamic session;
final String? sessionType = json['sessionType'] as String?;
String sessionType = json['sessionType'];
if (sessionType == "Halyk") {
session = HalykPosSession.fromJson(json['session']);
} else if (sessionType == "Forte") {

View File

@ -1,10 +1,8 @@
import 'package:aman_kassa_flutter/core/entity/Goods.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/route_names.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/navigator_service.dart';
import 'package:aman_kassa_flutter/redux/actions/kassa_actions.dart';
@ -32,7 +30,6 @@ class KassaTab extends StatelessWidget {
final NavigatorService _navigatorService = locator<NavigatorService>();
final DialogService _dialogService = locator<DialogService>();
final DataService _dataService = locator<DataService>();
final NctService _nctService = locator<NctService>();
final int index;
@ -224,20 +221,8 @@ class KassaTab extends StatelessWidget {
if (goods != null && goods.isNotEmpty) {
await Redux.store!.dispatch(addProductToKassaItems(goods.first, dataMatrix));
} else {
try {
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');
}
_dialogService.showDialog(
description: 'Товар не найден: $barcode');
}
} else if (result.type == ResultType.Error) {
_dialogService.showDialog(description: 'Не верный формат QR кода');
@ -262,12 +247,6 @@ 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) {
if (action == 'add') {

View File

@ -1,6 +1,3 @@
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';
@ -10,39 +7,29 @@ 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});
ProductAddBottomSheet({this.scrollController});
@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 ?? '');
nameController = new TextEditingController();
countController = new TextEditingController();
priceController = new TextEditingController();
_foundNtinCode = widget.initialNtinCode;
}
@override
void dispose() {
ntinController.dispose();
nameController.dispose();
countController.dispose();
priceController.dispose();
@ -64,48 +51,10 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
),
),
body: Padding(
padding: EdgeInsets.only(top: 15, left: 10, right: 15, bottom: 0),
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(
@ -129,6 +78,8 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
decimal: true,
),
inputFormatters: <TextInputFormatter>[
// WhitelistingTextInputFormatter.digitsOnly
// FilteringTextInputFormatter.digitsOnly
FilteringTextInputFormatter.allow(RegExp("^[0-9.]*")),
],
controller: countController,
@ -201,52 +152,17 @@ 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() {
if (nameController.text.isEmpty ||
countController.text.isEmpty ||
priceController.text.isEmpty) {
_showErrorDialog('Введите наименование, количество и цену');
_showDialog();
} else {
Redux.store!.dispatch(addCustomProductToKassaItems(
nameController.text,
double.parse(countController.text),
double.parse(priceController.text),
sum,
ntin: _foundNtinCode));
sum));
Navigator.pop(context);
}
}
@ -269,17 +185,18 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
}
}
void _showErrorDialog(String message) {
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text("Aman Касса"),
content: new Text(message),
content: new Text("Введите наименование, количество и цену"),
actions: <Widget>[
TextButton(
FlatButton(
child: Text(
"ОК",
"ОK",
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
onPressed: () {

View File

@ -56,7 +56,6 @@ class _PaymentViewState extends State<PaymentView> {
dynamic _bankService;
final NavigatorService _navigatorService = locator<NavigatorService>();
final TextEditingController _iinController = new TextEditingController();
final TextEditingController _ticketNumberController = new TextEditingController();
late bool isBusy;
late bool isBankApiAccess;
@ -73,7 +72,6 @@ class _PaymentViewState extends State<PaymentView> {
@override
void dispose() {
_iinController.dispose();
_ticketNumberController.dispose();
super.dispose();
}
@ -179,24 +177,6 @@ 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() {
return Expanded(
child: Column(
@ -208,15 +188,6 @@ class _PaymentViewState extends State<PaymentView> {
decoration: InputDecoration(
labelText: 'ИИН Покупателя', hintText: "Введите ИИН покупателя"),
),
if (_isReturn)
TextField(
keyboardType: TextInputType.text,
controller: _ticketNumberController,
decoration: InputDecoration(
labelText: 'Фискальный признак *',
hintText: 'Введите фискальный признак'),
),
if (_isReturn) verticalSpaceSmall,
Container(
height: 150,
child: Row(
@ -226,7 +197,7 @@ class _PaymentViewState extends State<PaymentView> {
child: BusyButton(
title: 'Оплатить картой',
onPressed: () {
_onPaymentPressed('card', null);
pressPayment('card', null);
},
mainColor: primaryColor,
)),
@ -235,7 +206,7 @@ class _PaymentViewState extends State<PaymentView> {
child: BusyButton(
title: 'Мобильный',
onPressed: () {
_onPaymentPressed('mobile', null);
pressPayment('mobile', null);
},
mainColor: redColor,
)),
@ -244,7 +215,7 @@ class _PaymentViewState extends State<PaymentView> {
child: BusyButton(
title: 'Наличными',
onPressed: () {
_onPaymentPressed('cash', null);
pressPayment('cash', null);
},
mainColor: greenColor,
)),
@ -389,8 +360,7 @@ class _PaymentViewState extends State<PaymentView> {
calcItems: calcItems,
mode: _mode,
cardData: cardData,
contragent: _iinController.text,
ticketNumber: _isReturn ? _ticketNumberController.text.trim() : null,
contragent: _iinController.text
);
setState(() {
isBusy = false;

View File

@ -1,6 +1,6 @@
name: aman_kassa_flutter
description: A new Flutter project.
version: 1.4.0+45
version: 1.4.0+43
environment:
sdk: '>=2.15.0 <4.0.0'
dependencies: