enhance: Enhances data models and reception details

Improves JSON deserialization for product and stock picking models by
using custom `fromJson` methods and explicit `JsonKey` mappings.
This ensures robust parsing of fields like barcode, display name, and product IDs.

Adds a new `ecart` getter to calculate the difference between requested
and received quantities for stock moves.

Updates the reception details page to display a list of products
with their requested, received, and calculated difference quantities.

Removes redundant JSON-RPC fields from the `stock.picking/web_read` API call.
This commit is contained in:
mandreshope 2025-07-30 14:16:27 +03:00
parent 6af1bfc4ab
commit 6e49971883
9 changed files with 122 additions and 35 deletions

View File

@ -181,8 +181,6 @@ class ApiCalls {
final response = await dioService.post(
path: '/web/dataset/call_kw/stock.picking/web_read',
data: {
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "stock.picking",
"method": "web_read",

View File

@ -1,4 +1,5 @@
import 'package:e_scan/backend/objectbox/entities/product/product_entity.dart';
import 'package:e_scan/utils/utils.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product_model.freezed.dart';
@ -6,8 +7,12 @@ part 'product_model.g.dart';
@Freezed(toJson: true)
abstract class ProductModel with _$ProductModel {
factory ProductModel({int? id, String? barcode, String? displayName}) =
_ProductModel;
factory ProductModel({
int? id,
@JsonKey(fromJson: stringFromJson) String? barcode,
@JsonKey(name: 'display_name', fromJson: stringFromJson)
String? displayName,
}) = _ProductModel;
factory ProductModel.fromJson(Map<String, dynamic> json) =>
_$ProductModelFromJson(json);

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ProductModel {
int? get id; String? get barcode; String? get displayName;
int? get id;@JsonKey(fromJson: stringFromJson) String? get barcode;@JsonKey(name: 'display_name', fromJson: stringFromJson) String? get displayName;
/// Create a copy of ProductModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -49,7 +49,7 @@ abstract mixin class $ProductModelCopyWith<$Res> {
factory $ProductModelCopyWith(ProductModel value, $Res Function(ProductModel) _then) = _$ProductModelCopyWithImpl;
@useResult
$Res call({
int? id, String? barcode, String? displayName
int? id,@JsonKey(fromJson: stringFromJson) String? barcode,@JsonKey(name: 'display_name', fromJson: stringFromJson) String? displayName
});
@ -82,12 +82,12 @@ as String?,
@JsonSerializable()
class _ProductModel implements ProductModel {
_ProductModel({this.id, this.barcode, this.displayName});
_ProductModel({this.id, @JsonKey(fromJson: stringFromJson) this.barcode, @JsonKey(name: 'display_name', fromJson: stringFromJson) this.displayName});
factory _ProductModel.fromJson(Map<String, dynamic> json) => _$ProductModelFromJson(json);
@override final int? id;
@override final String? barcode;
@override final String? displayName;
@override@JsonKey(fromJson: stringFromJson) final String? barcode;
@override@JsonKey(name: 'display_name', fromJson: stringFromJson) final String? displayName;
/// Create a copy of ProductModel
/// with the given fields replaced by the non-null parameter values.
@ -122,7 +122,7 @@ abstract mixin class _$ProductModelCopyWith<$Res> implements $ProductModelCopyWi
factory _$ProductModelCopyWith(_ProductModel value, $Res Function(_ProductModel) _then) = __$ProductModelCopyWithImpl;
@override @useResult
$Res call({
int? id, String? barcode, String? displayName
int? id,@JsonKey(fromJson: stringFromJson) String? barcode,@JsonKey(name: 'display_name', fromJson: stringFromJson) String? displayName
});

View File

@ -9,13 +9,13 @@ part of 'product_model.dart';
_ProductModel _$ProductModelFromJson(Map<String, dynamic> json) =>
_ProductModel(
id: (json['id'] as num?)?.toInt(),
barcode: json['barcode'] as String?,
displayName: json['displayName'] as String?,
barcode: stringFromJson(json['barcode']),
displayName: stringFromJson(json['display_name']),
);
Map<String, dynamic> _$ProductModelToJson(_ProductModel instance) =>
<String, dynamic>{
'id': instance.id,
'barcode': instance.barcode,
'displayName': instance.displayName,
'display_name': instance.displayName,
};

View File

@ -72,6 +72,7 @@ List<MoveWithoutPackageModel>? _moveIdsWithoutPackageFromJson(dynamic json) =>
abstract class MoveLineWithoutPackageModel with _$MoveLineWithoutPackageModel {
const factory MoveLineWithoutPackageModel({
int? id,
@JsonKey(name: 'product_id', fromJson: _productFromJson)
ProductModel? productId,
double? quantity,
}) = _MoveLineWithoutPackageModel;
@ -84,15 +85,23 @@ abstract class MoveLineWithoutPackageModel with _$MoveLineWithoutPackageModel {
abstract class MoveWithoutPackageModel with _$MoveWithoutPackageModel {
const factory MoveWithoutPackageModel({
int? id,
@JsonKey(name: 'product_id', fromJson: _productFromJson)
ProductModel? productId,
double? quantity,
double? productUomQty,
@JsonKey(name: 'product_uom_qty') double? productUomQty,
}) = _MoveWithoutPackageModel;
factory MoveWithoutPackageModel.fromJson(Map<String, dynamic> json) =>
_$MoveWithoutPackageModelFromJson(json);
}
ProductModel? _productFromJson(dynamic json) =>
objectFromJson(json, ProductModel.fromJson);
extension StockPickingRecordModelExt on StockPickingRecordModel {
bool get isDone => state == "done";
}
extension MoveWithoutPackageModelExt on MoveWithoutPackageModel {
double get ecart => ((quantity ?? 0) - (productUomQty ?? 0));
}

View File

@ -350,7 +350,7 @@ $StockPickingTypeModelCopyWith<$Res>? get pickingTypeId {
/// @nodoc
mixin _$MoveLineWithoutPackageModel {
int? get id; ProductModel? get productId; double? get quantity;
int? get id;@JsonKey(name: 'product_id', fromJson: _productFromJson) ProductModel? get productId; double? get quantity;
/// Create a copy of MoveLineWithoutPackageModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -383,7 +383,7 @@ abstract mixin class $MoveLineWithoutPackageModelCopyWith<$Res> {
factory $MoveLineWithoutPackageModelCopyWith(MoveLineWithoutPackageModel value, $Res Function(MoveLineWithoutPackageModel) _then) = _$MoveLineWithoutPackageModelCopyWithImpl;
@useResult
$Res call({
int? id, ProductModel? productId, double? quantity
int? id,@JsonKey(name: 'product_id', fromJson: _productFromJson) ProductModel? productId, double? quantity
});
@ -428,11 +428,11 @@ $ProductModelCopyWith<$Res>? get productId {
@JsonSerializable()
class _MoveLineWithoutPackageModel implements MoveLineWithoutPackageModel {
const _MoveLineWithoutPackageModel({this.id, this.productId, this.quantity});
const _MoveLineWithoutPackageModel({this.id, @JsonKey(name: 'product_id', fromJson: _productFromJson) this.productId, this.quantity});
factory _MoveLineWithoutPackageModel.fromJson(Map<String, dynamic> json) => _$MoveLineWithoutPackageModelFromJson(json);
@override final int? id;
@override final ProductModel? productId;
@override@JsonKey(name: 'product_id', fromJson: _productFromJson) final ProductModel? productId;
@override final double? quantity;
/// Create a copy of MoveLineWithoutPackageModel
@ -468,7 +468,7 @@ abstract mixin class _$MoveLineWithoutPackageModelCopyWith<$Res> implements $Mov
factory _$MoveLineWithoutPackageModelCopyWith(_MoveLineWithoutPackageModel value, $Res Function(_MoveLineWithoutPackageModel) _then) = __$MoveLineWithoutPackageModelCopyWithImpl;
@override @useResult
$Res call({
int? id, ProductModel? productId, double? quantity
int? id,@JsonKey(name: 'product_id', fromJson: _productFromJson) ProductModel? productId, double? quantity
});
@ -513,7 +513,7 @@ $ProductModelCopyWith<$Res>? get productId {
/// @nodoc
mixin _$MoveWithoutPackageModel {
int? get id; ProductModel? get productId; double? get quantity; double? get productUomQty;
int? get id;@JsonKey(name: 'product_id', fromJson: _productFromJson) ProductModel? get productId; double? get quantity;@JsonKey(name: 'product_uom_qty') double? get productUomQty;
/// Create a copy of MoveWithoutPackageModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@ -546,7 +546,7 @@ abstract mixin class $MoveWithoutPackageModelCopyWith<$Res> {
factory $MoveWithoutPackageModelCopyWith(MoveWithoutPackageModel value, $Res Function(MoveWithoutPackageModel) _then) = _$MoveWithoutPackageModelCopyWithImpl;
@useResult
$Res call({
int? id, ProductModel? productId, double? quantity, double? productUomQty
int? id,@JsonKey(name: 'product_id', fromJson: _productFromJson) ProductModel? productId, double? quantity,@JsonKey(name: 'product_uom_qty') double? productUomQty
});
@ -592,13 +592,13 @@ $ProductModelCopyWith<$Res>? get productId {
@JsonSerializable()
class _MoveWithoutPackageModel implements MoveWithoutPackageModel {
const _MoveWithoutPackageModel({this.id, this.productId, this.quantity, this.productUomQty});
const _MoveWithoutPackageModel({this.id, @JsonKey(name: 'product_id', fromJson: _productFromJson) this.productId, this.quantity, @JsonKey(name: 'product_uom_qty') this.productUomQty});
factory _MoveWithoutPackageModel.fromJson(Map<String, dynamic> json) => _$MoveWithoutPackageModelFromJson(json);
@override final int? id;
@override final ProductModel? productId;
@override@JsonKey(name: 'product_id', fromJson: _productFromJson) final ProductModel? productId;
@override final double? quantity;
@override final double? productUomQty;
@override@JsonKey(name: 'product_uom_qty') final double? productUomQty;
/// Create a copy of MoveWithoutPackageModel
/// with the given fields replaced by the non-null parameter values.
@ -633,7 +633,7 @@ abstract mixin class _$MoveWithoutPackageModelCopyWith<$Res> implements $MoveWit
factory _$MoveWithoutPackageModelCopyWith(_MoveWithoutPackageModel value, $Res Function(_MoveWithoutPackageModel) _then) = __$MoveWithoutPackageModelCopyWithImpl;
@override @useResult
$Res call({
int? id, ProductModel? productId, double? quantity, double? productUomQty
int? id,@JsonKey(name: 'product_id', fromJson: _productFromJson) ProductModel? productId, double? quantity,@JsonKey(name: 'product_uom_qty') double? productUomQty
});

View File

@ -76,9 +76,7 @@ _MoveLineWithoutPackageModel _$MoveLineWithoutPackageModelFromJson(
Map<String, dynamic> json,
) => _MoveLineWithoutPackageModel(
id: (json['id'] as num?)?.toInt(),
productId: json['productId'] == null
? null
: ProductModel.fromJson(json['productId'] as Map<String, dynamic>),
productId: _productFromJson(json['product_id']),
quantity: (json['quantity'] as num?)?.toDouble(),
);
@ -86,7 +84,7 @@ Map<String, dynamic> _$MoveLineWithoutPackageModelToJson(
_MoveLineWithoutPackageModel instance,
) => <String, dynamic>{
'id': instance.id,
'productId': instance.productId,
'product_id': instance.productId,
'quantity': instance.quantity,
};
@ -94,18 +92,16 @@ _MoveWithoutPackageModel _$MoveWithoutPackageModelFromJson(
Map<String, dynamic> json,
) => _MoveWithoutPackageModel(
id: (json['id'] as num?)?.toInt(),
productId: json['productId'] == null
? null
: ProductModel.fromJson(json['productId'] as Map<String, dynamic>),
productId: _productFromJson(json['product_id']),
quantity: (json['quantity'] as num?)?.toDouble(),
productUomQty: (json['productUomQty'] as num?)?.toDouble(),
productUomQty: (json['product_uom_qty'] as num?)?.toDouble(),
);
Map<String, dynamic> _$MoveWithoutPackageModelToJson(
_MoveWithoutPackageModel instance,
) => <String, dynamic>{
'id': instance.id,
'productId': instance.productId,
'product_id': instance.productId,
'quantity': instance.quantity,
'productUomQty': instance.productUomQty,
'product_uom_qty': instance.productUomQty,
};

View File

@ -52,6 +52,13 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
),
const SizedBox(height: 16),
],
Text(
'Détails réception',
style: AppTheme.of(
context,
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
StockPickingCard(
isDone: reception?.isDone == true,
margin: EdgeInsets.symmetric(horizontal: 5),
@ -62,6 +69,68 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
origin: reception?.origin,
status: reception?.state,
),
SizedBox(height: 20),
ListView(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
primary: true,
children: [
Text(
'Produits',
style: AppTheme.of(
context,
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
...reception?.moveIdsWithoutPackage
?.map(
(move) => Card(
color: AppTheme.of(context).primaryBackground,
child: ListTile(
title: Text(
move.productId?.displayName ?? '',
style: TextStyle(color: Colors.black),
),
subtitle: Wrap(
spacing: 5,
children: [
Chip(
backgroundColor: AppTheme.of(
context,
).secondaryBackground,
label: Text(
"Qté demandée: ${move.productUomQty}",
),
),
Chip(
backgroundColor: AppTheme.of(
context,
).secondaryBackground,
label: Text(
"Qté reçue: ${move.quantity}",
),
),
Chip(
backgroundColor: AppTheme.of(
context,
).secondaryBackground,
label: Text(
"Ecart: ${move.ecart}",
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
)
.toList() ??
[],
],
),
],
),
),

View File

@ -16,6 +16,16 @@ String? stringFromJson(dynamic json) {
return null;
}
double? doubleFromJson(dynamic json) {
if (json is double) return json;
return null;
}
int? intFromJson(dynamic json) {
if (json is int) return json;
return null;
}
T? objectFromJson<T>(dynamic json, T Function(Map<String, dynamic>) fromJson) {
if (json is Map<String, dynamic>) return fromJson(json);
return null;