Инвентаризация ++

null-safety-migration
suvaysov 2022-11-15 16:40:23 +06:00
parent 94db86ee66
commit e461fb5662
26 changed files with 737 additions and 184 deletions

View File

@ -0,0 +1,38 @@
import 'package:json_annotation/json_annotation.dart';
part 'good_item.g.dart';
@JsonSerializable(explicitToJson: true)
class GoodInventarization {
GoodInventarization({
required this.id,
required this.eaccGoodId,
required this.name,
required this.cnt,
required this.price,
required this.cntBuh,
required this.priceBuh,
this.articul,
this.category,
this.ean13,
});
int id;
@JsonKey(name: 'eacc_good_id')
int eaccGoodId;
double cnt;
double price;
@JsonKey(name: 'cnt_buh')
double cntBuh;
@JsonKey(name: 'price_buh')
double priceBuh;
String name;
int? articul;
String? ean13;
String? category;
factory GoodInventarization.fromJson(Map<String, dynamic> json) =>
_$GoodInventarizationFromJson(json);
Map<String, dynamic> toJson() => _$GoodInventarizationToJson(this);
}

View File

@ -0,0 +1,36 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'good_item.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GoodInventarization _$GoodInventarizationFromJson(Map<String, dynamic> json) =>
GoodInventarization(
id: json['id'] as int,
eaccGoodId: json['eacc_good_id'] as int,
name: json['name'] as String,
cnt: (json['cnt'] as num).toDouble(),
price: (json['price'] as num).toDouble(),
cntBuh: (json['cnt_buh'] as num).toDouble(),
priceBuh: (json['price_buh'] as num).toDouble(),
articul: json['articul'] as int?,
category: json['category'] as String?,
ean13: json['ean13'] as String?,
);
Map<String, dynamic> _$GoodInventarizationToJson(
GoodInventarization instance) =>
<String, dynamic>{
'id': instance.id,
'eacc_good_id': instance.eaccGoodId,
'cnt': instance.cnt,
'price': instance.price,
'cnt_buh': instance.cntBuh,
'price_buh': instance.priceBuh,
'name': instance.name,
'articul': instance.articul,
'ean13': instance.ean13,
'category': instance.category,
};

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:satu/core/models/inventarization/good_item/good_item.dart';
part 'invetarization_good_list.g.dart';
@JsonSerializable(explicitToJson: true)
class InventarizationGoodList {
InventarizationGoodList({
required this.goodsList
});
@JsonKey(name: 'goods_list')
List<GoodInventarization> goodsList;
factory InventarizationGoodList.fromJson(Map<String, dynamic> json) => _$InventarizationGoodListFromJson(json);
Map<String, dynamic> toJson() => _$InventarizationGoodListToJson(this);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'invetarization_good_list.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
InventarizationGoodList _$InventarizationGoodListFromJson(
Map<String, dynamic> json) =>
InventarizationGoodList(
goodsList: (json['goods_list'] as List<dynamic>)
.map((e) => GoodInventarization.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$InventarizationGoodListToJson(
InventarizationGoodList instance) =>
<String, dynamic>{
'goods_list': instance.goodsList.map((e) => e.toJson()).toList(),
};

View File

@ -48,6 +48,7 @@ class ResponseOriginal {
int? page; int? page;
int? perpage; int? perpage;
List<dynamic>? data; List<dynamic>? data;
dynamic result;
Map<String, List<String>>? errors; Map<String, List<String>>? errors;
String? message; String? message;

View File

@ -33,6 +33,7 @@ ResponseOriginal _$ResponseOriginalFromJson(Map<String, dynamic> json) =>
..page = json['page'] as int? ..page = json['page'] as int?
..perpage = json['perpage'] as int? ..perpage = json['perpage'] as int?
..data = json['data'] as List<dynamic>? ..data = json['data'] as List<dynamic>?
..result = json['result']
..errors = (json['errors'] as Map<String, dynamic>?)?.map( ..errors = (json['errors'] as Map<String, dynamic>?)?.map(
(k, e) => (k, e) =>
MapEntry(k, (e as List<dynamic>).map((e) => e as String).toList()), MapEntry(k, (e as List<dynamic>).map((e) => e as String).toList()),
@ -45,6 +46,7 @@ Map<String, dynamic> _$ResponseOriginalToJson(ResponseOriginal instance) =>
'page': instance.page, 'page': instance.page,
'perpage': instance.perpage, 'perpage': instance.perpage,
'data': instance.data, 'data': instance.data,
'result': instance.result,
'errors': instance.errors, 'errors': instance.errors,
'message': instance.message, 'message': instance.message,
}; };

View File

@ -51,8 +51,8 @@ class ApiService extends BaseService {
// log.i(host); // log.i(host);
// log.i(url); // log.i(url);
// log.i(headers); // log.i(headers);
log.i(jsonEncode(requestBody)); //log.i(jsonEncode(requestBody));
log.i(jsonEncode(response.body)); //log.i(jsonEncode(response.body));
} }
return response.body; return response.body;
} }

View File

@ -54,8 +54,8 @@ class DialogService {
Future<DialogResponse> showConfirmationDialogInput({ Future<DialogResponse> showConfirmationDialogInput({
required String title, required String title,
required String requestPrice,
required String requestCount, required String requestCount,
String? requestPrice,
String? description, String? description,
String confirmationTitle = 'ПОДТВЕРДИТЬ', String confirmationTitle = 'ПОДТВЕРДИТЬ',
String cancelTitle = 'Отмена', String cancelTitle = 'Отмена',

View File

@ -11,24 +11,26 @@ import 'package:satu/core/utils/locator.dart';
import 'api_service.dart'; import 'api_service.dart';
import 'db_service.dart'; import 'db_service.dart';
Good goodResponseToGood(GoodResponseEntity response) {
return Good()
..id = response.id
..name = response.name
..basePrice = response.basePrice
..optPrice = response.optPrice
..price = response.price
..articul = response.articul
..categoryId = response.categoryId
..categoryName = response.categoryName
..divisible = response.divisible
..ean = response.ean13
..appCompanyId = response.appCompanyId;
}
class DictionaryService extends BaseService { class DictionaryService extends BaseService {
final ApiService _api = locator<ApiService>(); final ApiService _api = locator<ApiService>();
final DbService _db = locator<DbService>(); final DbService _db = locator<DbService>();
Good goodResponseToGood(GoodResponseEntity response) {
return Good()
..id = response.id
..name = response.name
..basePrice = response.basePrice
..optPrice = response.optPrice
..price = response.price
..articul = response.articul
..categoryId = response.categoryId
..categoryName = response.categoryName
..divisible = response.divisible
..ean = response.ean13
..appCompanyId = response.appCompanyId;
}
Category categoryResponseToCategory(CategoryResponse response) { Category categoryResponseToCategory(CategoryResponse response) {
return Category() return Category()

View File

@ -1,11 +1,13 @@
import 'package:satu/core/base/base_service.dart'; import 'package:satu/core/base/base_service.dart';
import 'package:satu/core/models/inventarization/inventarization_response.dart'; import 'package:satu/core/models/inventarization/invetarization_good_list/invetarization_good_list.dart';
import 'package:satu/core/models/inventarization/response/inventarization_response.dart';
import 'package:satu/core/utils/locator.dart'; import 'package:satu/core/utils/locator.dart';
import '../models/inventarization/good_item/good_item.dart';
import '../models/response/response_entity.dart'; import '../models/response/response_entity.dart';
import 'api_service.dart'; import 'api_service.dart';
class InventarizationService extends BaseService { class InventarizationService extends BaseService {
final ApiService _api = locator<ApiService>(); final ApiService _api = locator<ApiService>();
Future<List<InventarizationResponse>> getList( Future<List<InventarizationResponse>> getList(
@ -17,12 +19,13 @@ class InventarizationService extends BaseService {
'perpage': perpage 'perpage': perpage
}; };
ResponseEntity categories = await _api ResponseEntity categories = await _api.postRequest('/goods_inventory_get',
.postRequest('/goods_inventory_get', requestBody: requestBody); requestBody: requestBody);
if (categories.original.data != null && if (categories.original.data != null &&
categories.original.data!.isNotEmpty) { categories.original.data!.isNotEmpty) {
for (final dynamic map in categories.original.data!) { for (final dynamic map in categories.original.data!) {
final InventarizationResponse item = InventarizationResponse.fromJson(map); final InventarizationResponse item =
InventarizationResponse.fromJson(map);
list.add(item); list.add(item);
} }
} }
@ -32,4 +35,63 @@ class InventarizationService extends BaseService {
return list; return list;
} }
Future<List<GoodInventarization>> getGoodByInventarizationId(
{required int page, required int perpage, required int id}) async {
List<GoodInventarization> list = [];
try {
final Map<String, dynamic> requestBody = <String, dynamic>{
'page': page,
'perpage': perpage,
'id': id
};
ResponseEntity categories = await _api.postRequest(
'/goods_inventory_get_edit_form_data',
requestBody: requestBody);
if (categories.original.result != null) {
InventarizationGoodList listResponse = InventarizationGoodList.fromJson(categories.original.result);
list.addAll(listResponse.goodsList);
}
} catch (e, stack) {
log.e('getList', e, stack);
}
return list;
}
Future<bool> addGoodToList(int inventoryId, int? goodId) async {
bool result = false;
try {
final Map<String, dynamic> requestBody = <String, dynamic>{
'page': 1,
'perpage': 1,
'id': inventoryId,
'good_id' : goodId
};
ResponseEntity response = await _api.postRequest(
'/goods_inventory_select_item',
requestBody: requestBody);
result = response.original.result != null;
} catch (e, stack) {
log.e('getList', e, stack);
}
return result ;
}
Future<bool> setCountToItem(int inventoryId, int inventoryItemId, double value) async {
bool result = false;
try {
final Map<String, dynamic> requestBody = <String, dynamic>{
'id': inventoryId,
'inventory_item_id' : inventoryItemId,
'cnt_buh': value
};
ResponseEntity response = await _api.postRequest(
'/goods_inventory_set_cnt_item',
requestBody: requestBody);
result = response.original.result != null;
} catch (e, stack) {
log.e('getList', e, stack);
}
return result ;
}
} }

View File

@ -20,6 +20,9 @@ const String goodsDictionaryViewRoute = 'goodsDictionaryViewRoute';
const String contragentSelectViewRoute = 'ContragentSelectViewRoute'; const String contragentSelectViewRoute = 'ContragentSelectViewRoute';
const String contragentEditRoute = 'contragentEditRoute'; const String contragentEditRoute = 'contragentEditRoute';
// inventarization
const String inventarizationEditRoute = 'inventarizationEditRoute';
// setting - ble printer // setting - ble printer
const String settingPrinterBluetoothViewRoute = 'SettingPrinterBluetoothView'; const String settingPrinterBluetoothViewRoute = 'SettingPrinterBluetoothView';
const String settingPrinterBluetoothSelectViewRoute = const String settingPrinterBluetoothSelectViewRoute =

View File

@ -6,6 +6,7 @@ import 'package:satu/views/dictionaries/category/category_edit.dart';
import 'package:satu/views/dictionaries/category/category_select_view.dart'; import 'package:satu/views/dictionaries/category/category_select_view.dart';
import 'package:satu/views/dictionaries/contragents/contragents_edit.dart'; import 'package:satu/views/dictionaries/contragents/contragents_edit.dart';
import 'package:satu/views/dictionaries/goods/goods_edit.dart'; import 'package:satu/views/dictionaries/goods/goods_edit.dart';
import 'package:satu/views/inventarization/view/inventarization_edit_view.dart';
import 'package:satu/views/login/login_view.dart'; import 'package:satu/views/login/login_view.dart';
import 'package:satu/views/main/main_view.dart'; import 'package:satu/views/main/main_view.dart';
import 'package:satu/views/settings/printer_bluetooth/printer_encoding_select.dart'; import 'package:satu/views/settings/printer_bluetooth/printer_encoding_select.dart';
@ -19,6 +20,7 @@ import 'package:satu/views/work/views/payment/payment_view.dart';
import 'package:satu/views/work/views/receipt/receipt_view.dart'; import 'package:satu/views/work/views/receipt/receipt_view.dart';
import 'package:satu/views/work/work_view.dart'; import 'package:satu/views/work/work_view.dart';
import '../core/models/inventarization/response/inventarization_response.dart';
import './route_names.dart'; import './route_names.dart';
Route<dynamic> generateRoute(RouteSettings settings) { Route<dynamic> generateRoute(RouteSettings settings) {
@ -117,6 +119,14 @@ Route<dynamic> generateRoute(RouteSettings settings) {
transactionId: data, transactionId: data,
), ),
); );
case inventarizationEditRoute:
final InventarizationResponse inventarizationResponse = settings.arguments! as InventarizationResponse;
return _getPageRoute(
routeName: settings.name,
viewToShow: InventarizationEditView(
item: inventarizationResponse,
),
);
default: default:
return MaterialPageRoute( return MaterialPageRoute(
builder: (_) => Scaffold( builder: (_) => Scaffold(

View File

@ -109,6 +109,7 @@ class _GoodsDictionaryViewState extends State<GoodsDictionaryView> {
itemBuilder: itemBuilder:
(BuildContext context, GoodResponseEntity good, int index) { (BuildContext context, GoodResponseEntity good, int index) {
return DictionaryTile( return DictionaryTile(
key: Key('good_${good.id}'),
onPress: () async { onPress: () async {
final dynamic result = await _navigatorService final dynamic result = await _navigatorService
.push(goodsEditRoute, arguments: good); .push(goodsEditRoute, arguments: good);

View File

@ -0,0 +1,241 @@
import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:satu/core/models/inventarization/response/inventarization_response.dart';
import 'package:satu/core/services/dialog_service.dart';
import 'package:satu/views/inventarization/widget/good_inventarization_list_item.dart';
import '../../../core/entity/goods_entity.dart';
import '../../../core/models/inventarization/good_item/good_item.dart';
import '../../../core/services/dictionary_service.dart';
import '../../../core/services/inventarization_service.dart';
import '../../../core/services/navigator_service.dart';
import '../../../core/utils/locator.dart';
import '../../../routes/route_names.dart';
import '../../../shared/app_colors.dart';
import '../../../shared/shared_styles.dart';
import '../../../shared/ui_helpers.dart';
import '../../../widgets/bar/products_app_bar.dart';
class InventarizationEditView extends StatefulWidget {
const InventarizationEditView({
required this.item,
Key? key,
}) : super(key: key);
final InventarizationResponse item;
@override
State<InventarizationEditView> createState() =>
_InventarizationEditViewState();
}
class _InventarizationEditViewState extends State<InventarizationEditView> {
final InventarizationService _service = locator<InventarizationService>();
final NavigatorService _navigatorService = locator<NavigatorService>();
final DialogService _dialogService = locator<DialogService>();
static const _pageSize = 20;
int _pageCurrent = 1;
bool _isLastPage = false;
final PagingController<int, GoodInventarization> _pagingController =
PagingController(firstPageKey: 1);
@override
void initState() {
_pagingController.addPageRequestListener((pageKey) {
_pageCurrent = pageKey;
_fetchData(pageKey, _pageSize);
});
super.initState();
}
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const ProductsAppBar(
title: 'Создание инвентаризации',
drawerShow: false,
),
body: Column(
children: [
verticalSpaceSmall,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: SizedBox(
width: double.infinity,
child: Row(
children: [
Expanded(
flex: 4,
child: Text(
'Список товара',
style: textGray11Style,
),
),
Expanded(
flex: 2,
child: Text(
'Количество',
style: textGray11Style,
),
),
Expanded(
flex: 1,
child: Text(
'Сумма',
style: textGray11Style,
),
),
],
),
),
),
verticalSpaceSmall,
Expanded(
child: PagedListView<int, GoodInventarization>.separated(
//physics: const BouncingScrollPhysics(),
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 1.0,
color: disableColor,
);
},
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<GoodInventarization>(
itemBuilder: (BuildContext context, GoodInventarization item,
int index) {
return GoodInventarizationListItem(
key: Key(
'good_inv_${item.id}',
),
inventoryId: widget.item.id,
inventoryItemId: item.id,
name: item.name,
categoryName: item.category,
count: item.cntBuh,
price: item.priceBuh,
ean: item.ean13,
isOdd: index % 2 == 0,
refresh: () {
_refreshData(_pageCurrent, _pageSize);
},
);
},
),
),
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: floatingActionButtonRender(),
);
}
/// render floating buttons
Widget floatingActionButtonRender() {
return Padding(
padding: EdgeInsets.all(15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Visibility(
visible: true,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
mini: true,
elevation: 2,
backgroundColor: successColor,
onPressed: () {},
child: Icon(
Icons.check,
color: whiteColor,
size: 35,
),
),
)),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
elevation: 2,
mini: true,
onPressed: () async {
final Good? good = await locator<NavigatorService>()
.push(addProductViewRoute) as Good?;
if (good != null) {
bool result =
await _service.addGoodToList(widget.item.id, good.id);
if (result) {
_pagingController.refresh();
} else {
_dialogService.showDialog(
description: 'Товара отсутсвует в остатке');
}
}
},
child: Icon(
Icons.add_rounded,
size: 40,
color: whiteColor,
),
),
verticalSpaceSmall,
FloatingActionButton(
elevation: 2,
mini: true,
onPressed: () async {
final NavigatorService _nav = locator<NavigatorService>();
final dynamic result = await _nav.push(addByBarcodeViewRoute);
if (result != null) {
final List<Good> goods = await locator<DictionaryService>()
.getGoodsByNameOrEan(result as String);
if (goods.isNotEmpty) {
}
}
},
child: Icon(Icons.qr_code_rounded, size: 30, color: whiteColor),
),
],
)
],
),
);
}
Future<void> _fetchData(int pageKey, int perPage) async {
final List<GoodInventarization> newItems =
await _service.getGoodByInventarizationId(
page: pageKey, perpage: perPage, id: widget.item.id);
_isLastPage = newItems.length < _pageSize;
_pageCurrent = pageKey;
if (_isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
final nextPageKey = pageKey + 1;
_pageCurrent = nextPageKey;
_pagingController.appendPage(newItems, nextPageKey);
}
}
Future<void> _refreshData(int pageKey, int perPage) async {
print('${pageKey} - ${perPage}');
final List<GoodInventarization> newItems =
await _service.getGoodByInventarizationId(
page: pageKey, perpage: perPage, id: widget.item.id);
final List<GoodInventarization> oldList = _pagingController.value.itemList ?? [];
oldList.setAll((pageKey - 1) * perPage , newItems);
setState(() {
_pagingController.itemList = oldList;
});
}
}

View File

@ -1,14 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:satu/core/models/inventarization/inventarization_response.dart'; import 'package:intl/intl.dart';
import 'package:satu/core/models/inventarization/response/inventarization_response.dart';
import 'package:satu/core/services/inventarization_service.dart'; import 'package:satu/core/services/inventarization_service.dart';
import 'package:satu/core/services/navigator_service.dart';
import 'package:satu/core/utils/locator.dart'; import 'package:satu/core/utils/locator.dart';
import 'package:satu/shared/app_colors.dart'; import 'package:satu/shared/app_colors.dart';
import 'package:satu/shared/shared_styles.dart'; import 'package:satu/shared/shared_styles.dart';
import 'package:satu/shared/ui_helpers.dart'; import 'package:satu/shared/ui_helpers.dart';
import 'package:satu/views/inventarization/widget/inventarization_list_tile.dart'; import 'package:satu/views/inventarization/widget/inventarization_list_tile.dart';
import '../../../routes/route_names.dart';
import '../../../widgets/bar/products_app_bar.dart'; import '../../../widgets/bar/products_app_bar.dart';
import '../../dictionaries/component/dictionary_list_tile.dart';
class InventarizationView extends StatefulWidget { class InventarizationView extends StatefulWidget {
const InventarizationView({Key? key}) : super(key: key); const InventarizationView({Key? key}) : super(key: key);
@ -20,12 +24,15 @@ class InventarizationView extends StatefulWidget {
class _InventarizationViewState extends State<InventarizationView> { class _InventarizationViewState extends State<InventarizationView> {
final InventarizationService _service = locator<InventarizationService>(); final InventarizationService _service = locator<InventarizationService>();
final NavigatorService _navigatorService = locator<NavigatorService>();
static const _pageSize = 20; static const _pageSize = 20;
final PagingController<int, InventarizationResponse> _pagingController = final PagingController<int, InventarizationResponse> _pagingController =
PagingController(firstPageKey: 1); PagingController(firstPageKey: 1);
final DateFormat formatterDay = DateFormat('dd.MM.yyyy');
@override @override
void initState() { void initState() {
_pagingController.addPageRequestListener((pageKey) { _pagingController.addPageRequestListener((pageKey) {
@ -89,16 +96,37 @@ class _InventarizationViewState extends State<InventarizationView> {
builderDelegate: PagedChildBuilderDelegate<InventarizationResponse>( builderDelegate: PagedChildBuilderDelegate<InventarizationResponse>(
itemBuilder: (BuildContext context, InventarizationResponse item, itemBuilder: (BuildContext context, InventarizationResponse item,
int index) { int index) {
return InventarizationListTile( return DictionaryTile(
key: Key('category_${item.id}'), key: Key('category_${item.id}'),
// onPress: () async { onPress: () async {
// final dynamic result = await _navigatorService final dynamic result = await _navigatorService
// .push(categoryEditRoute, arguments: item); .push(inventarizationEditRoute, arguments: item);
// if (result != null && true == (result as bool)) { if (result != null && true == (result as bool)) {
// _pagingController.refresh(); _pagingController.refresh();
// } }
// }, },
item: item, child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 2,
child: InventarizationCellTile(
value: item.docNumber.toString()),
),
Expanded(
flex: 2,
child: InventarizationCellTile(
value: formatterDay.format(item.createdAt),
),
),
Expanded(
flex: 1,
child: InventarizationCellButton(
value: item.act,
),
),
],
),
); );
}, },
), ),

View File

@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:satu/core/models/dialog_models.dart';
import 'package:satu/core/redux/actions/sell_actions.dart';
import 'package:satu/core/redux/store.dart';
import 'package:satu/core/services/dialog_service.dart';
import 'package:satu/core/services/inventarization_service.dart';
import 'package:satu/core/utils/locator.dart';
import 'package:satu/core/utils/utils_parse.dart';
import 'package:satu/shared/app_colors.dart';
import 'package:satu/shared/shared_styles.dart';
import 'package:satu/views/work/views/add_by_barcode/add_by_barcode_view.dart';
import 'package:satu/widgets/ui/product_title_widget.dart';
class GoodInventarizationListItem extends StatefulWidget {
const GoodInventarizationListItem({
required Key key,
required this.price,
required this.count,
required this.inventoryItemId,
required this.inventoryId,
required this.refresh,
this.name = '',
this.ean,
this.categoryName,
this.isOdd = true,
this.transactionId,
}) : super(key: key);
final int inventoryItemId;
final int inventoryId;
final String name;
final String? ean;
final String? categoryName;
final double price;
final double count;
final bool isOdd;
final int? transactionId;
final void Function() refresh;
@override
_GoodInventarizationListItemState createState() =>
_GoodInventarizationListItemState();
}
class _GoodInventarizationListItemState
extends State<GoodInventarizationListItem> {
final DialogService _dialogService = locator<DialogService>();
final InventarizationService _service = locator<InventarizationService>();
@override
Widget build(BuildContext context) {
return Dismissible(
background: Container(
alignment: AlignmentDirectional.centerEnd,
color: dangerColor,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Удалить',
style: TextStyle(color: whiteColor, fontWeight: FontWeight.w500),
),
),
),
direction: DismissDirection.endToStart,
confirmDismiss: (DismissDirection direction) async {
final DialogResponse response =
await _dialogService.showConfirmationDialog(
title: 'Внимание',
description: 'Удалить из списка товар '
'"${widget.name}"'
' - ${widget.count} ед. ?',
confirmationTitle: 'Удалить',
cancelTitle: 'Отмена');
return response.confirmed;
},
onDismissed: (direction) {},
key: widget.key!,
child: ListTile(
key: widget.key!,
onTap: () {
editProductModal();
},
contentPadding:
const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0),
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 4,
child: ProductTitleWidget(
name: widget.name,
ean: widget.ean,
categoryName: widget.categoryName,
),
),
Expanded(
flex: 2,
child: Text(
formatDecimal(widget.count),
style: const TextStyle(fontSize: 12, color: textColor),
overflow: TextOverflow.ellipsis,
)),
Expanded(
flex: 1,
child: Text(
formatDecimal(widget.price),
style: const TextStyle(fontSize: 12, color: textColor),
overflow: TextOverflow.ellipsis,
)),
],
),
tileColor: !widget.isOdd ? whiteColor : whiteColor,
),
);
}
Future<void> editProductModal() async {
final DialogResponse response =
await _dialogService.showConfirmationDialogInput(
title: widget.name,
requestCount: formatDecimal(widget.count),
);
if (response.confirmed) {
if (isNumeric(response.responseCount)) {
bool result = await _service.setCountToItem(widget.inventoryId, widget.inventoryItemId, double.parse(response.responseCount!));
if(result) {
widget.refresh();
} else {
_dialogService.showDialog(description: 'Что то пошло не так!');
}
} else {
_dialogService.showDialog(description: 'Не верный формат');
}
}
}
}

View File

@ -1,52 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:satu/core/models/inventarization/inventarization_response.dart'; import 'package:satu/core/models/inventarization/response/inventarization_response.dart';
import 'package:satu/shared/app_colors.dart'; import 'package:satu/shared/app_colors.dart';
class InventarizationListTile extends StatefulWidget {
const InventarizationListTile({required this.item, Key? key})
: super(key: key);
final InventarizationResponse item;
@override
State<InventarizationListTile> createState() =>
_InventarizationListTileState();
}
class _InventarizationListTileState extends State<InventarizationListTile> {
final DateFormat formatterDay = DateFormat('dd.MM.yyyy');
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: whiteColor),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 2,
child: InventarizationCellTile(
value: widget.item.docNumber.toString()),
),
Expanded(
flex: 2,
child: InventarizationCellTile(
value: formatterDay.format(widget.item.createdAt),
),
),
Expanded(
flex: 1,
child: InventarizationCellButton(
value: formatterDay.format(widget.item.createdAt),
),
),
],
),
);
}
}
class InventarizationCellTile extends StatelessWidget { class InventarizationCellTile extends StatelessWidget {
const InventarizationCellTile({ const InventarizationCellTile({
required this.value, required this.value,
@ -58,7 +14,6 @@ class InventarizationCellTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: EdgeInsets.all(15.0),
child: Text( child: Text(
value, value,
style: TextStyle(fontSize: 12), style: TextStyle(fontSize: 12),
@ -73,20 +28,24 @@ class InventarizationCellButton extends StatelessWidget {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
final String value; final int? value;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.only( right: 15.0), padding: const EdgeInsets.only( right: 15.0),
child: OutlinedButton( child: ElevatedButton(
onPressed: () {}, onPressed: value == null ? () {} : () {},
child: Text( child: Text(
'Акт', 'Акт',
style: TextStyle(fontSize: 12, color: whiteColor), style: TextStyle(fontSize: 12, color: whiteColor),
), ),
style: OutlinedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: blueColor, backgroundColor: value == null ? disableColor : blueColor
), ),
), ),
); );

View File

@ -78,7 +78,7 @@ class _ProductListItemState extends State<ProductListItem> {
}, },
key: Key(widget.name), key: Key(widget.name),
child: ListTile( child: ListTile(
//onTap: () => _onItemTapped(context), key: Key(widget.name),
onTap: () { onTap: () {
editProductModal(); editProductModal();
}, },

View File

@ -16,6 +16,9 @@ import 'package:satu/widgets/bar/products_header_bar.dart';
import 'package:satu/widgets/bar/products_title_bar.dart'; import 'package:satu/widgets/bar/products_title_bar.dart';
import 'package:satu/views/work/tabs/utils/product_utils.dart'; import 'package:satu/views/work/tabs/utils/product_utils.dart';
import '../../../core/entity/goods_entity.dart';
import '../../../core/redux/actions/sell_actions.dart';
import '../../../core/services/dictionary_service.dart';
import 'component/contagent_select_bar.dart'; import 'component/contagent_select_bar.dart';
class SellView extends StatelessWidget { class SellView extends StatelessWidget {
@ -95,7 +98,7 @@ class SellView extends StatelessWidget {
converter: (Store<AppState> store) => store.state.sellState!, converter: (Store<AppState> store) => store.state.sellState!,
builder: (_, SellState snapshot) { builder: (_, SellState snapshot) {
return Padding( return Padding(
padding: EdgeInsets.all(15.w), padding: EdgeInsets.all(15),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@ -108,7 +111,7 @@ class SellView extends StatelessWidget {
backgroundColor: successColor, backgroundColor: successColor,
onPressed: () => onPressed: () =>
locator<NavigatorService>().push(paymentViewRoute), locator<NavigatorService>().push(paymentViewRoute),
child: Icon(Icons.check, color: whiteColor, size: 35.sp), child: Icon(Icons.check, color: whiteColor, size: 35),
)), )),
Column( Column(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
@ -116,11 +119,16 @@ class SellView extends StatelessWidget {
FloatingActionButton( FloatingActionButton(
elevation: 2, elevation: 2,
mini: true, mini: true,
onPressed: () => onPressed: () async {
locator<NavigatorService>().push(addProductViewRoute), final Good? good = await locator<NavigatorService>().push(addProductViewRoute) as Good?;
if(good !=null) {
Redux.store!.dispatch(addSellItem(good: good));
}
},
child: Icon( child: Icon(
Icons.add_rounded, Icons.add_rounded,
size: 40.sp, size: 40,
color: whiteColor, color: whiteColor,
), ),
), ),
@ -134,17 +142,17 @@ class SellView extends StatelessWidget {
final dynamic result = final dynamic result =
await _nav.push(addByBarcodeViewRoute); await _nav.push(addByBarcodeViewRoute);
if (result != null) { if (result != null) {
// final List<Good> goods = final List<Good> goods =
// await locator<DictionaryService>() await locator<DictionaryService>()
// .getGoodsByEan(result as String); .getGoodsByNameOrEan(result as String);
// if (goods.isNotEmpty) { if (goods.isNotEmpty) {
// Redux.store Redux.store
// ?.dispatch(addSellItem(good: goods.first)); ?.dispatch(addSellItem(good: goods.first));
// } }
} }
}, },
child: Icon(Icons.qr_code_rounded, child: Icon(Icons.qr_code_rounded,
size: 30.sp, color: whiteColor), size: 30, color: whiteColor),
), ),
], ],
) )

View File

@ -16,7 +16,6 @@ class AddByBarcodeView extends StatefulWidget {
} }
class _AddByBarcodeViewState extends State<AddByBarcodeView> { class _AddByBarcodeViewState extends State<AddByBarcodeView> {
String _code = '';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -30,11 +29,6 @@ class _AddByBarcodeViewState extends State<AddByBarcodeView> {
resultCallback: (String code) { resultCallback: (String code) {
final NavigatorService _nav = locator<NavigatorService>(); final NavigatorService _nav = locator<NavigatorService>();
_nav.pop(code); _nav.pop(code);
//Navigator.pop(context, code);
// setState(() {
// _code = code;
// });
}, },
), ),
), ),

View File

@ -1,18 +1,18 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:satu/core/entity/category_entity.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:satu/core/entity/goods_entity.dart'; import 'package:satu/core/entity/goods_entity.dart';
import 'package:satu/core/redux/actions/sell_actions.dart';
import 'package:satu/core/redux/store.dart';
import 'package:satu/core/services/dictionary_service.dart'; import 'package:satu/core/services/dictionary_service.dart';
import 'package:satu/core/services/navigator_service.dart'; import 'package:satu/core/services/navigator_service.dart';
import 'package:satu/core/utils/locator.dart'; import 'package:satu/core/utils/locator.dart';
import 'package:satu/shared/app_colors.dart'; import 'package:satu/shared/app_colors.dart';
import 'package:satu/shared/ui_helpers.dart'; import 'package:satu/shared/ui_helpers.dart';
import 'package:satu/views/work/views/add_product/component/add_category_list_item.dart';
import 'package:satu/widgets/bar/products_app_bar.dart'; import 'package:satu/widgets/bar/products_app_bar.dart';
import 'package:satu/widgets/bar/products_title_bar.dart'; import 'package:satu/widgets/bar/products_title_bar.dart';
import 'package:satu/widgets/fields/input_field.dart'; import 'package:satu/widgets/fields/input_field.dart';
import '../../../../core/models/dictionary/good/good_response_entity.dart';
import 'component/add_product_list_item.dart'; import 'component/add_product_list_item.dart';
class AddProductView extends StatefulWidget { class AddProductView extends StatefulWidget {
@ -24,27 +24,30 @@ class _AddProductViewState extends State<AddProductView> {
final DictionaryService _dictionaryService = locator<DictionaryService>(); final DictionaryService _dictionaryService = locator<DictionaryService>();
final NavigatorService _navigatorService = locator<NavigatorService>(); final NavigatorService _navigatorService = locator<NavigatorService>();
late TextEditingController _searchTextController; late TextEditingController _searchTextController;
final FocusNode _searchFocusNode = new FocusNode(); final FocusNode _searchFocusNode = FocusNode();
static const _pageSize = 20;
String query = '';
Timer? _debounce;
List<Category>? _history; final PagingController<int, Good> _pagingController =
List<Category>? _categories; PagingController(firstPageKey: 1);
List<Good>? _goods;
@override @override
void initState() { void initState() {
_searchTextController = TextEditingController(); _searchTextController = TextEditingController();
_searchTextController.addListener(() { _searchTextController.addListener(() {
if (_searchTextController.text.isNotEmpty) { setState(() {
searchByField(_searchTextController.text); query = _searchTextController.text;
} else { });
reset(); if (_debounce?.isActive ?? false) _debounce?.cancel();
} _debounce = Timer(const Duration(milliseconds: 500), () {
_pagingController.refresh();
});
});
_pagingController.addPageRequestListener((pageKey) {
_fetchData(pageKey, _pageSize, query);
}); });
_history = [Category()..id = null];
_categories = [];
_goods = [];
super.initState(); super.initState();
navigateCategory(null);
} }
@override @override
@ -56,8 +59,7 @@ class _AddProductViewState extends State<AddProductView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
int catSize = _categories?.length ?? 0;
int goodSize = _goods?.length ?? 0;
return Scaffold( return Scaffold(
appBar: ProductsAppBar( appBar: ProductsAppBar(
title: 'Категория', title: 'Категория',
@ -72,39 +74,33 @@ class _AddProductViewState extends State<AddProductView> {
), ),
verticalSpaceTiny, verticalSpaceTiny,
ProductsTitleBarBar( ProductsTitleBarBar(
title: goodSize > 0 ? 'Выберите товар' : 'Выберите категорию', title: 'Выберите товар',
), ),
Expanded( Expanded(
child: ListView.separated( child: PagedListView<int, Good>.separated(
physics: BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
itemCount: catSize + goodSize,
itemBuilder: (BuildContext context, int index) {
if (index < catSize) {
Category category = _categories![index];
return AddCategoryListItem(
name: category.name,
key: Key('category_${category.id}'),
onPress: () => onCategoryPress(category),
);
}
Good good = _goods![index - catSize];
return AddProductListItem(
key: Key('product_${good.id}'),
ean: good.ean,
name: good.name,
price: good.price,
categoryName: _history?.last.name,
onPress: () {
onGoodPress(good);
},
);
},
separatorBuilder: (BuildContext context, int index) { separatorBuilder: (BuildContext context, int index) {
return Divider( return const Divider(
height: 1.0, height: 1.0,
color: disableColor, color: disableColor,
); );
}, },
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<Good>(
itemBuilder:
(BuildContext context, Good good, int index) {
return AddProductListItem(
key: Key('product_${good.id}'),
ean: good.ean,
name: good.name,
price: good.price,
categoryName: good.categoryName,
onPress: () {
onGoodPress(good);
},
);
},
),
), ),
), ),
], ],
@ -112,39 +108,29 @@ class _AddProductViewState extends State<AddProductView> {
); );
} }
void onCategoryPress(Category category) {
_history!.add(category);
navigateCategory(category.id!);
}
void onGoodPress(Good good) { void onGoodPress(Good good) {
Redux.store!.dispatch(addSellItem(good: good)); _navigatorService.pop<Good>(good);
_navigatorService.pop<String>();
} }
void reset() { void reset() {
_history = [Category()..id = 0];
navigateCategory(0);
_searchTextController.clear(); _searchTextController.clear();
} }
void navigateCategory(int? categoryId) async { Future<void> _fetchData(int pageKey, int perPage, String? query) async {
List<Category> categories = final List<GoodResponseEntity> newItems = await _dictionaryService.getGoods(
await _dictionaryService.getCategoryByParentId(categoryId); page: pageKey,
List<Good> goods = filter: {'col': 'name', 'action': 'like', 'val': query ?? ''},
await _dictionaryService.getGoodsByCategoryId(categoryId); perpage: perPage);
setState(() {
_categories = categories;
_goods = goods;
});
}
void searchByField(String query) async { List<Good> items = newItems.map((e) => goodResponseToGood(e)).toList();
List<Category> categories = []; final isLastPage = newItems.length < _pageSize;
List<Good> goods = await _dictionaryService.getGoodsByNameOrEan(query); if (isLastPage) {
setState(() { _pagingController.appendLastPage(items);
_categories = categories; } else {
_goods = goods; final nextPageKey = pageKey + 1;
}); _pagingController.appendPage(items, nextPageKey);
}
} }
} }

View File

@ -25,6 +25,7 @@ class AddProductListItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Material(
key: key,
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () => onPress!(), onTap: () => onPress!(),
@ -51,7 +52,7 @@ class AddProductListItem extends StatelessWidget {
Text( Text(
'$price', '$price',
style: TextStyle( style: TextStyle(
fontSize: ScreenUtil().setSp(20.0), fontSize: 15,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ),
], ],

View File

@ -132,13 +132,16 @@ class _DialogManagerState extends State<DialogManager> {
verticalSpaceSmall, verticalSpaceSmall,
const Divider(), const Divider(),
verticalSpaceSmall, verticalSpaceSmall,
InputFieldRounded( Visibility(
controller: _controllerPrice, visible: request.requestPrice != null,
placeholder: '', child: InputFieldRounded(
suffixText: '', controller: _controllerPrice,
labelText: 'Стоимость товара', placeholder: '',
textInputType: suffixText: '',
const TextInputType.numberWithOptions(decimal: true), labelText: 'Стоимость товара',
textInputType:
const TextInputType.numberWithOptions(decimal: true),
),
), ),
InputFieldRounded( InputFieldRounded(
textInputType: textInputType:

View File

@ -18,9 +18,9 @@ class ProductTitleWidget extends StatelessWidget {
children: [ children: [
Text( Text(
name, name,
style: const TextStyle(fontSize: 14, color: textColor), style: const TextStyle(fontSize: 12, color: textColor),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 2, maxLines: 1,
), ),
verticalSpaceTiny, verticalSpaceTiny,
if (ean != null) if (ean != null)