barcode_scanner/lib/backend/api/api_calls.dart
mandreshope c231790f59 feat: Implements backorder confirmation flow for receptions
Corrects API endpoints for creating, processing with, and processing without backorder confirmations.

Adds a new `isBackorder` getter to stock picking records for identifying backordered items, and improves the robustness of the `isDone` getter.

Integrates backorder detection into the reception validation process, prompting a dedicated confirmation dialog when a backorder is present.

Introduces new state management and methods (`withBackorder`, `withoutBackorder`) in the reception details model to handle the backorder confirmation logic.

Enhances the primary button component with a `backgroundColor` property for greater customization.
2025-08-04 09:37:26 +03:00

561 lines
18 KiB
Dart

import 'package:e_scan/backend/objectbox/entities/stock_picking/stock_picking_record_entity.dart';
import 'package:e_scan/backend/objectbox/objectbox_manager.dart';
import 'package:e_scan/backend/schema/auth/auth_model.dart';
import 'package:e_scan/backend/schema/stock_picking/stock_picking_model.dart';
import 'package:e_scan/backend/schema/stock_picking/stock_picking_record_model.dart';
import 'package:e_scan/backend/schema/stock_picking/update_move_line_dto.dart';
import 'package:e_scan/provider_container.dart';
import 'package:e_scan/services/dio_service.dart';
import 'package:e_scan/services/token_provider.dart';
import 'package:e_scan/utils/utils.dart';
import 'package:flutter/foundation.dart';
import 'package:multiple_result/multiple_result.dart';
class ApiCalls {
static DioService dioService = DioService(
tokenProvider: providerContainer.read(tokenProvider),
);
static Future<Result<AuthModel, Error>> signIn({
required String email,
required String password,
}) async {
try {
final response = await dioService.post(
path: '/simpos/v1/sign_in',
data: {
"params": {
"login": email,
"password": password,
"db": "bitnami_odoo",
},
},
);
if (response.statusCode == 200) {
final data = response.data;
if (data['result']['success'] == true) {
String? cookie;
if (response.headers.map.containsKey('set-cookie')) {
cookie = response.headers.map['set-cookie']?.firstOrNull;
}
final auth = AuthModel.fromJson(
data['result']['data'],
).copyWith(sessionId: cookie);
return Result.success(auth);
} else {
return Result.error(Error(data['result']['success']));
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return Result.error(Error(response.statusMessage));
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return Result.error(Error(e));
}
}
static Future<Result<List<StockPickingRecordEntity>, Error>>
getAllStockPiking() async {
try {
final stockPickingRecords = objectboxManager.store
.box<StockPickingRecordEntity>();
if (!(await checkInternetConnexion())) {
// return local data
return Result.success(stockPickingRecords.getAll());
}
final response = await dioService.post(
path: '/web/dataset/call_kw/stock.picking/web_search_read',
data: {
//"id": 23,
//"jsonrpc": "2.0",
//"method": "call",
"params": {
"model": "stock.picking",
"method": "web_search_read",
"args": [],
"kwargs": {
"specification": {
"company_id": {"fields": {}},
"priority": {},
"name": {},
"partner_id": {
"fields": {"display_name": {}},
},
"location_dest_id": {
"fields": {"complete_name": {}},
},
"location_id": {
"fields": {"complete_name": {}},
},
"user_id": {
"fields": {"display_name": {}},
},
"scheduled_date": {},
"picking_type_code": {},
"products_availability_state": {},
"products_availability": {},
"date_deadline": {},
"date_done": {},
"origin": {},
"backorder_id": {
"fields": {"display_name": {}},
},
"picking_type_id": {
"fields": {"display_name": {}},
},
"state": {},
"activity_exception_decoration": {},
"activity_exception_icon": {},
"json_popover": {},
"move_line_ids_without_package": {
"fields": {
"product_id": {
"fields": {"display_name": {}, "barcode": {}},
},
"quantity": {}, // quantité
},
},
"move_ids_without_package": {
"fields": {
"product_id": {
"fields": {"display_name": {}, "barcode": {}},
},
"quantity": {}, // quantité
"product_uom_qty": {}, //quantité demandé
},
"context": {
"form_view_ref": "stock.view_stock_move_operations",
},
"limit": 40,
"order": "",
},
},
"offset": 0,
"order": "",
"limit": 80,
"context": {
"lang": "en_US",
"tz": "Africa/Nairobi",
"uid": 2,
"allowed_company_ids": [1],
"bin_size": true,
"active_model": "stock.picking.type",
"active_id": 2,
"active_ids": [2],
"contact_display": "partner_address",
"default_picking_type_id": 2,
"default_company_id": 1,
"current_company_id": 1,
},
"count_limit": 10001,
"domain": [
["picking_type_code", "=", "incoming"],
],
},
},
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('result')) {
final result = StockPickingResponseModel.fromJson(data);
final recordsModel =
result.result?.records ?? <StockPickingRecordModel>[];
// add data to local
final records = recordsModel.map((e) => e.toEntity()).toList();
return Result.success(records);
} else {
return Result.error(Error(data['error']));
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return Result.error(Error(response.statusMessage));
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return Result.error(Error(e));
}
}
static Future<Result<StockPickingRecordEntity, Error>> getStockPikingById({
required int id,
}) async {
try {
final stockPickingRecords = objectboxManager.store
.box<StockPickingRecordEntity>();
if (!(await checkInternetConnexion())) {
// return local data
final entity = stockPickingRecords.get(id);
if (entity == null) return Result.error(Error('Not found'));
return Result.success(entity);
}
final response = await dioService.post(
path: '/web/dataset/call_kw/stock.picking/web_read',
data: {
"params": {
"model": "stock.picking",
"method": "web_read",
"args": [
[id],
],
"kwargs": {
"context": {
"lang": "en_US",
"tz": "Africa/Nairobi",
"uid": 2,
"allowed_company_ids": [1],
"bin_size": true,
"active_model": "stock.picking.type",
"active_id": 2,
"active_ids": [2],
"contact_display": "partner_address",
"default_picking_type_id": 2,
"default_company_id": 1,
},
"specification": {
"state": {},
"return_count": {},
"priority": {},
"picking_type_code": {},
"name": {},
"partner_id": {
"fields": {"display_name": {}},
},
"picking_type_id": {
"fields": {"display_name": {}},
},
"location_id": {
"fields": {"complete_name": {}},
},
"location_dest_id": {
"fields": {"complete_name": {}},
},
"backorder_id": {
"fields": {"display_name": {}},
},
"use_create_lots": {},
"scheduled_date": {},
"json_popover": {},
"date_deadline": {},
"products_availability_state": {},
"products_availability": {},
"date_done": {},
"origin": {},
"picking_properties": {},
"move_line_ids_without_package": {
"fields": {
"product_id": {
"fields": {"display_name": {}, "barcode": {}},
},
"quantity": {}, // quantité
},
},
"move_ids_without_package": {
"fields": {
"product_id": {
"fields": {"display_name": {}, "barcode": {}},
},
"quantity": {}, // quantité
"product_uom_qty": {}, //quantité demandé
},
"context": {
"form_view_ref": "stock.view_stock_move_operations",
},
"limit": 40,
"order": "",
},
"id": {},
"package_level_ids": {
"fields": {
"is_fresh_package": {},
"company_id": {"fields": {}},
"package_id": {
"fields": {"display_name": {}},
},
"state": {}, //done, ready, waiting,
"is_done": {},
},
"limit": 40,
"order": "",
},
"move_type": {},
"user_id": {
"fields": {"display_name": {}},
},
"sale_id": {
"fields": {"display_name": {}},
},
"note": {},
"show_check_availability": {},
"has_scrap_move": {},
"has_packages": {},
"is_locked": {},
"show_next_pickings": {},
"company_id": {"fields": {}},
"picking_type_entire_packs": {},
"display_name": {},
},
},
},
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('result')) {
final datas = data['result'] as List;
if (datas.isNotEmpty) {
final recordModel = StockPickingRecordModel.fromJson(datas.first);
final entity = recordModel.toEntity();
return Result.success(entity);
} else {
return Result.error(Error('Data not found'));
}
} else {
return Result.error(Error(data['error']));
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return Result.error(Error(response.statusMessage));
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return Result.error(Error(e));
}
}
static Future<bool> updateAllMoveLineOnStockPicking({
required int stockPickingId,
required List<UpdateMoveLineDto> moveLineDto,
}) async {
try {
final response = await dioService.post(
path: '/web/dataset/call_kw/stock.picking/write',
data: {
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "stock.picking",
"method": "write",
"args": [
[
stockPickingId, //id stock piking
],
{
"move_line_ids": [...moveLineDto.map((e) => e.toList())],
},
],
"kwargs": {"context": {}},
},
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('result')) {
return true;
} else {
return false;
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return false;
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return false;
}
}
static Future<bool> validateStockPicking({
required int stockPickingId,
}) async {
try {
final response = await dioService.post(
path: '/web/dataset/call_kw/stock.picking/button_validate',
data: {
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "stock.picking",
"method": "button_validate",
"args": [
[stockPickingId],
],
"kwargs": {"context": {}},
},
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('result')) {
return true;
} else {
return false;
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return false;
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return false;
}
}
/// If the requested quantity of the product to be received is greater than
/// the quantity actually received,
/// a confirmation popup must be displayed.
/// The user will then need to perform two actions:
/// choose whether or not to create a backorder.
/// Therefore, this function must be called to generate this confirmation.
/// This function returns the `ID of the backorder` confirmation popup,
/// which must be used to perform the next action,
/// either `withBackorder()` or `withoutBackorder()`.
static Future<int?> createBackorderConfirmation({
required int stockPickingId,
}) async {
try {
final response = await dioService.post(
path: '/web/dataset/call_kw/stock.backorder.confirmation/create',
data: {
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "stock.backorder.confirmation",
"method": "create",
"args": [
{
"pick_ids": [
[
6,
0,
[
stockPickingId, // stock picking id
],
],
],
"backorder_confirmation_line_ids": [
[
0,
0,
{
"picking_id": stockPickingId, // stock picking id
//"to_backorder": false // true = avec reliquat / false = sans reliquat
},
],
],
},
],
"kwargs": {},
},
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('result')) {
return data['result'];
} else {
return null;
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return null;
}
}
static Future<bool> withBackorder({
required int stockPickingId,
required int createBackorderConfirmationId,
}) async {
try {
final response = await dioService.post(
path: '/web/dataset/call_kw/stock.backorder.confirmation/process',
data: {
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "stock.backorder.confirmation",
"method": "process",
"args": [
[
createBackorderConfirmationId, // id popup backorder confirmation reponse -> create popup reliquat
],
],
"kwargs": {
"context": {
"button_validate_picking_ids": [
stockPickingId, // picking id]
],
},
},
},
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('result')) {
return data['result'];
} else {
return false;
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return false;
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return false;
}
}
static Future<bool> withoutBackorder({
required int stockPickingId,
required int createBackorderConfirmationId,
}) async {
try {
final response = await dioService.post(
path:
'/web/dataset/call_kw/stock.backorder.confirmation/process_cancel_backorder',
data: {
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": "stock.backorder.confirmation",
"method": "process_cancel_backorder",
"args": [
[
createBackorderConfirmationId, // id popup backorder confirmation reponse -> create popup reliquat
],
],
"kwargs": {
"context": {
"button_validate_picking_ids": [
stockPickingId, // stock picking id
],
},
},
},
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
if (data.containsKey('result')) {
return data['result'];
} else {
return false;
}
} else {
debugPrint('Erreur réseau: ${response.statusCode}');
return false;
}
} catch (e) {
debugPrint('Erreur lors de la requête: $e');
return false;
}
}
}