feat: Adds draft status for stock pickings

Introduces an `isDraft` property to `StockPickingRecordEntity` to mark receptions with local modifications.

Automatically sets a reception to draft status when a product's quantity is incremented via scanning. Displays a 'Brouillon' chip on reception cards to provide visual feedback for draft operations.

Ensures reception details are refreshed after scanning to reflect the updated draft status and provides immediate error feedback when no product is found during a scan.
This commit is contained in:
your-name 2025-07-30 20:26:17 +03:00
parent 46b93d7fa4
commit ab4a56ed41
8 changed files with 104 additions and 25 deletions

View File

@ -5,6 +5,7 @@ import 'package:objectbox/objectbox.dart';
class StockPickingRecordEntity {
StockPickingRecordEntity({
this.id = 0,
this.isDraft = false,
this.priority,
this.name,
this.pickingTypeCode,
@ -28,7 +29,7 @@ class StockPickingRecordEntity {
});
@Id(assignable: true)
int id;
bool isDraft;
String? priority;
String? name;
String? pickingTypeCode;

View File

@ -156,7 +156,7 @@
},
{
"id": "7:7263194599189060077",
"lastPropertyId": "26:6083080926308657518",
"lastPropertyId": "27:7762235054004255701",
"name": "StockPickingRecordEntity",
"properties": [
{
@ -239,6 +239,11 @@
"flags": 520,
"indexId": "9:5054232387620309172",
"relationTarget": "StockPickingTypeEntity"
},
{
"id": "27:7762235054004255701",
"name": "isDraft",
"type": 1
}
],
"relations": []

View File

@ -193,7 +193,7 @@ final _entities = <obx_int.ModelEntity>[
obx_int.ModelEntity(
id: const obx_int.IdUid(7, 7263194599189060077),
name: 'StockPickingRecordEntity',
lastPropertyId: const obx_int.IdUid(26, 6083080926308657518),
lastPropertyId: const obx_int.IdUid(27, 7762235054004255701),
flags: 0,
properties: <obx_int.ModelProperty>[
obx_int.ModelProperty(
@ -284,6 +284,12 @@ final _entities = <obx_int.ModelEntity>[
indexId: const obx_int.IdUid(9, 5054232387620309172),
relationTarget: 'StockPickingTypeEntity',
),
obx_int.ModelProperty(
id: const obx_int.IdUid(27, 7762235054004255701),
name: 'isDraft',
type: 1,
flags: 0,
),
],
relations: <obx_int.ModelRelation>[],
backlinks: <obx_int.ModelBacklink>[
@ -707,7 +713,7 @@ obx_int.ModelDefinition getObjectBoxModel() {
final originOffset = object.origin == null
? null
: fbb.writeString(object.origin!);
fbb.startTable(27);
fbb.startTable(28);
fbb.addInt64(0, object.id);
fbb.addOffset(1, priorityOffset);
fbb.addOffset(2, nameOffset);
@ -721,6 +727,7 @@ obx_int.ModelDefinition getObjectBoxModel() {
fbb.addInt64(23, object.locationId.targetId);
fbb.addInt64(24, object.locationDestId.targetId);
fbb.addInt64(25, object.pickingTypeId.targetId);
fbb.addBool(26, object.isDraft);
fbb.finish(fbb.endTable());
return object.id;
},
@ -733,6 +740,12 @@ obx_int.ModelDefinition getObjectBoxModel() {
4,
0,
);
final isDraftParam = const fb.BoolReader().vTableGet(
buffer,
rootOffset,
56,
false,
);
final priorityParam = const fb.StringReader(
asciiOptimization: true,
).vTableGetNullable(buffer, rootOffset, 6);
@ -760,6 +773,7 @@ obx_int.ModelDefinition getObjectBoxModel() {
);
final object = StockPickingRecordEntity(
id: idParam,
isDraft: isDraftParam,
priority: priorityParam,
name: nameParam,
pickingTypeCode: pickingTypeCodeParam,
@ -1057,6 +1071,11 @@ class StockPickingRecordEntity_ {
_entities[6].properties[12],
);
/// See [StockPickingRecordEntity.isDraft].
static final isDraft = obx.QueryBooleanProperty<StockPickingRecordEntity>(
_entities[6].properties[13],
);
/// see [StockPickingRecordEntity.moveLineIdsWithoutPackage]
static final moveLineIdsWithoutPackage =
obx.QueryBacklinkToMany<

View File

@ -11,9 +11,11 @@ class StockPickingCard extends StatelessWidget {
required this.origin,
required this.status,
required this.isDone,
this.isDraft = false,
this.margin,
});
final bool isDone;
final bool isDraft;
final String? reference;
final String? from;
final String? to;
@ -36,17 +38,41 @@ class StockPickingCard extends StatelessWidget {
children: [
Row(
children: [
const Icon(Icons.receipt_long, color: Colors.blue),
const SizedBox(width: 8),
Expanded(
child: Text(
reference ?? 'Référence inconnue',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
child: Row(
spacing: 8,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.receipt_long, color: Colors.blue),
Expanded(
child: Text(
reference ?? 'Référence inconnue',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
if (isDraft)
Chip(
backgroundColor: Colors.red,
label: Row(
spacing: 8,
children: [
Icon(Icons.cloud_off_outlined, color: Colors.white),
Text(
'Brouillon',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
const SizedBox(height: 12),

View File

@ -57,7 +57,16 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
onTapScan: () {
final id = reception?.id;
if (id == null) return;
ReceptionScanRoute(receptionId: id).push(context);
ReceptionScanRoute(receptionId: id)
.push(context)
.then(
(v) => ref
.read(
receptionDetailsPageModelProvider
.notifier,
)
.getReceptionById(id: widget.receptionId),
);
},
),
const SizedBox(height: 16),
@ -70,6 +79,7 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
),
SizedBox(height: 10),
StockPickingCard(
isDraft: reception?.isDraft == true,
isDone: reception?.isDone == true,
margin: EdgeInsets.symmetric(horizontal: 5),
reference: reception?.name ?? '',
@ -144,7 +154,13 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
onPressed: () {
final id = reception?.id;
if (id == null) return;
ReceptionScanRoute(receptionId: id).push(context);
ReceptionScanRoute(receptionId: id)
.push(context)
.then(
(v) => ref
.read(receptionDetailsPageModelProvider.notifier)
.getReceptionById(id: widget.receptionId),
);
},
child: Icon(Icons.qr_code, color: AppTheme.of(context).white),
)

View File

@ -79,6 +79,7 @@ class _ReceptionPageState extends ConsumerState<ReceptionPage> {
).push(context);
},
child: StockPickingCard(
isDraft: reception.isDraft == true,
margin: EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,

View File

@ -99,12 +99,17 @@ class _ReceptionScanPageState extends ConsumerState<ReceptionScanPage>
if (qrcodeValue == null) {
debugPrint("Qrcode non valide");
Toast.showError('Aucun produit trouvé.');
return;
}
// find product in local database
final isProductExist = model.isProductExist(barcode: qrcodeValue);
if (isProductExist) {
final product = model.getProduct(barcode: qrcodeValue);
model.incrementMoveLineQuantity(
barcode: qrcodeValue,
receptionId: widget.receptionId,
);
//show dialog
await showDialog(
barrierDismissible: false,
@ -146,6 +151,7 @@ class _ReceptionScanPageState extends ConsumerState<ReceptionScanPage>
},
);
} else {
Toast.showError('Aucun produit trouvé.');
debugPrint('Aucun produit trouvé.');
}
}
@ -160,7 +166,10 @@ class _ReceptionScanPageState extends ConsumerState<ReceptionScanPage>
final isProductExist = model.isProductExist(barcode: qrcodeValue);
if (isProductExist) {
final product = model.getProduct(barcode: qrcodeValue);
model.incrementMoveLineQuantity(barcode: qrcodeValue);
model.incrementMoveLineQuantity(
barcode: qrcodeValue,
receptionId: widget.receptionId,
);
//show dialog
await showDialog(
barrierDismissible: false,

View File

@ -4,7 +4,6 @@ import 'package:e_scan/backend/objectbox/objectbox_manager.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
part 'reception_scan_page_model.freezed.dart';
@ -49,9 +48,14 @@ class ReceptionScanPageModel
.findFirst();
}
void incrementMoveLineQuantity({required String barcode}) {
void incrementMoveLineQuantity({
required String barcode,
required int receptionId,
}) {
final moveLineBox = objectboxManager.store
.box<MoveLineWithoutPackageEntity>();
final stockPickingRecordBox = objectboxManager.store
.box<StockPickingRecordEntity>();
final moveBox = objectboxManager.store.box<MoveWithoutPackageEntity>();
final productBox = objectboxManager.store.box<ProductEntity>();
final productEntity = productBox
@ -59,6 +63,7 @@ class ReceptionScanPageModel
.build()
.findFirst();
final productId = productEntity?.id;
final stockPickingRecord = stockPickingRecordBox.get(receptionId);
if (productId != null) {
final moveLineEntity = moveLineBox
.query(MoveLineWithoutPackageEntity_.productId.equals(productId))
@ -68,21 +73,18 @@ class ReceptionScanPageModel
.query(MoveWithoutPackageEntity_.productId.equals(productId))
.build()
.findFirst();
if (moveLineEntity != null && moveEntity != null) {
if (moveLineEntity != null &&
moveEntity != null &&
stockPickingRecord != null) {
moveLineEntity.quantity = (moveLineEntity.quantity ?? 0) + 1;
moveEntity.quantity = (moveEntity.quantity ?? 0) + 1;
stockPickingRecord.isDraft = true;
moveLineBox.put(moveLineEntity);
moveBox.put(moveEntity);
stockPickingRecordBox.put(stockPickingRecord);
}
}
}
Future handleBarcode({required BarcodeCapture barcodeCapture}) async {
try {
final stockPickingRecords = objectboxManager.store
.box<StockPickingRecordEntity>();
} catch (e) {}
}
}
@freezed