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( final response = await dioService.post(
path: '/web/dataset/call_kw/stock.picking/web_read', path: '/web/dataset/call_kw/stock.picking/web_read',
data: { data: {
"jsonrpc": "2.0",
"method": "call",
"params": { "params": {
"model": "stock.picking", "model": "stock.picking",
"method": "web_read", "method": "web_read",

View File

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

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$ProductModel { 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 /// Create a copy of ProductModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -49,7 +49,7 @@ abstract mixin class $ProductModelCopyWith<$Res> {
factory $ProductModelCopyWith(ProductModel value, $Res Function(ProductModel) _then) = _$ProductModelCopyWithImpl; factory $ProductModelCopyWith(ProductModel value, $Res Function(ProductModel) _then) = _$ProductModelCopyWithImpl;
@useResult @useResult
$Res call({ $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() @JsonSerializable()
class _ProductModel implements ProductModel { 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); factory _ProductModel.fromJson(Map<String, dynamic> json) => _$ProductModelFromJson(json);
@override final int? id; @override final int? id;
@override final String? barcode; @override@JsonKey(fromJson: stringFromJson) final String? barcode;
@override final String? displayName; @override@JsonKey(name: 'display_name', fromJson: stringFromJson) final String? displayName;
/// Create a copy of ProductModel /// Create a copy of ProductModel
/// with the given fields replaced by the non-null parameter values. /// 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; factory _$ProductModelCopyWith(_ProductModel value, $Res Function(_ProductModel) _then) = __$ProductModelCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $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 _$ProductModelFromJson(Map<String, dynamic> json) =>
_ProductModel( _ProductModel(
id: (json['id'] as num?)?.toInt(), id: (json['id'] as num?)?.toInt(),
barcode: json['barcode'] as String?, barcode: stringFromJson(json['barcode']),
displayName: json['displayName'] as String?, displayName: stringFromJson(json['display_name']),
); );
Map<String, dynamic> _$ProductModelToJson(_ProductModel instance) => Map<String, dynamic> _$ProductModelToJson(_ProductModel instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'barcode': instance.barcode, '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 { abstract class MoveLineWithoutPackageModel with _$MoveLineWithoutPackageModel {
const factory MoveLineWithoutPackageModel({ const factory MoveLineWithoutPackageModel({
int? id, int? id,
@JsonKey(name: 'product_id', fromJson: _productFromJson)
ProductModel? productId, ProductModel? productId,
double? quantity, double? quantity,
}) = _MoveLineWithoutPackageModel; }) = _MoveLineWithoutPackageModel;
@ -84,15 +85,23 @@ abstract class MoveLineWithoutPackageModel with _$MoveLineWithoutPackageModel {
abstract class MoveWithoutPackageModel with _$MoveWithoutPackageModel { abstract class MoveWithoutPackageModel with _$MoveWithoutPackageModel {
const factory MoveWithoutPackageModel({ const factory MoveWithoutPackageModel({
int? id, int? id,
@JsonKey(name: 'product_id', fromJson: _productFromJson)
ProductModel? productId, ProductModel? productId,
double? quantity, double? quantity,
double? productUomQty, @JsonKey(name: 'product_uom_qty') double? productUomQty,
}) = _MoveWithoutPackageModel; }) = _MoveWithoutPackageModel;
factory MoveWithoutPackageModel.fromJson(Map<String, dynamic> json) => factory MoveWithoutPackageModel.fromJson(Map<String, dynamic> json) =>
_$MoveWithoutPackageModelFromJson(json); _$MoveWithoutPackageModelFromJson(json);
} }
ProductModel? _productFromJson(dynamic json) =>
objectFromJson(json, ProductModel.fromJson);
extension StockPickingRecordModelExt on StockPickingRecordModel { extension StockPickingRecordModelExt on StockPickingRecordModel {
bool get isDone => state == "done"; 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 /// @nodoc
mixin _$MoveLineWithoutPackageModel { 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 /// Create a copy of MoveLineWithoutPackageModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -383,7 +383,7 @@ abstract mixin class $MoveLineWithoutPackageModelCopyWith<$Res> {
factory $MoveLineWithoutPackageModelCopyWith(MoveLineWithoutPackageModel value, $Res Function(MoveLineWithoutPackageModel) _then) = _$MoveLineWithoutPackageModelCopyWithImpl; factory $MoveLineWithoutPackageModelCopyWith(MoveLineWithoutPackageModel value, $Res Function(MoveLineWithoutPackageModel) _then) = _$MoveLineWithoutPackageModelCopyWithImpl;
@useResult @useResult
$Res call({ $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() @JsonSerializable()
class _MoveLineWithoutPackageModel implements MoveLineWithoutPackageModel { 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); factory _MoveLineWithoutPackageModel.fromJson(Map<String, dynamic> json) => _$MoveLineWithoutPackageModelFromJson(json);
@override final int? id; @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? quantity;
/// Create a copy of MoveLineWithoutPackageModel /// 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; factory _$MoveLineWithoutPackageModelCopyWith(_MoveLineWithoutPackageModel value, $Res Function(_MoveLineWithoutPackageModel) _then) = __$MoveLineWithoutPackageModelCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $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 /// @nodoc
mixin _$MoveWithoutPackageModel { 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 /// Create a copy of MoveWithoutPackageModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -546,7 +546,7 @@ abstract mixin class $MoveWithoutPackageModelCopyWith<$Res> {
factory $MoveWithoutPackageModelCopyWith(MoveWithoutPackageModel value, $Res Function(MoveWithoutPackageModel) _then) = _$MoveWithoutPackageModelCopyWithImpl; factory $MoveWithoutPackageModelCopyWith(MoveWithoutPackageModel value, $Res Function(MoveWithoutPackageModel) _then) = _$MoveWithoutPackageModelCopyWithImpl;
@useResult @useResult
$Res call({ $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() @JsonSerializable()
class _MoveWithoutPackageModel implements MoveWithoutPackageModel { 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); factory _MoveWithoutPackageModel.fromJson(Map<String, dynamic> json) => _$MoveWithoutPackageModelFromJson(json);
@override final int? id; @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? quantity;
@override final double? productUomQty; @override@JsonKey(name: 'product_uom_qty') final double? productUomQty;
/// Create a copy of MoveWithoutPackageModel /// Create a copy of MoveWithoutPackageModel
/// with the given fields replaced by the non-null parameter values. /// 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; factory _$MoveWithoutPackageModelCopyWith(_MoveWithoutPackageModel value, $Res Function(_MoveWithoutPackageModel) _then) = __$MoveWithoutPackageModelCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $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, Map<String, dynamic> json,
) => _MoveLineWithoutPackageModel( ) => _MoveLineWithoutPackageModel(
id: (json['id'] as num?)?.toInt(), id: (json['id'] as num?)?.toInt(),
productId: json['productId'] == null productId: _productFromJson(json['product_id']),
? null
: ProductModel.fromJson(json['productId'] as Map<String, dynamic>),
quantity: (json['quantity'] as num?)?.toDouble(), quantity: (json['quantity'] as num?)?.toDouble(),
); );
@ -86,7 +84,7 @@ Map<String, dynamic> _$MoveLineWithoutPackageModelToJson(
_MoveLineWithoutPackageModel instance, _MoveLineWithoutPackageModel instance,
) => <String, dynamic>{ ) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'productId': instance.productId, 'product_id': instance.productId,
'quantity': instance.quantity, 'quantity': instance.quantity,
}; };
@ -94,18 +92,16 @@ _MoveWithoutPackageModel _$MoveWithoutPackageModelFromJson(
Map<String, dynamic> json, Map<String, dynamic> json,
) => _MoveWithoutPackageModel( ) => _MoveWithoutPackageModel(
id: (json['id'] as num?)?.toInt(), id: (json['id'] as num?)?.toInt(),
productId: json['productId'] == null productId: _productFromJson(json['product_id']),
? null
: ProductModel.fromJson(json['productId'] as Map<String, dynamic>),
quantity: (json['quantity'] as num?)?.toDouble(), quantity: (json['quantity'] as num?)?.toDouble(),
productUomQty: (json['productUomQty'] as num?)?.toDouble(), productUomQty: (json['product_uom_qty'] as num?)?.toDouble(),
); );
Map<String, dynamic> _$MoveWithoutPackageModelToJson( Map<String, dynamic> _$MoveWithoutPackageModelToJson(
_MoveWithoutPackageModel instance, _MoveWithoutPackageModel instance,
) => <String, dynamic>{ ) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'productId': instance.productId, 'product_id': instance.productId,
'quantity': instance.quantity, '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), const SizedBox(height: 16),
], ],
Text(
'Détails réception',
style: AppTheme.of(
context,
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(height: 10),
StockPickingCard( StockPickingCard(
isDone: reception?.isDone == true, isDone: reception?.isDone == true,
margin: EdgeInsets.symmetric(horizontal: 5), margin: EdgeInsets.symmetric(horizontal: 5),
@ -62,6 +69,68 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
origin: reception?.origin, origin: reception?.origin,
status: reception?.state, 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; 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) { T? objectFromJson<T>(dynamic json, T Function(Map<String, dynamic>) fromJson) {
if (json is Map<String, dynamic>) return fromJson(json); if (json is Map<String, dynamic>) return fromJson(json);
return null; return null;